ppb_MS_microseq() MS_OP_PUT/GET handler recurses with no depth limit β transfer loop containing MS_OP_PUT exhausts the kernel stack (DOUBLE FAULT)
Summary
Each PUT/GET recurses into the interpreter twice (initxfer + xfer->loop); the only thing keeping depth finite is a comment. A loop registered via ppb_MS_init() that itself contains MS_OP_PUT resolves to the same loop every level: harness -> DOUBLE FAULT - KERNEL STACK GUARD HIT, RIP inside ppb_MS_microseq, guest dead. No in-tree driver violates the invariant (vpo/imm register RASSERT/RFETCH/C_CALL bodies) - driver-misuse-to-DoS class. Per-bus re-entry guard + return-capture fix validated (rc=22 EINVAL, maxdepth=1, guest alive, multi-PUT sequences unaffected).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5574 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.5 KB | β raw | |
| VERDICT.md | β | 4.6 KB | β raw | |
| msqhx.c | β | 19.3 KB | view raw | |
| Makefile | β | 311 B | β download | |
| build.sh | β | 126 B | view raw | |
| run.sh | β | 181 B | view raw | |
| build.log | β | 7.7 KB | view raw | |
| run.log | β | 588 B | view raw | |
| fix_run.log | β | 342 B | view raw | |
| fix.diff | β | 3.2 KB | view raw | |
| env.txt | β | 670 B | view raw | |
| panic.txt | β | 479 B | view raw |
DF-5574 β ppb_MS_microseq() MS_OP_PUT/GET handler recurses with no depth limit β a driver-registered transfer loop containing MS_OP_PUT exhausts the kernel stack (DOUBLE FAULT)
Severity: Low Β· Confidence: certain Β· File: sys/bus/ppbus/ppb_msq.c (pass 2)
Reproduce
sh build.shβ compiles the verbatim productionppb_msq.cintomsqhx.kosh run.shβ case 2 (FATAL on unfixed kernel)- Observe dmesg (
dmesg | grep MSQHX) / the serial console.
Expected
BASELINE (unfixed): guest dies with 'DOUBLE FAULT - KERNEL STACK GUARD HIT!' / 'panic: double fault', RIP inside ppb_MS_microseq. PATCHED: 'DF-5574 ppb_MS_microseq returned rc=22 ret=119 maxdepth=1 (no crash)' and the guest stays up.
Harness (shared by DF-5573..DF-5577)
msqhx.c + Makefile compile the verbatim production
/usr/src/sys/bus/ppbus/ppb_msq.c (sha256 223e7f9184c81ab83f49228ae7d1f99a
6e3936b9a452b01c6bb6176b150dda67, identical to the repo sys/ copy) into a
KLD, together with a fake ppbus controller on nexus whose
ppbus_exec_microseq method implements the exact dispatch semantics of
ppc_exec_microseq() (sys/dev/misc/ppc/ppc.c:1332-1542) for the exercised
opcodes. The interpreter target device's softc is a real struct ppb_data
and its child's ivars are a real struct ppb_device, so mode2xfer(),
ppb_MS_init(), ppb_MS_exec(), ppb_MS_microseq(), ppb_MS_loop() and
ppb_MS_init_msq() all run their production code paths. Evidence class:
"KLD-harness-with-production-code" (no parallel port in the QEMU rig) β
family standard, cf. DF-5418.
Build
# inside the guest, from this directory sh build.sh # -> /tmp/msqhx-build/msqhx.ko
Run
sh run.sh # runs this pack's case via sysctl hw.msqhx_case sh run.sh <case> # 0=suite(5575+5573) 2=5574 4=5576a 5=5576b 6=5577
Cases 2/4/5 PANIC the guest on unfixed production code (that is the finding);
the panics land on the serial console (dfbsd-qemu/vm.sh log).
Fix validation
fix.diff (sha256 8e3dfc90e6049ce1704c3cef833cba0904b6404f11360bdd299e37980e9327bb) patches sys/bus/ppbus/ppb_msq.c +
sys/bus/ppbus/ppbconf.h (all five findings; never applied to the repo's
read-only sys/ tree). Validation procedure: copy the fixed files into the
guest's /usr/src/sys/bus/ppbus/, sh build.sh again (recompiles the
production source), re-run the same case. Baseline vs patched outputs are
in run.log / fix_run.log (plus panic*.txt / fix_run.log for the
fatal cases).
DF-5574 β verdict narrative
Finding
ppb_MS_microseq() MS_OP_PUT/GET handler recurses with no depth limit β a driver-registered transfer loop containing MS_OP_PUT exhausts the kernel stack (DOUBLE FAULT)
Bottom line (status: reproduced, impact: panic)
Reproduced on the QEMU guest (DragonFly 6.5-DEVELOPMENT x86_64). ppb_MS_microseq()'s MS_OP_PUT/GET case recurses into itself twice per transfer (ppb_msq.c:301 initxfer, :309 xfer->loop) with no depth limit; the invariant that keeps this finite β 'the xfer microsequence should not contain any MS_OP_PUT or MS_OP_GET!' β exists only as a comment (ppb_msq.c:306-307). A transfer loop registered via ppb_MS_init() that itself contains MS_OP_PUT resolves to the same loop in mode2xfer() every level, so each recursion re-enters ppb_MS_microseq() one net frame deeper, forever. Harness case 2 registers exactly such a loop (through the production ppb_MS_init) and executes a two-instruction top sequence through the production interpreter: the unfixed kernel dies with 'DOUBLE FAULT - KERNEL STACK GUARD HIT!' / 'panic: double fault', RIP = module_base+0x340 inside ppb_MS_microseq (panic.txt). No in-tree driver currently registers a violating loop (vpo/imm register loop bodies of RASSERT_P/RFETCH_P/C_CALL instructions only), so reachability is a buggy/malicious KLD or future driver β severity Low, but the class is unbounded-recursion-to-corruption (CWE-674) in the interpreter's state machine. Fix validated: a re-entry guard (ppb->ppb_in_xfer around the two recursive calls, cleared on all exits) plus propagation of the recursive calls' return values (see DF-5577 β without it the guard's EINVAL was silently swallowed) turns the same case into 'rc=22 ret=119 maxdepth=1 (no crash)' with the guest fully alive; a legitimate multi-PUT top-level sequence still works because the guard is cleared between transfers.
Exploit chain
KLD/buggy-driver registers put_xfer[mode].loop containing MS_OP_PUT -> any transfer on that bus recurses ppb_MS_microseq unboundedly -> kernel stack guard page -> DOUBLE FAULT panic (denial of service; the recursion writes only its own stack frames, so the primitive is stack-exhaustion, not controlled overwrite)
Evidence
panic.txt ('DOUBLE FAULT - KERNEL STACK GUARD HIT!', 'panic: double fault', RIP=0xffffffff82600340 = msqhx module base + ppb_MS_microseq) from the baseline run; run.log (trigger lines); fix_run.log ('DF-5574 ppb_MS_microseq returned rc=22 ret=119 maxdepth=1 (no crash)') on the fix.diff-patched production source with the guest alive; build.log proves the pristine file was exercised
Kernel references
sys/bus/ppbus/ppb_msq.c:296sys/bus/ppbus/ppb_msq.c:301sys/bus/ppbus/ppb_msq.c:306sys/bus/ppbus/ppb_msq.c:309sys/bus/ppbus/ppb_msq.c:312sys/bus/ppbus/ppbconf.h:156
Fix validation (fixed)
Re-entry guard (ppb->ppb_in_xfer) around the two recursive calls + return-value capture: the same case that DOUBLE-FAULTED now returns rc=22 (EINVAL) with maxdepth=1 and the guest stays alive; no legitimate in-tree flow is affected (guard cleared between transfers).
fix.diff (sha256 8e3dfc90e6049ce1704c3cef833cba0904b6404f11360bdd299e37980e9327bb) carries all five ppb_msq.c fixes for this pass;
the hunks relevant to this finding are marked with its DF id in the comments.
Fix validation followed the family standard (cf. DF-5418): the fixed sources
were installed into the guest's /usr/src/sys/bus/ppbus/, the harness was
rebuilt (recompiling the production file), and the exact same case was
re-run β baseline vs patched outputs are in run.log / fix_run.log
(+ panic*.txt). The repo's sys/ tree was never modified; the guest was
vm.sh reset with-src afterwards (sources restored+verified by snapshot).
PoC changes vs seed
No seed PoC existed. Case 2 originally demonstrated only the panic; the first guard fix exposed that the guard's EINVAL was discarded at the :309 call site (rc printed 0) β that discovery became DF-5577 and the fix was revised to capture the recursive return values.
Guest: DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 Harness sha256: 4b728315b40157934f62c99e847f386b30202eef77083dc1a5c065b7b0448ded
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
fixedRe-entry guard (ppb->ppb_in_xfer) around the two recursive calls + return-value capture: the same case that DOUBLE-FAULTED now returns rc=22 (EINVAL) with maxdepth=1 and the guest stays alive; no legitimate in-tree flow is affected (guard cleared between transfers).
fix_run.log (patched production source, same case); panic.txt / panic.get.txt / panic.mode.txt (baseline) and fix_run.log (patched); fix.diff sha256 8e3dfc90e6049ce1704c3cef833cba0904b6404f11360bdd299e37980e9327bb
Confirmed kernel references
Detail
Exploit chain
KLD/buggy-driver registers put_xfer[mode].loop containing MS_OP_PUT -> any transfer on that bus recurses ppb_MS_microseq unboundedly -> kernel stack guard page -> DOUBLE FAULT panic (denial of service; the recursion writes only its own stack frames, so the primitive is stack-exhaustion, not controlled overwrite)
Evidence (decisive lines)
panic.txt ('DOUBLE FAULT - KERNEL STACK GUARD HIT!', 'panic: double fault', RIP=0xffffffff82600340 = msqhx module base + ppb_MS_microseq) from the baseline run; run.log (trigger lines); fix_run.log ('DF-5574 ppb_MS_microseq returned rc=22 ret=119 maxdepth=1 (no crash)') on the fix.diff-patched production source with the guest alive; build.log proves the pristine file was exercised
PoC changes
No seed PoC existed. Case 2 originally demonstrated only the panic; the first guard fix exposed that the guard's EINVAL was discarded at the :309 call site (rc printed 0) β that discovery became DF-5577 and the fix was revised to capture the recursive return values.
Verified recommended fix
Guard the MS_OP_PUT/GET recursion with a per-bus re-entry flag (struct ppb_data.ppb_in_xfer) and propagate the recursive calls' return values
Verdict
Reproduced on the QEMU guest (DragonFly 6.5-DEVELOPMENT x86_64). ppb_MS_microseq()'s MS_OP_PUT/GET case recurses into itself twice per transfer (ppb_msq.c:301 initxfer, :309 xfer->loop) with no depth limit; the invariant that keeps this finite β 'the xfer microsequence should not contain any MS_OP_PUT or MS_OP_GET!' β exists only as a comment (ppb_msq.c:306-307). A transfer loop registered via ppb_MS_init() that itself contains MS_OP_PUT resolves to the same loop in mode2xfer() every level, so each recursion re-enters ppb_MS_microseq() one net frame deeper, forever. Harness case 2 registers exactly such a loop (through the production ppb_MS_init) and executes a two-instruction top sequence through the production interpreter: the unfixed kernel dies with 'DOUBLE FAULT - KERNEL STACK GUARD HIT!' / 'panic: double fault', RIP = module_base+0x340 inside ppb_MS_microseq (panic.txt). No in-tree driver currently registers a violating loop (vpo/imm register loop bodies of RASSERT_P/RFETCH_P/C_CALL instructions only), so reachability is a buggy/malicious KLD or future driver β severity Low, but the class is unbounded-recursion-to-corruption (CWE-674) in the interpreter's state machine. Fix validated: a re-entry guard (ppb->ppb_in_xfer around the two recursive calls, cleared on all exits) plus propagation of the recursive calls' return values (see DF-5577 β without it the guard's EINVAL was silently swallowed) turns the same case into 'rc=22 ret=119 maxdepth=1 (no crash)' with the guest fully alive; a legitimate multi-PUT top-level sequence still works because the guard is cleared between transfers.
No comments yet.