# DF-4890 — VERDICT

**Finding**: `AcpiOsAcquireLock`/`AcpiOsReleaseLock`
(sys/dev/acpica/Osd/OsdSynch.c:374-429) implement one ACPICA lock with two
*disjoint* primitives chosen by caller class — `spin_lock(&Spin->slock)` for
the idle thread (OsdSynch.c:383-384, :423-424) and `lockmgr(&Spin->lock,
LK_EXCLUSIVE)`+`crit_enter()` for every other thread (OsdSynch.c:385-387,
:425-428). The primitives are unrelated: an idle-thread holder on CPU A and a
normal-thread holder on CPU B are both "inside" the lock at the same time.
ACPICA requires `AcpiOsAcquireLock` to be a single exclusive lock
(see the file's own comment, OsdSynch.c:367-373: "OS-dependent locking
primitives... called from an interrupt-handler or cpu_idle thread").

**Status: not_reproduced (on this guest) / statically certain (code-provable).**

## Static proof chain (every hop verified in-tree)

1. Idle-thread entry into ACPICA under the OSL lock:
   - `cpu_idle_hook = acpi_cst_idle` — sys/dev/acpica/acpi_cpu_cstate.c:786
     (runs in `gd_idlethread` context by definition of the idle hook).
   - C3-with-bus-master path: `AcpiReadBitRegister(ACPI_BITREG_BUS_MASTER_STATUS)`
     (acpi_cpu_cstate.c:933) and `AcpiWriteBitRegister(...,1)` (:935);
     ARB preamble: `AcpiWriteBitRegister(ACPI_BITREG_ARB_DISABLE,1|0)`
     (:958, :977).
   - `AcpiWriteBitRegister` wraps a read-modify-write of PM1_STS/PM2_CNT in
     `AcpiOsAcquireLock(AcpiGbl_HardwareLock)` .. `AcpiOsReleaseLock` —
     sys/contrib/dev/acpica/source/components/hardware/hwxface.c:411-471.
   - In the idle thread that acquire takes the **`spin_lock(&Spin->slock)`**
     branch only (OsdSynch.c:383-384).
2. Normal-thread takers of the *same* lock instance:
   - `AcpiWriteBitRegister(BUS_MASTER_RLD)` in CST probe/attach/notify paths —
     acpi_cpu_cstate.c:668, :810, :1082-1086 (normal kernel threads).
   - `AcpiHwClearAcuiStatus()` —
     sys/contrib/dev/acpica/source/components/hardware/hwregs.c:574-581, taken
     on ACPI subsystem enable and sleep-state transitions (S3 enter/exit).
   - Those acquires take the **`lockmgr(&Spin->lock)`** branch only
     (OsdSynch.c:385-387) — `slock` is never taken by normal threads, and
     `Spin->lock` is never taken by the idle thread. **No exclusion between
   the two classes.**
3. Both sides perform masked RMW of the same PM1/PM2 registers inside their
   "locked" region → interleaving loses updates (lost BM_STS clear, lost
   ARB_DISABLE set/clear, lost RLD write, mangled PM1_STS clears during
   resume).

Net effect: the ACPICA hardware-lock invariant is broken across CPUs;
real-world symptoms are ACPI power-management malfunction (missed wake
events, wrong sleep transitions, C3 entered with bus-master arbitration
disabled). No kernel-memory corruption: the lock protects register I/O
serialization only (`AcpiGbl_GpeLock`/`AcpiGbl_ReferenceCountLock` are never
touched from the idle thread — verified by rg over sys/dev/acpica:
`gd_idlethread` appears only in OsdSynch.c).

## Why the mutex/semaphore side is NOT broken (audit answers for the record)

- ACPICA mutexes on DFly are 1-unit counting semaphores:
  `#define AcpiOsAcquireMutex(H,T) AcpiOsWaitSemaphore(H,1,T)` —
  sys/contrib/dev/acpica/source/include/actypes.h:382-385.
- Recursive AML-mutex acquire is handled *inside ACPICA* before the OSL is
  called (`AcquisitionDepth`, exmutex.c:302-308/461-469), so the
  non-recursive semaphore mapping cannot self-deadlock via AML.
- ACPICA's own global-mutex ordering/deadlock check is compiled only under
  `ACPI_MUTEX_DEBUG` (utmutex.c:381-415); a coding slip there would
  self-block — but bounded: `ACPI_WAIT_FOREVER` is capped at ~66 s by
  OsdSynch.c:158-161, and PCATCH signals break the ssleep
  (OsdSynch.c:215, EINTR falls to the absolute-deadline check :236-245).
- No ACPICA site nests the same OSL spinlock (HardwareLock sections are
  leaf), so the non-recursive `lockmgr LK_EXCLUSIVE` cannot self-panic.

## Guest run (attempt)

Command (see run.log): `dfbsd-qemu/vm.sh run_root 'uname -a; sysctl hw.ncpu;
sysctl -a | grep cx_; dmesg | grep cst'` → env.txt.

Result: `hw.ncpu: 6`, `cpu_cst0..5` attached, but every CPU reports
`cx_supported: C1/0` and `hw.acpi.cpu.cx_lowest: C1`. QEMU's ACPI tables
expose no C2/C3, so `acpi_cst_idle` always selects the C1 HLT path
(acpi_cpu_cstate.c:943-947) which performs **no** ACPICA bit-register
access — the idle-thread side of the race never executes in this guest.
`sysctl -w hw.acpi.cpu.cx_lowest=C3` cannot create states the platform does
not advertise. Hence: **not_reproduced on this guest, preconditions absent**
(target hardware feature — deep ACPI C-states — not implemented by QEMU's
DSDT). The defect itself is code-provable; on real SMP laptops/servers with
CST C3 (the default configuration of the hardware class) both sides are live.

## How to demonstrate on real hardware (methodology, for whoever has it)

Instrumented kernel (in-guest /usr/src copy, NOT the audit tree): add
counters in both branches of `AcpiOsAcquireLock` gated on
`Spin == AcpiGbl_HardwareLock`, and a kprintf when both counters are
non-zero with overlapping TSC intervals; boot with `cx_lowest=C3`, then run
`race_trigger.sh` (CST notify + RLD churn) for minutes. Expected: overlap
counter > 0. Stock-kernel observable: dmesg ACPI errors / missed wakeups /
S3 anomalies under the same load, non-deterministic.

## Exploitability assessment

Impact ceiling is availability + platform-state integrity (PM1/PM2 register
lost updates): no user-controllable data crosses the race, no kernel memory
is corrupted, no privilege boundary is crossed. An unprivileged user can
only amplify the coincidence rate (inducing idle+resume/notify overlap);
there is no weaponizable primitive beyond DoS/malfunction. Severity Low
per the project rubric ("narrow trigger, low impact, specific hardware
class"), confidence **certain** for the lock-exclusion defect itself.

## Fix validation

fix.diff authored against the read-only sys/ tree (normal threads also take
`slock` around the section; lockmgr retained as the sleepable contention
lock; idle threads keep pure-spin). Not built/validated: the baseline race
is not hostable on the QEMU guest (no C2/C3), so there is no baseline to
compare a patched kernel against. Fix reasoning is line-accurate:
HardwareLock holders never block inside the section (hwxface.c:411-471,
hwregs.c:574-581 — pure register I/O), so holding `slock` across the
section is safe; the idle thread never takes GpeLock/ReferenceCountLock,
so no idle-thread spin-against-sleeper hazard is introduced.

| aspect | value |
|---|---|
| status | not_reproduced (guest lacks C2/C3 ACPI states; static proof certain) |
| reproduced | 0 |
| impact | none observed (potential dos on affected hardware) |
| confidence | certain (defect), likely (real-world manifestation) |

## 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.
