atom_index_iio indexes the 10-entry static atom_iio_len[] with an unvalidated VBIOS opcode byte (OOB read of kernel .data) and the garbage stride drives an unbounded walk past the BIOS image at attach
Summary
The IIO table walk is 'while (CU8(base) != END) base += atom_iio_len[CU8(base)];' - the byte is 0..255 but the table has 10 entries, so opcodes >9 read adjacent kernel globals and the garbage stride walks past the bios allocation - hang or page-fault panic during atom_parse. Identical twin in the amdgpu atom.c. Guest logic PoC (opcode 200 -> SIGSEGV; fixed logic stops cleanly). Range-check fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4338 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.6 KB | β raw | |
| VERDICT.md | β | 1.9 KB | β raw | |
| atom_harness.c | β | 7.5 KB | view raw | |
| run.sh | β | 99 B | view raw | |
| build.log | β | 7 B | view raw | |
| run_cases.log | β | 507 B | view raw | |
| env.txt | β | 467 B | view raw | |
| fix.diff | β | 557 B | view raw | |
| verdict.json | β | 2.3 KB | view raw |
DF-4338 β atom_index_iio indexes a 10-entry static array with an unvalidated VBIOS byte
Severity: Medium Confidence: certain File: sys/dev/drm/radeon/atom.c:1254-1268
What
static int atom_iio_len[] = { 1, 2, 3, 3, 3, 3, 4, 4, 4, 3 }; /* 10 entries */
...
while (CU8(base) != ATOM_IIO_END)
base += atom_iio_len[CU8(base)]; /* CU8(base) is 0..255 */
The IIO (indirect-I/O) table walker takes its stride from a raw VBIOS byte.
atom_iio_len has 10 entries (opcodes ATOM_IIO_NOP..ATOM_IIO_END, 0..9);
any opcode byte > 9 reads out of bounds in the kernel .data (adjacent
globals), and the garbage stride then walks base forward through β and
past β the bios heap allocation with no termination condition other than
stumbling on a byte equal to ATOM_IIO_END (9). A crafted IIO table
therefore gives: OOB read of static kernel data + an unbounded OOB walk
(kernel hang at attach while parsing, or page-fault panic).
Reached from atom_parse() (atom.c:1309) at driver attach on every ATOM
card; the amdgpu twin has the identical function (amd/amdgpu/atom.c:1281).
Reproduce (logic level; kernel runtime needs ATOM hardware β env.txt)
atom_harness.c compiles the verbatim loop with atom_iio_len relocated
onto a guard page; the iio case plants opcode 200:
cc -O1 -g -o atom_harness atom_harness.c && ./atom_harness iio # -> Segmentation fault (exit 139) == atom_iio_len[200] is OOB
Fix
fix.diff (git-apply checked): range-check the opcode (op > ATOM_IIO_END
β stop) before indexing. The bios_size bound for the walk itself is part of
DF-4337's central fix.
DF-4338 VERDICT
Status: inconclusive (kernel runtime hardware-gated; logic-level OOB reproduced on the audit guest). Defect: certain.
Root cause
atom.c:1254 atom_iio_len[] has 10 entries; atom.c:1265
base += atom_iio_len[CU8(base)] indexes it with an unvalidated VBIOS byte
(0..255). OOB read of static kernel data (.data adjacency), and the loaded
garbage stride drives the while (CU8(base) != ATOM_IIO_END) walk
unboundedly forward from the IIO table (atom.c:1264-1265) β through the rest
of the bios allocation and beyond. Called once per attach from
atom_parse (atom.c:1309). The amdgpu twin is identical
(amd/amdgpu/atom.c:1281-1293) and must be fixed in parallel.
Reproduction
Guest has no ATI GPU (env.txt) β kernel path unrunnable. Logic-level PoC on
the guest: verbatim loop, atom_iio_len on a guard-page-backed mapping,
bios image 512 bytes with IIO opcode byte 200 at 0x42.
Result (run_cases.log): iio case β Segmentation fault, exit 139:
atom_iio_len[200] reads 800 bytes past the 10-int table. In-kernel the
same expression reads adjacent globals and then the walk continues with an
uncontrolled stride (hang or OOB walk until page fault).
Fix validation (logic level)
fix.diff: int op = CU8(base); if (op > ATOM_IIO_END) return; before the
index. git apply --check OK. Fixed-logic harness (DF-4337's
atom_harness_fixed.c iio case) β "walk stopped cleanly on opcode 200",
exit 0 (fix_run.log in DF-4337/, same pack content).
Kernel-build/on-hardware validation not possible on this guest (no ATOM hardware) β fix_status inconclusive.
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
inconclusivefix.diff git apply --check OK; fixed-logic mirror stops cleanly on opcode 200 (exit 0) where baseline traps (139). Kernel-runtime validation impossible on this guest (no ATOM hardware).
fix.diff; DF-4337/fix_run.log iio case
Confirmed kernel references
Detail
Evidence (decisive lines)
run_cases.log [iio] Segmentation fault exit=139; VERDICT.md; DF-4337/fix_run.log iio fixed case (exit 0)
PoC changes
no seed; atom_iio_len relocated onto a guard-page mapping in the mirror so the OOB index traps instead of silently reading adjacent globals (declaration-only change, expression verbatim)
Verified recommended fix
Range-check the IIO opcode byte (op > ATOM_IIO_END -> stop) before indexing atom_iio_len[]
Verdict
Code defect certain: atom_index_iio (atom.c:1264-1265) indexes the 10-entry static array atom_iio_len with an unvalidated VBIOS opcode byte (0..255) β an OOB read of kernel .data whose garbage value then drives an unbounded forward walk through/past the bios allocation at driver attach. Kernel runtime hardware-gated (guest has no ATI GPU); the verbatim loop mirrored on the guest with atom_iio_len on a guard page SIGSEGVs (exit 139) on opcode 200, proving the OOB index. Fix (opcode > ATOM_IIO_END check) is git-apply clean and the fixed-logic mirror stops cleanly.
No comments yet.