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

Interpreter panics instead of returning errors on unreachable states: MS_OP_GET without an initialized mode loop (ppb_msq.c:292) and mode2xfer() unknown-mode/EPP-protocol (:70/:93/:101, the latter also printing the wrong variable)

Summary

MS_OP_GET with get_xfer[mode].loop==NULL panics instead of EOPNOTSUPP, and mode2xfer panics on any mode outside {0,1,2,4,8} - including the PPB_SPP (0x3) alias ppbconf.h itself defines and ppb_set_mode stores verbatim (the panic even prints ppbdev->mode instead of the bus mode - wrong-variable proof). vpio_instr() has exactly the vulnerable call shape (GET loops initialized for one mode only). Two fatal harness cases with production backtraces; error-return fix validated (45/EOPNOTSUPP and 22/EINVAL, guest alive).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5576 Β· 13 files
FileTypeDescriptionSize
README.md β€” 2.7 KB ↓ raw
VERDICT.md β€” 4.2 KB ↓ raw
msqhx.c β€” 19.3 KB view raw
Makefile β€” 311 B ↓ download
build.sh β€” 126 B view raw
run.sh β€” 163 B view raw
build.log β€” 7.7 KB view raw
run.log β€” 1.4 KB view raw
fix_run.log β€” 185 B view raw
fix.diff β€” 3.2 KB view raw
env.txt β€” 670 B view raw
panic.get.txt β€” 755 B view raw
panic.mode.txt β€” 680 B view raw

DF-5576 β€” Interpreter treats unreachable states as panic() instead of errors: MS_OP_GET without an initialized mode loop (ppb_msq.c:292) and mode2xfer() unknown-mode/EPP-protocol panics (:70/:93/:101) β€” the latter also printing the wrong variable

Severity: Low Β· Confidence: certain Β· File: sys/bus/ppbus/ppb_msq.c (pass 2)

Reproduce

  1. sh build.sh β€” compiles the verbatim production ppb_msq.c into msqhx.ko
  2. sh run.sh β€” case 4 (FATAL on unfixed kernel)
  3. Observe dmesg (dmesg | grep MSQHX) / the serial console.

Expected

BASELINE (unfixed): case 4 -> 'panic: ppb_MS_microseq: IEEE1284 read not supported' (production backtrace ppb_MS_microseq+0x244 <- ppb_MS_exec); case 5 -> 'panic: mode2xfer: unknown mode (0)' β€” note the mode was 3 (PPB_SPP): the message prints ppbdev->mode instead of the bus mode. PATCHED: case 4 returns 45 (EOPNOTSUPP), case 5 returns 22 (EINVAL); no panic.

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).

VERDICT.md
↓ download raw

DF-5576 β€” verdict narrative

Finding

Interpreter treats unreachable states as panic() instead of errors: MS_OP_GET without an initialized mode loop (ppb_msq.c:292) and mode2xfer() unknown-mode/EPP-protocol panics (:70/:93/:101) β€” the latter also printing the wrong variable

Bottom line (status: reproduced, impact: panic)

Reproduced on the QEMU guest (DragonFly 6.5-DEVELOPMENT x86_64), both variants, with production backtraces through ppb_MS_exec(). (a) MS_OP_GET with get_xfer[mode].loop == NULL panics at ppb_msq.c:292 instead of returning an error β€” vpoio_instr() (vpoio.c:461-476) reaches this exact ppb_MS_exec(MS_OP_GET) shape, and get_xfer slots are initialized for exactly ONE mode at attach (vpoio.c:612-633): any driver flow that reads before GET-init, or after the bus mode moved to an uninitialized slot, kills the kernel; the panic fires before the error path can run, so the 'error/abort' contract of the engine is unenforceable for reads. (b) mode2xfer() panics on any ppb->mode outside {0,1,2,4,8} (ppb_msq.c:100-102) β€” ppbconf.h itself defines mode aliases such as PPB_SPP=0x3 (ppbconf.h:47) that ppb_set_mode() stores verbatim after masking 0x0f (ppb_base.c:138); the harness sets exactly that alias and panics. The panic message also prints ppbdev->mode (the ivars field, 0 here) instead of the bus mode (3) β€” empirically demonstrated by 'panic: mode2xfer: unknown mode (0)' while the mode was 3. Reachability in-tree today requires a root-gated device node (ppi is 0600 root:wheel, ppi.c:164-166; vpo goes through CAM), so severity Low (robustness/local-DoS-class). Fix validated: GET-without-loop returns EOPNOTSUPP(45), unknown mode returns EINVAL(22) via NULL-from-mode2xfer handling in ppb_MS_init/ppb_MS_microseq; no panic, guest alive.

Exploit chain

privileged device access (e.g. /dev/vpo0 flow that GETs in an uninitialized mode, or a future driver calling ppb_set_mode(PPB_SPP)) -> immediate kernel panic (local DoS); no memory-safety consequence

Evidence

panic.get.txt (case 4: 'panic: ppb_MS_microseq: IEEE1284 read not supported', backtrace ppb_MS_microseq+0x244 <- ppb_MS_exec <- userland_sysctl); panic.mode.txt (case 5: 'panic: mode2xfer: unknown mode (0)' with the mode actually 3 β€” wrong-variable proof); fix_run.log ('DF-5576a returned 45 (no panic)', 'DF-5576b returned 22 (no panic)') on the patched production source

Kernel references

Fix validation (fixed)

GET-without-loop returns EOPNOTSUPP (was panic) and mode2xfer() returns NULL on unknown mode/protocol with EINVAL at both call sites (also retiring the wrong-variable panic message): case 4 -> 45, case 5 -> 22, no panic, guest alive.

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; two fatal cases (4/5) driven one boot each with vm.sh resets between.

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

fixed
baseline reproduced→ patch + rebuild →patched clean

GET-without-loop returns EOPNOTSUPP (was panic) and mode2xfer() returns NULL on unknown mode/protocol with EINVAL at both call sites (also retiring the wrong-variable panic message): case 4 -> 45, case 5 -> 22, no panic, guest alive.

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
↓ fix.diffper-fix-DF-5576

Confirmed kernel references

Detail

Exploit chain

privileged device access (e.g. /dev/vpo0 flow that GETs in an uninitialized mode, or a future driver calling ppb_set_mode(PPB_SPP)) -> immediate kernel panic (local DoS); no memory-safety consequence

Evidence (decisive lines)

panic.get.txt (case 4: 'panic: ppb_MS_microseq: IEEE1284 read not supported', backtrace ppb_MS_microseq+0x244 <- ppb_MS_exec <- userland_sysctl); panic.mode.txt (case 5: 'panic: mode2xfer: unknown mode (0)' with the mode actually 3 β€” wrong-variable proof); fix_run.log ('DF-5576a returned 45 (no panic)', 'DF-5576b returned 22 (no panic)') on the patched production source

PoC changes

No seed PoC existed; two fatal cases (4/5) driven one boot each with vm.sh resets between.

Verified recommended fix

Replace the ppb_msq.c:292 GET-without-loop panic with EOPNOTSUPP and make mode2xfer() return NULL (-> EINVAL) on unknown mode/EPP protocol instead of panicking

Verdict

Reproduced on the QEMU guest (DragonFly 6.5-DEVELOPMENT x86_64), both variants, with production backtraces through ppb_MS_exec(). (a) MS_OP_GET with get_xfer[mode].loop == NULL panics at ppb_msq.c:292 instead of returning an error β€” vpoio_instr() (vpoio.c:461-476) reaches this exact ppb_MS_exec(MS_OP_GET) shape, and get_xfer slots are initialized for exactly ONE mode at attach (vpoio.c:612-633): any driver flow that reads before GET-init, or after the bus mode moved to an uninitialized slot, kills the kernel; the panic fires before the error path can run, so the 'error/abort' contract of the engine is unenforceable for reads. (b) mode2xfer() panics on any ppb->mode outside {0,1,2,4,8} (ppb_msq.c:100-102) β€” ppbconf.h itself defines mode aliases such as PPB_SPP=0x3 (ppbconf.h:47) that ppb_set_mode() stores verbatim after masking 0x0f (ppb_base.c:138); the harness sets exactly that alias and panics. The panic message also prints ppbdev->mode (the ivars field, 0 here) instead of the bus mode (3) β€” empirically demonstrated by 'panic: mode2xfer: unknown mode (0)' while the mode was 3. Reachability in-tree today requires a root-gated device node (ppi is 0600 root:wheel, ppi.c:164-166; vpo goes through CAM), so severity Low (robustness/local-DoS-class). Fix validated: GET-without-loop returns EOPNOTSUPP(45), unknown mode returns EINVAL(22) via NULL-from-mode2xfer handling in ppb_MS_init/ppb_MS_microseq; no panic, guest alive.