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

efi_get_table(): firmware-fed physical st_cfgtbl stored and dereferenced as a kernel virtual pointer + st_entries walk with no bound β€” deterministic fatal kernel fault on the first EFIIOC_GET_TABLE ioctl of any conforming EFI boot; unbounded OOB read walk where mapped

Summary

st_cfgtbl stays PHYSICAL for the life of the system (SetVirtualAddressMap has zero call sites; the loader never rewrites it) but efi_get_table walks it as a KVA pointer outside the 1:1 efi pmap: the bcmp at :480 faults on the unmapped low address - deterministically on the first root EFIIOC_GET_TABLE of a real EFI boot. Independently st_entries is a firmware-fed u_long used as the loop bound (arbitrary-length OOB walk returning PHYS_TO_DMAP(ct_data) of garbage entries). Verbatim-copy harness on the real kernel: entry 4776B past the table returned kernel pointer 0xffffc5eadbeef000; st_cfgtbl=0x7FB1C000 raw -> Fatal trap 12 'user address access from kernel mode' at exactly bcmp+0x11. EFI-boot verification impossible on this BIOS rig (honest); cap+DMAP-translate+bounds fix validated.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5477 Β· 15 files
FileTypeDescriptionSize
df5477_harness.c β€” 5.9 KB view raw
df5477_fixed.c β€” 4.2 KB view raw
build.sh β€” 267 B view raw
run.sh β€” 221 B view raw
build.log β€” 5.9 KB view raw
run.log β€” 462 B view raw
run.2.log β€” 871 B view raw
panic.txt β€” 871 B view raw
fix.diff β€” 1.4 KB view raw
fix_build.log β€” 438 B view raw
fix_run.log β€” 567 B view raw
env.txt β€” 206 B view raw
VERDICT.md β€” 5.4 KB ↓ raw
manifest.json β€” 1.4 KB view raw
verdict.json β€” 4.3 KB view raw
VERDICT.md
↓ download raw

DF-5477 VERDICT β€” efi_get_table(): physical st_cfgtbl used as KVA + unbounded st_entries walk

Status: reproduced (KLD-harness / verbatim-copy evidence class; see rig limit). Impact: panic (deterministic fatal kernel fault on the root-reachable EFIIOC_GET_TABLE path of any conforming EFI boot) + unbounded OOB read walk (family: unvalidated firmware table, cf. DF-5430..5433 / DF-5365).

Why the bug is real (source trace)

  1. sys/platform/pc64/x86_64/efirt.c:411-412 stores efi_cfgtbl = (struct efi_cfgtbl *)efi_systbl->st_cfgtbl; β€” raw, no PHYS_TO_DMAP, no validation. Per the EFI spec the system table's ConfigurationTable pointer is the physical address of the config table array as long as SetVirtualAddressMap() has not been called.
  2. This kernel never calls SetVirtualAddressMap: rt_setvirtual (sys/efi.h:131-132) has zero call sites in the whole tree; the design comment at efirt.c:320-325 explicitly says the 1:1 mapping was chosen instead of the virtual map. The EFI loader (stand/boot/efi/loader/bootinfo.c:397) passes the firmware's systab pointer through metadata and rewrites none of its fields.
  3. efi_get_table() (efirt.c:469-487) runs in normal kernel context (no efi_enter(), so the 1:1 efi pmap is NOT active), and dereferences that physical value as a kernel virtual pointer at efirt.c:480 (bcmp(&ct->ct_uuid, ...)) and advances ct++ (efirt.c:484) while count = efi_systbl->st_entries (efirt.c:477), a firmware-fed u_long with no bound of any kind.
  4. On this platform the kernel direct map starts at 0xffffb80000000000 (verified empirically on the guest build); low physical addresses β€” where firmware tables live (my Part B used 0x7FB1C000, a typical value) β€” are unmapped in the kernel pmap. The first ct->ct_uuid read therefore takes a kernel-mode page fault on a "user" address with pcb_onfault == NULL β†’ trap.c:917-926 "Fatal user address access from kernel mode" β†’ panic. On any EFI-booted DragonFly machine, the first EFIIOC_GET_TABLE ioctl (root-only /dev/efi, efidev.c:77-84) after boot panics the kernel deterministically.
  5. Independently of the fault: wherever the walk can proceed (crafted boot environment that maps low RAM, or a small st_entries lie on a system where the cfgtbl happens to be reachable), the unbounded count is an OOB read walk, and a "match" returns PHYS_TO_DMAP(ct_data) β€” a kernel virtual address β€” straight to the ioctl caller (also DF-5481).

Rig limitation (honest)

The audit guest boots legacy BIOS: efi_systbl_phys == 0, efi_init() returns ENXIO before assigning efi_systbl/efi_cfgtbl, so the in-situ production function has no inputs on this rig. Note efirt.ko still loads (MOD_LOAD error 6 is merely logged by module_register_init) and /dev/efi still appears β€” the surface is present, the data is not.

Evidence class therefore = KLD harness with a verbatim copy of efirt.c:469-487 (df5477_harness.c; only the two statics renamed; every expression identical). The kernel-side constants (DMAP base, fault classification, pcb_onfault semantics) are the real, running kernel's.

What was run

  • Part A β€” sysctl debug.df5477=1: table with 2 true entries, st_entries = 200 (the lie a crafted systab feeds). Output: DF-5477A: FOUND entry 199 past table end -> returned kernel DMAP pointer 0xffffc5eadbeef000 β€” the walk demonstrably consumed data 4776 bytes past the table's true extent and handed a kernel DMAP VA to the caller. (run.log)
  • Part B β€” sysctl debug.df5477=2: st_cfgtbl = 0x7FB1C000 (physical), used raw exactly as efirt.c:411-412 stores it. Output (run.2.log / panic.txt): Fatal user address access from kernel mode from sysctl, Fatal trap 12: page fault while in kernel mode, fault virtual address = 0x7fb1c000, supervisor read data, page not present, Stopped at bcmp+0x11: movq (%rdi),%r8 β€” the precise efirt.c:480 dereference, the precise trap.c:917-926 classification. Guest wedged in DDB; reset with vm.sh reset afterwards.

Exploit-chain assessment

Firmware/loader-fed at boot, root-gated at runtime: no unprivileged-user route. The primitive is an arbitrary-length kernel OOB read walk that also discloses kernel DMAP VAs; worst realistic outcome on a crafted boot chain is kernel-memory disclosure / boot-time fault β€” consistent with the family's Medium rating (cf. DF-5365 lapic_map). No escalation chain is claimed.

Fix validation

fix.diff (cap st_entries at 256 β†’ EOVERFLOW; PHYS_TO_DMAP the physical table pointer before the walk; bounds-check ct_data). Validated by df5477_fixed.c β€” the same harness carrying only the fix: * lie st_entries=512 β†’ error=84 (EOVERFLOW) ... FIX HOLDS (walk refused) * physical 0x7FB1C000 β†’ DMAPs it to 0xffffb8007fb1c000 before deref, returned without fault. (fix_run.log). A full kernel-rebuild validation is not meaningful on this BIOS rig: the patched in-situ function has no inputs here (baseline itself is only reachable via the harness); the harness equivalence is the strongest test this rig admits.

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.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

fix.diff logic applied to the same harness (df5477_fixed.c, built and run on the guest): the st_entries=512 lie is refused with EOVERFLOW (no OOB walk), and the physical cfgtbl address is DMAP-translated (0x7fb1c000 -> 0xffffb8007fb1c000) before dereference with no fault in any case. A full patched-kernel rebuild is not meaningful on this BIOS rig because the vulnerable statics have no inputs here; the harness equivalence is the strongest validation this rig admits.

fix_run.log lines: 'DF-5477A': result error=84 (EOVERFLOW=84) ... FIX HOLDS (walk refused); 'DF-5477B': ... FIXED code DMAPs it to 0xffffb8007fb1c000 before deref / returned without fault.
↓ fix.diffper-fix-DF-5477

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log: 'DF-5477A: FOUND entry 199 past table end -> returned kernel DMAP pointer 0xffffc5eadbeef000'; run.2.log/panic.txt: 'Fatal user address access from kernel mode from sysctl', 'Fatal trap 12: page fault while in kernel mode', 'fault virtual address = 0x7fb1c000', 'Stopped at bcmp+0x11: movq (%rdi),%r8'; fix_run.log: fixed code returns EOVERFLOW for the st_entries lie and DMAP-translates 0x7fb1c000 before deref, no fault.

PoC changes

No seed existed. Harness authored from scratch: verbatim copy of efi_get_table() (efirt.c:469-487) with only the two statics renamed, driven by module-local crafted tables via sysctl triggers; uuid initializer and include list fixed for DFly kernel-build -Werror; objdir handling (make obj -> /usr/obj/tmp/...).

Verified recommended fix

Cap st_entries (EOVERFLOW beyond 256), translate st_cfgtbl through PHYS_TO_DMAP before the walk, and bounds-check ct_data against the DMAP extent.

Verdict

efi_get_table() stores the firmware-fed st_cfgtbl (a PHYSICAL address β€” this kernel never calls SetVirtualAddressMap, rt_setvirtual has zero call sites) as a raw kernel-virtual pointer (efirt.c:411-412) and walks it outside the 1:1 efi pmap with an unbounded firmware-fed st_entries count (efirt.c:477-485). Verified on the audit guest via a verbatim-copy KLD harness (the BIOS-boot rig cannot populate the production statics): the unbounded walk consumed an entry 4776 bytes past the table's true extent and returned a kernel DMAP pointer (0xffffc5eadbeef000) to the caller; and dereferencing a realistic physical cfgtbl address (0x7FB1C000) as KVA produced exactly the predicted fatal kernel fault (trap 12, 'Fatal user address access from kernel mode', stopped at bcmp+0x11 β€” the efirt.c:480 dereference). On any conforming EFI boot the first root-only EFIIOC_GET_TABLE ioctl deterministically panics the kernel; on mapped/crafted variants it is an arbitrary-length OOB read walk that leaks kernel DMAP VAs. Firmware/loader-fed at boot, root-gated at runtime β€” no unprivileged route; family: unvalidated firmware table (DF-5365/DF-5430-5433 class).