DF-5653 / verdict.json
{ "finding_id": "DF-5653", "status": "reproduced", "verdict": "Live-verified and MEASURED on the QEMU/i440fx guest (6 vCPU): elcr_write_trigger() (elcr.c:103-117) performs an unlocked read-modify-write of the global elcr_status shadow followed by a byte outb \u2014 no lock, no interrupt disable, no atomic \u2014 and its caller icu_abi_legacy_intr_config() (icu_abi.c:366-397) serializes nothing either (the MSI paths in the same file take icu_irqmap_tok; the legacy config path takes no token). Two kernel threads pinned to cpu0/cpu1, each issuing 300,000 real elcr_write_trigger() calls toggling irq5/irq7 (same 32-bit shadow word, same 0x4d0 byte) between LEVEL and EDGE with a post-write shadow read-back, recorded 1,827 (irq5) + 2,158 (irq7) lost updates in a 2-second run \u2014 each mismatch is a stale-base store by the peer that silently reverted the just-written trigger bit in BOTH the shadow and the hardware port. On real hardware a dropped level-trigger programming means an edge-run level device: interrupt storms or lost interrupts. Reachability gates severity at Low: no unprivileged-user path; all in-tree config paths are boot-serialized or cpu0-pinned (acpi_fadt.c:222 KKASSERT(mycpuid==0)), so concurrency requires root (kldload racing bus_config_intr) or a future MP probe path. Post-run state restored exactly (R2 ok=YES, E9 coherent=YES guest HEALTHY).", "reproduced": true, "impact": "none", "confidence": "certain", "exploit_chain": "", "evidence": "run.log R1 line: R1: done=2 waited=2s lost_irq5=1827 lost_irq7=2158 (read-back mismatches = lost trigger-mode updates) \u2014 ~0.6% of writes lost; R2: restored: hw=0x0c00 (was 0x0c00) ... ok=YES and E9: final hw=0x0c00 baseline=0x0c00 coherent=YES ... guest HEALTHY (restore discipline); VERDICT.md carries the interleaving diagram and the reachability analysis", "kernel_refs": [ "sys/platform/pc64/icu/elcr.c:103", "sys/platform/pc64/icu/elcr.c:108", "sys/platform/pc64/icu/elcr.c:111", "sys/platform/pc64/icu/elcr.c:113", "sys/platform/pc64/icu/elcr.c:115", "sys/platform/pc64/icu/elcr.c:117", "sys/platform/pc64/icu/icu_abi.c:366", "sys/platform/pc64/icu/icu_abi.c:463", "sys/platform/pc64/acpica/acpi_fadt.c:195" ], "poc_changes": "Harness authored from scratch (no seed). v1s spin-barrier starved the kldload thread and wedged the guest (LWKT same-priority starvation \u2014 harness bug, see DF-5652/wedge_v1.log); v2 dropped the barrier, uses natural overlap of the 300k-iteration loops plus lwkt_yield() every 16k iterations. Detection design note: the read-back check (elcr_read_trigger immediately after each write, reading the shadow the peer may have clobbered) undercounts by construction (reverts landing after the check are missed) \u2014 3,985 counted losses is a lower bound.", "attempts": 2, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "runtime_sec": 240, "guest_dirty": 0, "build_cmd": "cd /root/elcrx && sh build.sh (bsd.kmod.mk; cc -Werror -> elcrx.ko)", "run_cmd": "cd /root/elcrx && sh run.sh (phase R)", "code_hash": "39f7f459b92ea7e3b0202e25426ff64e974521f94aea3482396d270771cef13b (elcrx.c)", "notes": "Impact field none because no security impact is reachable by an unprivileged user and no wedge/leak/corruption-of-memory was demonstrated \u2014 the demonstrated artifact is silent trigger-mode loss (functional corruption of a hardware config register), which is a reliability/security hybrid: a device told its IRQ is level while hardware runs edge is a classic lost-interrupt/storm failure. Cross-checks killed: (a) mask-vs-trigger register tearing \u2014 ELCR (0x4d0/0x4d1) and IMR (0x21/0xa1) are distinct registers, no interleave hazard (DF-5616 P5 also proved ICW reprograms dont clobber ELCR); (b) same-CPU interrupt-handler interleave between shadow store (elcr.c:113) and outb (elcr.c:115-117) \u2014 handlers own write uses the then-current shadow and converges, benign; (c) elcr_status word tearing \u2014 naturally-aligned 32-bit access on x86, no tearing.", "recommended_fix": "Serialize elcr_write_trigger() with a spinlock (SPINLOCK_INITIALIZER idiom) covering the shadow RMW and the port write, with the early-out check inside the lock (elcr.c:103-117)", "fix_status": "not_applicable", "fix_baseline_reproduced": 0, "fix_patched_reproduced": 0, "fix_verdict": "Low/hardening finding \u2014 single-fix kernel rebuild not warranted (root-gated reach, no in-tree concurrent caller today). fix.diff wraps the RMW+outb in a spinlock with the early-out moved inside; behavioral delta vs the live R1 run is lost counts go to zero under the identical 2-CPU hammer, which follows directly from the serialization (the measured mechanism is exactly the unlocked window). No memory-corruption baseline exists to regression-test.", "fix_evidence": "fix.diff" } |