atom_parse name[512] stays unterminated when the VBIOS message is 511 printable bytes β %s leaks uninitialized kernel stack past the buffer to console/log; msg string also walked unbounded past the image
Summary
The copy loop writes at most name[0..510] and only NUL-terminates on the break path; 511 consecutive printable bytes leave name[511] uninitialized and DRM_INFO('%s') reads past the buffer until an incidental NUL. The msg string walk is also unbounded against the allocation. amdgpu twin prints the raw string with no filter. Guest PoC: strlen=515 past the 512-byte buffer. {0}-init + central-gate fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4339 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.5 KB | β raw | |
| VERDICT.md | β | 2.0 KB | β raw | |
| atom_harness.c | β | 7.5 KB | view raw | |
| run.sh | β | 125 B | view raw | |
| run_cases.log | β | 507 B | view raw | |
| env.txt | β | 467 B | view raw | |
| fix.diff | β | 251 B | view raw | |
| verdict.json | β | 2.2 KB | view raw |
DF-4339 β atom_parse name[] can stay unterminated and the msg string is walked unbounded
Severity: Low Confidence: certain File: sys/dev/drm/radeon/atom.c:1315-1326
What
char name[512];
str = CSTR(CU16(base + ATOM_ROM_MSG_PTR)); /* u16 ptr, unvalidated */
while (*str && ((*str == '\n') || (*str == '\r'))) str++; /* unbounded skip */
for (i = 0; i < 511; i++) {
name[i] = str[i]; /* OOB read past bios alloc */
if (name[i] < '.' || name[i] > 'z') { name[i] = 0; break; }
}
DRM_INFO("ATOM BIOS: %s\n", name);
Two defects:
1. If all 511 copied bytes are in [0x2E,0x7A] the loop finishes with
i==511 and name[511] is never written β name is an uninitialized
512-byte stack buffer, and %s then walks past name[510] through
adjacent kernel stack until an incidental NUL, printing those bytes to
the console/log.
2. str is walked (skip loop + 511-byte copy) with no bound against the
bios allocation β OOB heap read when the msg pointer lands near the
image end.
Reproduce (logic level; kernel runtime needs ATOM hardware β env.txt)
cc -O1 -g -o atom_harness atom_harness.c ./atom_harness name # -> Segmentation fault (str walk past image end) ./atom_harness name_nul # -> strlen(name)=515: reads 4 bytes PAST name[511]
Fix
fix.diff (git-apply checked): char name[512] = { 0 }; β the buffer is
always NUL-terminated. The bios_size bound for the str walk comes from
DF-4337's central fix (atom_in_bios gates in atom_parse).
DF-4339 VERDICT
Status: inconclusive (kernel runtime hardware-gated; logic-level reproduced on the audit guest). Defect: certain.
Root cause
atom.c:1276 char name[512] is not initialized; the copy loop
atom.c:1319-1325 writes at most name[0..510] and only NUL-terminates inside
the break path. If the bios message contains 511 consecutive bytes in
[0x2E,0x7A] the loop completes without break and name[511] keeps its
uninitialized stack value; DRM_INFO("ATOM BIOS: %s\n", name)
(atom.c:1326) then reads past the buffer into adjacent kernel stack until a
NUL. Additionally str = CSTR(CU16(base + ATOM_ROM_MSG_PTR)) (atom.c:1315)
and the copy walk are unbounded against the bios allocation (OOB heap read).
Reproduction
Guest has no ATI GPU (env.txt) β kernel path unrunnable; logic-level PoC on the guest (verbatim loop):
namecase: msg string of 300 'B's ending exactly at the (guarded) image end β Segmentation fault, exit 139 (the str walk reads past the bios allocation).name_nulcase: fully in-bounds 511 printable bytes, name[511] poisoned like uninit stack β strlen(name) = 515: the%sread runs 4 bytes past the 512-byte buffer (stops at an incidental NUL at name[515]). In-kernel the same read discloses kernel-stack bytes to the console/log.
Fix validation (logic level)
fix.diff: char name[512] = { 0 }; β harness name_nul fixed case gives
strlen=511 (always terminated); name case with DF-4337's bios bound stops
at the image end (fix_run.log in DF-4337/). git apply --check OK.
Kernel/on-hardware validation not possible (no ATOM GPU on guest) β
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 gives strlen(name)=511 (terminated) and stops at the image end. Kernel-runtime validation impossible on this guest.
fix.diff; DF-4337/fix_run.log name/name_nul fixed cases
Confirmed kernel references
Detail
Evidence (decisive lines)
run_cases.log [name] Segmentation fault exit=139 and [name_nul] strlen(name)=515; VERDICT.md
PoC changes
no seed; verbatim loop with name[511] poisoned to simulate uninit stack
Verified recommended fix
Initialize name[512] = { 0 } (always terminated); bound the msg-string walk by bios_size (provided by DF-4337's atom_in_bios)
Verdict
Code defect certain: the atom_parse name loop (atom.c:1319-1325) never writes name[511] when all 511 bytes are printable, so DRM_INFO("%s", name) at atom.c:1326 reads uninitialized kernel stack past the 512-byte buffer until an incidental NUL (guest mirror: strlen=515, i.e. 4 bytes past the buffer); the msg pointer itself is also walked unbounded past the bios allocation (mirror: SIGSEGV 139). Impact is a limited kernel-stack disclosure to console/log, gated on a crafted VBIOS β Low. Kernel runtime hardware-gated (no ATI GPU on guest).
No comments yet.