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

Missing NUL-termination of PnP string enables OOB stack read in search_token()

Summary

ppb_pnp_detect() allocates 257-byte stack buffer str[PPB_PnP_STRING_SIZE+1] but ppb_1284_read_id only bzeros 256 bytes. The 257th byte uninitialized. If peripheral returns exactly 256 non-NUL bytes str[256] is uninitialized stack garbage and search_token UNKNOWN_LENGTH scan loop walks past str[255] into adjacent stack memory reading kernel stack contents (info leak of return addresses/pointers) and potentially crashing if walks into unmapped guard page. ppb_pnp_detect never executes str[len]=\0. [VERIFIED pass2: reproduced - 256-byte NUL-free ID crosses uninitialized str 256 plus adjacent stack, colon-scan leaves object, NULL+1 panic; same walk is the console leak when a colon is found; str 256 NUL fix-validated]

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2328 Β· 8 files
FileTypeDescriptionSize
ppbharness3.c β€” 13.1 KB view raw
Makefile β€” 484 B ↓ download
run.log β€” 1.3 KB view raw
panic.txt β€” 3.3 KB view raw
panic.ref-2327.txt β€” 1.0 KB view raw
fix.diff β€” 584 B view raw
verdict.json β€” 2.5 KB view raw
README.md β€” 1.4 KB ↓ raw

DF-2328 β€” missing NUL termination of the 257-byte PnP buffer enables OOB stack reads

What (sys/bus/ppbus/ppbconf.c:219 + ppb_1284.c:595)

ppb_pnp_detect declares char str[PPB_PnP_STRING_SIZE+1] (257 bytes) but ppb_1284_read_id bzeros only max = 256 bytes and never writes a terminator; ppb_pnp_detect never executes str[len]=0. With a 256-byte NUL-free device ID, str[256] is uninitialized stack garbage and every search_token(..., UNKNOWN_LENGTH, ...) scan walks past str[255] into uninitialized and out-of-bounds stack (info leak of stack contents to the console when a ':' is crossed; page fault when the walk returns NULL and the caller computes NULL+1).

Rig / Reproduce (same ppbharness3 rig, one boot)

sysctl hw.ppbx3.test=2          # 256 'A's, "REV" at 252, no colon

Result (run.log + panic.txt)

ppb: <PnP> 256 characters: A ... A R E V A
ppbus0: <unknown unknown.panic with 1 spinlocks held
panic: page fault ... strlen() <- kprintf

The ID contains NO colon and NO NUL: the REV ':'-scan necessarily left the object (crossing str[256] and the adjacent stack) before returning NULL -> NULL+1 -> trap. The same unchecked walk is the info leak whenever the crossed stack bytes happen to contain ':'.

Fix

fix.diff terminates str[PPB_PnP_STRING_SIZE] = '\0' before parsing. Validated on the patched rig (VERDICT.md): no trap, clean parse.

Fix verification

fixed
↓ fix.diffper-fix-DF-2328

Confirmed kernel references

Detail

Exploit chain

256-byte NUL-free ID from malicious peripheral -> read_id clears only 256 of 257 bytes -> UNKNOWN_LENGTH scan leaves the object -> crosses uninitialized str[256] and stack (OOB read; console leak if a colon is crossed) -> NULL on colon-free stacks -> NULL+1 -> Fatal trap 12

Evidence (decisive lines)

run.log + panic.txt: console dump of the full 256-byte ID (A...REVA), the <unknown unknown. partial print, strlen/kprintf backtrace

PoC changes

Same rig as DF-2327 (test=2); crafted ID generated in the worker.

Verified recommended fix

Force str[PPB_PnP_STRING_SIZE] = 0 before parsing (fix.diff); belt-and-braces with the DF-2327 guard.

Verdict

REPRODUCED: with the same verbatim-ppbconf rig, a 256-byte NUL-free device ID ending in REVA (REV at offset 252, no colon anywhere) drives the REV branch search_token(&str[252], UNKNOWN_LENGTH, :) past str[255] into str[256] -- which ppb_1284_read_id never clears (it bzeros only max=256 of the 257-byte object, ppb_1284.c:595) -- and onward into adjacent stack: the walk finds no colon in the uninitialized/out-of-bounds bytes, returns NULL, and ppbconf.c:259 hands NULL+1 to kprintf: panic page fault in strlen() from kprintf (guest dies). The OOB walk itself is proven by construction: the delivered ID (console dump shows all 256 bytes, colon-free) contains no terminator and no colon, so every byte scanned past str[255] was uninitialized or out-of-bounds stack. The identical unchecked walk is the info-leak flavor whenever the crossed stack contains a colon (the bytes would be kprintf-ed to the console).