⬢ DragonFlyBSD Kernel Audit
DF-5620 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-5620",
  "status": "reproduced",
  "reproduced": true,
  "impact": "none",
  "confidence": "certain",
  "verdict": "REPRODUCED as a live differential: a planted _CST whose C2 FixedHardware/native descriptor carries an INVALID mwait hint (0x99999971 -> MWAIT_EAX_TO_CX=8 >= CPU_MWAIT_CX_MAX) is correctly REJECTED by the stock driver (console C2: invalid mwait hint 0x99999971 per CPU, cx_supported stays C1/1, guest healthy) -- yet on the stock kernel machdep.mwait.CX.c3_preamble drops 3 -> 0 because acpi_cst_cx_mwait_setup()s done: block still executed cpu_mwait_cx_no_bmsts() twice and cpu_mwait_cx_no_bmarb() on the rejection path (acpi_cstate_machdep.c:161-190), globally disabling the C3 BM_STS check and bus-master-arbitration discipline of the machine-dependent MWAIT idle loop for the whole system (machdep.c:3400-3409). Identical boot with a C1-only _CST (same CPU, same helper, same rig): c3_preamble stays 3. Because this QEMU 7.2 never advertises CPUID.05H.ECX.EXT (and masks CPUID.01H:ECX.MON), the mwaitforce.ko helper substituted only those missing CPUID advertisement bits from a SYSINIT at SI_SUB_CONFIGURE/SI_ORDER_FIRST (after every CPUs identify_cpu() global write, before acpi probe) -- DF-5483-estforce honest gap methodology; all subsequent instructions are the stock driver. With fix.diff (rejection returns EINVAL before any side effects) on a rebuilt -Werror-clean kernel, the same attack boots with the rejection lines unchanged and c3_preamble stays 3. Ceiling: firmware/AML-driven global degradation of C3 entry discipline (on pre-Core2 MP Intel: C3 entered without BM checks -- the coherency hazard class the PIIX4 quirks in this driver exist for); no memory corruption, no user-triggerable path; severity Low, demonstrated artifact is the preamble corruption itself (impact recorded none among {panic,dos,leak,uid0} since no crash/leak materializes from it on this guest).",
  "exploit_chain": "",
  "evidence": "run.attack_sysctl.txt: machdep.mwait.CX.c3_preamble: 0 together with six C2: invalid mwait hint 0x99999971 rejection lines (state rejected, not installed) on the stock kernel; run.control_sysctl.txt: same boot config with C1-only _CST -> c3_preamble 3, zero mwait lines; run.fixedkernel_badhint.txt: kernel rebuilt with fix.diff -> c3_preamble 3, rejection lines unchanged.",
  "kernel_refs": [
    "sys/platform/pc64/acpica/acpi_cstate_machdep.c:146",
    "sys/platform/pc64/acpica/acpi_cstate_machdep.c:155",
    "sys/platform/pc64/acpica/acpi_cstate_machdep.c:161",
    "sys/platform/pc64/acpica/acpi_cstate_machdep.c:165",
    "sys/platform/pc64/acpica/acpi_cstate_machdep.c:171",
    "sys/platform/pc64/acpica/acpi_cstate_machdep.c:177",
    "sys/platform/pc64/acpica/acpi_cstate_machdep.c:190",
    "sys/platform/pc64/x86_64/machdep.c:3400",
    "sys/platform/pc64/x86_64/machdep.c:3409"
  ],
  "poc_changes": "No seeded PoC existed (pass-2 finding). Built mwaitforce.c after two failed strategies: (1) forcing cpu_mwait_feature at MOD_LOAD/SI_SUB_DRIVERS is silently undone because every APs identify_cpu() rewrites the globals (identcpu.c:645/649) after SI_BOOT2_START_APS; (2) QEMU 7.2 has no -cpu mwait property and hides both CPUID.01H:ECX.MON and CPUID.05H:ECX.EXT under TCG and KVM. Final helper re-forces both words at SI_SUB_CONFIGURE/SI_ORDER_FIRST (after all CPU idents, immediately before acpi0 probe). The badhint _CST DSDT was generated with the DF-5619 packs mkdsdt.py.",
  "attempts": 5,
  "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 (baseline); fix-validation kernel: DragonFly 6.5-DEVELOPMENT #1: Thu Oct  8 20:54:15 UTC 2026",
  "runtime_sec": 3600,
  "guest_dirty": 0,
  "build_cmd": "guest: cd /tmp/mwf && make SYSDIR=/usr/src/sys && cp mwaitforce.ko /boot/kernel/ (+ mwaitforce_load=YES in /boot/loader.conf)  |  host: python3 mkdsdt.py badhint dsdt_cst_badhint.dsl; guest iasl; python3 gen_cst.py dsdt_cst_badhint.aml /tmp/opencode/cst_badhint.bin  |  fix kernel: cd /usr/src && patch -p1 < cst_combined.diff && make -j6 nativekernel KERNCONF=X86_64_GENERIC && make installkernel",
  "run_cmd": "vm.sh down; QEMU_EXTRA=\"-machine accel=tcg -cpu SandyBridge -device loader,file=/tmp/opencode/cst_badhint.bin,addr=0x40000000,force-raw=on\" vm.sh up 240; vm.sh run_root sysctl machdep.mwait.CX.c3_preamble; dmesg | grep -a \"invalid mwait\"   # stock: 0 + rejection lines; control blob: 3; fixed kernel: 3 + rejection lines",
  "code_hash": "eb28dc91bd211452e0d1a2e6945b5117aff8363797bc917634c5f4263de39c54",
  "notes": "The mwait-extension prerequisite means this path is unreachable on the standard QEMU rig CPUs without the CPUID substitution (leaf5 ECX=0x2 everywhere); on real Intel hardware (MON + MWAIT EXT + INTBRK, i.e. everything from roughly Core onward) the path is live for any firmware-supplied _CST. The EOPNOTSUPP early-returns (no MONITOR / no extensions, acpi_cstate_machdep.c:149-153) correctly skip the side effects; only the invalid-hint rejection path leaks them. Related hardening gap in the same function (filed as DF-5621, Info): bits 31:8 of the firmware GAS.Address are forwarded verbatim to MWAIT EAX (only bits 7:0 are validated).",
  "recommended_fix": "In acpi_cst_cx_mwait_setup(), return EINVAL directly on invalid mwait hint (no goto) and run the cpu_mwait_cx_no_bmsts()/cpu_mwait_cx_no_bmarb() fixups only on the success path, matching the EOPNOTSUPP paths behavior.",
  "fix_status": "fixed",
  "fix_baseline_reproduced": true,
  "fix_patched_reproduced": false,
  "fix_verdict": "fix.diff applied in guest /usr/src (together with DF-5619s fix), make -j6 nativekernel KERNCONF=X86_64_GENERIC rc=0 -Werror-clean, installkernel, reboot. Attack blob (C1 + badhint C2) on the patched kernel: C2: invalid mwait hint 0x99999971 still printed per CPU (rejection semantics unchanged) and machdep.mwait.CX.c3_preamble stays 3 -- the rejected descriptor no longer mutates the global C3 preamble. Baseline stock kernel with the same blob: c3_preamble 0.",
  "fix_evidence": "run.fixedkernel_badhint.txt (uname #1 Oct 8 20:54:15 + c3_preamble 3 + four invalid mwait hint lines) versus run.attack_sysctl.txt (stock kernel c3_preamble 0 + rejection lines); diff in fix.diff."
}