CVE-2025-53101

ADVISORY - github

Summary

Hi, we have found a stack buffer overflow and would like to report this issue. Could you confirm if this qualifies as a security vulnerability? I am happy to provide any additional information needed.

Summary

In ImageMagick's magick mogrify command, specifying multiple consecutive %d format specifiers in a filename template causes internal pointer arithmetic to generate an address below the beginning of the stack buffer, resulting in a stack overflow through vsnprintf().

Additional information

Upon further investigation, we found that the same issue occurs not only with mogrify but also with the following subcommands: compare, composite, conjure, convert, identify, mogrify, and montage.

Furthermore, we confirmed that this vulnerability has the potential to lead to RCE. RCE is possible when ASLR is disabled and there is a suitable one_gadget in libc, provided that options and filenames can be controlled.

Details

  • Vulnerability Type: CWE-124: Buffer Underwrite
  • Affected Component: MagickCore/image.c - Format processing within InterpretImageFilename()
  • Affected Version: ImageMagick 7.1.1-47 (as of commit 82572afc, June 2025)
  • CWE-124: Buffer Underwrite: A vulnerability where writing occurs to memory addresses before the beginning of a buffer. This is caused by a design flaw in fixed offset correction, resulting in negative pointer arithmetic during consecutive format specifier processing.

Reproduction

Tested Environment

  • Operating System: Ubuntu 22.04 LTS
  • Architecture: x86_64
  • Compiler: gcc with AddressSanitizer (gcc version: 11.4.0)

Reproduction Steps

# Clone source
git clone --depth 1 --branch 7.1.1-47 https://github.com/ImageMagick/ImageMagick.git ImageMagick-7.1.1
cd ImageMagick-7.1.1

# Build with ASan
CFLAGS="-g -O0 -fsanitize=address -fno-omit-frame-pointer" CXXFLAGS="$CFLAGS" LDFLAGS="-fsanitize=address" ./configure --enable-maintainer-mode --enable-shared && make -j$(nproc) && make install

# Trigger crash
./utilities/magick mogrify %d%d

Output

==4155==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffda834caae at pc 0x7f1ea367fb27 bp 0x7ffda834b680 sp 0x7ffda834ae10
WRITE of size 2 at 0x7ffda834caae thread T0
    #0 0x7f1ea367fb26 in __interceptor_vsnprintf ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:1668
    #1 0x7f1ea2dc9e3e in FormatLocaleStringList MagickCore/locale.c:470
    #2 0x7f1ea2dc9fd9 in FormatLocaleString MagickCore/locale.c:495
    #3 0x7f1ea2da0ad5 in InterpretImageFilename MagickCore/image.c:1696
    #4 0x7f1ea2c6126b in ReadImages MagickCore/constitute.c:1051
    #5 0x7f1ea27ef29b in MogrifyImageCommand MagickWand/mogrify.c:3858
    #6 0x7f1ea278e95d in MagickCommandGenesis MagickWand/magick-cli.c:177
    #7 0x560813499a0c in MagickMain utilities/magick.c:153
    #8 0x560813499cba in main utilities/magick.c:184
    #9 0x7f1ea1c0bd8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #10 0x7f1ea1c0be3f in __libc_start_main_impl ../csu/libc-start.c:392
    #11 0x560813499404 in _start (/root/workdir/ImageMagick/utilities/.libs/magick+0x2404)

Address 0x7ffda834caae is located in stack of thread T0 at offset 62 in frame
    #0 0x7f1ea2c60f62 in ReadImages MagickCore/constitute.c:1027

  This frame has 2 object(s):
    [32, 40) 'images' (line 1033)
    [64, 4160) 'read_filename' (line 1029) <== Memory access at offset 62 underflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
      (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:1668 in __interceptor_vsnprintf
Shadow bytes around the buggy address:
  0x100035061900: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100035061910: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100035061920: 00 00 00 00 00 00 00 00 f3 f3 f3 f3 f3 f3 f3 f3
  0x100035061930: f3 f3 f3 f3 f3 f3 f3 f3 00 00 00 00 00 00 00 00
  0x100035061940: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x100035061950: f1 f1 00 f2 f2[f2]00 00 00 00 00 00 00 00 00 00
  0x100035061960: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100035061970: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100035061980: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x100035061990: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000350619a0: 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
  Shadow gap:              cc
==4155==ABORTING

Affected Code

In MagickCore/image.c, within the InterpretImageFilename() function:

MagickExport size_t InterpretImageFilename(const ImageInfo *image_info,
  Image *image,const char *format,int value,char *filename,
  ExceptionInfo *exception)
{
...
  for (p=strchr(format,'%'); p != (char *) NULL; p=strchr(p+1,'%'))
  {
    q=(char *) p+1;
    if (*q == '%')
      {
        p=q+1;
        continue;
      }
    field_width=0;
    if (*q == '0')
      field_width=(ssize_t) strtol(q,&q,10);
    switch (*q)
    {
      case 'd':
      case 'o':
      case 'x':
      {
        q++;
        c=(*q);
        *q='\0';
        /*--------Affected--------*/
        (void) FormatLocaleString(filename+(p-format-offset),(size_t)
          (MagickPathExtent-(p-format-offset)),p,value);
        offset+=(4-field_width);
        /*--------Affected--------*/
        *q=c;
        (void) ConcatenateMagickString(filename,q,MagickPathExtent);
        canonical=MagickTrue;
        if (*(q-1) != '%')
          break;
        p++;
        break;
      }
      case '[':
      {
        ...
      }
      default:
        break;
    }
  }

Technical Analysis

This vulnerability is caused by an inconsistency in the template expansion processing within InterpretImageFilename().

The format specifiers %d, %o, and %x in templates are replaced with integer values by FormatLocaleString(), but the output buffer position is calculated by filename + (p - format - offset).

The offset variable is cumulatively incremented to correct the output length of %d etc., but the design using a static offset += (4 - field_width) causes offset to increase excessively when % specifiers are consecutive in the template, creating a dangerous state where the write destination address points before filename.

The constant 4 was likely chosen based on the character count of typical format specifiers like %03d (total of 4 characters: %, 0, 3, d). However, in reality, there are formats with only 2 characters like %d, and formats with longer width specifications (e.g., %010d), so this uniform constant-based correction is inconsistent with actual template structures.

As a result, when the correction value becomes excessive, offset exceeds the relative position p - format within the template, generating a negative index. This static and template-independent design of the correction processing is the root cause of this vulnerability.

This causes vsnprintf() to write outside the stack buffer range, which is detected by AddressSanitizer as a stack-buffer-overflow.

Proposed Fix

In MagickCore/image.c, within the InterpretImageFilename() function:

MagickExport size_t InterpretImageFilename(const ImageInfo *image_info,
  Image *image,const char *format,int value,char *filename,
  ExceptionInfo *exception)
{
...
  /*--------Changed--------*/
  ssize_t
    field_width,
    offset,
    written; // Added
  /*--------Changed--------*/
...
  for (p=strchr(format,'%'); p != (char *) NULL; p=strchr(p+1,'%'))
  {
    q=(char *) p+1;
    if (*q == '%')
      {
        p=q+1;
        continue;
      }
    field_width=0;
    if (*q == '0')
      field_width=(ssize_t) strtol(q,&q,10);
    switch (*q)
    {
      case 'd':
      case 'o':
      case 'x':
      {
        q++;
        c=(*q);
        *q='\0';
        written = FormatLocaleString(filename+(p-format-offset),(size_t)
          (MagickPathExtent-(p-format-offset)),p,value);
        /*--------Changed--------*/
        if (written <= 0 || written > (MagickPathExtent - (p - format - offset)))
          return 0;
        offset += (ssize_t)((q - p) - written);
        /*--------Changed--------*/
        *q=c;
        (void) ConcatenateMagickString(filename,q,MagickPathExtent);
        canonical=MagickTrue;
        if (*(q-1) != '%')
          break;
        p++;
        break;
      }
      case '[':
      {
        ...
      }
      default:
        break;
    }
  }
  • By updating offset based on the difference between template description length (q - p) and the number of output bytes written, buffer position consistency is maintained.
  • Correction is performed according to the actual template structure, ensuring stable behavior regardless of format length without relying on static constants.
  • Range checking of written allows detection of vsnprintf failures and excessive writes.

Commits

Fixed in https://github.com/ImageMagick/ImageMagick/commit/66dc8f51c11b0ae1f1cdeacd381c3e9a4de69774 and https://github.com/ImageMagick/ImageMagick6/commit/643deeb60803488373cd4799b24d5786af90972e

EPSS Score: 0.00114 (0.300)

Common Weakness Enumeration (CWE)

ADVISORY - nist

Buffer Underwrite ('Buffer Underflow')

ADVISORY - github

Buffer Underwrite ('Buffer Underflow')

ADVISORY - gitlab

OWASP Top Ten 2017 Category A9 - Using Components with Known Vulnerabilities

Buffer Underwrite ('Buffer Underflow')

OWASP Top Ten 2013 Category A9 - Using Components with Known Vulnerabilities

ADVISORY - redhat

Buffer Underwrite ('Buffer Underflow')


Docker

CREATED

UPDATED

ADVISORY ID

CVE-2025-53101

EXPLOITABILITY SCORE

-

EXPLOITS FOUND
-
COMMON WEAKNESS ENUMERATION (CWE)-
RATING UNAVAILABLE FROM ADVISORY

NIST

CREATED

UPDATED

EXPLOITABILITY SCORE

2.2

EXPLOITS FOUND
COMMON WEAKNESS ENUMERATION (CWE)

CVSS SCORE

7.4high

GitHub

CREATED

UPDATED

EXPLOITABILITY SCORE

2.2

EXPLOITS FOUND
-
COMMON WEAKNESS ENUMERATION (CWE)

CVSS SCORE

7.4high

Alpine

CREATED

UPDATED

EXPLOITABILITY SCORE

-

EXPLOITS FOUND
-
COMMON WEAKNESS ENUMERATION (CWE)-
RATING UNAVAILABLE FROM ADVISORY

Debian

CREATED

UPDATED

EXPLOITABILITY SCORE

-

EXPLOITS FOUND
-
COMMON WEAKNESS ENUMERATION (CWE)-
RATING UNAVAILABLE FROM ADVISORY

Ubuntu

CREATED

UPDATED

EXPLOITABILITY SCORE

3.9

EXPLOITS FOUND
-
COMMON WEAKNESS ENUMERATION (CWE)-

CVSS SCORE

9.8medium

GitLab

CREATED

UPDATED

ADVISORY ID

CVE-2025-53101

EXPLOITABILITY SCORE

2.2

EXPLOITS FOUND
-
COMMON WEAKNESS ENUMERATION (CWE)

CVSS SCORE

7.4high

Amazon

CREATED

UPDATED

EXPLOITABILITY SCORE

-

EXPLOITS FOUND
-
COMMON WEAKNESS ENUMERATION (CWE)-

CVSS SCORE

N/Amedium

Amazon

CREATED

UPDATED

EXPLOITABILITY SCORE

-

EXPLOITS FOUND
-
COMMON WEAKNESS ENUMERATION (CWE)-

CVSS SCORE

N/Amedium

Red Hat

CREATED

UPDATED

EXPLOITABILITY SCORE

2.2

EXPLOITS FOUND
-
COMMON WEAKNESS ENUMERATION (CWE)

CVSS SCORE

6.5medium

Chainguard

CREATED

UPDATED

ADVISORY ID

CGA-4rpv-55mg-r83x

EXPLOITABILITY SCORE

-

EXPLOITS FOUND
-
COMMON WEAKNESS ENUMERATION (CWE)-
RATING UNAVAILABLE FROM ADVISORY

Chainguard

CREATED

UPDATED

ADVISORY ID

CGA-jx77-2gmv-4gf9

EXPLOITABILITY SCORE

-

EXPLOITS FOUND
-
COMMON WEAKNESS ENUMERATION (CWE)-
RATING UNAVAILABLE FROM ADVISORY

Photon

CREATED

UPDATED

ADVISORY ID

CVE-2025-53101

EXPLOITABILITY SCORE

-

EXPLOITS FOUND
-
COMMON WEAKNESS ENUMERATION (CWE)-

CVSS SCORE

9.8critical

minimos

CREATED

UPDATED

ADVISORY ID

MINI-vj3r-pfcr-96g9

EXPLOITABILITY SCORE

-

EXPLOITS FOUND
-
COMMON WEAKNESS ENUMERATION (CWE)-
RATING UNAVAILABLE FROM ADVISORY