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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4339 Β· 8 files
FileTypeDescriptionSize
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).

VERDICT.md
↓ download raw

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):

  • name case: 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_nul case: fully in-bounds 511 printable bytes, name[511] poisoned like uninit stack β†’ strlen(name) = 515: the %s read 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

inconclusive
baseline no→ patch + rebuild →patched clean

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

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).