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

amdgpu 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 (DF-4338's amdgpu twin)

Summary

Byte-identical to the radeon twin: the IIO table walk indexes the 10-entry static table with a 0..255 opcode byte (up to ~990B past it in kernel .data) and the garbage stride walks past the bios allocation at attach. Guest mirror: opcode 200 -> SIGSEGV; fixed logic stops clean. Opcode-bound fix (full walk bounded by the DF-4345 port).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4346 Β· 14 files
FileTypeDescriptionSize
README.md β€” 1.1 KB ↓ raw
VERDICT.md β€” 2.1 KB ↓ raw
atom_harness.c β€” 14.3 KB view raw
atom_harness_fixed.c β€” 11.4 KB view raw
build.sh β€” 95 B view raw
run.sh β€” 99 B view raw
run_fixed.sh β€” 233 B view raw
build.log β€” 26 B view raw
run.log β€” 815 B view raw
fix_run.log β€” 1.1 KB view raw
env.txt β€” 382 B view raw
fix.diff β€” 845 B view raw
manifest.json β€” 684 B view raw
verdict.json β€” 2.3 KB view raw

DF-4346 β€” amdgpu atom_index_iio: unvalidated VBIOS opcode indexes 10-entry atom_iio_len[] (kernel .data OOB) and drives an unbounded walk past the BIOS image

sys/dev/drm/amd/amdgpu/atom.c:1291 β€” base += atom_iio_len[CU8(base)] with an opcode byte straight from the VBIOS (0..255) into the 10-entry static atom_iio_len[] (atom.c:1280). Out-of-range opcodes read past the array in kernel .data; the resulting stride walks the IIO table past the bios allocation at driver attach. Amdgpu twin of DF-4338 (radeon), previously unfiled for this file.

Reproduce (logic level β€” guest has no AMD GPU)

cc -O1 -g -o atom_harness atom_harness.c
./atom_harness iio          # opcode 200 -> atom_iio_len[200] -> SIGSEGV/139
./atom_harness_fixed iio    # op > ATOM_IIO_END -> clean stop, exit 0

See run.log case iio and fix_run.log. Kernel-runtime validation requires an AMD card with crafted VBIOS (not emulatable in QEMU).

Fix

fix.diff β€” bound the opcode before indexing (return on op > ATOM_IIO_END); full bios_size-bounded walk in the DF-4345 port.

VERDICT.md
↓ download raw

DF-4346 VERDICT β€” atom_index_iio OOB opcode index + unbounded walk (amdgpu twin of DF-4338)

Status: inconclusive (kernel runtime hardware-gated; logic-level OOB reproduced on the audit guest). Defect: certain.

Root cause

atom_index_iio (atom.c:1280-1294), byte-identical to the radeon twin DF-4338 fixed:

1287: while (CU8(base) == ATOM_IIO_START) {
1288:         ctx->iio[CU8(base + 1)] = base + 2;
1289:         base += 2;
1290:         while (CU8(base) != ATOM_IIO_END)
1291:                 base += atom_iio_len[CU8(base)];
1292:         base += 3;
1293: }
  • atom_iio_len (atom.c:1280) has 10 entries (ATOM_IIO_NOP..ATOM_IIO_END = 0..9). The opcode byte CU8(base) is unvalidated VBIOS data (0..255): atom_iio_len[CU8(base)] reads up to 990 bytes past the static array in kernel .data (info leak into the walk, garbage stride).
  • The garbage (or a crafted in-range) stride then drives base forward with no bound β€” the walk reads past the bios allocation at attach until it happens to hit ATOM_IIO_END or faults.

Runs at driver attach via amdgpu_atom_parse (atom.c:1334) on the IIO pointer taken from the master data table.

Reproduction

Guest (no AMD GPU): ./atom_harness iio β€” verbatim loop, atom_iio_len placed at a guard page. Opcode byte 200 β†’ atom_iio_len[200] β†’ SIGSEGV exit=139 (run.log, case iio). Fixed logic (./atom_harness_fixed iio) rejects op > ATOM_IIO_END and bounds the walk β†’ exit 0 (fix_run.log).

Fix

fix.diff β€” the standalone opcode-bound fix (same shape as DF-4338's): validate the opcode against ATOM_IIO_END before indexing, and stop the walk. The complete bound (walk also limited by bios_size) is included in the central DF-4345 port.

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 (standalone opcode bound, same shape as DF-4338's) passes git apply --check; fixed-logic mirror rejects opcode 200 without trapping. No kernel runtime possible on this guest.

fix.diff; fix_run.log case iio
↓ fix.diffper-fix-DF-4346

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log case iio (exit=139 at atom_iio_len[200]); fix_run.log case iio (clean stop, exit 0); fix.diff (git apply --check OK)

PoC changes

iio case carried in the shared amdgpu harness (guarded atom_iio_len, opcode 200)

Verified recommended fix

Validate the IIO opcode against ATOM_IIO_END before indexing atom_iio_len[] and bound the walk by the bios image length

Verdict

Code defect certain: atom_index_iio (amd/amdgpu/atom.c:1291) indexes the 10-entry static atom_iio_len[] with an unvalidated VBIOS opcode byte (0..255) -- an OOB read of kernel .data whose garbage stride then drives an unbounded forward walk past the bios allocation at driver attach (atom.c:1287-1293, called from amdgpu_atom_parse at atom.c:1334). Byte-identical to the radeon twin DF-4338 fixed; previously unfiled for amdgpu. Kernel runtime hardware-gated (no AMD GPU on the guest); the verbatim loop mirrored on the guest with atom_iio_len at a guard page: opcode 200 -> SIGSEGV exit 139 (run.log case iio); fixed logic (op > ATOM_IIO_END -> return) stops cleanly, exit 0 (fix_run.log).