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

ppb_1284_read_id() early error returns leave the *read out-parameter uninitialized (caller consumes kernel stack on any peer-driven negotiation failure)

Summary

Both negotiate-failure returns exit with the caller's int untouched (the sibling spp_1284_read correctly zeroes it): every peer-controlled failure leaves the slot stale - sentinel 0x5A5A5A5A survives the production call. Sole in-tree caller checks error before use, so latent hardening; *read=0-up-front fix validated.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5419 Β· 17 files
FileTypeDescriptionSize
harness.c β€” 9.4 KB view raw
Makefile β€” 496 B ↓ download
build.sh β€” 135 B view raw
run.sh β€” 196 B view raw
fix_validate.sh β€” 1.4 KB view raw
capture_logs.sh β€” 1.4 KB view raw
ppb_1284.pristine.c β€” 18.3 KB view raw
ppb_1284.fixA.c β€” 18.3 KB view raw
ppb_1284.fixB.c β€” 18.3 KB view raw
build.log β€” 2.9 KB view raw
run.log β€” 729 B view raw
run.full.dmesg.log β€” 881 B view raw
fix_build.log β€” 2.9 KB view raw
fix_run.log β€” 729 B view raw
env.txt β€” 627 B view raw
fix.diff β€” 175 B view raw
VERDICT.md β€” 2.5 KB ↓ raw
VERDICT.md
↓ download raw

DF-5419 β€” VERDICT

Status: reproduced (KLD-harness-with-production-code; guest DragonFly 6.5-DEVELOPMENT x86_64; same ppbharness.ko rig as DF-5418, test T4).

Root cause (static)

ppb_1284_read_id() (ppb_1284.c:588-606) never initializes *read. Both early error returns (ppb_1284.c:600-601 and 605-606) return the negotiation error without writing the out-parameter, and the function only reaches spp_1284_read() (which sets it) on the success path. The sibling reader spp_1284_read() demonstrates the correct pattern β€” *read = len = 0; at ppb_1284.c:526 β€” so the omission in ppb_1284_read_id() is clearly an oversight, not a contract.

Every peer-controlled negotiation failure (device not IEEE 1284 compliant β†’ PPB_NOT_IEEE1284, timeout, mode unsupported) leaves the caller's int untouched.

Observed (run.log, baseline)

PPBHX: DF-5419 T4 read_id-negotiate-fail: ret=19 read=0x5a5a5a5a state=0 error=2

The 0x5A5A5A5A poison sentinel survived the call through the production ppb_1284_read_id() β€” *read was never written. error=2 (PPB_NOT_IEEE1284) confirms the production error path (Event 2 timeout in ppb_1284_negotiate) was taken.

Fix validation (fix_run.log, fixB phase / ppb_1284.fixB.c)

One-line fix β€” *read = 0; before bzero():

PPBHX: DF-5419 T4 read_id-negotiate-fail: ret=19 read=0x00000000 state=0 error=2

Sentinal replaced by 0 on the same error path β†’ fixed. Rebuilt with -Werror, pristine source restored + cmp-verified.

Impact ceiling

None in-tree: the only caller, ppb_pnp_detect() (ppbconf.c:224), checks the return code before ever looking at len. The defect is a latent-contract hazard β€” any present or future caller that reads *read after an error return consumes an uninitialized kernel stack value (info-leak-grade primitive of 4 bytes of adjacent stack). Filed as Info (hardening), confidence certain (static + demonstrated through production code).

Note: the related "missing NUL-termination of the PnP string" defect that lets ppb_pnp_detect() walk past the 256-byte device-ID buffer is already filed as DF-2328 (ppbconf.c, pass 1) and is intentionally not re-reported here.

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

fixed
baseline reproduced→ patch + rebuild →patched clean

Applied '*read = 0;' before bzero() (ppb_1284.fixB.c = fixA + this fix) to the guest's /usr/src copy, rebuilt, re-ran: T4 now prints read=0x00000000 on the identical error path; the uninitialized out-parameter behavior is gone.

fix_run.log (fixB phase line 'DF-5419 T4 ... read=0x00000000'), fix_build.log, VERDICT.md 'Fix validation'
↓ fix.diffper-fix-DF-5419

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log line 'DF-5419 T4 read_id-negotiate-fail: ret=19 read=0x5a5a5a5a state=0 error=2'; fix_run.log (fixB phase: read=0x00000000); VERDICT.md; ppb_1284.pristine.c sha256 proves verbatim production code

PoC changes

No seed PoC existed; test T4 written fresh inside the DF-5418 harness (same rig). No iterations specific to this test.

Verified recommended fix

ppb_1284.c: ppb_1284_read_id() must set '*read = 0;' before any return (mirror of spp_1284_read():526).

Verdict

Reproduced on the QEMU guest (DragonFly 6.5-DEVELOPMENT x86_64) via the KLD-harness-with-production-code evidence class (same ppbharness.ko rig as DF-5418: unmodified production ppb_1284.c driven by a scripted malicious IEEE1284 peer). ppb_1284_read_id() at ppb_1284.c:588-606 never initializes read; both early error returns (:600-601, :605-606) return the negotiation error with the caller's out-parameter untouched, while the sibling spp_1284_read() correctly does 'read = len = 0;' at :526. Harness T4: a peer failing IEEE1284 Event 2 (NOT_IEEE1284 error path) makes the production ppb_1284_read_id() return ENODEV with the caller's 0x5A5A5A5A poison sentinel intact (read=0x5a5a5a5a in run.log). One-line fix ('read = 0;' before bzero) validated: same error path now yields read=0x00000000. In-tree impact none: the only caller (ppb_pnp_detect, ppbconf.c:224) checks the error before using len; the defect is a latent contract hazard turning any future error-path reader of read into a 4-byte uninitialized-kernel-stack consumer. Filed as Info/hardening.