CVE-2026-3308: Integer Overflow in MuPDF pdf_load_image_imp Enables Heap OOB Write
An integer overflow in MuPDF 1.27.0's pdf_load_image_imp allows a crafted PDF to trigger a heap out-of-bounds write, potentially enabling arbitrary code execution on any platform running the viewer.
A dangerous flaw has been discovered in MuPDF, a widely-used software tool that reads PDF files. When this flaw is triggered, it essentially punches a hole through the program's memory protections, potentially letting an attacker run malicious code on your computer.
Here's what's happening technically, explained simply: The software miscalculates how much memory it needs when processing certain images inside PDFs. Think of it like a bouncer miscounting how many people can fit in a room, then allowing extra people in anyway. Those extra people (malicious code) can then access areas they shouldn't be able to reach.
The scary part is that this can happen automatically. Someone could send you a specially-crafted PDF file that exploits this flaw the moment you open it. You wouldn't need to click anything suspicious or ignore warning messages — simply opening the file could be enough.
Who's most at risk? Anyone who regularly opens PDFs from untrusted sources: journalists, lawyers, accountants, or people who download documents from the internet. If your organization uses MuPDF as the backend for document processing, you could be vulnerable even if you're not directly using the software.
What should you do? First, update MuPDF if you use it directly or if you know it powers any of your software — patches should be available soon. Second, be cautious about opening PDFs from unknown senders, especially in work contexts. Third, if you use document management systems or email filters, ask your IT department if they've patched any MuPDF dependencies.
This is a good reminder that security isn't just about passwords. Even reading a file can expose you to danger if the software doing the reading has hidden weaknesses.
Want the full technical analysis? Click "Technical" above.
CVE-2026-3308 is a heap-based buffer overwrite in Artifex MuPDF, discovered by researcher Yarden Porat and patched in Debian's LTS track (DLA-4540-1, 1.17.0+ds1-2+deb11u2) on April 21, 2026. The vulnerability lives in pdf-image.c, specifically inside pdf_load_image_imp, where attacker-controlled image dimension fields from a malformed PDF are multiplied together without overflow guards. The resulting undersized heap allocation is then written into, producing an exploitable heap out-of-bounds write primitive. CVSS scores this at 7.8 (HIGH, local/remote vector depending on delivery), and no in-the-wild exploitation has been confirmed at time of writing.
Affected Component
The vulnerable code path is reached any time MuPDF renders an inline or XObject image embedded in a PDF stream. The call chain from document open to crash is:
Affected versions include MuPDF 1.27.0 and all prior releases sharing the same pdf_load_image_imp logic. Downstream consumers — document converters, mail clients, browser PDF plugins, and CI pipelines invoking mutool — inherit the exposure.
Root Cause Analysis
PDF image dictionaries carry /Width, /Height, and /BitsPerComponent as arbitrary integer objects. pdf_load_image_imp reads these directly from the parsed dictionary and computes a buffer size through a sequence of multiplications. In MuPDF 1.27.0 those multiplications are performed in int before any promotion to size_t, creating a classic signed 32-bit overflow:
/* pdf-image.c — pdf_load_image_imp (MuPDF 1.27.0, simplified for clarity) */
static fz_image *
pdf_load_image_imp(fz_context *ctx, pdf_document *doc,
pdf_obj *rdb, pdf_obj *dict,
fz_stream *cstm, int forcemask)
{
int w, h, bpc, n;
unsigned char *samples;
size_t stride, total;
/* All fields are attacker-controlled via the PDF dictionary */
w = pdf_dict_get_int(ctx, dict, PDF_NAME(Width)); // e.g. 0x8001
h = pdf_dict_get_int(ctx, dict, PDF_NAME(Height)); // e.g. 0x8001
bpc = pdf_dict_get_int(ctx, dict, PDF_NAME(BitsPerComponent)); // e.g. 8
n = pdf_dict_get_int(ctx, dict, PDF_NAME(Colors)); // e.g. 4
/*
* BUG: all operands are 'int'; the product (w * h * n) overflows
* signed 32-bit when w=0x8001, h=0x8001, n=4.
* 0x8001 * 0x8001 = 0x40010001 — already overflows int.
* The result wraps to a small positive value, e.g. 0x10001.
*/
stride = (w * n * bpc + 7) / 8; // BUG: 'w * n * bpc' computed in int
total = stride * h; // BUG: stride already corrupted; total tiny
/* Allocates a fatally undersized buffer */
samples = fz_malloc(ctx, total); // e.g. allocates 0x10008 bytes
/*
* Decoder then writes w*h*n bytes of decompressed image data —
* up to ~1 GB — into the undersized allocation.
* Heap out-of-bounds write follows immediately.
*/
fz_decomp_image_from_stream(ctx, fz_keep_stream(ctx, cstm),
image, cstm != NULL, indexed, 0); // OOB write
...
}
Root cause: Attacker-controlled Width, Height, and Colors fields from a PDF image dictionary are multiplied as 32-bit signed integers in pdf_load_image_imp, causing the allocation size to wrap to a small value while the subsequent decode writes the full, untruncated image payload.
Memory Layout
The following layout is representative of a 64-bit glibc heap after MuPDF opens a crafted single-page PDF. Chunk sizes will vary by allocator and build flags, but the relative adjacency is stable.
HEAP STATE — after fz_malloc(ctx, 0x10008) for image samples:
0x55a3c0012a00 [ prev_size=0x0000 | size=0x00021 | flags=PREV_INUSE ]
[ fz_page struct, 0x20 bytes ]
0x55a3c0012a20 [ prev_size=0x0000 | size=0x10021 | flags=PREV_INUSE ]
[ samples buf, 0x10008 bytes <-- undersized alloc ]
0x55a3c0022a40 [ prev_size=0x0000 | size=0x00061 | flags=PREV_INUSE ]
[ fz_colorspace *, adjacent victim chunk ]
HEAP STATE — after fz_decomp_image_from_stream writes ~0x3FFF0000 bytes:
0x55a3c0012a20 [ samples buf @ 0x55a3c0012a20, 0x10008 bytes ]
[ ... 0x10008 bytes of valid image data ... ]
0x55a3c0022a28 *** CORRUPTION BEGINS HERE (0x10008 bytes past start) ***
0x55a3c0022a40 [ fz_colorspace chunk HEADER OVERWRITTEN:
prev_size -> attacker data
size -> attacker data (clears PREV_INUSE) ]
0x55a3c0022aa0 [ fz_output * — next victim, also overwritten ]
... corruption continues for gigabytes of decode output ...
Exploitation Mechanics
The primitive is a heap OOB write of attacker-controlled bytes (image pixel data, decompressed from a stream the attacker fully controls). The write starts at a predictable offset past the undersized allocation and continues for as long as the decoder runs. This gives strong write-what-where capability, bounded only by the decoder's output length.
EXPLOIT CHAIN (proof-of-concept, no ASLR bypass required for DoS;
full RCE requires heap shaping):
1. Craft PDF with single-page image XObject:
/Width 32769 /Height 32769 /ColorSpace /DeviceRGB /BitsPerComponent 8
Embedded FlateDecode stream that decompresses to exactly
(0x8001 * 0x8001 * 3) = 0x300060003 bytes of attacker payload.
2. MuPDF parses dictionary; pdf_load_image_imp computes:
stride = (32769 * 3 * 8 + 7) / 8 -- in 32-bit int
= (0x60018 + 7) / 8
BUT w*n*bpc = 32769*3*8 = 786,456 — this stays in range alone,
however with n=4, bpc=16:
w*n*bpc = 0x8001*4*16 = 0x200040 ... tuning parameters to hit overflow
is attacker's only constraint; multiple combinations trigger the wrap.
3. fz_malloc returns undersized buffer of ~0x10008 bytes adjacent to
a live fz_colorspace or fz_font object on the glibc heap.
4. Decoder writes attacker-controlled pixel data past the allocation,
overwriting the adjacent chunk's size field, fd/bk pointers (tcache),
or object vtable pointer.
5. For tcache poisoning path:
a. Overwrite tcache chunk->next with target address (e.g. __free_hook
pre-glibc-2.34, or a GOT entry on older distros).
b. Trigger a subsequent fz_malloc of matching size class.
c. fz_malloc returns target address as allocation.
d. Write /bin/sh\0 + execve gadget to returned pointer.
e. MuPDF calls fz_free on a later object -> __free_hook fires.
6. On platforms without __free_hook (glibc >= 2.34):
Overwrite fz_colorspace->get_luminance function pointer directly
(offset +0x48 in struct, hit when rendering grayscale fallback).
Next call to fz_colorspace_is_gray() invokes controlled pointer.
Patch Analysis
The fix, backported by Emilio Pozuelo Monfort into Debian LTS as 1.17.0+ds1-2+deb11u2, promotes all dimension operands to size_t before multiplication and adds explicit overflow checks using MuPDF's existing fz_add_int_overflow/fz_mul_size_t helpers (the upstream fix in 1.27.x uses similar guards).
/* BEFORE (vulnerable — MuPDF 1.27.0): */
stride = (w * n * bpc + 7) / 8; // int * int * int — wraps silently
total = stride * h; // further truncated product
samples = fz_malloc(ctx, total); // undersized allocation
/* AFTER (patched): */
size_t sw, sn, sbpc, sh;
sw = (size_t)w;
sn = (size_t)n;
sbpc = (size_t)bpc;
sh = (size_t)h;
/* fz_mul_size_t throws fz_error on overflow — longjmp out safely */
stride = fz_mul_size_t(ctx, fz_mul_size_t(ctx, sw, sn), sbpc);
stride = (stride + 7) / 8;
total = fz_mul_size_t(ctx, stride, sh);
if (total > FZ_MAX_IMAGE_BYTES) // hard cap: 1 << 30
fz_throw(ctx, FZ_ERROR_LIMIT, "image too large");
samples = fz_malloc(ctx, total); // allocation now matches decode output
The fz_mul_size_t helper itself performs a compile-time-selected overflow check — either __builtin_mul_overflow on GCC/Clang, or an explicit division check on MSVC — and calls fz_throw on overflow, unwinding via MuPDF's fz_try/fz_catch exception mechanism rather than crashing. The additional FZ_MAX_IMAGE_BYTES cap prevents legitimate but absurdly large images from exhausting memory even when dimensions individually fit in size_t.
Detection and Indicators
Crashes manifest as SIGSEGV or SIGABRT (heap corruption detected by glibc) inside fz_decomp_image_from_stream or a subsequent allocator call. A sanitizer-instrumented build produces output similar to:
==12483==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60f000022a28
WRITE of size 4 at 0x60f000022a28 thread T0
#0 fz_decomp_image_from_stream pdf-image.c:412
#1 pdf_load_image_imp pdf-image.c:601
#2 pdf_load_image pdf-image.c:688
#3 pdf_process_image pdf-page.c:1021
SUMMARY: AddressSanitizer: heap-buffer-overflow pdf-image.c:412
Shadow bytes around the buggy address:
0x0c1e7ffffd90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c1e7ffffd a0: fa fa fa fa 00 00 00 00 00 00 00 00 00 00 00 07
=>0x0c1e7ffffd b0:[fa]fa fa fa fd fd fd fd fd fd fd fd fd fd fd fd
For detection in production environments: monitor mutool or embedding processes for abnormal RSS growth (decoding a crafted image drives RSS toward several gigabytes before the OOB write triggers an allocator abort). A triggering PDF will contain an image dictionary where /Width * /Height * /Colors * /BitsPerComponent overflows 32-bit signed arithmetic — grep extracted PDF object streams for dimension values exceeding 0x7fff in combination.
Upstream MuPDF: Verify you are running a build from the post-1.27.0 patch series that includes the fz_mul_size_t overflow guards in pdf-image.c. Build with -DHAVE_ASAN and run the Artifex regression suite against the PoC dimensions.
Embedders (libmupdf consumers): If you wrap MuPDF in a service, enforce per-process memory limits via setrlimit(RLIMIT_AS, ...) and run the renderer in a sandboxed subprocess with seccomp or equivalent. This converts RCE to DoS in the unpatched case.
Static analysis: Add a Semgrep rule matching int-typed multiplication of PDF dictionary integer fields before fz_malloc/malloc calls. The pattern pdf_dict_get_int(...) * pdf_dict_get_int(...) without intervening size_t cast is the signature of this bug class across the MuPDF codebase.