ImageMagick / ImageMagick/ImageMagick
Memory leak in ReadInlineImage on unsupported "data:" URI types (regression from 71489bb)
Nobody has claimed this yet.
- Dominant language
- C
- Stars
- 17.5k
- Forks
- 1.7k
- Avg merge
- 10h 17m
- Merged PRs (30d)
- 28
Description
ImageMagick version
7.1.2-32
Operating system
Linux
Operating system, version and so on
Linux (Ubuntu 22.04, x86_64), built with clang/gcc + AddressSanitizer (Q16-HDRI)
Description
There is a memory leak in ReadInlineImage() (MagickCore/constitute.c) whenever an inline "data:" image uses a MIME subtype that is not a registered/authorized format.
ReadInlineImage() first allocates:
- blob = Base64Decode(...)
- read_info = CloneImageInfo(image_info) // ~13 KB ImageInfo
then validates the media type:
if (GetImplicitDataImageType(content, read_info->magick, exception) == MagickFalse)
ThrowReaderException(ImageError, "ImageTypeNotSupported");
ThrowReaderException (MagickCore/exception-private.h:76) expands to a plain return ((Image *) NULL), so on this validation failure both read_info and blob are leaked. The cleanup lines at the end of the function (RelinquishMagickMemory(blob); DestroyImageInfo(read_info);) are never reached.
This looks like a regression introduced by commit 71489bb ("deny inline implicit image formats", 2026-08-02, part of GHSA-3rjr-534c-8v67): the new GetImplicitDataImageType() check was inserted after the allocations but its failure path returns without freeing them. The leak is structural (control flow guarantees it), not layout dependent.
Impact: ~13 KB per triggering input. A single CLI invocation has little impact (memory is reclaimed at process exit), but long-running processes that batch-decode untrusted SVG (web upload previews, server-side thumbnailers) will grow memory without bound when fed images with unsupported data: URIs. Please assess whether this warrants a security advisory; at minimum it is a small, safe fix.
Steps to Reproduce
PoC file "poc_inline_unknown.svg":
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24">
<image width="24" height="24" xlink:href="data:image/zzz;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=="/>
</svg>
Reproduce with an ASan build (any of: cmake --preset fuzzing, or a plain -fsanitize=address build). Decode the same file twice in one process so the leak is reported by LeakSanitizer at exit:
magick poc_inline_unknown.svg poc_inline_unknown.svg info:
Expected output (ASan/LeakSanitizer report):
==PID==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 13024 byte(s) in 1 object(s) allocated from:
#0 ... in malloc
#1 ... in AcquireMagickMemory .../MagickCore/memory.c:537
#2 ... in AcquireCriticalMemory .../MagickCore/memory.c:620
#3 ... in AcquireImageInfo .../MagickCore/image.c:341
#4 ... in CloneImageInfo .../MagickCore/image.c:958
#5 ... in ReadInlineImage .../MagickCore/constitute.c:1194
#6 ... in SVGEndElement .../coders/svg.c:2751
#7 ... libxml2
Direct leak of 72 byte(s) in 1 object(s) allocated from: ...
Equivalent minimal C driver (no CLI needed), link against an ASan MagickCore build:
#include "MagickCore/MagickCore.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char** argv) {
if (argc < 2) return 1;
FILE* f = fopen(argv[1], "rb");
fseek(f, 0, SEEK_END); long n = ftell(f); fseek(f, 0, SEEK_SET);
unsigned char* b = malloc((size_t)n);
fread(b, 1, (size_t)n, f);
fclose(f);
MagickCoreGenesis(NULL, MagickFalse);
ImageInfo* info = AcquireImageInfo();
(void) strcpy(info->filename, "blob:");
ExceptionInfo* ex = AcquireExceptionInfo();
for (int i = 0; i < 2; i++) {
Image* im = BlobToImage(info, b, (size_t)n, ex);
if (im) DestroyImageList(im);
ClearMagickException(ex);
}
DestroyImageInfo(info);
DestroyExceptionInfo(ex);
MagickCoreTerminus();
free(b);
return 0;
}
Note: decoding the file only once does not report a leak (single-shot process-exit path); two decodes in one process reproduce it deterministically. Controls: a valid data:image/png;base64 URI does not leak; a zero-length base64 payload returns before the allocation and does not leak.
Affected entry points (same defect, all call ReadInlineImage()):
- coders/svg.c:2751 - SVG (shown above)
- coders/inline.c:135,177 - INLINE coder
- MagickCore/draw.c:5777 - text primitive with a data: URI
Images
No image can be attached: the bug lives on the decode-rejection path of an inline data: URI, so no output image is ever produced. The complete reproducer is the 6-line SVG text above; the base64 payload inside it is a valid 1x1 PNG, only the declared MIME subtype "image/zzz" is unsupported, which is what drives the leak path.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in MagickCore/constitute.c at ReadInlineImage(), then inspect the failure path after GetImplicitDataImageType() and the callers in coders/svg.c, coders/inline.c, and MagickCore/draw.c. Run the supplied poc_inline_unknown.svg twice through an AddressSanitizer build; done means unsupported data: URI types no longer produce LeakSanitizer reports while valid and zero-length inputs retain their behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c
- Domain
- computer-graphics
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100