DF-4647 / verdict.json
{ "finding_id": "DF-4647", "status": "reproduced", "reproduced": 1, "impact": "none", "confidence": "certain", "verdict": "acpi_fujitsus CTLFLAG_ANYBODY sysctl handler (raw softc pointer in oid_arg1, driver serial held across the whole ACPI evaluation) has no serialization against detach: acpi_fujitsu_detach removes the notify handler and frees the sysctl context, after which the devclass teardown kfrees the softc and kldunload frees the module text \u2014 all while a queued writers handler is still in flight. The oid_running drain in kern_sysctl.c that is commented as necessary for module unload correctness is dead code (never incremented), so the oid is lockuninitd and kfreed under the executing handler. Demonstrated on the guest with a crafted SSDT (FUJ02B1 device whose SVOL method Sleeps ~6s): timestamped run.log shows the kldunload completing (rc=0, module gone) with writer B queued inside its handler since 4s earlier, and B completing on the freed softc/oids/module-text (rc=1 ENOENT \u2014 its oid was destroyed underneath it). The UAF executed silently: the freed 208-byte M_BUS chunk, oid chunk and module text are type-stable on this idle guest and retained stale-but-valid contents, so no crash or leak materialized (hence impact=none observed); the realistic ceiling under heap pressure is a kernel panic via wild sc->handle/nv.name pointers into AcpiEvaluateObject. Two crash-forcing strategies (per-CPU zone floods with address-exact detection; a same-oid waiter on the destroyed oid lock) also survived for the same type-stability reason. Severity Low, matching the DF-2381 notify-path twin, but with an unprivileged, indefinitely-holdable window (ANYBODY sysctl) instead of a firmware keypress.", "exploit_chain": "unprivileged writer A: sysctl -w hw.acpi.fujitsu.volume=N -> handler holds fujitsu serial + oid lock ~6s inside SVOL (firmware-controlled method duration); writer B: sysctl -w hw.acpi.fujitsu.lcd_brightness=N -> queues inside its handler on the serial; root: kldunload acpi_fujitsu -> blocks behind As ACPICA interpreter; A unwinds: interpreter released first (unblocks unload tail: oid lockuninit+kfree, softc kfree, module text free) THEN serial released; B wakes and executes its whole remaining handler on freed memory (sc->gbls, sc->handle through AcpiEvaluateObject, return through lockmgr LK_RELEASE on the freed oid) -> silent survival on type-stable heap; panic if any chunk is reused in the window.", "evidence": "run.log (timestamped decisive run: T_UNLOAD_DONE=583 module_gone while B queued since 578, B_DONE=583 ENOENT); run.D.log (same-oid waiter variant \u2014 also survives); VERDICT.md (full narrative incl. unwind-order proof and the dead oid_running drain); fujibang.c (softc recorder + zone flood, negative result documented); ssdt_fuj.asl/.aml (FUJ02B1 device with stalling SVOL); fixrun.attempt.log + fix.diff + acpi_fujitsu_fixed.c (fix candidate that wedges 2/2 \u2014 not_testable)", "kernel_refs": [ "sys/dev/acpica/acpi_fujitsu/acpi_fujitsu.c:340", "sys/dev/acpica/acpi_fujitsu/acpi_fujitsu.c:415", "sys/dev/acpica/acpi_fujitsu/acpi_fujitsu.c:433", "sys/kern/kern_sysctl.c:388", "sys/kern/kern_sysctl.c:1455", "sys/kern/subr_bus.c:1943" ], "poc_changes": "Built the whole rig from scratch: crafted SSDT (ACPICA clamps a single AML Sleep to ~2s \u2014 three chained Sleep(2000) needed for the 6s hold; first attempt with Sleep(6000) silently held only ~2s and the races resolved benignly), fujibang.kl trigger/record/flood module (multiple iterations: M_ZERO wiped stale contents before detection; per-CPU slab magazines kept the freed chunk from ever being re-served to a flooding CPU; same-thread free->alloc via the driver detach method hard-wedges when invoked from a sysctl-handler thread \u2014 synthetic-context artifact, documented). Final decisive evidence is the timestamped kldunload run against the stock module.", "attempts": 10, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #3: Tue Sep 22 13:35:01 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (pristine baseline kernel)", "runtime_sec": 3000, "guest_dirty": 0, "build_cmd": "guest: iasl ssdt_fuj.asl; kldload acpi_fujitsu; (cd bang && make && kldload ./fujibang.ko); boot with QEMU_EXTRA=-acpitable file=ssdt_fuj.aml", "run_cmd": "sh /root/df4647final.sh (A: volume write [6s SVOL hold]; B: brightness write [queues on serial]; root kldunload acpi_fujitsu)", "code_hash": "2fe6875cc15519c2fde219b6e582303461811ceebc6007cc417d4ab71041bec8", "notes": "impact=none means exactly: the freed-memory execution was reproduced but produced no user-visible damage on this guest (type-stable idle heap); the findings severity rests on the structural guarantee that the handler executes after the free plus the realistic heap-pressure ceiling (panic), consistent with the DF-2381/DF-4488 family precedents. The dead oid_running drain (kern_sysctl.c:391) is load-bearing for this class and is cited in the finding; fixing it would defend every sysctl-using driver at once. Guest reset with-src performed at end of run.", "recommended_fix": "Serialize detach against handlers (quiesce/drain state machine that waits for in-flight acpi_fujitsu_sysctl handlers before freeing anything \u2014 the naive ACPI_SERIAL_BEGIN-in-detach candidate wedges the unload path, see fix.diff discussion) and/or implement the oid_running drain in kern_sysctl.c so sysctl_remove_oid actually waits for running handlers before lockuninit/kfree of the oid.", "fix_status": "not_testable", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "Candidate fix (hold the fujitsu ACPI serial across acpi_fujitsu_detach, fix.diff/acpi_fujitsu_fixed.c) is correct for the handler race by inspection and the module builds, but validating it with the rig deterministically WEDGES the guest hard (2/2 runs, no console output, ssh dead; fixrun.attempt.log) \u2014 blocking inside the linker-unload path on the driver serial violates an ordering requirement there. A safe fix needs a teardown redesign or the kern_sysctl.c oid_running drain; cannot be validated as-is (not_testable).", "fix_evidence": "findings/poc/DF-4647/fix.diff; acpi_fujitsu_fixed.c (built module); fixrun.attempt.log (partial log of the wedged validation run); VERDICT.md Fix validation section; boot.log tail on host shows the wedge point (attach line then silence) for both attempts." } |