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)
PoC verification
Evidence pack
findings/poc/DF-5419 Β· 17 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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
fixedApplied '*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'
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.
No comments yet.