# DF-5619 — `_CST` C-state descriptors with no operational entry method are
# accepted: unattended boot panics (KASSERT on INVARIANTS kernels,
# unconditional `panic()` on production kernels)

## Root cause chain (all stock kernel code)

```
crafted _CST (DSDT, planted via hint.acpi.0.rsdp + QEMU loader)
  └─ acpi_cst_cx_probe_cst()          sys/dev/acpica/acpi_cpu_cstate.c:481
       ├─ acpi_PkgRawGas(pkg,0,&gas)            :581
       ├─ acpi_bus_alloc_gas() rejects SystemIO/addr0 (EINVAL, acpi.c:1280)
       │    -> cx->res stays NULL
       └─ acpi_cst_cx_setup() -> acpi_cst_md_cx_setup()
            sys/platform/pc64/acpica/acpi_cstate_machdep.c:67
            Intel branch : case SYSTEM_IO/SYSTEM_MEMORY -> break -> return 0   (:91-93)
            non-Intel    : same, return 0                                        (:81-87)
            *** SUCCESS for a descriptor whose only possible entry method
                (bus-space io_enter, installed ONLY when res != NULL,
                 acpi_cpu_cstate.c:601) does not exist -> enter == NULL ***
       └─ state installed: KASSERT(enter != NULL) (:612)
            INVARIANTS kernel  -> panic "C2 enter is not set"        [observed]
            production kernel  -> state accepted; acpi_cst_idle() (:970)
                                  calls cx_next->enter == NULL
```

Companion hole (also fixed): a `_CST` whose every state is rejected still
parses as SUCCESS with `cst_cx_count == 0` (or, with the NO_C3 quirk, has a
rejected state resurrected via `cst_cx_count = cst_non_c3 + 1`,
acpi_cpu_cstate.c:746-750) -> reaches `acpi_cst_startup()`'s UNCONDITIONAL
`panic("C%d startup setup failed")` (:842-844) — fires on production
kernels too. [observed: `panic: C1 startup setup failed: 22`]

## Scenarios (same stock kernel; only the planted _CST differs)

| scenario   | _CST content                                    | stock kernel                          | fixed kernel                          |
|------------|-------------------------------------------------|---------------------------------------|---------------------------------------|
| control    | 1x C1 (halt), lat 1                             | boots; cx_supported "C1/1"            | boots; "C1/1" (unchanged)             |
| nullenter  | 1x C2 SystemIO/8/addr=0, lat 10                 | `panic: C2 enter is not set` at boot  | boots; "no usable Cx state in _CST" + FADT C1 fallback |
| zerostate  | 1x C2 FixedHW/8/off=0 (BitOffset!=2 => EINVAL)  | `panic: C1 startup setup failed: 22` at boot | boots; same FADT fallback    |
| (noinv)    | nullenter on non-INVARIANTS kernel              | state resident in idle table (cx_supported "C1/10"); no KASSERT | — |
| (noinv)    | zerostate on non-INVARIANTS kernel              | `panic: C1 startup setup failed: 22` — unconditional panic, production shape | — |

## Prerequisites

* QEMU guest, DragonFly 6.5-DEVELOPMENT X86_64_GENERIC (this repo's rig)
* planted-ACPI rig (DF-5483/DF-5583 vector): `hint.acpi.0.rsdp=0x40000000`
  in /boot/loader.conf + QEMU `-device loader,file=<blob>,addr=0x40000000,force-raw=on`
* TCG CPU model `-machine accel=tcg -cpu SandyBridge` (Intel vendor so the
  Intel md branch runs; the non-Intel branch has the identical hole)
* iasl (guest /usr/sbin/iasl 20251212) for the DSDTs

## Build

    python3 mkdsdt.py control   dsdt_cst_control.dsl
    python3 mkdsdt.py nullenter dsdt_cst_nullenter.dsl
    python3 mkdsdt.py zerostate dsdt_cst_zerostate.dsl
    scp dsdt_cst_*.dsl dfbsd-maxx:/tmp/          # via dfbsd-qemu/config
    vm.sh run_root 'cd /tmp && for f in dsdt_cst_control dsdt_cst_nullenter dsdt_cst_zerostate; do iasl $f.dsl; done'
    scp 'dfbsd-maxx:/tmp/dsdt_cst_*.aml' .
    python3 gen_cst.py dsdt_cst_control.aml   /tmp/opencode/cst_control.bin
    python3 gen_cst.py dsdt_cst_nullenter.aml /tmp/opencode/cst_nullenter.bin
    python3 gen_cst.py dsdt_cst_zerostate.aml /tmp/opencode/cst_zerostate.bin
    vm.sh run_root 'printf "hint.acpi.0.rsdp=0x40000000\nboot_verbose=YES\n" >> /boot/loader.conf'

## Run

    # control (boots, driver engaged)
    vm.sh down; QEMU_EXTRA="-machine accel=tcg -cpu SandyBridge \
      -device loader,file=/tmp/opencode/cst_control.bin,addr=0x40000000,force-raw=on" vm.sh up 240
    vm.sh run_root 'sysctl hw.acpi.cpu0.cx_supported'     # C1/1

    # attack (unattended boot panic)
    vm.sh down; QEMU_EXTRA="-machine accel=tcg -cpu SandyBridge \
      -device loader,file=/tmp/opencode/cst_nullenter.bin,addr=0x40000000,force-raw=on" vm.sh up 240
    # -> TIMEOUT, vm.sh status == down, boot.log ends with:
    #    cpu_cst0: <ACPI CPU C-State> [tentative] on cpu0
    #    panic: C2 enter is not set
    # (zerostate blob -> "panic: C1 startup setup failed: 22" in
    #  acpi_cst_postattach)

## Expected decisive evidence

* run.attack_panic.log / panic.txt — `panic: C2 enter is not set` with
  backtrace into `acpi_cst_cx_probe_cst` during cpu_cst0 attach.
* run.attack_zerostate_panic.log — `C2: BitWidth(vendor) 8 ...` bootverbose
  line (mwait_setup ran on the rejected descriptor) then
  `panic: C1 startup setup failed: 22` from acpi_cst_postattach.
* run.noinv_zerostate_panic.log — same unconditional panic on a
  non-INVARIANTS kernel (KASSERT string absent from that kernel binary).
* run.control_sysctl.txt — same rig, C1-only _CST: full boot, C1/1.
* run.fixedkernel_*.txt — with fix.diff applied and kernel rebuilt, all
  attack blobs boot to ssh with the graceful "no usable Cx state in _CST"
  FADT fallback; control blob unchanged.
