β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-5479

efi_enter()/efi_leave(): per-CPU CR4.SMEP disable with no critical section or CPU pinning (the comment claims one) β€” preempt+migrate inside the runtime window leaves the source CPU with SMEP permanently off and the migrated firmware call SMEP-fault-looping with efi_lock held forever

Summary

The comment claims fpu_kern_enter 'enters critical section' but the port replaced it with npxpush() which enters none; smap_smep_disable clears per-CPU CR4.SMEP with no context-switch restore: preempt+migrate leaves the source CPU with SMEP off FOREVER (kernel-executes-userspace unimpeded on that CPU) and the destination faults at CPL0 on the U/S=1 1:1 map - pcb_onfault bypasses the fatal check and vm_fault 'succeeds' -> infinite fault loop holding efi_lock (all EFI ioctls + shutdown block). No PoC possible on this BIOS rig (honest inconclusive); full trace. crit-section-or-per-CPU-borrow fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5479 Β· 4 files
FileTypeDescriptionSize
README.md β€” 2.9 KB ↓ raw
VERDICT.md β€” 4.9 KB ↓ raw
verdict.json β€” 3.4 KB view raw
manifest.json β€” 746 B view raw

DF-5479 β€” efi_enter()/efi_leave(): per-CPU CR4.SMEP disable with no CPU pinning or critical section β€” preempt+migrate inside the runtime-services window leaves the source CPU with SMEP permanently disabled and the migrated runtime call SMEP-fault-looping with efi_lock held forever.

FILE: sys/platform/pc64/x86_64/efirt.c (pass 2)

ROOT CAUSE (path:line): * efirt.c:342-359 efi_enter(): takes efi_lock, swaps lwp vmspace to the 1:1 efi_vmspace, npxpush(&efi_ctx), cpu_invltlb(), then smap_smep_disable() β€” and that is ALL. No critical section, no CPU pinning, interrupts left enabled by design (comment efirt.c:332-337). * The comment at efirt.c:327-330 claims the FreeBSD original's fpu_kern_enter() "enters critical section, giving us neccessary protection against context switch" β€” but the DragonFly port replaced it with npxpush() (npx.c:340-374), which only saves FPU state and enters NO critical section. The claimed protection does not exist. * smap_smep_disable() (pmap.c:1366-1390) mutates PER-CPU state: CR4.SMEP cleared (pmap.c:1389), plus per-thread STAC + pcb_onfault=1 (pmap.c:1383). smap_smep_enable() (pmap.c:1393-1399) restores on whatever CPU efi_leave() runs. CR4.SMAP/SMEP are never restored by the context-switch path β€” the only other CR4_SMEP touchpoint is boot-time enable (pmap.c:1355). * Therefore, if the calling thread is preempted inside the firmware window and migrates to another CPU (usched_dfly pulls runnable threads across CPUs), (a) the DESTINATION CPU still has SMEP set: firmware executes at 1:1 U/S=1 addresses (the efi map is a user map, efirt.c:214/224-231) β†’ SMEP #PF at CPL0 β†’ trap.c:894-928 (pcb_onfault=1 bypasses the fatal check) β†’ vm_fault() against the efi map succeeds (page present, exec allowed) β†’ reexecute β†’ fault β€” an infinite fault loop at kernel priority with efi_lock held forever: every later EFI ioctl and the shutdown-time efi_reset_system() (efirt.c:374-386, 520-531) block permanently; and (b) the SOURCE CPU keeps CR4.SMEP cleared FOREVER (efi_leave runs elsewhere; no other code re-arms it) β€” a permanent per-CPU SMEP bypass for any other kernel-mode-executes-userpage attack. Even without migration, ANY thread preempting the EFI caller on the source CPU runs with SMEP disabled for the duration of the firmware call (transient hardening window).

THREAT: requires an EFI-booted machine (runtime services used by root via /dev/efi ioctls, debug.efi_time, shutdown reset) plus preemption/migration under load. Effect: permanent wedge of all EFI services + permanent per-CPU SMEP defeat. Not verified on the audit rig (legacy BIOS boot β€” efi_runtime == NULL; all runtime paths return ENXIO) β€” status inconclusive by rig limitation, established by code trace only.

PACK: analysis-only (README.md, VERDICT.md, verdict.json).

VERDICT.md
↓ download raw

DF-5479 VERDICT β€” per-CPU SMEP window in efi_enter()/efi_leave() with no pinning

Status: inconclusive (rig limit: BIOS-boot guest, efi_runtime == NULL). Established by code trace; expected impact if triggered on EFI hardware: permanent wedge of all EFI runtime services (DoS) + permanent per-CPU SMEP defeat (hardening kill).

The trace, step by step

  1. efi_enter() (efirt.c:342-359) acquires efi_lock and performs pmap_setlwpvm(td->td_lwp, efi_vmspace) β†’ load_cr3 of the 1:1 map (pmap.c:6408-6450), npxpush(&efi_ctx) (FPU save only β€” npx.c:340-374, no crit_enter), cpu_invltlb(), smap_smep_disable().
  2. smap_smep_disable() (pmap.c:1366-1390): * smap_open() = STAC β†’ per-thread RFLAGS.AC (does NOT leak across context switches; AC is saved/restored with RFLAGS), * curthread->td_pcb->pcb_onfault = (void *)1 (pmap.c:1383) β€” bypasses the "Fatal user address access from kernel mode" software failsafe (trap.c:917-926) for this thread, * load_cr4(rcr4() & ~CR4_SMEP) (pmap.c:1389) β€” global per-CPU state, not per-thread.
  3. The in-tree comment (efirt.c:327-330) asserts the FreeBSD original's fpu_kern_enter() "enters critical section, giving us neccessary protection against context switch". FreeBSD's fpu_kern_enter indeed does critical_enter(). The DragonFly port calls npxpush() instead, which has no such protection. The documented invariant is false.
  4. Nothing prevents preemption inside the window: interrupts stay enabled (efirt.c:332-337, deliberate), efi_lock does not pin, and DFly's scheduler (usched_dfly) pulls runnable threads across CPUs.
  5. Migration outcome on the destination CPU B (SMEP still armed there): * firmware instruction fetch at a 1:1 address: the efi map was created by vmspace_alloc(VM_MIN_USER_ADDRESS, VM_MAX_USER_ADDRESS) (efirt.c:214) and inserted RWX (efirt.c:224-231) β†’ PTEs are U/S=1 user pages β†’ SMEP violation at CPL0; * trap β†’ trap_pfault (trap.c:629-631 β†’ 860+): va < VM_MIN_KERNEL_ADDRESS, usermode == 0; pcb_onfault != NULL skips the fatal branch (trap.c:917-926); map = &efi_vmspace->vm_map (trap.c:928); * vm_fault(map, va, VM_PROT_EXECUTE, ...) (trap.c:961): the page is present and exec-permitted β†’ KERN_SUCCESS β†’ trap returns; * re-execute at the same RIP β†’ SMEP faults again β†’ infinite fault loop at kernel priority, efi_lock held forever. Consequences: every subsequent efi_enter() β€” i.e. every EFI ioctl (efidev.c), efi_get_time/efi_set_time, and the shutdown-final efi_reset_system() (efirt.c:374-386) β€” blocks forever; one CPU is spun in the trap path.
  6. Migration outcome on the source CPU A: smap_smep_enable() runs on CPU B (wherever efi_leave executes); nothing on CPU A re-arms CR4.SMEP β€” the only other CR4_SMEP touchpoint in the kernel is the boot-time enable (pmap.c:1355; verified by grep). CPU A runs with SMEP permanently disabled β€” any subsequent kernel-mode-executes-userspace attack on that CPU is unimpeded (no gadget/ROP constraint from SMEP).
  7. Even without migration, any thread that preempts the EFI caller on CPU A executes with CR4.SMEP disabled for the whole firmware call β€” a transient per-CPU hardening window open to unrelated attackers.

Why it could not be verified on this rig

The audit guest boots legacy BIOS: efi_systbl_phys == 0, efi_init() returns ENXIO (efirt.c:399-403), efi_runtime == NULL, and every runtime entry (efi_enter) fails at efirt.c:347-348. The window cannot be opened here. An EFI-booted VM (OVMF) plus an SMP preemption workload would be required; outside this rig's scope by construction.

Fix direction

Make the window non-migratable and the CR4 mutation paired per-CPU: enter a critical section or pin the thread to the current CPU for the duration (restoring before efi_leave), or track "SMEP disabled by EFI" per-CPU and re-arm it in the context-switch/IPI path (e.g. set the bit in mdcpu and have cpu_...switch restore CR4 when the EFI caller is descheduled). One-line hardening at minimum: crit_enter() around smap_smep_disable..smap_smep_enable does not fix migration (crit sections in DFly do prevent preemption while held β€” crit_enter DOES block preemption of this thread on this CPU; combined with the fact that an LWKT thread in a crit section cannot be pulled by another CPU, that is the correct minimal fix mirroring FreeBSD's fpu_kern_enter).

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.

(No fix.diff for this item: documented recommendation only - see Fix above.)

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

No fix validation possible on this rig (baseline itself not reachable: BIOS boot, efi_runtime NULL).

per-fix-DF-5479

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md step-by-step trace with path:line for every claim: efirt.c:327-359 (no crit/pin, comment mismatch), pmap.c:1366-1399 (CR4.SMEP global-per-CPU mutation), npx.c:340-374 (no critical section), trap.c:894-997 (pcb_onfault=1 bypass at :920, vm_fault success at :961, no rip reload at :990-995), pmap.c:1355 (only other CR4_SMEP touchpoint).

PoC changes

No PoC built: path requires EFI boot (runtime services active); the BIOS-boot rig cannot open the window. Analysis-only evidence pack.

Verified recommended fix

Enter a critical section for the whole efi_enter..efi_leave window (or pin to the CPU and restore CR4.SMEP on deschedule) so the per-CPU SMEP mutation cannot leak across a migration.

Verdict

efi_enter() (efirt.c:342-359) opens a runtime-services window that mutates PER-CPU state (CR4.SMEP cleared via smap_smep_disable, pmap.c:1389) with no critical section and no CPU pinning β€” the comment at efirt.c:327-330 claims fpu_kern_enter enters a critical section, but the DragonFly port calls npxpush() (npx.c:340-374) which enters none. If the calling thread is preempted inside the window and migrates: the destination CPU still has SMEP armed, so firmware executing at 1:1 U/S=1 addresses (the efi map is a user map inserted RWX, efirt.c:214/224-231) takes an SMEP #PF at CPL0 that vm_fault 'fixes' (page present+exec, trap.c:961) and immediately retakes β€” an infinite fault loop holding efi_lock forever (all EFI ioctls and shutdown efi_reset_system block permanently); meanwhile the source CPU keeps CR4.SMEP cleared forever (only boot pmap.c:1355 and efi_leave pmap.c:1396 ever touch the bit; verified by grep β€” no context-switch restore). Not verifiable on this rig: the guest boots legacy BIOS, efi_runtime == NULL, efi_enter fails at efirt.c:347-348, the window cannot be opened. Established by complete code trace; needs an EFI (OVMF) SMP guest with a preemption workload to reproduce.