β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-4338

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4338 Β· 9 files
FileTypeDescriptionSize
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.

VERDICT.md
↓ download raw

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

inconclusive
baseline no→ patch + rebuild →patched clean

fix.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
↓ fix.diffper-fix-DF-4338

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.