{"id":"JLSEC-2026-918","summary":"ImageMagick has a Stack Buffer Overflow in image.c","details":"Hi, we have found a stack buffer overflow and would like to report this issue.\nCould you confirm if this qualifies as a security vulnerability? I am happy to provide any additional information needed.\n\n## Summary\n\nIn 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()`.\n\n### Additional information\n\nUpon 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.\n\nFurthermore, 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.\n\n## Details\n\n  - **Vulnerability Type:** CWE-124: Buffer Underwrite\n  - **Affected Component:** MagickCore/image.c - Format processing within InterpretImageFilename()\n  - **Affected Version:** ImageMagick 7.1.1-47 (as of commit 82572afc, June 2025)\n  - **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.\n\n## Reproduction\n\n### Tested Environment\n\n  - **Operating System:** Ubuntu 22.04 LTS\n  - **Architecture:** x86_64\n  - **Compiler:** gcc with AddressSanitizer (gcc version: 11.4.0)\n\n### Reproduction Steps\n\n```bash\n# Clone source\ngit clone --depth 1 --branch 7.1.1-47 https://github.com/ImageMagick/ImageMagick.git ImageMagick-7.1.1\ncd ImageMagick-7.1.1\n\n# Build with ASan\nCFLAGS=\"-g -O0 -fsanitize=address -fno-omit-frame-pointer\" CXXFLAGS=\"$CFLAGS\" LDFLAGS=\"-fsanitize=address\" ./configure --enable-maintainer-mode --enable-shared && make -j$(nproc) && make install\n\n# Trigger crash\n./utilities/magick mogrify %d%d\n```\n\n### Output\n\n```plaintext\n==4155==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffda834caae at pc 0x7f1ea367fb27 bp 0x7ffda834b680 sp 0x7ffda834ae10\nWRITE of size 2 at 0x7ffda834caae thread T0\n    #0 0x7f1ea367fb26 in __interceptor_vsnprintf ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:1668\n    #1 0x7f1ea2dc9e3e in FormatLocaleStringList MagickCore/locale.c:470\n    #2 0x7f1ea2dc9fd9 in FormatLocaleString MagickCore/locale.c:495\n    #3 0x7f1ea2da0ad5 in InterpretImageFilename MagickCore/image.c:1696\n    #4 0x7f1ea2c6126b in ReadImages MagickCore/constitute.c:1051\n    #5 0x7f1ea27ef29b in MogrifyImageCommand MagickWand/mogrify.c:3858\n    #6 0x7f1ea278e95d in MagickCommandGenesis MagickWand/magick-cli.c:177\n    #7 0x560813499a0c in MagickMain utilities/magick.c:153\n    #8 0x560813499cba in main utilities/magick.c:184\n    #9 0x7f1ea1c0bd8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58\n    #10 0x7f1ea1c0be3f in __libc_start_main_impl ../csu/libc-start.c:392\n    #11 0x560813499404 in _start (/root/workdir/ImageMagick/utilities/.libs/magick+0x2404)\n\nAddress 0x7ffda834caae is located in stack of thread T0 at offset 62 in frame\n    #0 0x7f1ea2c60f62 in ReadImages MagickCore/constitute.c:1027\n\n  This frame has 2 object(s):\n    [32, 40) 'images' (line 1033)\n    [64, 4160) 'read_filename' (line 1029) \u003c== Memory access at offset 62 underflows this variable\nHINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork\n      (longjmp and C++ exceptions *are* supported)\nSUMMARY: AddressSanitizer: stack-buffer-overflow ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:1668 in __interceptor_vsnprintf\nShadow bytes around the buggy address:\n  0x100035061900: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n  0x100035061910: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n  0x100035061920: 00 00 00 00 00 00 00 00 f3 f3 f3 f3 f3 f3 f3 f3\n  0x100035061930: f3 f3 f3 f3 f3 f3 f3 f3 00 00 00 00 00 00 00 00\n  0x100035061940: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n=\u003e0x100035061950: f1 f1 00 f2 f2[f2]00 00 00 00 00 00 00 00 00 00\n  0x100035061960: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n  0x100035061970: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n  0x100035061980: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n  0x100035061990: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n  0x1000350619a0: 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  Shadow gap:              cc\n==4155==ABORTING\n```\n\n### Affected Code\n\nIn `MagickCore/image.c`, within the `InterpretImageFilename()` function:\n\n```c\nMagickExport size_t InterpretImageFilename(const ImageInfo *image_info,\n  Image *image,const char *format,int value,char *filename,\n  ExceptionInfo *exception)\n{\n...\n  for (p=strchr(format,'%'); p != (char *) NULL; p=strchr(p+1,'%'))\n  {\n    q=(char *) p+1;\n    if (*q == '%')\n      {\n        p=q+1;\n        continue;\n      }\n    field_width=0;\n    if (*q == '0')\n      field_width=(ssize_t) strtol(q,&q,10);\n    switch (*q)\n    {\n      case 'd':\n      case 'o':\n      case 'x':\n      {\n        q++;\n        c=(*q);\n        *q='\\0';\n        /*--------Affected--------*/\n        (void) FormatLocaleString(filename+(p-format-offset),(size_t)\n          (MagickPathExtent-(p-format-offset)),p,value);\n        offset+=(4-field_width);\n        /*--------Affected--------*/\n        *q=c;\n        (void) ConcatenateMagickString(filename,q,MagickPathExtent);\n        canonical=MagickTrue;\n        if (*(q-1) != '%')\n          break;\n        p++;\n        break;\n      }\n      case '[':\n      {\n        ...\n      }\n      default:\n        break;\n    }\n  }\n```\n\n## Technical Analysis\n\nThis vulnerability is caused by an inconsistency in the template expansion processing within `InterpretImageFilename()`.\n\nThe 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)`.\n\nThe `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`.\n\nThe 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.\n\nAs 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.\n\nThis causes `vsnprintf()` to write outside the stack buffer range, which is detected by AddressSanitizer as a `stack-buffer-overflow`.\n\n## Proposed Fix\n\nIn `MagickCore/image.c`, within the `InterpretImageFilename()` function:\n\n```c\nMagickExport size_t InterpretImageFilename(const ImageInfo *image_info,\n  Image *image,const char *format,int value,char *filename,\n  ExceptionInfo *exception)\n{\n...\n  /*--------Changed--------*/\n  ssize_t\n    field_width,\n    offset,\n    written; // Added\n  /*--------Changed--------*/\n...\n  for (p=strchr(format,'%'); p != (char *) NULL; p=strchr(p+1,'%'))\n  {\n    q=(char *) p+1;\n    if (*q == '%')\n      {\n        p=q+1;\n        continue;\n      }\n    field_width=0;\n    if (*q == '0')\n      field_width=(ssize_t) strtol(q,&q,10);\n    switch (*q)\n    {\n      case 'd':\n      case 'o':\n      case 'x':\n      {\n        q++;\n        c=(*q);\n        *q='\\0';\n        written = FormatLocaleString(filename+(p-format-offset),(size_t)\n          (MagickPathExtent-(p-format-offset)),p,value);\n        /*--------Changed--------*/\n        if (written \u003c= 0 || written \u003e (MagickPathExtent - (p - format - offset)))\n          return 0;\n        offset += (ssize_t)((q - p) - written);\n        /*--------Changed--------*/\n        *q=c;\n        (void) ConcatenateMagickString(filename,q,MagickPathExtent);\n        canonical=MagickTrue;\n        if (*(q-1) != '%')\n          break;\n        p++;\n        break;\n      }\n      case '[':\n      {\n        ...\n      }\n      default:\n        break;\n    }\n  }\n```\n\n  - 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.\n  - Correction is performed according to the actual template structure, ensuring stable behavior regardless of format length without relying on static constants.\n  - Range checking of `written` allows detection of vsnprintf failures and excessive writes.\n\n### Commits\n\nFixed in https://github.com/ImageMagick/ImageMagick/commit/66dc8f51c11b0ae1f1cdeacd381c3e9a4de69774 and https://github.com/ImageMagick/ImageMagick6/commit/643deeb60803488373cd4799b24d5786af90972e","modified":"2026-07-30T18:35:57.579065048Z","published":"2026-07-30T16:02:27.435Z","upstream":["CVE-2025-53101","EUVD-2025-21389","GHSA-qh3h-j545-h8c9"],"database_specific":{"license":"CC-BY-4.0","sources":[{"database_specific":{"status":"Modified"},"id":"CVE-2025-53101","imported":"2026-07-30T14:08:44.131Z","modified":"2026-06-17T09:37:37.633Z","published":"2025-07-14T20:15:29.180Z","url":"https://services.nvd.nist.gov/rest/json/cves/2.0?cveId=CVE-2025-53101","html_url":"https://nvd.nist.gov/vuln/detail/CVE-2025-53101"},{"published":"2025-08-25T15:43:37Z","url":"https://api.github.com/advisories/GHSA-qh3h-j545-h8c9","html_url":"https://github.com/advisories/GHSA-qh3h-j545-h8c9","id":"GHSA-qh3h-j545-h8c9","imported":"2026-07-30T14:10:08.985Z","modified":"2025-11-03T21:34:10Z"},{"html_url":"https://euvd.enisa.europa.eu/vulnerability/EUVD-2025-21389","id":"EUVD-2025-21389","imported":"2026-07-30T14:08:55.836Z","modified":"2025-11-03T18:13:12Z","published":"2025-07-14T19:51:53Z","url":"https://euvdservices.enisa.europa.eu/api/enisaid?id=EUVD-2025-21389"}]},"references":[{"type":"WEB","url":"https://github.com/ImageMagick/ImageMagick/commit/66dc8f51c11b0ae1f1cdeacd381c3e9a4de69774"},{"type":"WEB","url":"https://github.com/ImageMagick/ImageMagick/security/advisories/GHSA-qh3h-j545-h8c9"},{"type":"WEB","url":"https://github.com/ImageMagick/ImageMagick6/commit/643deeb60803488373cd4799b24d5786af90972e"},{"type":"WEB","url":"https://github.com/advisories/GHSA-qh3h-j545-h8c9"},{"type":"WEB","url":"https://github.com/dlemstra/Magick.NET/releases/tag/14.7.0"},{"type":"WEB","url":"https://lists.debian.org/debian-lts-announce/2025/09/msg00012.html"},{"type":"WEB","url":"https://nvd.nist.gov/vuln/detail/CVE-2025-53101"}],"affected":[{"package":{"name":"ImageMagick_jll","ecosystem":"Julia","purl":"pkg:julia/ImageMagick_jll?uuid=c73af94c-d91f-53ed-93a7-00f77d67a9d7"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"7.1.2001+0"}]}],"database_specific":{"source":"https://github.com/JuliaLang/SecurityAdvisories.jl/tree/generated/osv/2026/JLSEC-2026-918.json"}}],"schema_version":"1.7.5","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"}],"credits":[{"name":"YutoIn","contact":["https://github.com/YutoIn"],"type":"FINDER"},{"name":"iwashiira","contact":["https://github.com/iwashiira"],"type":"FINDER"},{"name":"momo-trip","contact":["https://github.com/momo-trip"],"type":"FINDER"},{"name":"utshina","contact":["https://github.com/utshina"],"type":"FINDER"}]}