⬢ DragonFlyBSD Kernel Audit
DF-5653 / verdict.json
← back to finding ↓ download raw
{
  "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"
}