isofncmp ';version' parse: signed int overflow (UB) enables false-positive filename matches via modular wrap
Summary
The ISO9660 version-number parser accumulates digits into a signed int on both sides: user-supplied at :118 (i = i*10 + *fn++ - '0') and image-supplied at :123 (j = j*10 + c - '0'), then returns i - j at :127. Version fields >=10 digits (e.g. 'NAME;4294967297') overflow int (UB, wraps mod 2^32) so lookup('NAME;4294967297') compares i==1 and matches an image entry 'NAME;1'. isofncmp returns 0 (exact match) for unequal names; cd9660_lookup.c:233 treats 0 as exact. Image-side loop also performs no digit validation (c is an arbitrary u_short). Honest ceiling: LOW/none as a boundary crossing - iso9660 is read-only/ACL-less, a false-positive match only resolves an entry the requester could address by its true name; no memory corruption (stack locals). UB noise, ISO9660 semantics divergence, theoretical confusion vs programs treating 'X;N' as distinct. Fix: clamp accumulators + validate image-side digits. [VERIFIED pass2: reproduced - crafted plain-ISO stat NAME 4294967297 matches NAME 1 on stock via 2 to the 32 plus 1 wrapping the int accumulator; F40FIX ENOENT with NAME 1 unchanged]
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3246 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 235 B | β raw | |
| df3246.c | β | 1.0 KB | view raw | |
| fix.diff | β | 1.2 KB | view raw | |
| manifest.json | β | 503 B | view raw | |
| mkiso3246.py | β | 1.8 KB | view raw | |
| run.log | β | 445 B | view raw | |
| verdict.json | β | 2.0 KB | view raw |
DF-3246 evidence pack
What
isofncmp ';version' signed overflow enables false-positive matches
Verdict
status=reproduced impact=none confidence=certain
See VERDICT.md for the full narrative and fix.diff for the verified fix.
Fix verification
fixedA/B complete: NAME;4294967297 now ENOENT on F40FIX (was a false-positive match on stock); NAME;1 still resolves (no regression).
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md with path:line citations; run.log where a live demo exists; fix.diff
PoC changes
image generator written (host python): PVD@16/VDST@17/root@32/data@33; first two generator attempts had wrong root-record offset (must be PVD byte 156) and wrong dirrec length byte - fixed
Verified recommended fix
refuse version fields whose accumulation would overflow int (both user and image sides)
Verdict
Reproduced on the stock INVARICTIONS guest with a minimal crafted plain ISO9660 image (no RRIP/Joliet) holding one file stored as NAME;1: stat(/mnt/NAME;4294967297) SUCCEEDS (returns the inode of NAME;1) while the controls behave correctly - NAME;2 -> ENOENT, NAME;4294967298 (wraps to 2) -> ENOENT, NAME;0000000001 (10-digit but i==1) matches legitimately. 4294967297 = 2^32+1 wraps the int accumulator at cd9660_util.c:118 (i = i10 + fn++ - 0) to 1, matching the image-side j=1 via return i - j (:127); cd9660_lookup.c:233 treats 0 as an exact match. Signed overflow is UB; on this compiler it wraps. Post-fix A/B on F40FIX: NAME;4294967297 -> ENOENT (version field refused as too large).
No comments yet.