ppb_MS_init_msq(): instruction index `ins` never bounds-checked β controlled OOB kernel write up to +8184 bytes from the microsequence array
Summary
The parameter initializer validates only the arg slot but uses the 8-bit MS_INS(param) position directly as msq[ins].arg[arg], writing a fully controlled int/pointer/function-pointer at up to 255*32+24 bytes past the caller's array with no diagnostic (the MS_INS_MASK comment even carries an XXX). Harness on production code: MS_PARAM(60,0,INT) and MS_PARAM(200,2,PTR) landed at +1792/+6416 into a canaried 256-instruction buffer. All in-tree callers pass static descriptors (ppi's user microseq path is #if 0'd and 0600) - driver-misuse/hardening class today, memory-corruption primitive for a future KLD. ins>=PPB_MS_MAXLEN fail-fast fix validated.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5573 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.5 KB | β raw | |
| VERDICT.md | β | 4.9 KB | β raw | |
| msqhx.c | β | 19.3 KB | view raw | |
| Makefile | β | 311 B | β download | |
| build.sh | β | 126 B | view raw | |
| run.sh | β | 156 B | view raw | |
| build.log | β | 7.7 KB | view raw | |
| run.log | β | 967 B | view raw | |
| fix_run.log | β | 1.1 KB | view raw | |
| fix.diff | β | 3.2 KB | view raw | |
| env.txt | β | 670 B | view raw |
DF-5573 β ppb_MS_init_msq(): instruction index ins is never bounds-checked β controlled OOB kernel write of up to ~8 KB from the microsequence array
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 0 (non-fatal)- Observe dmesg (
dmesg | grep MSQHX) / the serial console.
Expected
BASELINE (unfixed): 'OOB ins=60 ... CONTROLLED WRITE LANDED' and 'OOB ins=200 ... CONTROLLED WRITE LANDED' β kernel memory outside the caller's array is written with fully controlled 4/8-byte values. PATCHED: in-bounds init still works; ins=200 (>= PPB_MS_MAXLEN) fails fast with 'panic: ppb_MS_init_msq: instruction out of range (200)' before any write.
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-5573 β verdict narrative
Finding
ppb_MS_init_msq(): instruction index ins is never bounds-checked β controlled OOB kernel write of up to ~8 KB from the microsequence array
Bottom line (status: reproduced, impact: none)
Reproduced on the QEMU guest (DragonFly 6.5-DEVELOPMENT x86_64) via the KLD-harness-with-production-code evidence class: the unmodified /usr/src/sys/bus/ppbus/ppb_msq.c (sha256 223e7f91..., identical to the repo sys/ copy) was compiled with -Werror into msqhx.ko and driven against a fake ppbus controller implementing the ppc_exec_microseq() contract. ppb_MS_init_msq() extracts the instruction position with MS_INS(param) (8 bits wide, ppb_msq.h:38) but validates only the argument index (arg >= PPB_MS_MAXARGS panic at ppb_msq.c:206-208); ins is used directly as msq[ins].arg[arg] at ppb_msq.c:218/222/226/230 with no bound. Harness case 0 proves the primitive on a private kmalloc'd 256-instruction buffer: MS_PARAM(60,0,MS_TYP_INT) writes 0x41424344 at +1792 bytes past a 4-instruction caller array and MS_PARAM(200,2,MS_TYP_PTR) writes a pointer value at +6416 bytes (max reachable: ins=255, arg=2, MS_TYP_PTR/FUN -> +8184 bytes, 8-byte value). A buggy or third-party KLD driver that computes a descriptor from runtime data (instead of the compile-time MS_PARAM constants all in-tree callers use) gets silent kernel memory corruption rather than the panic the half-sibling arg check provides. Impact ceiling: with no unprivileged input path into descriptors (ppi's user-reachable microseq code is #if 0'd, ppi.c:419-442; all other callers pass static descriptors) this is a kernel-API hardening defect (driver-misuse -> corruption), not a direct privilege boundary; severity Low, bucket memcorrupt-class primitive. Fix validated: ins >= PPB_MS_MAXLEN(64) now panics fail-fast ('instruction out of range (200)', production backtrace ppb_MS_init_msq+0x1f9 in fix_run.log) and the in-bounds regression (ins=2) still initializes correctly. Residual: the function cannot know the caller's actual array length, so ins<64 remains trusting; a complete fix would pass the array length (KPI change).
Exploit chain
n/a (kernel-internal API; no unprivileged descriptor input exists in-tree β primitive characterized on a private buffer instead)
Evidence
run.log lines 'OOB ins=60 ... CONTROLLED WRITE LANDED' / 'OOB ins=200 ... CONTROLLED WRITE LANDED' (baseline, pristine production code); fix_run.log (in-bounds ok + 'panic: ppb_MS_init_msq: instruction out of range (200)' with production backtrace); build.log ('-c /usr/src/sys/bus/ppbus/ppb_msq.c' with -Werror, sha256 223e7f91...); VERDICT.md
Kernel references
sys/bus/ppbus/ppb_msq.c:201sys/bus/ppbus/ppb_msq.c:206sys/bus/ppbus/ppb_msq.c:218sys/bus/ppbus/ppb_msq.c:222sys/bus/ppbus/ppb_msq.c:226sys/bus/ppbus/ppb_msq.c:230sys/bus/ppbus/ppb_msq.h:38sys/bus/ppbus/ppbconf.h:135
Fix validation (fixed)
Applied 'ins >= PPB_MS_MAXLEN -> fail-fast panic' to the guest's /usr/src copy and rebuilt the harness: the ins=200 OOB write is now blocked by 'panic: ppb_MS_init_msq: instruction out of range (200)' (production frame ppb_MS_init_msq+0x1f9) and the in-bounds ins=2 initialization still works. Residual documented: ins<64 cannot be checked against the caller's real array length without a KPI change.
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; harness written fresh. Iterations: (1) load-time kenv tunable replaced by sysctl hw.msqhx_case after the first module could not be unloaded (drivers initially lacked detach methods); (2) diagnostic reads of the fix-added ppb_in_xfer field removed so the same harness compiles against pristine and patched headers. Production ppb_msq.c compiled clean on first attempt.
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
fixedApplied 'ins >= PPB_MS_MAXLEN -> fail-fast panic' to the guest's /usr/src copy and rebuilt the harness: the ins=200 OOB write is now blocked by 'panic: ppb_MS_init_msq: instruction out of range (200)' (production frame ppb_MS_init_msq+0x1f9) and the in-bounds ins=2 initialization still works. Residual documented: ins<64 cannot be checked against the caller's real array length without a KPI change.
fix_run.log (patched production source, same case); run.log (baseline) vs fix_run.log (patched); fix.diff sha256 8e3dfc90e6049ce1704c3cef833cba0904b6404f11360bdd299e37980e9327bb
Confirmed kernel references
Detail
Exploit chain
n/a (kernel-internal API; no unprivileged descriptor input exists in-tree β primitive characterized on a private buffer instead)
Evidence (decisive lines)
run.log lines 'OOB ins=60 ... CONTROLLED WRITE LANDED' / 'OOB ins=200 ... CONTROLLED WRITE LANDED' (baseline, pristine production code); fix_run.log (in-bounds ok + 'panic: ppb_MS_init_msq: instruction out of range (200)' with production backtrace); build.log ('-c /usr/src/sys/bus/ppbus/ppb_msq.c' with -Werror, sha256 223e7f91...); VERDICT.md
PoC changes
No seed PoC existed; harness written fresh. Iterations: (1) load-time kenv tunable replaced by sysctl hw.msqhx_case after the first module could not be unloaded (drivers initially lacked detach methods); (2) diagnostic reads of the fix-added ppb_in_xfer field removed so the same harness compiles against pristine and patched headers. Production ppb_msq.c compiled clean on first attempt.
Verified recommended fix
ppb_MS_init_msq(): panic fail-fast when ins >= PPB_MS_MAXLEN, mirroring the existing arg check
Verdict
Reproduced on the QEMU guest (DragonFly 6.5-DEVELOPMENT x86_64) via the KLD-harness-with-production-code evidence class: the unmodified /usr/src/sys/bus/ppbus/ppb_msq.c (sha256 223e7f91..., identical to the repo sys/ copy) was compiled with -Werror into msqhx.ko and driven against a fake ppbus controller implementing the ppc_exec_microseq() contract. ppb_MS_init_msq() extracts the instruction position with MS_INS(param) (8 bits wide, ppb_msq.h:38) but validates only the argument index (arg >= PPB_MS_MAXARGS panic at ppb_msq.c:206-208); ins is used directly as msq[ins].arg[arg] at ppb_msq.c:218/222/226/230 with no bound. Harness case 0 proves the primitive on a private kmalloc'd 256-instruction buffer: MS_PARAM(60,0,MS_TYP_INT) writes 0x41424344 at +1792 bytes past a 4-instruction caller array and MS_PARAM(200,2,MS_TYP_PTR) writes a pointer value at +6416 bytes (max reachable: ins=255, arg=2, MS_TYP_PTR/FUN -> +8184 bytes, 8-byte value). A buggy or third-party KLD driver that computes a descriptor from runtime data (instead of the compile-time MS_PARAM constants all in-tree callers use) gets silent kernel memory corruption rather than the panic the half-sibling arg check provides. Impact ceiling: with no unprivileged input path into descriptors (ppi's user-reachable microseq code is #if 0'd, ppi.c:419-442; all other callers pass static descriptors) this is a kernel-API hardening defect (driver-misuse -> corruption), not a direct privilege boundary; severity Low, bucket memcorrupt-class primitive. Fix validated: ins >= PPB_MS_MAXLEN(64) now panics fail-fast ('instruction out of range (200)', production backtrace ppb_MS_init_msq+0x1f9 in fix_run.log) and the in-bounds regression (ins=2) still initializes correctly. Residual: the function cannot know the caller's actual array length, so ins<64 remains trusting; a complete fix would pass the array length (KPI change).
No comments yet.