Unvalidated hw.icu.msi_start boot tunable: negative value -> negative-index OOB scan/write in icu_abi_msi_alloc_intern() + msi_intr[] negative OOB read + setidt() overwrite of exception gates 0..31 -> triple-fault cold reset (ICU twin of DF-1037)
Summary
The knob parses via strtoq (accepts '-') and masks with signed ~0x1f with NO range check, initmap running unconditionally every boot: hw.icu.msi_start=-32 poisons every later ICU MSI allocation - the scan starts at i=-32, aliases the previous cpu's row tail (passes the window check), OOB-writes im_type/im_msi_base, returns negative vectors, and msi_setup's msi_intr[-k] OOB rodata reads feed setidt(0..31, garbage) - exception-gate overwrite on any cpu. Boot-config-proven END-TO-END: with the knob in loader.conf the trigger alloc returned rc=0 with zone[0]=-32, then int3 on cpu1 produced a silent TRIPLE FAULT (serial: int3 line immediately followed by the loader menu - cold self-reset). No KASSERT guards this path on ANY config. Range-check-after-mask fix validated with the knob still set. The DF-1037 ioapic twin deserves re-verification.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5466 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| icu_msi_start.c | β | 3.5 KB | view raw | |
| build.sh | β | 126 B | view raw | |
| run.sh | β | 208 B | view raw | |
| build.log | β | 11.2 KB | view raw | |
| run.log | β | 1.0 KB | view raw | |
| run_fixed.log | β | 524 B | view raw | |
| panic_triplefault.txt | β | 865 B | view raw | |
| env.txt | β | 2.3 KB | view raw | |
| VERDICT.md | β | 4.0 KB | β raw | |
| fix.diff | β | 2.2 KB | view raw | |
| manifest.json | β | 1.1 KB | view raw | |
| verdict.json | β | 5.1 KB | view raw |
DF-5466 VERDICT β reproduced (negative boot tunable β negative vectors β exception-gate smash β triple fault), Low severity
Bottom line
REPRODUCED on the production-behaviour kernel (X86_64_NOINV), and β
unlike DF-5464/DF-5465 β no INVARIANTS KASSERT guards this path at
all, so every kernel config is equally affected. hw.icu.msi_start
is parsed by icu_abi_initmap() (icu_abi.c:323-324) with
kgetenv_int β strtoq (accepts '-'), masked with signed ~0x1f
(-32), and never range-checked. icu_abi_initmap() runs
unconditionally at boot (machdep.c:2785), so a single
hw.icu.msi_start="-32" in /boot/loader.conf poisons every later ICU
MSI allocation:
- the scan loop runs with i = -32 (icu_abi.c:473); on cpu1 the window
icu_irqmaps[1][-32..-1]aliases the previous row's tail[0][160..191](all UNUSED), so the "free window" check passes; - OOB writes
im_type=MSI, im_msi_base=-32into cpu0's row (icu_abi.c:495-496) andintrs[j] = -32+j(icu_abi.c:498); msi_setup(-32..-1, 1)(icu_abi.c:499) readsmsi_intr[-32..-1]OOB (msi.c:433-435 β the 192-entry table lives in .rodata at 0xffffffff80d9d800, so the "handlers" are rodata garbage) and callssetidt(0x20-32+j = 0..31, garbage, ..., cpu1)β overwriting cpu1's exception gates 0..31 (#DE/#PF/#GP/#BP/...).
The rig then pinned itself to cpu1 and executed int $3: the serial
console shows icu_msi_start: on cpu1, triggering int3 ... followed
immediately by the bootloader menu with zero trap text β #BP
(gate 3, garbage RIP) β instruction-fetch #PF (gate 14, garbage) β #DF
(gate 8, garbage) β triple fault β cold reset. The machine
rebooted by itself (fresh dmesg, module gone).
This is the icu twin of DF-1037 (hw.ioapic.msi_start, ioapic_abi.c:753, Info, pass-1, unverified); this pack verifies the class end-to-end.
How it was reproduced (boot-config variant, run.log + panic_triplefault.txt)
# /boot/loader.conf: hw.icu.msi_start="-32" (kenv confirms -32) kldload icu_msi_start.ko icu_msi_start: rc=0 zone[0]=-32 zone[31]=-1 <- NEGATIVE vectors icu_msi_start: NEGATIVE vectors allocated ... gates 0..31 of cpu1 smashed icu_msi_start: on cpu1, triggering int3 ... <serial: next line is the boot loader menu β triple fault, no dump>
Primitive characterization
The poisoned tunable turns msi_setup() into an arbitrary-offset IDT
gate writer (gates 0..31 of any online cpu, offsets read from OOB
rodata before msi_intr[]) and the map writes corrupt the previous cpu's
MSI bookkeeping. On a real ICU-ABI machine (no IOAPIC) the first PCI
MSI allocation at boot hits this with no KLD involved. Trigger needs
root (loader.conf / kenv) β hence Low severity despite the hard
memory corruption + control-flow redirection.
Fix validation (fix.diff, applied to guest /usr/src, incremental rebuild)
Same boot config on the fixed kernel β knob still "-32" in /boot/loader.conf (kenv verified at runtime):
icu_msi_start: rc=0 zone[0]=32 zone[31]=63 icu_msi_start: allocation not OOB ... -- no gate smash, exiting cleanly
initmap clamps the poisoned start into range; allocation lands at the legal window 32..63; no OOB, no smash, guest healthy in the same boot.
Kernel references
- sys/platform/pc64/icu/icu_abi.c:323-324 (unvalidated tunable parse/mask)
- sys/platform/pc64/icu/icu_abi.c:167 ("for testing only" comment)
- sys/platform/pc64/icu/icu_abi.c:473-499 (negative-i scan, OOB writes, msi_setup)
- sys/platform/pc64/x86_64/machdep.c:2785 (initmap runs on every kernel)
- sys/platform/pc64/x86_64/msi.c:431-436 (msi_intr[intr] + setidt, no bounds)
- sys/kern/kern_environment.c:379-388 (kgetenv_int β strtoq, '-' accepted)
- findings/DF-1037 (ioapic twin, pass 1)
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
fixedfix.diff applied to guest /usr/src/sys/platform/pc64/icu/icu_abi.c, X86_64_NOINV rebuilt and rebooted WITH THE SAME BOOT KNOB still in /boot/loader.conf (kenv hw.icu.msi_start = -32 verified at runtime): the trigger module now gets rc=0 zone[0]=32 zone[31]=63 β a legal allocation at vectors 32..63 after the clamp β prints 'no gate smash, exiting cleanly' and returns; guest healthy in the same boot (run_fixed.log). Bad behavior gone with the boot config unchanged.
run_fixed.log ('rc=0 zone[0]=32 zone[31]=63', 'allocation not OOB (fixed or clamped kernel?) -- no gate smash, exiting cleanly'; uptime continuous)
Confirmed kernel references
Detail
Exploit chain
root sets hw.icu.msi_start=-32 in loader.conf (or kenv) -> boot-time icu_abi_initmap() computes -32 (icu_abi.c:323-324) -> any ICU MSI alloc (on ICU-ABI machines: first PCI MSI probe at boot via nexus.c:595; here: KLD trigger) -> icu_abi.c:473 loop with negative i -> OOB read/write at icu_irqmaps[cpuid][-32..-1] (previous cpu's row) + intrs[j]=-32+j + msi_setup(-32+j) -> msi_intr[-k] OOB read (rodata) + setidt(0..31, garbage, cpu) = arbitrary-cpu exception-gate overwrite -> any exception on that cpu redirects RIP to OOB-read kernel data -> triple fault / RIP-controlled crash. On a real ICU-ABI machine this fires during boot with no KLD involved.
Evidence (decisive lines)
run.log + panic_triplefault.txt (serial: 'rc=0 zone[0]=-32 zone[31]=-1', 'gates 0..31 of cpu1 smashed', 'triggering int3' immediately followed by the bootloader menu β triple fault with no dump; post-reset fresh dmesg/uptime confirm the reset); run_fixed.log (same knob, clamped: zone[0]=32, no smash)
PoC changes
Boot-config variant written fresh: no ksetenv/initmap hacks β the knob is set in /boot/loader.conf and a reboot lets the real boot-time initmap() parse it; the trigger module only calls msi_alloc and, if negative vectors were allocated, pins to cpu1 (lwkt_migratecpu) and executes int3 to exercise the smashed gate deterministically (on a fixed kernel zone[0]>=0 and the int3 is skipped).
Verified recommended fix
Range-check icu_abi_msi_start after the mask in icu_abi_initmap() (if (<0 || >=IDT_HWI_VECTORS) = 0), mirroring DF-1037's suggested fix for the ioapic twin (see fix.diff).
Verdict
REPRODUCED on a production-behaviour kernel via the pure boot-config route: 'hw.icu.msi_start="-32"' in /boot/loader.conf (kenn confirmed -32; icu_abi_initmap() runs unconditionally at machdep.c:2785) poisons icu_abi_msi_start to -32 because kgetenv_int/strtoq accepts the sign and the &~0x1f mask never range-checks (icu_abi.c:323-324). The first ICU MSI allocation then scans from i=-32: on cpu1 the window icu_irqmaps[1][-32..-1] aliases cpu0's row tail [160..191] (all UNUSED) so it 'succeeds' β msi_alloc returns rc=0 with NEGATIVE vectors (zone[0]=-32 zone[31]=-1), performs OOB map writes (icu_abi.c:495-496) and calls msi_setup(-32..-1, 1) (icu_abi.c:499), whose msi_intr[-32..-1] OOB reads (msi.c:433, rodata garbage) feed setidt(0..31, ..., cpu1) β overwriting cpu1's exception gates 0..31. The rig's int3 on cpu1 then produced a TRIPLE FAULT: the serial console shows 'triggering int3' immediately followed by the bootloader menu with zero trap text (#BP->smashed gate->fetch #PF->smashed gate->#DF->smashed gate->cold reset). No INVARIANTS KASSERT exists on this path β every kernel config is equally affected. Fix (clamp msi_start into [0, IDT_HWI_VECTORS)) validated on a rebuilt kernel with the SAME boot knob still set: allocation lands legally at vectors 32..63 (zone[0]=32), no OOB, no smash, guest healthy.
No comments yet.