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)
PoC verification
Evidence pack
findings/poc/DF-5477 Β· 15 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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)
sys/platform/pc64/x86_64/efirt.c:411-412storesefi_cfgtbl = (struct efi_cfgtbl *)efi_systbl->st_cfgtbl;β raw, noPHYS_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 asSetVirtualAddressMap()has not been called.- 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. efi_get_table()(efirt.c:469-487) runs in normal kernel context (noefi_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 advancesct++(efirt.c:484) whilecount = efi_systbl->st_entries(efirt.c:477), a firmware-fedu_longwith no bound of any kind.- 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_uuidread therefore takes a kernel-mode page fault on a "user" address withpcb_onfault == NULLβtrap.c:917-926"Fatal user address access from kernel mode" β panic. On any EFI-booted DragonFly machine, the firstEFIIOC_GET_TABLEioctl (root-only /dev/efi, efidev.c:77-84) after boot panics the kernel deterministically. - 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 withvm.sh resetafterwards.
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
fixedfix.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.
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).
No comments yet.