What this actually is.
Technical background, root cause, and affected surface.
https://github.com/ImageMagick/ImageMagick/security/advisories/GHSA-4g75-9r48-jf92
- Vendor
- ImageMagick
- Product
- ImageMagick
- Status
- Published
- CWE
- CWE-362 Race Condition · CWE-567 Unsynchronized Shared Data Access
- Severity
- Medium
- CVSS Score
- 4.1
- Vector
- CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:H/I:N/A:N
From one request
to root shell.
Reproduced in a sandboxed environment. Requires only LAN or WiFi adjacency.
Prerequisite: the Distributed Pixel Cache (DPC) server is an opt-in build and must be running.
Constraint: exploitation is opportunistic rather than deterministic. The attacker cannot choose which session is hijacked, only force collisions until one occurs.
The bug, and the fix.
File: MagickCore/distribute-cache.c Affected functions: DistributePixelCacheServer() accept loop, and the DistributePixelCacheClient() worker.
The accept loop declares the client socket as a loop-scoped automatic variable and passes its ADDRESS to pthread_create():
1028 for ( ; ; ) 1029 { 1030 SOCKET_TYPE 1031 client_socket; /* one stack slot, reused every iteration */ 1032 1033 socklen_t 1034 length; 1035 1036 length=(socklen_t) sizeof(address); 1037 client_socket=accept(server_socket,(struct sockaddr *) &address,&length); 1038 if (client_socket == -1) 1039 ThrowFatalException(CacheFatalError,"UnableToEstablishConnection"); 1040 #if defined(MAGICKCORE_THREAD_SUPPORT) 1041 status=pthread_create(&threads,&attributes,DistributePixelCacheClient, 1042 (void *) &client_socket); /* <-- RACE: address of a stack variable */ 1043 if (status == -1) 1044 ThrowFatalException(CacheFatalError,"UnableToCreateClientThread"); 1045 #elif defined(_MSC_VER) 1046 if (CreateThread(0,0,DistributePixelCacheClient,(void*) &client_socket,0,&threadID) == (HANDLE) NULL)
The spawned worker dereferences that pointer only after it has been scheduled, and only after it has already derived the session key:
0866 shared_secret=GetPolicyValue("cache:shared-secret"); 0871 session_key=GetMagickSignature(nonce); 0879 client_socket=(*(SOCKET_TYPE *) socket); /* <-- late read of the shared slot */ 0880 count=dpc_send(client_socket,sizeof(session_key),&session_key);
Root cause: pthread_create()'s void *arg must outlive the child thread's first read of it. Here a single stack slot backs every connection, so the next accept() at line 1037 overwrites the value before a not-yet-scheduled worker reaches line 879. That worker then reads a file descriptor belonging to a different connection.
The pointer itself remains valid throughout, so nothing faults and no crash is produced - the server silently serves one client's session over another client's socket, and sends the victim session's key to the wrong peer at line 880. The identical defect is present on the Windows CreateThread() path at line 1046.
What an attacker does to you.
Post-exploitation outcomes mapped to CVSS impact metrics.
A hijacked descriptor causes a worker thread to send its freshly derived session key, and then serve pixel-cache reads, over a socket belonging to a different client. An attacker who wins the race receives raw pixel data from a concurrent session; on a shared or multi-tenant host that can be another tenant's image content moving through an upload or transformation pipeline. Because the session key is transmitted at distribute-cache.c:880 before the descriptor is tied to any client identity, the hijack also hands the attacker the credential for the victim's session, compounding the separate weakness that DPC session keys are echo-verified in cleartext.
C:H · I:N · A:N
CWE-362 Race Condition · CWE-567 Unsynchronized Shared Data Access
Fix it. In this order.
A runbook, not a checklist. Sequence matters — assume compromise before you act.
Upgrade. Fixed in ImageMagick 7.1.2-23 and 6.9.13-48. Magick.NET users should move to 14.12.0 or later. Debian shipped the fix in DSA-6298-1.
Upstream fix (commit 9971127, 14 May 2026) replaces the shared stack slot with a per-connection heap allocation, addressing the underlying pthread_create() argument-lifetime bug rather than merely narrowing the window:
the accept loop allocates a SOCKET_TYPE with AcquireMagickMemory() on each iteration, stores the accepted descriptor there, and passes that pointer to pthread_create() / CreateThread();
DistributePixelCacheClient() copies the value out of the allocation and releases it with RelinquishMagickMemory();
both thread-creation paths now close the socket and free the allocation when thread creation fails.
An equivalent and allocation-free fix is to pass the descriptor by value instead of by reference - (void *)(intptr_t) client_fd, recovered in the worker as (SOCKET_TYPE)(intptr_t) arg - which removes the shared object entirely and cannot fail under memory pressure.
Mitigations where upgrading is not immediately possible:
Do not run the DPC server. It is opt-in: rebuild without --enable-distributed-pixel-cache, or simply do not start magick -distribute-cache.
If it must run, bind it to loopback or an isolated segment and firewall the port (default 6668) so only trusted peers can connect. The DPC protocol provides no transport authentication or encryption of its own.
Do not share a single DPC server across tenants or trust boundaries; run one per trust domain.
Set a strong cache:shared-secret policy value. This constrains who can complete a session but does not close the race, since the descriptor is read after the key is derived.
Monitor for repeated short-lived connection pairs to the DPC port, and for image operations failing with DPC protocol errors, which is the observable side effect of a won race.
disclose@securin.ioVendors moved in days.
Attackers in hours.
Reconstructed from vendor advisories, CISA bulletins, and Securin research records.
Race condition identified in the ImageMagick Distributed Pixel Cache accept loop and reported to the ImageMagick maintainers via GitHub Security Advisory
CVE-2026-46693 disclosure date recorded
Securin Zero-Day advisory published
Upstream fix committed (9971127): per-connection heap allocation replaces the shared stack slot on both the pthread_create and CreateThread paths
Fix landed for release in ImageMagick 7.1.2-23 and 6.9.13-48, tracked as GitHub Security Advisory GHSA-4g75-9r48-jf92
Debian Security Advisory DSA-6298-1 ships the fix to distribution users
Disclosed 0 days after discovery
Cite, verify, go deeper.
Primary sources — NVD, CISA KEV, and machine-readable IoC feed.