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

amdgpu VBIOS part-number string walked and printed past the BIOS image: unvalidated idx deref + %s/strlcpy on an unterminated source (DF-4339's amdgpu twin, worse shape β€” no bounded copy at all)

Summary

The amdgpu parse tail differs from radeon's name[512] loop: the 16-bit part-string offset is unvalidated, and CSTR(idx) goes straight to pr_info('%s') and strlcpy - both walk the source until a NUL that may live past the bios allocation, leaking adjacent kernel-heap bytes to the console/log and into vbios_version. Guest mirror: unterminated string to the guard -> SIGSEGV; fixed requires NUL inside the image. Bounded-copy fix (full bound in the port).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4347 Β· 14 files
FileTypeDescriptionSize
README.md β€” 1.1 KB ↓ raw
VERDICT.md β€” 2.6 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 β€” 103 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 β€” 731 B view raw
manifest.json β€” 705 B view raw
verdict.json β€” 2.4 KB view raw

DF-4347 β€” amdgpu VBIOS part-number string walked/printed past the BIOS image (%s + strlcpy on unterminated source)

sys/dev/drm/amd/amdgpu/atom.c:1340-1348 β€” idx = CU16(0x6E) is an unvalidated 16-bit VBIOS offset; str = CSTR(idx) is then passed to pr_info("ATOM BIOS: %s\n", str) and strlcpy(vbios_version, str, 20). Both walk the source until a NUL that may live past the bios allocation: kernel heap bytes end up in the console/log and (≀19 bytes) in ctx->vbios_version. Amdgpu twin of DF-4339 (radeon), with a worse shape (no bounded intermediate copy at all). Previously unfiled for this file.

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

cc -O1 -g -o atom_harness atom_harness.c
./atom_harness partstr       # string to image end, no NUL -> SIGSEGV/139
./atom_harness_fixed partstr # NUL-in-image required -> reject, exit 0

See run.log case partstr, fix_run.log. Kernel runtime needs an AMD card with crafted VBIOS (not emulatable in QEMU).

Fix

fix.diff β€” minimal bounded copy; full validation (idx in image + NUL in image) in the central DF-4345 port.

VERDICT.md
↓ download raw

DF-4347 VERDICT β€” amdgpu part-number VBIOS string printed/copied unbounded (DF-4339's amdgpu twin, different shape)

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

Root cause

The amdgpu amdgpu_atom_parse tail (atom.c:1340-1348) differs from radeon's name[512] loop (which was DF-4339): here the part number is used in place, with no bounded copy at all:

1340: idx = CU16(ATOM_ROM_PART_NUMBER_PTR);       /* 0x6E, unvalidated */
1341: if (idx == 0)
1342:         idx = 0x80;
1344: str = CSTR(idx);
1345: if (*str != '\0') {
1346:         pr_info("ATOM BIOS: %s\n", str);
1347:         strlcpy(ctx->vbios_version, str, sizeof(ctx->vbios_version));
1348: }

Two unbounded reads: 1. idx (any 16-bit VBIOS value) is dereferenced at bios+idx with no check β€” *str itself can be outside the allocation. 2. %s (printk) and strlcpy (as source) both walk str until a NUL β€” an image whose last bytes are non-NUL walks off the end of the kzalloc'd bios buffer to the first NUL in adjacent kernel heap, printing those bytes to the console/log (kernel heap info leak in the log stream) and copying up to 19 of them into vbios_version (later readable via the vbios_version sysfs/connector interface).

Compared with DF-4339 (radeon): same class (unterminated VBIOS string β†’ unbounded walk), worse shape here because there is no destination bound enforced on the source walk before printing (radeon at least copied into name[512] first; amdgpu prints the raw pointer).

Reproduction

Guest (no AMD GPU): ./atom_harness partstr β€” 512-byte image, string from 0x100 to the (guarded) image end, no NUL β†’ the %s walk traps: SIGSEGV exit=139 (run.log, case partstr). Fixed logic requires a NUL inside the image before printing β†’ clean reject, exit 0 (fix_run.log).

Fix

fix.diff (minimal, standalone): bounded copy into vbios_version (19 bytes + forced NUL) and print the bounded copy β€” removes the unbounded print/copy and the unterminated-destination leak; residual: up to 19 bytes of over-read if idx sits at the image end. The complete fix (idx validated against bios_size AND NUL required inside the image) is part of the central DF-4345 port (fix.diff there).

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 (bounded copy + forced termination) passes git apply --check; fixed-logic mirror rejects the unterminated string. No kernel runtime possible on this guest.

fix.diff; fix_run.log case partstr
↓ fix.diffper-fix-DF-4347

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log case partstr (exit=139 at the %s/strlcpy walk); fix_run.log case partstr (rejected, exit 0); fix.diff (git apply --check OK)

PoC changes

amdgpu part-string shape (idx=CU16(0x6E), %s on CSTR) mirrored, not the radeon name[512] loop

Verified recommended fix

Validate idx against the bios length and require the NUL terminator inside the image before printing/copying the part number (or at minimum copy bounded + force-terminate)

Verdict

Code defect certain: the amdgpu amdgpu_atom_parse tail (amd/amdgpu/atom.c:1340-1348) takes idx=CU16(0x6E) unvalidated, dereferences CSTR(idx), and walks it with printk %s and strlcpy-as-source until a NUL that may live past the bios allocation -- adjacent kernel-heap bytes are printed to the console/log and up to 19 copied into ctx->vbios_version. amdgpu twin of DF-4339 (radeon) with a worse shape (no bounded intermediate copy at all); previously unfiled for amdgpu. Kernel runtime hardware-gated; guest mirror: 512-byte image, string to the guarded image end, no NUL -> SIGSEGV exit 139 (run.log case partstr); fixed logic requires the NUL inside the image -> clean reject, exit 0 (fix_run.log).