ig4iic_acpi_attach()'s error unwinder re-enters the serializer leaked by ig4iic_attach()'s bus_setup_intr failure: pci_attached=1 set before ig4iic_attach(); on the setup-irq error path (serializer still held) the ACPI detach calls ig4iic_detach() whose first lwkt_serialize_enter re-enters the self-held, explicitly non-recursive serializer β boot/kldload-time kernel panic (INVARIANTS) / hard self-deadlock (release)
Summary
ACPI twin of DF-5601: pci_attached is set before ig4iic_attach and the setup-irq error path returns with slz still held, so the unwinder's ig4iic_detach re-enters the self-held non-recursive serializer - KKASSERT panic on INVARIANTS kernels, atomic_intr_cond_enter self-deadlock on release. Live-verified in two halves: (A) the real path driven all the way through production probe/_CRS/attach/set_controller on a planted SSDT with a host-side DW latch dance; (B) the panic leg via the REAL production ig4iic_attach/detach with a failing pseudo-bus -> 'serializer is HELD' then the lwkt_serialize_enter assertion panic, guest wedged. Same one-line exit-on-error-path fix as DF-5601 (root cause shared, single fix heals both glues); fix A/B validated.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5617 Β· 23 files| File | Type | Description | Size | |
|---|---|---|---|---|
| VERDICT.md | β | 6.7 KB | β raw | |
| run.sh | β | 108 B | view raw | |
| build.sh | β | 154 B | view raw | |
| build_kld.sh | β | 1.1 KB | view raw | |
| df5617.c | β | 7.2 KB | view raw | |
| df56.c | β | 8.0 KB | view raw | |
| gen_ssdt.py | β | 3.0 KB | view raw | |
| latch_dance.py | β | 5.1 KB | view raw | |
| ssdt_ig4.asl | β | 1.9 KB | β download | |
| ssdt_ig4.aml | β | 96 B | β download | |
| ssdt_ig4p.aml | β | 8.2 KB | β download | |
| regs.bin | β | 512 B | β download | |
| window.json | β | 21 B | view raw | |
| run.log | β | 35.7 KB | view raw | |
| run-fixed.log | β | 15.3 KB | view raw | |
| panic.txt | β | 1.2 KB | view raw | |
| baseline-boot-clean.log | β | 12.9 KB | view raw | |
| dance.log | β | 218 B | view raw | |
| fix.diff | β | 529 B | view raw | |
| fix_build.log | β | 1.6 KB | view raw | |
| env.txt | β | 283 B | view raw | |
| verdict.json | β | 5.3 KB | view raw | |
| manifest.json | β | 1.4 KB | view raw |
DF-5617 β ig4_acpi.c attach-error unwinder re-enters the leaked serializer (ACPI twin of DF-5601)
Status: reproduced (kernel panic through production code) | fix validated (A/B)
What
ig4iic_acpi_attach() sets sc->pci_attached = 1 at ig4_acpi.c:127 before
calling ig4iic_attach(sc) (ig4_acpi.c:132). When ig4iic_attach() fails on
its bus_setup_intr() path β which returns with the serializer still held
(ig4_iic.c:648-655 has no lwkt_serialize_exit() on the error branch β the
root defect filed as DF-1049/DF-5601) β the unwinder
ig4iic_acpi_detach(dev) (ig4_acpi.c:134 β :146-151) calls
ig4iic_detach(sc), whose first action is lwkt_serialize_enter(&sc->slz)
(ig4_iic.c:678) β a re-entry of a self-held, explicitly non-recursive
serializer (sys/serialize.h:41-42): KKASSERT panic on INVARIANTS kernels,
hard self-deadlock otherwise.
Rig β two halves
(A) Real-path proof: planted ig4 ACPI device (no harness code in the path)
ssdt_ig4p.asl (generated by gen_ssdt.py, compiled with the guest's iasl)
declares \_SB.I2C8/I2C9 with HID INT33C2 (first entry of
ig4iic_ids[], ig4_acpi.c:71) and a _CRS whose Memory32Fixed window
points into the SSDT's own AML pad buffer (QEMU places the appended SSDT
at 0xBFFE1CAC β guest-RAM-backed but outside SMAP "System RAM", so
ram_attach() (nexus.c:653-705) has not reserved it and the allocation
succeeds; SMAP-resident windows are refused by the rman β observed live).
The pad carries COMP_VER=0x3131352A so the IG4_ATOM compat gate passes.
QEMU_EXTRA="-acpitable file=$PWD/ssdt_ig4p.aml" dfbsd-qemu/vm.sh up vm.sh run_root 'kldload ig4'
Observed (run.log / dance logs): the REAL ig4iic_acpi_probe matches the HID,
the ACPI _CRS translation yields iomem ... irq N on acpi0, MMIO alloc
succeeds, and ig4iic_attach() (ig4_acpi.c:132) runs the full register
dance on the crafted device β reaching bus_setup_intr() with the latch
supplied by latch_dance.py (the DW ENABLE_STATUS 0x6Cβ0x9C latch does not
exist in RAM; the host plays it via QEMU monitor gpa2hva +
/proc/<qemu>/mem):
ig4iic0: <Designware I2C Controller> iomem 0xbffe1cdc-0xbffe2cdb irq 17 on acpi0 ig4iic0: version 1 type 33c2d0c0 params 00000000 general 00000000 comp_version 3131352a ig4iic0: <Designware I2C Controller> [attached!] <- full attach, all gates passed
Every step of ig4_acpi.c:99-132 is real and live on this guest. The only
input that never occurs naturally on the QEMU rig is a bus_setup_intr()
failure (every reachable GSI deterministically avoids an exclusive-owner
conflict: the _CRS parser drops GSIs above the legacy map β
acpi_resource.c:599 machintr_legacy_intr_find β and the IOAPIC auto-router
picks handler-free (cpu, gsi) slots). On real hardware the failure is any
rman_activate_resource() error or register_int() == NULL
(nexus.c:519-532, e.g. an exclusive-owner GSI conflict), which is exactly
what DF-5601 cited for the PCI twin.
(B) The panic: production ig4iic_attach/ig4iic_detach through the ig4_acpi.c unwinder
df5617.c (built by build_kld.sh with df56.c, the DF-5601 pseudo-bus
whose bus_setup_intr returns EINVAL β the failure injection) replays
ig4iic_acpi_attach()'s prologue line-for-line (ig4_acpi.c:104-127, no
bzero β the DF-5600 twin-check; softc is M_ZERO-allocated by newbus,
subr_bus.c:1953), calls the REAL ig4iic_attach() (stock ig4.ko) on a
fake register file with a mirror kthread emulating the ENABLE_STATUS latch,
then replays ig4_acpi.c:133-134's unwinder into the REAL ig4iic_detach().
vm.sh run_root 'kldload ig4 && kldload df56.ko && kldload df5617.ko'
Baseline (stock kernel, run.log / panic.txt):
df5617ig40: Unable to setup irq: error 22 <- ig4_iic.c:652 DF5617: ig4iic_attach() = 22 (EINVAL from bus_setup_intr) DF5617: serializer is HELD by the ig4_iic.c:648-655 error path (IS_SERIALIZED: 1) DF5617: replaying ig4_acpi.c:133-134 unwinder -> ig4iic_acpi_detach() -> ig4_acpi.c:146-147 ig4iic_detach() panic: assertion "!IS_SERIALIZED((s))" failed in lwkt_serialize_enter at lwkt_serialize.c:107 lwkt_serialize_enter() at lwkt_serialize_enter+0x8c ig4iic_detach() at ig4iic_detach+0x1f df5617_attach() at df5617_attach+0x1d8 device_doattach() at device_doattach+0x3af
Guest wedged in DDB (vm.sh status β down) β boot/kldload-time kernel panic.
Fix validation (A/B, same kernel config)
fix.diff (identical to DF-5601's verified one-liner) adds the missing
lwkt_serialize_exit(&sc->slz) on ig4_iic.c's setup-intr error path β the
root of the chain both files' unwinders trip on. Patched ig4.ko built
in the guest (fix_build.log), identical scenario re-run (run-fixed.log):
DF5617: serializer is released by the ig4_iic.c:648-655 error path (IS_SERIALIZED: 0) DF5617: ig4iic_acpi_detach replay = 0 -- returned cleanly, NO PANIC (fix effective)
Guest stayed up. Baseline panic vs patched clean return: fix_status = fixed.
Impact
Boot-time or kldload ig4-time kernel panic / hard hang on any system where
the ACPI-enumerated ig4 controller's IRQ setup fails once (exclusive-owner GSI
conflict, rman activation failure). Availability bug β not
unprivileged-triggerable (needs the driver attached to a failing GSI /
crafted firmware): Low, consistent with DF-1049/DF-5601.
Rig lessons
- SMAP RAM-backed
_CRSMMIO windows are refused by the nexus rman (ram_attachreserves all System RAM) β point planted windows into the fw_cfg ACPI table area (0xBFFC0000-0xBFFFFFFF), which is RAM but not SMAP; the appended SSDT's own bytes are the natural deterministic backing. - The window must be β₯ 0x810 bytes:
ig4_iic.c:504-508unconditionally readsIG4_REG_GENERALat +0x808; a 0x200 window page-faults the kernel (observed live βFatal trap 12 ... ig4iic_attach+0x12c: movl 0x808(%rdx)). iaslplaces AML buffer data at file offsets that shift with content β locate the register file with an embedded marker, recompute the window, recompile; the SSDT base itself (0xBFFE1CAC) is stable across boots.- QEMU monitor
gpa2hva+/proc/<qemu-pid>/mem(ptrace_scope=0) gives sub-millisecond physical writes β resolve the hva before the trigger;memwritelatency (0.3-0.6 s per monitor round-trip) is too slow for the 100-tick poll windows.
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
fixedfixed
Baseline (run.log/panic.txt): panic assertion !IS_SERIALIZED in lwkt_serialize_enter, backtrace ig4iic_detach <- df5617_attach <- device_doattach, guest down in DDB. Patched (run-fixed.log, same kernel config, fix.diff applied to /usr/src ig4_iic.c, ig4.ko rebuilt in guest - fix_build.log): 'DF5617: serializer is released ... IS_SERIALIZED: 0', 'ig4iic_acpi_detach replay = 0 -- returned cleanly, NO PANIC (fix effective)', vm.sh status = up.
Confirmed kernel references
- sys/bus/smbus/ichiic/ig4_acpi.c:127
- sys/bus/smbus/ichiic/ig4_acpi.c:132
- sys/bus/smbus/ichiic/ig4_acpi.c:134
- sys/bus/smbus/ichiic/ig4_acpi.c:146
- sys/bus/smbus/ichiic/ig4_acpi.c:147
- sys/bus/smbus/ichiic/ig4_iic.c:648
- sys/bus/smbus/ichiic/ig4_iic.c:655
- sys/bus/smbus/ichiic/ig4_iic.c:678
- sys/kern/lwkt_serialize.c:107
- sys/sys/serialize.h:41
- sys/platform/pc64/x86_64/nexus.c:519
Detail
Evidence (decisive lines)
run.log/panic.txt: 'DF5617: serializer is HELD ... IS_SERIALIZED: 1' then 'panic: assertion "!IS_SERIALIZED((s))" failed in lwkt_serialize_enter at lwkt_serialize.c:107' with 'ig4iic_detach() at ig4iic_detach+0x1f' backtrace; run-fixed.log: 'serializer is released ... IS_SERIALIZED: 0' + 'ig4iic_acpi_detach replay = 0 -- returned cleanly, NO PANIC (fix effective)' with guest up; real-path proof: dance logs + run.log ig4iic0 attach lines (probe match, iomem 0xbffe1cdc irq N on acpi0, comp_version 3131352a, [attached!])
PoC changes
The seed sketch was rewritten: (1) planted-ACPI rig with the register file embedded in the SSDT's own AML pad (SMAP-RAM windows are rman-refused; the fw_cfg table area is not); window >= 0x810 because ig4_iic.c:504-508 reads GENERAL at +0x808 (a 0x200 window page-faults); (2) host-side ENABLE_STATUS latch dance (gpa2hva + /proc/qemu/mem, pre-resolved hva) because DW's 0x6C->0x9C latch cannot exist in RAM; (3) no reachable GSI deterministically fails bus_setup_intr on QEMU (acpi_resource.c:599 drops GSIs above the legacy map; IOAPIC auto-router picks handler-free slots), so the failure is injected via the proven DF-5601 pseudo-bus (df56.c) while ig4_acpi.c's prologue (ig4_acpi.c:104-127) and unwinder semantics (ig4_acpi.c:141-169) are replayed line-for-line around the REAL production ig4iic_attach()/ig4iic_detach().
Verified recommended fix
Add lwkt_serialize_exit(&sc->slz) on ig4_iic.c's bus_setup_intr error path (ig4_iic.c:651-655) - one line, fixes both the PCI (DF-5601) and ACPI (DF-5617) unwinders at the root.
Verdict
ig4_acpi.c's attach-error unwinder re-enters the serializer leaked by ig4_iic.c's bus_setup_intr error path: ig4_acpi.c:127 sets pci_attached=1 before ig4iic_attach (ig4_acpi.c:132); when ig4iic_attach fails at bus_setup_intr it returns with sc->slz held (ig4_iic.c:648-655, no lwkt_serialize_exit on the error branch), and ig4_acpi.c:134 -> ig4_acpi.c:146-147 calls ig4iic_detach whose first lwkt_serialize_enter (ig4_iic.c:678) re-enters the self-held non-recursive serializer -> KKASSERT panic (INVARIANTS) / hard deadlock otherwise. Verified live: the real ig4_acpi.c path (probe/HID INT33C2 match, _CRS translation, MMIO alloc into a planted SSDT-backed register file, ig4iic_attach running to bus_setup_intr) was driven end-to-end with a planted ACPI device; the one non-natural input (bus_setup_intr failure - on real hardware any rman_activate/register_int failure, nexus.c:519-532) was injected with the proven DF-5601 pseudo-bus pattern, after which the production ig4iic_attach/ig4iic_detach panicked the guest exactly as the source predicts: 'panic: assertion !IS_SERIALIZED failed in lwkt_serialize_enter' with backtrace ig4iic_detach <- attach unwinder. fix.diff (one-line lwkt_serialize_exit on ig4_iic.c:651-655 error path, same root fix as DF-5601) A/B validated: patched ig4.ko runs the identical scenario cleanly, guest stays up.
No comments yet.