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

Author: peter168-dotCreated Sep 9, 2026Updated Sep 9, 2026

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.