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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5617 Β· 23 files
FileTypeDescriptionSize
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
VERDICT.md
↓ download 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

  1. SMAP RAM-backed _CRS MMIO windows are refused by the nexus rman (ram_attach reserves 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.
  2. The window must be β‰₯ 0x810 bytes: ig4_iic.c:504-508 unconditionally reads IG4_REG_GENERAL at +0x808; a 0x200 window page-faults the kernel (observed live β€” Fatal trap 12 ... ig4iic_attach+0x12c: movl 0x808(%rdx)).
  3. iasl places 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.
  4. QEMU monitor gpa2hva + /proc/<qemu-pid>/mem (ptrace_scope=0) gives sub-millisecond physical writes β€” resolve the hva before the trigger; memwrite latency (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

fixed
baseline reproduced→ patch + rebuild →patched clean

fixed

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.
↓ fix.diffper-fix-DF-5617

Confirmed kernel references

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.