Feb 24, 2026·5 min read·6 visits
ImageMagick has a feature to limit JPEG file size by brute-forcing quality settings. A logic error meant that if the encoder hiccuped (returned false), the loop would retry without changing parameters—forever. Update to 7.1.2-15 or 6.9.13-40 immediately.
A critical Denial of Service (DoS) vulnerability in ImageMagick's JPEG encoder allows attackers to trigger an infinite loop by abusing the `jpeg:extent` feature. By forcing a write failure during the file-size optimization process, the application enters a CPU-exhausting cycle that hangs the process indefinitely.
ImageMagick is the duct tape of the internet. If you upload a profile picture to a website, chances are some backend script is piping it through magick before it hits an S3 bucket. One of its handier, albeit computationally expensive, features is the jpeg:extent define.
This feature allows a user to say, "I don't care about the quality, just make sure this JPEG is under 50KB." Since JPEG compression isn't linear, ImageMagick can't just calculate the perfect quality setting mathematically. Instead, it does what computers do best: it guesses.
It implements a binary search, repeatedly encoding the image at different quality levels (0-100) until it finds the highest quality that fits within the target file size. It’s a brute-force optimization loop running inside your image processor. And as we know, loops are where the demons live.
A robust binary search needs three things: a lower bound, an upper bound, and a way to exit. In coders/jpeg.c, ImageMagick sets up this search loop to iterate through quality settings. Inside the loop, it calls WriteJPEGImage to generate a candidate image, checks its size, and adjusts the bounds accordingly.
But what happens if WriteJPEGImage fails? Not because the image is too big, but because of a resource error, an I/O glitch, or a library hiccup? The function returns MagickFalse.
The developers handled this error condition with a single instruction: continue.
Here lies the logic bomb. In a for loop, continue jumps to the increment step. But this was a while loop logic where the bounds (minimum/maximum quality) were only updated after a successful write. By hitting continue, the code skipped the logic that changes the loop state. It essentially said: "That failed. Let's try the exact same thing, with the exact same parameters, again."
It is the literal definition of insanity: doing the same thing over and over and expecting a different result. The process spins at 100% CPU, trying to encode a ghost image until the heat death of the universe or a sysadmin kill -9s it.
Let's look at the "smoking gun" in coders/jpeg.c. This is a classic example of how a single control flow statement can sink a battleship.
Vulnerable Logic (Simplified):
/* coders/jpeg.c before patch */
while (minimum <= maximum) {
// ... setup logic ...
status = WriteJPEGImage(extent_info, jpeg_image, exception);
if (status == MagickFalse)
continue; // <--- THE KILLER. Re-loops without changing state.
// The code below is unreachable if status is false
if (GetBlobSize(jpeg_image) <= extent)
minimum = jpeg_image->quality + 1;
else
maximum = jpeg_image->quality - 1;
}The fix was embarrassingly simple. If the write fails, stop trying to optimize. Abort the loop.
The Fix:
- if (status == MagickFalse)
- continue;
+ if (status == MagickFalse)
+ break;By changing continue to break, the loop terminates upon failure, returning control (and the error) to the caller rather than entering an infinite spin cycle.
To weaponize this, an attacker needs two ingredients: access to pass the jpeg:extent parameter (often exposed in resizing APIs) and a way to make WriteJPEGImage fail consistently inside that loop.
WriteJPEGImage can fail for various reasons:
An attacker targeting a web service that uses ImageMagick (e.g., convert input.jpg -define jpeg:extent=50kb output.jpg) could try to flood the service with requests. If they can trigger a condition where the temporary file creation inside the loop fails, the service enters a zombie state. Send enough of these, and you starve the server's CPU cores one by one until the load average hits the stratosphere.
This is a pure Availability vulnerability. There is no Remote Code Execution (RCE) here, and you aren't stealing database credentials. However, in the world of cloud computing, CPU cycles equal money, and availability equals trust.
A single malicious request can permanently peg a CPU core. If your server processes images on 4 cores, it only takes 4 requests to completely lock up the service. Because the loop is tight (CPU-bound), it doesn't wait for I/O, meaning it generates maximum heat and maximum lag.
For auto-scaling cloud groups, this is particularly nasty. The high CPU usage might trigger your auto-scaler to spin up more instances. The attacker continues to send requests, locking those up too. Essentially, this vulnerability allows an attacker to financially DoS you by forcing you to pay for a fleet of servers doing absolutely nothing but failing to compress a JPEG.
The remediation is straightforward: update your libraries. The patch exists in ImageMagick 7.1.2-15 and 6.9.13-40. If you are using .NET, pull the latest Magick.NET (14.10.3).
If you cannot patch immediately, you must sanitize your inputs. If your application allows users to pass arbitrary arguments to ImageMagick (which is a terrible idea for many other reasons), block the jpeg:extent define.
Detection:
Monitor your infrastructure for processes named magick or convert that have high CPU time but aren't exiting. A process running for 5+ minutes at 99% CPU is a strong indicator that you've hit this loop.
CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
ImageMagick ImageMagick | < 7.1.2-15 | 7.1.2-15 |
ImageMagick (Legacy) ImageMagick | < 6.9.13-40 | 6.9.13-40 |
Magick.NET dlemstra | < 14.10.3 | 14.10.3 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-835 |
| Attack Vector | Local / Context-Dependent |
| CVSS Score | 6.2 |
| Impact | High (Availability) |
| Exploit Status | PoC Pending |
| Patch Date | 2026-02-13 |
Loop with Unreachable Exit Condition ('Infinite Loop')