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)
PoC verification
Evidence pack
findings/poc/DF-5625 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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.
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_testablefix.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
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.
No comments yet.