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

efi_enter()/efi_leave() are not re-entrancy-safe despite LK_CANRECURSE: nested entry clobbers the global efi_savevm and the outer efi_leave() then installs a NULL vmspace β†’ pmap panic (latent landmine)

Summary

The recursive lock invites nesting but efi_enter unconditionally overwrites the global efi_savevm: a nested entry would save efi_vmspace over the real user vmspace and the outer leave installs NULL -> vmspace_pmap(NULL) panic. No nested path exists today (all six call sites verified). Drop-CANRECURSE-or-depth fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5482 Β· 4 files
FileTypeDescriptionSize
README.md β€” 1.6 KB ↓ raw
VERDICT.md β€” 1.9 KB ↓ raw
verdict.json β€” 2.2 KB view raw
manifest.json β€” 506 B view raw

DF-5482 β€” efi_enter()/efi_leave() are not re-entrancy-safe despite LK_CANRECURSE: a nested efi_enter() clobbers the global efi_savevm and the outer efi_leave() then restores a NULL vmspace β†’ pmap_setlwpvm(NULL) panic. Latent landmine (no nested path exists today).

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

ROOT CAUSE (path:line): * efirt.c:396: efi_lock is created LK_CANRECURSE β€” recursion is explicitly permitted by the lock, advertising nested EFI calls as legal. * efirt.c:352: efi_enter() unconditionally does efi_savevm = td->td_lwp->lwp_vmspace; β€” on a nested entry (same thread, already inside the window) lwp_vmspace is ALREADY efi_vmspace, so the outer context is lost. * efirt.c:367-370: efi_leave() restores pmap_setlwpvm(td->td_lwp, efi_savevm) and then sets efi_savevm = NULL β€” the inner leave leaves the thread on efi_vmspace with efi_savevm == NULL; the outer leave then calls pmap_setlwpvm(lp, NULL) β†’ lp->lwp_vmspace = NULL β†’ vmspace_pmap(NULL) deref (pmap.c:6408-6410) β†’ NULL-pointer panic.

REACHABILITY: none today β€” every efi_ entry point calls efi_enter() exactly once and firmware does not re-enter the kernel. The hazard fires the day any nested path appears (e.g. an eventhandler or var-service callback issuing another efi_ from inside the window, which is precisely what LK_CANRECURSE invites). Defensive/hardening finding: make the lock non-recursive (turn silent recursion into a deadlock/LKpanic at dev time) or track nesting depth per-thread and save/restore lwp_vmspace on a stack.

PACK: analysis-only; untested by design (Info hardening class).

VERDICT.md
↓ download raw

DF-5482 VERDICT β€” efi_enter/efi_leave reentrancy landmine (latent)

Status: untested (Info hardening class; no reachable nested path exists today β€” nothing to reproduce by construction).

Trace

  1. lockinit(&efi_lock, "efi", 0, LK_CANRECURSE) (efirt.c:396) β€” nested acquisition by the same thread is permitted.
  2. Nested efi_enter() (same thread): efi_savevm = td->td_lwp->lwp_vmspace (efirt.c:352) now stores efi_vmspace (set by the outer entry via pmap_setlwpvm), destroying the saved user context.
  3. Inner efi_leave(): pmap_setlwpvm(td->td_lwp, efi_savevm) restores efi_vmspace onto itself; efi_savevm = NULL (efirt.c:367-370).
  4. Outer efi_leave(): pmap_setlwpvm(td->td_lwp, NULL) β†’ lp->lwp_vmspace = NULL (pmap.c:6408) β†’ pmap = vmspace_pmap(newvm) (pmap.c:6410) dereferences NULL β†’ panic.

No current caller nests (all six entry points wrap one efi_enter; the firmware cannot call back into the kernel), so this is latent. The mismatch between the lock advertising recursion and the save/restore machinery being single-flight is the defect; simplest fix: drop LK_CANRECURSE from efi_lock so any future nesting fails loudly at development time (or add a per-thread depth counter).

Verification

None possible/needed: the trigger path does not exist in the current call graph (verified by reading all callers of efi_enter: efirt.c:496, 525, 540, 569, 584, 600 β€” each wrapped once, no nesting possible from firmware side). Filed as Info so the landmine is on record.

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

Not applicable β€” latent defect, nothing to validate.

per-fix-DF-5482

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md four-step trace: efirt.c:396 (LK_CANRECURSE), efirt.c:352 (unconditional global save), efirt.c:367-370 (restore+NULL), pmap.c:6408-6410 (NULL vmspace deref).

PoC changes

None β€” trigger path does not exist in the current call graph (verified against all callers).

Verified recommended fix

Remove LK_CANRECURSE from efi_lock (or track nesting depth per-thread and stack the saved vmspace).

Verdict

efi_lock is created LK_CANRECURSE (efirt.c:396), inviting nested acquisition, but efi_enter()/efi_leave() are single-flight by construction: efi_enter() unconditionally overwrites the global efi_savevm (efirt.c:352) and efi_leave() clears it (efirt.c:370). A nested entry (same thread) would save efi_vmspace over the real user vmspace; the inner leave then restores efi_vmspace onto itself and NULLs efi_savevm; the outer leave calls pmap_setlwpvm(td->td_lwp, NULL) β†’ lp->lwp_vmspace = NULL (pmap.c:6408) β†’ vmspace_pmap(NULL) at pmap.c:6410 β†’ NULL-pointer panic. No nested path exists today (all six efi_enter call sites β€” efirt.c:496/525/540/569/584/600 β€” wrap exactly one call; firmware cannot re-enter the kernel), so this is a latent landmine, not a reachable bug: Info hardening. Untested by construction; recommend dropping LK_CANRECURSE so any future nesting fails loudly, or adding a per-thread depth counter.