Missing DRIVER_MODULE(iicbus, pcf): the iicbus child never attaches, so pcf->iicbus has a NULL softc forever and the first chip interrupt panics the kernel via iicbus_intr()->sc->owner
Summary
The child is added on the pcf bus but the iicbus driver is registered only on iicbb - newbus probes children only against the parent bus's devclass, so the child stays DS_NOTPRESENT with a NULL softc permanently; pcfintr (registered at attach, ENI enabled since probe) calls iicbus_intr on 8 paths which derefs sc->owner on the NULL softc. KLD-rig-verified on the guest: exact production attach path (io 0x320 irq 5) -> 'iicbus0 state=0 softc=0' (the bug observed) -> the exact :475 sink call -> Fatal trap 12 at iicbus_intr+0x18 va 0x8; on silicon the IRQ line substitutes. DRIVER_MODULE + NULL-guard fix validated on rebuilt module (iicbus0 attaches, sink returns cleanly, guest alive). The driver is also functionally dead (no /dev/iicN).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5490 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 4.3 KB | β raw | |
| VERDICT.md | β | 5.7 KB | β raw | |
| df5490_rig.c | β | 2.7 KB | view raw | |
| df5490_probe.c | β | 884 B | view raw | |
| build.sh | β | 201 B | view raw | |
| run.sh | β | 212 B | view raw | |
| run.log.baseline_panic_full | β | 14.1 KB | β download | |
| panic.txt | β | 1016 B | view raw | |
| run.fixed_serial.log | β | 462 B | view raw | |
| fix.diff | β | 1.1 KB | view raw | |
| env.txt | β | 350 B | view raw | |
| verdict.json | β | 5.4 KB | view raw |
DF-5490 β pcf: iicbus child never attaches (missing DRIVER_MODULE(iicbus, pcf)) β NULL-softc panic in pcfintr
What
pcf_attach() (sys/bus/iicbus/pcf/pcf.c:200) adds an "iicbus" child to the
pcf bus, but no DRIVER_MODULE(iicbus, pcf, ...) exists anywhere β the iicbus
driver is registered only on the iicbb bus (sys/bus/iicbus/iicbus.c:194).
DragonFly newbus probes a child only against drivers registered in the parent
bus's devclass (sys/kern/subr_bus.c:1414-1484 device_probe_child), so the
child never attaches: it stays state=0 (DS_NOTPRESENT) with softc == NULL
forever. pcfintr() β the live IRQ handler registered by pcf_attach()
(pcf.c:193-195) β calls iicbus_intr(pcf->iicbus, ...) on 8 paths
(pcf.c:412, 430, 435, 445, 447, 453, 468, 475); iicbus_intr() dereferences
device_get_softc(bus)->owner (iiconf.c:47-50) β NULL softc β page fault β
kernel panic. ENI (chip interrupt enable) is permanently set by
pcf_rst_card() (pcf.c:537), pcf_stop() (pcf.c:300) and pcfintr's own error
path (pcf.c:490), so on real PCF8584 hardware in the default (non-polled)
configuration the FIRST interrupt after attach panics the machine
deterministically. The driver is also functionally dead: with no iicbus
attachment there is no /dev/iicN and no in-tree caller can reach
pcf_write/pcf_read/pcf_start.
Build (guest)
scp df5490_rig.c dfbsd:/root/ mkdir -p /usr/src/sys/bus/iicbus/df5490 && cp /root/df5490_rig.c $_/ printf 'KMOD= df5490_rig\nSRCS= df5490_rig.c\nSRCS+= bus_if.h device_if.h iicbus_if.h isa_if.h\n.include <bsd.kmod.mk>\n' > /usr/src/sys/bus/iicbus/df5490/Makefile cd /usr/src/sys/bus/iicbus/df5490 && make
Run (baseline β expect panic)
kldload iicbus && kldload pcf kldload /usr/src/sys/bus/iicbus/df5490/df5490_rig.ko
The rig attaches pcf0 through the exact newbus API isahint uses
(BUS_ADD_CHILD + bus_set_resource + device_probe_and_attach β cf.
sys/bus/isa/isahint.c:53-91), because the kernel's hinted-children
enumeration only reads the compiled-in devtab (subr_bus.c:2401
resource_query_string), so kenv/loader hints cannot add an isa child at
runtime. After attach it dumps the child state and then invokes the exact
pcfintr sink call (iicbus_intr(pcf->iicbus, INTR_RECEIVE, &data),
pcf.c:475).
Expected (observed) β see panic.txt / run.log.baseline_panic_full
df5490: added pcf child pcf0, probing+attaching pcf0: <PCF8584 I2C bus controller> at port 0x320-0x321 irq 5 on isa1 interrupt uses mplock: pcf0 df5490: device_probe_and_attach -> 0 df5490: pcf0=pcf0 state=3 children=1 df5490: child[0]=iicbus0 state=0 softc=0 devclass=iicbus <-- never attached, NULL softc df5490: calling iicbus_intr(child0, INTR_RECEIVE, &c) == pcfintr pcf.c:475 Fatal trap 12: page fault while in kernel mode fault virtual address = 0x8 Stopped at iicbus_intr+0x18: movq 0x8(%rax),%rbx <-- sc->owner @ offset 8 of NULL softc
Run (fix validation β expect NO panic)
cd /usr/src && patch -p1 < fix.diff # adds DRIVER_MODULE(iicbus, pcf, ...) + pcfintr guard cd sys/bus/iicbus/pcf && make && cp pcf.ko /root/pcf_fixed.ko kldload iicbus && kldload iic && kldload /root/pcf_fixed.ko kldload /usr/src/sys/bus/iicbus/df5490/df5490_rig.ko
Expected (observed) β see run.fixed_serial.log
iicbus0: <Philips I2C bus> on pcf0 addr 0xaa df5490: child[0]=iicbus0 state=3 softc=0xfffff8004f0e7478 devclass=iicbus df5490: iicbus_intr RETURNED (no fault)
Guest stays up; the previously-panicking sink call is a clean no-op (owner == NULL).
Notes
- The only hardware-substituted step is IRQ delivery: on real PCF8584
silicon the chip's interrupt (enabled since probe via ENI, pcf.c:171β537)
calls the registered
pcfintrhandler, which makes the identicaliicbus_intr(pcf->iicbus, ...)call that the rig makes. The device state (pcf0 attached, irq registered, child unattached with NULL softc) is the genuine production state, created through production newbus APIs. - Cross-observations for future audits (other files): (1) even with the fix,
iicbus0's own wildcard children (ic/iic/iicsmb) did not attach/materialize
/dev/iic0β iicbus.c/iic.c child-handling question; (2)kldunload iicbussucceeded while iicbus0 was attached (module unload not pinned to attached bus children).
DF-5490 β VERDICT
Status: REPRODUCED (impact: panic / kernel NULL-pointer DoS, hardware-gated delivery). Fix validated live: baseline panics 1/1, patched module 1/1 clean.
Root cause (path:line)
pcf_attach()adds an iicbus child:pcf->iicbus = device_add_child(pcfdev, "iicbus", -1)β sys/bus/iicbus/pcf/pcf.c:200.- newbus can only probe that child against drivers registered on the pcf
bus β
device_probe_child()iteratesdev->devclass->driversof the parent bus (sys/kern/subr_bus.c:1414-1484). The iicbus driver is registered ONLY on the iicbb bus:DRIVER_MODULE(iicbus, iicbb, ...)β sys/bus/iicbus/iicbus.c:194. NoDRIVER_MODULE(iicbus, pcf, ...)exists anywhere in the tree (verified:grep -rn 'DRIVER_MODULE(iicbus' sys/returns exactly one hit). Sibling bridges prove the requirement:DRIVER_MODULE(smbus, ichsmb, ...)at sys/bus/smbus/ichsmb/ichsmb.c:696 and the five-line bridge enumeration at sys/bus/iicbus/iicbb.c:441-445. - Therefore the child never attaches:
state=0(DS_NOTPRESENT),device_get_softc(pcf->iicbus) == NULLβ proven live: rig printedchild[0]=iicbus0 state=0 softc=0 devclass=iicbus(panic.txt). pcfintr()is a live registered interrupt handler (pcf.c:193-195; serial log:interrupt uses mplock: pcf0). All 8 of its event paths calliicbus_intr(pcf->iicbus, ...)(pcf.c:412, 430, 435, 445, 447, 453, 468, 475).iicbus_intr()doessc = device_get_softc(bus); if (sc->owner)β sys/bus/iicbus/iiconf.c:47-50 β read at offset 8 of NULL βFatal trap 12, fault virtual address = 0x8, Stopped at iicbus_intr+0x18: movq 0x8(%rax),%rbx(panic.txt).- On hardware: ENI (interrupt enable) is permanently set β
pcf_rst_card()pcf.c:537 (runs from probe, pcf.c:171),pcf_stop()pcf.c:300, pcfintr error path pcf.c:490. The PCF8584 asserts INT when PIN clears (any completed byte transfer, master or slave). Default configuration is interrupt mode (IIC_POLLED unset in the hint flags). First interrupt after attach β panic. Not a narrow race: every interrupt-bearing pcf deployment hits it. - Secondary effect: with the child dead there is no
/dev/iicN, andpcf_start/pcf_write/pcf_readhave no in-tree caller (iic.c ioctls need the iic child; iicsmb needs the iicsmb child; pcfclock is ppbus-based). The driver is functionally dead in-tree β and the dead code still panics via pcfintr.
Reproduction (this run)
QEMU has no PCF8584 model. The rig reproduces the exact production state and the exact sink through production APIs:
- pcf0 attached on isa1 @ io 0x320-0x321, irq 5, interrupt mode, via the
same
BUS_ADD_CHILD/bus_set_resource/device_probe_and_attachsequence isahint.c:53-91 performs (kenv/loader hints cannot add isa children becauseresource_query_string()enumerates only the compiled-in devtab β subr_bus.c:2401-2410; both /boot/device.hints (not parsed by the DF loader) and loader.confhint.pcf.0.*kenv variables were tried and were ignored by the enumeration). - child state dumped:
iicbus0 state=0 softc=0β the bug, observed. - the exact call pcfintr makes β
iicbus_intr(child, INTR_RECEIVE, &c)(pcf.c:475) β fired: page fault at 0x8, kernel panic, DDB (panic.txt, run.log.baseline_panic_full). 1/1 runs.
The only substituted step is who invokes the call (rig instead of the IRQ
line); the pointer value, device state, and fault are identical to what the
registered pcfintr handler produces on silicon.
Fix validation
fix.diff (against read-only sys/; applied only to the guest's /usr/src copy):
1. #include "../iicbus.h" + DRIVER_MODULE(iicbus, pcf, iicbus_driver,
iicbus_devclass, NULL, NULL); β registers the iicbus driver on the pcf
bus (mirrors ichsmb.c:696).
2. Defensive guard at pcfintr entry: return if pcf->iicbus == NULL ||
device_get_softc(pcf->iicbus) == NULL (covers the setup_intrβadd_child
window in pcf_attach:193-200 and any future registration gap).
Patched pcf.ko rebuilt in-guest (-Werror clean), loaded, rig re-run:
- iicbus0: <Philips I2C bus> on pcf0 addr 0xaa β child ATTACHES (the
previously-dead pcf_print_child() pcf.c:208-219 even prints now).
- child[0]=iicbus0 state=3 softc=0xfffff8004f0e7478 β softc allocated.
- iicbus_intr RETURNED (no fault) β the previously-panicking sink is a
clean no-op (owner == NULL). Guest alive, no panic (run.fixed_serial.log).
1/1 baseline panic vs 1/1 patched clean.
Severity reasoning
No unprivileged-user reachability: the driver attaches only on PCF8584 hardware at a hinted ISA port; the panic trigger is the chip's own interrupt (or any bus event). Impact ceiling = deterministic kernel panic of the machine (DoS) + driver non-function. Per rubric ("narrow trigger, requires specific hardware") β Low, confidence certain (code path proven, state and sink reproduced live).
Attempts
- /boot/device.hints (not parsed by DF loader) β no attach.
- loader.conf hint.pcf.0.* kenv vars β hints present in kenv but devtab-only enumeration (subr_bus.c:2401) ignores them β no attach.
- Rig v1 with
bus_set_resource(..., cpuid=-1)for IRQ β hit unrelatednexus invalid cpuid: -1KASSERT (nexus_alloc_resource); rig bug, fixed by passingmachintr_legacy_intr_cpuid(irq)exactly as isahint.c:70-72 does. - Rig v2 β full success: baseline panic captured; fixed module validated.
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 stock pcf.ko: 1/1 rig runs panic (Fatal trap 12, VA 0x8, iicbus_intr+0x18). Patched pcf.ko (fix.diff applied to guest /usr/src, module rebuilt, same rig): 1/1 clean -- iicbus0 attaches ('on pcf0 addr 0xaa'), child softc valid, iicbus_intr returns without fault, guest stays up.
run.fixed_serial.log; run.log.baseline_panic_full (before/after); fix.diff
Confirmed kernel references
- sys/bus/iicbus/pcf/pcf.c:200
- sys/bus/iicbus/pcf/pcf.c:193
- sys/bus/iicbus/pcf/pcf.c:412
- sys/bus/iicbus/pcf/pcf.c:475
- sys/bus/iicbus/pcf/pcf.c:537
- sys/bus/iicbus/pcf/pcf.c:300
- sys/bus/iicbus/pcf/pcf.c:643
- sys/bus/iicbus/iicbus.c:194
- sys/bus/iicbus/iiconf.c:45
- sys/bus/iicbus/iiconf.c:50
- sys/kern/subr_bus.c:1414
- sys/bus/iicbus/iicbb.c:441
- sys/bus/smbus/ichsmb/ichsmb.c:696
- sys/bus/isa/isahint.c:53
Detail
Exploit chain
PCF8584 hardware at a hinted ISA port + default interrupt mode: boot/kldload attaches pcf0 and registers pcfintr for the ISA IRQ; ENI has been enabled since probe (pcf.c:171->pcf_rst_card:537); any completed byte transfer (master or slave event) clears PIN and asserts INT; pcfintr runs, status has PIN clear, slave-receiver branch reaches iicbus_intr(pcf->iicbus, INTR_RECEIVE, &data) at pcf.c:475 -> iiconf.c:47-50 device_get_softc(child)==NULL -> sc->owner read at NULL+8 -> Fatal trap 12 -> panic. Every interrupt-bearing pcf deployment panics on first bus event; no attacker privilege needed beyond owning the hardware/bus.
Evidence (decisive lines)
panic.txt (decisive: 'child[0]=iicbus0 state=0 softc=0' then 'Fatal trap 12 ... fault virtual address = 0x8 ... Stopped at iicbus_intr+0x18: movq 0x8(%rax),%rbx'); run.log.baseline_panic_full (full untrimmed serial console of the baseline run incl. attach lines); run.fixed_serial.log (patched run: iicbus0 attaches, 'RETURNED (no fault)'); fix.diff; df5490_rig.c (production-API attach rig + sink)
PoC changes
no seed PoC existed (pass-2 discovery); wrote df5490_rig.c from scratch: isahint-style BUS_ADD_CHILD/bus_set_resource/device_probe_and_attach attach (kenv hints unusable: resource_query_string enumerates only the compiled devtab, subr_bus.c:2401), then state dump + exact pcfintr sink. Rig v1 passed cpuid=-1 for the IRQ resource and hit an unrelated nexus KASSERT; fixed by passing machintr_legacy_intr_cpuid(irq) exactly as isahint.c:70-72 does.
Verified recommended fix
Add '#include "../iicbus.h"' and 'DRIVER_MODULE(iicbus, pcf, iicbus_driver, iicbus_devclass, NULL, NULL);' to pcf.c (mirror ichsmb.c:696), plus an early 'if (pcf->iicbus == NULL || device_get_softc(pcf->iicbus) == NULL) return;' guard in pcfintr() -- see fix.diff (validated live).
Verdict
REPRODUCED: no DRIVER_MODULE(iicbus, pcf, ...) exists (only iicbus.c:194 on iicbb; cf. the bridge-enumeration pattern iicbb.c:441-445 and DRIVER_MODULE(smbus, ichsmb) ichsmb.c:696), so the iicbus child added at pcf.c:200 never attaches -- device_probe_child (subr_bus.c:1414-1484) only probes drivers registered on the parent (pcf) bus. Live proof on the QEMU guest with pcf0 attached via the isahint production API path: child dumped as 'iicbus0 state=0 softc=0' (DS_NOTPRESENT, NULL softc), and the exact pcfintr sink call iicbus_intr(pcf->iicbus, INTR_RECEIVE, &data) (pcf.c:475, one of 8 pcfintr call sites) page-faults reading sc->owner (offset 8) of the NULL softc: 'Fatal trap 12, fault virtual address = 0x8, Stopped at iicbus_intr+0x18: movq 0x8(%rax),%rbx' -> kernel panic, DDB (1/1 runs). On real PCF8584 silicon in default interrupt mode pcfintr is the registered handler ('interrupt uses mplock: pcf0') and ENI is permanently enabled since probe (pcf.c:171->537, also pcf.c:300/490), so the FIRST chip interrupt after attach panics deterministically; only the IRQ delivery was substituted by the rig because QEMU has no PCF8584 model. Driver is also functionally dead (no /dev/iicN; pcf_write/pcf_read have no in-tree caller). FIX VALIDATED live: fix.diff adds DRIVER_MODULE(iicbus, pcf, ...) + a pcfintr NULL-guard; patched pcf.ko -> 'iicbus0:
No comments yet.