Bug 709029 - poc_vuln_C810149D-1: Double-free in fz_fill_pixmap_from_display_list error path (barcode API)
Summary: poc_vuln_C810149D-1: Double-free in fz_fill_pixmap_from_display_list error pa...
Status: RESOLVED FIXED
Alias: None
Product: MuPDF
Classification: Unclassified
Component: fitz (show other bugs)
Version: master
Hardware: All All
: P2 normal
Assignee: MuPDF bugs
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2026-01-12 18:04 UTC by Pavel Kohout
Modified: 2026-02-06 22:22 UTC (History)
3 users (show)

See Also:
Customer:
Word Size: ---


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Pavel Kohout 2026-01-12 18:04:29 UTC
## Summary

Double-free vulnerability in `fz_fill_pixmap_from_display_list()` when an error occurs during display list rendering. The function incorrectly drops a caller-owned pixmap in its error path before rethrowing, causing a double-free when callers also drop the same pixmap in their cleanup blocks. This leads to heap corruption and denial of service in applications using the MuPDF barcode decoding API.

## Vulnerable Code

`source/fitz/util.c:125-149`

```c
// source/fitz/util.c:125-149
fz_pixmap *
fz_fill_pixmap_from_display_list(fz_context *ctx, fz_display_list *list, fz_matrix ctm, fz_pixmap *pix)
{
    fz_device *dev = NULL;
    fz_var(dev);
    fz_try(ctx)
    {
        dev = fz_new_draw_device(ctx, ctm, pix);
        fz_run_display_list(ctx, list, dev, fz_identity, fz_infinite_rect, NULL);
        fz_close_device(ctx, dev);
    }
    fz_always(ctx)
    {
        fz_drop_device(ctx, dev);
    }
    fz_catch(ctx)
    {
        fz_drop_pixmap(ctx, pix);   // <-- BUG: drops caller-owned pixmap
        fz_rethrow(ctx);
    }
    return pix;
}
```

Affected caller demonstrating the double-free pattern (`source/fitz/barcode.c:162-189`):

```c
// source/fitz/barcode.c:173-186
pix = fz_new_pixmap_with_bbox(ctx, fz_device_gray(ctx), bbox, NULL, 0);
fz_clear_pixmap_with_value(ctx, pix, 0xFF);
fz_try(ctx)
{
    fz_fill_pixmap_from_display_list(ctx, list, fz_identity, pix);
    str = fz_decode_barcode_from_pixmap(ctx, type, pix, rotate);
}
fz_always(ctx)
{
    fz_drop_pixmap(ctx, pix);   // <-- DOUBLE-FREE if error occurred above
}
```

## Root Cause

The function `fz_fill_pixmap_from_display_list()` receives a pixmap from the caller but incorrectly assumes ownership during error handling. Unlike `fz_new_pixmap_from_display_list_with_separations()` which creates the pixmap internally (and thus owns it), this function operates on a caller-provided pixmap and should not drop it on error.

When an exception occurs inside the function (e.g., OOM during `fz_new_draw_device` or `fz_run_display_list`):
1. The function's `fz_catch` block drops the pixmap (refcount → 0, freed)
2. The function rethrows the exception
3. The caller's `fz_always` block drops the same pixmap again → **double-free**

Introduced in commit `c810149dfd28799cf7f6b40043645cade9bf02b8` (2023-04-28) with message: "Add fz_fill_pixmap_from_display_list()."

## Affected Functions

The vulnerability is triggered through:

- `fz_decode_barcode_from_display_list()` - Primary affected caller in `source/fitz/barcode.c`
- `DisplayList.decodeBarcode()` - JavaScript API via `mutool run`
- Java bindings - `DisplayList.decodeBarcode()` in `platform/java/jni/displaylist.c`
- Any application directly calling `fz_fill_pixmap_from_display_list()` with caller-owned pixmaps

**Prerequisites:**
- Application must use the barcode decoding API (standard PDF viewing is NOT affected)
- An error must occur during `fz_run_display_list()` (e.g., OOM, malformed content)

**NOT affected:**
- `mutool draw`, `mutool convert` - use different code paths
- `fz_decode_barcode_from_page()` - creates its own device, doesn't use vulnerable function
- Standard PDF rendering without barcode extraction

## Reproduction

### Triggering via mutool run with JavaScript

The intended reproduction method uses MuPDF's JavaScript bindings via `mutool run` with the `DisplayList.decodeBarcode()` API:

Create a large PDF to trigger OOM (`create_malicious_pdf.py`):
```python
#!/usr/bin/env python3
import zlib

content = b"q 0.5 g 0 0 200000 200000 re f Q"
compressed = zlib.compress(content)

pdf = f"""%PDF-1.4
1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj
2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj
3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 100000 100000] /Contents 4 0 R >> endobj
4 0 obj << /Length {len(compressed)} /Filter /FlateDecode >> stream
""".encode() + compressed + b"""
endstream endobj
trailer << /Root 1 0 R >>
%%EOF"""

with open("malicious.pdf", "wb") as f:
    f.write(pdf)
```

Create JavaScript trigger (`trigger_vuln.js`):
```javascript
var doc = Document.openDocument("malicious.pdf");
var page = doc.loadPage(0);
var displayList = page.toDisplayList();

// This calls fz_decode_barcode_from_display_list() -> VULNERABLE
try {
    var result = displayList.decodeBarcode();
} catch (e) {
    print("Error: " + e);
}
```

Build and run:
```bash
git clone --recursive https://github.com/ArtifexSoftware/mupdf.git
cd mupdf
make build=sanitize HAVE_ZXINGCPP=yes -j$(nproc)
./build/sanitize/mutool run trigger_vuln.js
```

**Expected result:** AddressSanitizer reports:
```
==XXXX==ERROR: AddressSanitizer: attempting double-free on 0xXXXXXXXX
```

### Reproduction Limitations

**We were unable to trigger this vulnerability with a PDF file and direct command.**

The vulnerable code path requires barcode support, which depends on the `zxing-cpp` third-party library. This functionality is **disabled by default** in MuPDF builds (`HAVE_ZXINGCPP=no`).

Attempts to build with barcode support enabled (`HAVE_ZXINGCPP=yes`) encountered compilation errors:

- The zxing-cpp submodule references a specific commit not available in the upstream repository
- Recent zxing-cpp versions require C++20 features (`std::countr_zero`, `std::numbers::pi`, `std::popcount`)
- Version mismatches between MuPDF's Makefile and available zxing-cpp releases result in missing symbols

**Conclusion:** The vulnerability exists in the source code and would affect applications that successfully compile with barcode support enabled. However, since barcode support is disabled by default and has compilation challenges, the practical exploitability is limited to specific deployment scenarios where barcode functionality has been explicitly enabled and working

## Impact

- **Immediate:** Heap corruption, application crash (DoS)
- **Potential:** Use-after-free exploitation if memory is reallocated between the two frees; heap metadata corruption could enable arbitrary write primitives depending on allocator (glibc ptmalloc, jemalloc, etc.)

**Constraints:**
- Limited attack surface: only affects applications using barcode decoding API
- Requires triggering an error during display list rendering (OOM, malformed content)
- Standard PDF viewers are NOT affected
- Barcode support is disabled by default and has compilation challenges

**CVSS v3.1:** 5.3 (Medium) - AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:N/A:H
- AC:H due to limited attack surface (barcode API only, disabled by default)

## Mitigation / Fix

Remove the `fz_drop_pixmap()` call from the catch block. The caller owns the pixmap and is responsible for cleanup in all cases.

```diff
--- a/source/fitz/util.c
+++ b/source/fitz/util.c
@@ -141,7 +141,6 @@ fz_fill_pixmap_from_display_list(...)
 	}
 	fz_catch(ctx)
 	{
-		fz_drop_pixmap(ctx, pix);
 		fz_rethrow(ctx);
 	}
```

**Side effects:** None. This restores correct ownership semantics where the caller manages the pixmap lifecycle.

**Alternative fix:** Take a reference with `fz_keep_pixmap()` at function entry and drop it on all exit paths, but this is more invasive and unnecessary.

## Detection

**Vulnerable versions:**
- All versions since commit `c810149dfd28799cf7f6b40043645cade9bf02b8` (2023-04-28)
- Function `fz_fill_pixmap_from_display_list()` with `fz_drop_pixmap` in catch block

**Detection method:**
```bash
grep -A 30 "^fz_fill_pixmap_from_display_list" source/fitz/util.c | \
    grep -A 5 "fz_catch" | grep -q "fz_drop_pixmap.*pix" && \
    echo "VULNERABLE" || echo "PATCHED"
```

**Runtime signals:**
- ASan: `attempting double-free`
- Crash in `free()` or allocator functions
- Heap corruption symptoms after barcode decoding errors

## Timeline

- **2023-04-28:** Vulnerable code introduced in commit c810149d
- **2026-01-12:** Vulnerability analyzed and PoC created
- **TBD:** Reported to vendor
- **TBD:** Fix released

## References

- CWE-415: Double Free
- Vulnerable commit: `c810149dfd28799cf7f6b40043645cade9bf02b8`
- Affected file: `source/fitz/util.c`
- Affected caller: `source/fitz/barcode.c`
- MuPDF repository: https://github.com/ArtifexSoftware/mupdf



## Credits

Pavel Kohout
Aisle Research
www.aisle.com
Comment 1 Robin Watts 2026-01-13 17:06:13 UTC
Fixed in:

commit d4743b6092d513321c23c6f7fe5cff87cde043c1
Author: Robin Watts <Robin.Watts@artifex.com>
Date:   Mon Jan 12 19:08:56 2026 +0000

    Bug 709029: Fix incorrect error-case free of pixmap.

    Don't free a pixmap we don't own!

Thanks for the report!
Comment 2 Pavel Kohout 2026-01-19 13:50:11 UTC
Hi Robin,

I am realising now that I have not submitted this as a security issue. I am sorry about it...do you consider this finding a security issue?

Regards,

Pavel
Comment 3 Pavel Kohout 2026-01-26 12:57:32 UTC
Hi Robbin,

Are you planning to request a CVE for this? If not, are you OK if I do it myself?

What do you think?

Regards,

Pavel
Comment 4 Robin Watts 2026-01-26 13:18:23 UTC
Hi,

(In reply to Pavel Kohout from comment #2)
> I am realising now that I have not submitted this as a security issue. I am
> sorry about it...do you consider this finding a security issue?

Frankly, no, we don't consider things like this to be security issues unless you can produce a document that demonstrates an active exploit using it.

> Are you planning to request a CVE for this? If not, are you OK if I do it
> myself?

As a rule, we don't apply for CVEs, we leave that to the reporter.

In my personal opinion, this does not really seem worthy of a CVE, but we can't stop you applying if you really want to.

Thanks.
Comment 5 Sebastian Rasmussen 2026-01-26 16:30:07 UTC
That said, if a CVE number is assigned it would be good for us to know about it.
Comment 6 Salvatore Bonaccorso 2026-02-06 20:39:18 UTC
Not the reporter here, but while triaging CVEs in a downstream, I noticed that https://www.cve.org/CVERecord?id=CVE-2026-25556 is assigned for it.
Comment 7 Pavel Kohout 2026-02-06 22:22:39 UTC
Thank you, seems like you were faster than me.