ImageMagick / ImageMagick/ImageMagick

Memory leak in ReadInlineImage on unsupported "data:" URI types (regression from 71489bb)

Open Beginner friendly
#8,949 1 comment 0 reactions 0 assignees View on GitHub

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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.