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

ppb_ecp_sync()/ppb_reset_epp_timeout() return indeterminate register garbage: interface declares int methods, the only implementations are void (C UB through the DEVMETHOD dispatch)

Summary

ppb_base returns PPBUS_RESET_EPP/PPBUS_ECP_SYNC results whose only implementations (ppc.c) are static void: the generated dispatch calls void functions through an int-returning prototype, so the exported int is whatever the callee left in %eax. Harness with void-shaped marker methods: returns tracked the callee's leftover %eax exactly (0x66/0x99/0x5a). All in-tree callers discard the value today - latent contract break for future error-checking consumers of these EXPORT_SYMS. Make-ppc-implementations-match-interface fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5625 Β· 11 files
FileTypeDescriptionSize
README.md β€” 2.1 KB ↓ raw
VERDICT.md β€” 3.4 KB ↓ raw
harness.c β€” 15.2 KB view raw
Makefile β€” 273 B ↓ download
build.sh β€” 154 B view raw
run.sh β€” 144 B view raw
build.log β€” 7.9 KB view raw
run.log β€” 2.8 KB view raw
run2.log β€” 5.4 KB view raw
env.txt β€” 411 B view raw
fix.diff β€” 980 B view raw

DF-5625 β€” ppb_ecp_sync()/ppb_reset_epp_timeout() return indeterminate values: interface declares int, the only implementations are void

File: sys/bus/ppbus/ppb_base.c:159-163,170-174 (+ interface/impl mismatch sys/bus/ppbus/ppbus_if.m:57-59,72-74 vs sys/dev/misc/ppc/ppc.c:144,1785)

Build (inside the DragonFly guest)

sh build.sh     # compiles VERBATIM /usr/src/sys/bus/ppbus/ppb_base.c
                # (sha256 printed by the script) into ppbbasehx.ko (-Werror)

Run (inside the guest)

sh run.sh       # kldload ./ppbbasehx.ko; tests run at attach; dmesg | grep PPBHX

Expected (decisive lines, see run.log)

PPBHX: DF-5625 ppb_ecp_sync()          -> 0x66 (void method left marker 0x66 in %eax)
PPBHX: DF-5625 ppb_ecp_sync()          -> 0x99 (void method left marker 0x99 in %eax)
PPBHX: DF-5625 ppb_reset_epp_timeout() -> 0x5a (void method left marker 0x5a in %eax; ...)

ppbus_if.m declares METHOD int reset_epp and METHOD int ecp_sync, but the only implementations in the tree β€” ppc_ecp_sync() (ppc.c:144) and ppc_reset_epp() (ppc.c:1785) β€” are static void. ppb_base.c does return (PPBUS_ECP_SYNC(...)) / return(PPBUS_RESET_EPP(...)), so the generated int-typed dispatch calls a void function through an int-returning prototype: the value handed to the caller is whatever the callee last left in %eax. The harness's fake controller implements the two methods with the production void shapes; the "protocol" value returned by the production ppb_base.c wrappers deterministically tracks a marker left in %eax by the void callee (0x66, 0x99, 0x5a), proving the return channel carries callee register garbage, not a defined result.

Impact today: none β€” all in-tree callers ignore the return (sys/dev/disk/vpo/vpoio.c:452,472, sys/dev/disk/vpo/immio.c:443,464). Any future error-checking caller receives nondeterministic results (C-level undefined behavior). Info severity.

Harness note

One shared KLD harness (harness.c, ppbbasehx) demonstrates DF-5625, DF-5626, DF-5627 and DF-5628 against the same verbatim production ppb_base.c; this pack's lines are the DF-5625 ones.

VERDICT.md
↓ download raw

DF-5625 VERDICT β€” reproduced (Info, impact: none)

What was tested

sys/bus/ppbus/ppb_base.c:159-163 (ppb_reset_epp_timeout) and :170-174 (ppb_ecp_sync) both return the result of a PPBUS_* interface dispatch. The interface (sys/bus/ppbus/ppbus_if.m:57-59 and :72-74) declares both methods as METHOD int, but the only implementations in the tree are void: ppc_ecp_sync() at sys/dev/misc/ppc/ppc.c:141-163 and ppc_reset_epp() at ppc.c:1785-1790. The DEVMETHOD machinery stores the void functions and the generated per-module dispatch calls them through the int-returning prototype β€” calling a function through an incompatible prototype is C undefined behavior; on amd64 the "return value" is whatever the callee last left in %eax.

How it was verified (KLD-harness-with-production-code evidence class)

The shared ppbbasehx KLD compiles the unmodified guest /usr/src/sys/bus/ppbus/ppb_base.c (make -Werror, sha256 in build.log) and drives it from a ppbshim child whose parent implements the two ppbus methods with the production void shapes (static void fake_ppc_ecp_sync(device_t) mirroring ppc.c:144, etc.). The void bodies end with a __noinline marker call (side-effecting, cannot be elided) whose return value sets %eax immediately before the void epilogue.

Result (run.log):

PPBHX: DF-5625 ppb_ecp_sync()          -> 0x66 (void method left marker 0x66 in %eax)
PPBHX: DF-5625 ppb_ecp_sync()          -> 0x99 (void method left marker 0x99 in %eax)
PPBHX: DF-5625 ppb_reset_epp_timeout() -> 0x5a (void method left marker 0x5a in %eax; marker hits=3)

The production ppb_base.c wrappers returned exactly the void callees' leftover register state, changing call-to-call with the callee. A defined int method cannot do that. Reproduced deterministically on 4 module loads (initial run, decisive run, stability run, DF-5627 fix run).

Impact ceiling

None today: every in-tree caller discards the value (vpoio.c:452,472 ppb_ecp_sync(ppbus); statement; immio.c:443,464 same for ppb_reset_epp_timeout). The defect is a latent ABI/contract break: any future caller that error-checks these exports (EXPORT_SYMS in sys/bus/ppbus/Makefile:14-15) gets nondeterministic results; codegen changes can silently alter the garbage. Info severity, CWE-843/CWE-755-adjacent (call through incompatible prototype).

Fix

fix.diff makes the two ppc.c implementations match the interface: ppc_ecp_sync() returns int (0, and ETIMEDOUT where it currently just prints "ECP sync failed"), ppc_reset_epp() returns 0. Not fix-validated on the guest: the harness compiles production ppb_base.c, not production ppc.c (an ISA driver), so a harness re-run would exercise the fake controller, not the fixed code; a single-fix kernel rebuild is not warranted for an Info finding whose in-tree callers ignore the value. fix_status: not_testable.

Environment

DragonFly 6.5-DEVELOPMENT x86_64 (X86_64_GENERIC, Thu Jul 2 2026), cc 8.3, 6 vCPU QEMU guest, no parallel port (family-standard KLD harness).

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

not_testable
baseline reproduced→ patch + rebuild →patched clean

fix.diff converts both ppc.c void implementations to int per the ppbus_if.m declarations. Not validated on the guest: the harness compiles production ppb_base.c but a fake controller (ppc.c is an ISA driver and is not harness-compilable), so a re-run would not exercise the fixed production code; a single-fix kernel rebuild is not warranted for an Info finding with zero in-tree consumers of the return value.

fix.diff; VERDICT.md 'Fix' section
↓ fix.diffper-fix-DF-5625

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log lines 'DF-5625 ppb_ecp_sync() -> 0x66/0x99' and 'ppb_reset_epp_timeout() -> 0x5a (marker hits=3)'; VERDICT.md narrative; build.log (verbatim production source, -Werror clean)

PoC changes

No seed PoC existed; shared harness written fresh (family pattern from DF-5418). fakeppc implements the two ppbus methods with the production void signatures so the production ppb_base.c wrappers are exercised exactly as against ppc.c.

Verified recommended fix

Make the ppc.c implementations match the interface: ppc_ecp_sync/ppc_reset_epp return int (0 on success, ETIMEDOUT when the ECP FIFO never drains).

Verdict

Reproduced via the family-standard KLD-harness-with-production-code evidence class (no parallel port in the QEMU rig): the unmodified /usr/src/sys/bus/ppbus/ppb_base.c was compiled with -Werror into ppbbasehx.ko and driven from a fake ppbus controller whose ecp_sync/reset_epp methods carry the PRODUCTION void shapes (ppc.c:144 ppc_ecp_sync, ppc.c:1785 ppc_reset_epp) against the int-typed interface declarations (ppbus_if.m:57/:72). ppb_base.c:162/:173 'return(PPBUS_RESET_EPP(...))'/'return (PPBUS_ECP_SYNC(...))' handed back exactly the void callee's leftover %eax (0x66, 0x99, 0x5a markers tracked 1:1 across calls) β€” the exported int return channel is indeterminate register state, i.e. C-level UB through the DEVMETHOD dispatch. Deterministic on 4 module loads. Impact today: none β€” every in-tree caller discards the value (vpoio.c:452,472; immio.c:443,464); the defect is a latent contract break for any future error-checking consumer of these EXPORT_SYMS. Info severity.