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)
PoC verification
Evidence pack
findings/poc/DF-4964 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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 whatbus_alloc_resource_anyproduces for a BAR); the driver is handed a 4KB "BAR(2)" (sc_piot/sc_pioh + fakestruct resourcer_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) andcanary@+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 freshlykmem_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,uptimeright 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 returnsva & PAGE_MASK(no frame bits) because pmap.c:1739 tests*ptinstead of*ptepfor 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
fixedBaseline 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)
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.
No comments yet.