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

sili(4): attach maps BAR(2) and carves per-port subregions with no size validation β€” undersized port-register window on a crafted/malfunctioning SiI 3124/3132 drives OOB MMIO writes/reads past the pmap-mapped window (kernel panic at boot/kldload, or silent corruption of the adjacent mapping)

Summary

sili_port_alloc carves each port at 8192*port with no check that the mapped window covers it (bus_space_subregion is unchecked arithmetic) - a device decoding an undersized BAR(2) puts the first per-port MMIO outside the mapping. REPRODUCED BOTH WAYS on guest via the kernel's own sili_port_alloc: canary mode shows silent writes 28KB past the window (0xfff/0x1); panic mode (BAR parked in a pmap-verified unmapped hole) Fatal trap 12 at sili_port_alloc+0x360, guest down. Fix (rman_get_size bound in port_alloc) validated on rebuilt kernel: EINVAL, canaries untouched, guest up. Crafted-hardware precondition; same trigger band as the ahci twin DF-4889.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4964 Β· 13 files
FileTypeDescriptionSize
trigger.c β€” 8.5 KB view raw
Makefile β€” 141 B ↓ download
build.sh β€” 129 B view raw
run.sh β€” 163 B view raw
build.log β€” 5.8 KB view raw
run.log β€” 3.1 KB view raw
run.2.log β€” 952 B view raw
panic.txt β€” 1.1 KB view raw
env.txt β€” 333 B view raw
fix_build.log β€” 279 B view raw
fix.diff β€” 854 B view raw
VERDICT.md β€” 5.3 KB ↓ raw
verdict.json β€” 5.0 KB view raw
VERDICT.md
↓ download raw

DF-4964 VERDICT β€” sili(4) undersized-BAR(2) OOB MMIO at attach

Bottom line

REPRODUCED β€” panic (demonstrated), plus silent adjacent-KVA corruption variant. Fix validated on the guest (both manifestations gone).

sili_pci_attach() maps BAR(2) as the port-register/LRAM window (sys/dev/disk/sili/sili_attach.c:170-179) and allocates ad_nports (2 or 4) ports (:253-268). sili_port_alloc() then carves each port's subregion at SILI_PORT_REGION(port) = 8192 * port (sys/dev/disk/sili/sili.c:156-163) with no check that the mapped window actually covers the subregion. bus_space_subregion() on x86_64 is unchecked pointer arithmetic (sys/cpu/x86_64/include/bus_dma.h:78-84), and bus_alloc_resource_any() maps exactly the BAR's decoded size. A SiI 3124/3132 whose BAR(2) decodes fewer than nports * 8KB bytes (malfunctioning silicon, re-flashed card, or crafted PCIe/Thunderbolt device) therefore makes the first per-port MMIO in sili_port_alloc() β€” sili_pwrite(ap, SILI_PREG_INT_DISABLE, ...) at sili.c:173, and the CTL_SET/STATUS accesses at sili.c:185-186 β€” land entirely outside the pmap-mapped window.

How it was reproduced (guest, DragonFly 6.5-DEVELOPMENT #0/#1,

X86_64_GENERIC, sili compiled in)

The PoC kld module df4964.ko fabricates the hardware state only and then calls the driver's own sili_port_alloc(sc, 3) (SiI3124 β†’ 4 ports β†’ port 3 subregion at +24KB):

  • canary mode (default): a real 64KB device mapping (phys 0x60000000 via pmap_mapdev_uncacheable, exactly what bus_alloc_resource_any produces for a BAR); the driver is handed a 4KB "BAR(2)" (sc_piot/sc_pioh + fake struct resource r_start/r_end). Canaries planted at +0x7000 and +0x7014 (outside the window, inside the real map). Result (run.log): canary@+0x7014 after = 00000fff (SILI_PREG_INT_MASK) and canary@+0x7000 after = 00000001 (SILI_PREG_CTL_RESET) β€” the driver's writes landed 28KB past its 4KB window, silently. (The v1 run additionally showed the OOB read at sili.c:186 returning garbage with READY set β†’ "Port 3 will not go into reset" β€” an OOB read flipping driver control flow.)
  • panic mode (kenv df4964.panic=1): the fake 4KB BAR is parked inside a freshly kmem_alloc+kmem_freed (PTE-torn-down, pmap_extract-verified) KVA hole β€” i.e. the BAR window is mapped but everything past it is not, which is the real-hardware undersized-BAR situation. The driver's first OOB write faults (panic.txt): Fatal trap 12: page fault while in kernel mode fault virtual address = 0xfffff80118c67014 (= fake_bar + 0x7014, as predicted) fault code = supervisor write data, page not present Stopped at sili_port_alloc+0x360: movl $0xfff,0x1014(%rdx) Guest died (vm.sh status β†’ down) β€” panic classification met.

Exploitability assessment

  • Trigger surface: requires a SiI 3124/3132 (or compatible-ID) PCIe device presenting an undersized BAR(2) β€” crafted/malfunctioning hardware, or a QEMU-emulated device. Not triggerable by unprivileged local users on normal hardware; boots/kldloads are when the path runs.
  • Primitive: OOB MMIO write at device-adjacent KVA, offset 8192*port + reg (port ≀ 3 from the ID table; reg small), value limited to fixed register constants (0xfff INT_MASK, 0x1 CTL_RESET, ...). OOB MMIO read feeding driver branches.
  • Ceiling: kernel page fault β†’ panic at attach/boot (DoS), or silent corruption of whatever mapping sits past the BAR (on this guest's layout, adjacent kld/mapdev mappings). uid=0 escalation is not realistic: the write offsets/values are device-register-shaped, not attacker-arbitrary, and targets are device-mapping KVA, not kernel heap objects. Severity therefore Low (crafted-hardware precondition).

Fix validation (same guest, fix.diff applied to /usr/src, quickkernel,

installkernel, reboot into #1 Sep 25 12:08 build)

  • canary mode: pci0: port regs window too small for port 3, sili_port_alloc returned 22 (EINVAL), canaries untouched (deadbeef/cafebabe intact) β€” run.2.log.
  • panic mode against the identical unmapped-hole construction that killed the unpatched kernel: TRIGGER: survived?? rc=22, guest stayed up (run.2.log, uptime right after).
  • Baseline (unpatched): canaries clobbered + Fatal trap 12 + guest down (run.log, panic.txt).

Notes

  • The vulnerable decision surface (mapping BAR(2) and choosing nports) is in sili_attach.c; the unchecked subregion arithmetic is in sili.c (sili_port_alloc), so the check belongs there (fix.diff) β€” it then covers any future caller.
  • Incidental observation while building the PoC (NOT this finding, pmap.c is out of scope for this file): pmap_extract() on a kernel VA whose PTE was cleared but whose PT page is still present returns va & PAGE_MASK (no frame bits) because pmap.c:1739 tests *pt instead of *ptep for PG_V β€” callers that treat any non-zero return as "mapped with this phys address" get a bogus physical address for unmapped kernel VAs.

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

Baseline unpatched kernel: canary mode clobbered both canaries with the driver's register constants and panic mode took Fatal trap 12 at sili_port_alloc+0x360 killing the guest. Patched kernel (quickkernel rebuilt with fix.diff, #1 Sep 25 12:08 build): both modes print 'pci0: port regs window too small for port 3', sili_port_alloc returns EINVAL(22) before any MMIO, canaries read deadbeef/cafebabe untouched, and the previously-fatal unmapped-hole target is never accessed - guest stays up.

run.2.log (patched canary + patched panic-mode runs with uptime shown); fix_build.log (kernel build/install completion); panic.txt (baseline panic); run.log (baseline canary corruption)
↓ fix.diffper-fix-DF-4964

Confirmed kernel references

Detail

Exploit chain

crafted/malfunctioning SiI3124/3132 (or emulated) PCIe device decodes BAR(2) < nports8KB -> sili_pci_attach maps it -> sili_port_alloc subregions port at 8192port unvalidated -> first sili_pwrite(INT_DISABLE) at +0x1014 past port base writes outside the mapped window -> adjacent KVA mapped: silent corruption (canary-proven); adjacent KVA unmapped: Fatal trap 12 kernel panic at attach/kldload (guest dies, db> prompt).

Evidence (decisive lines)

panic.txt (Fatal trap 12, fault VA 0xfffff80118c67014 = fake_bar+0x7014, Stopped at sili_port_alloc+0x360: movl $0xfff,0x1014(%rdx)); run.log (canary mode: canary@+0x7014 after=00000fff, canary@+0x7000 after=00000001 -> OOB writes proven, guest alive); run.2.log (patched kernel: 'port regs window too small for port 3', rc=22, canaries deadbeef/cafebabe untouched, panic-mode target never touched, guest up); fix.diff (rman_get_size bounds check in sili_port_alloc); VERDICT.md for the full narrative

PoC changes

Rewrote the seeded concept into a dual-mode kld module: canary mode (real 64KB pmap_mapdev backing + fake 4KB BAR resource; canary readback proves the silent OOB write on any KVA layout) and panic mode (fake BAR parked in a kmem_alloc/kmem_free'd, pmap_extract-verified unmapped hole -> deterministic Fatal trap 12). Fixed module build against the guest tree (generated bus_if/device_if/pci_if/opt_cam/pcidevs headers, no MODULE_DEPEND since sili lacks MODULE_VERSION), fake struct resource for the patched check, real DMA tags + leaked softc for the port-thread path, and worked around DFly pmap_extract's no-frame return for cleared PTEs.

Verified recommended fix

In sili_port_alloc(), reject ports whose subregion exceeds the mapped port-register window: if (sc->sc_pregs == NULL || rman_get_size(sc->sc_pregs) < SILI_PORT_REGION(port) + SILI_PORT_SIZE) return (EINVAL); (see fix.diff)

Verdict

sili_pci_attach() maps BAR(2) and sili_port_alloc() carves per-port subregions at 8192*port with no validation against the mapped window size (bus_space_subregion is unchecked pointer arithmetic), so a SiI 3124/3132 decoding an undersized BAR(2) drives OOB MMIO reads/writes past the pmap-mapped window. Reproduced on the guest by driving the kernel's own sili_port_alloc(sc,3) against a 4KB window: canary mode proved silent OOB writes at +0x7014/+0x7000 (values 0xfff/0x1 read back), panic mode produced Fatal trap 12 at sili_port_alloc+0x360 (movl $0xfff,0x1014(%rdx), fault VA exactly fake_bar+0x7014) and killed the guest. Crafted-hardware precondition, so uid0 escalation is not realistic (offsets device-constrained, values fixed constants, targets device-adjacent KVA); impact ceiling is boot/kldload panic or silent corruption of adjacent mappings. fix.diff (bounds-check rman_get_size(sc_pregs) in sili_port_alloc) validated on a rebuilt kernel: both modes return EINVAL with canaries untouched and no panic.