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)
PoC verification
Evidence pack
findings/poc/DF-4347 Β· 14 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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.
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
inconclusivefix.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
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).
No comments yet.