DF-5119 / verdict.json
{ "finding_id": "DF-5119", "status": "reproduced", "verdict": "All three EFI variable ioctls in sys/dev/misc/efirt/efidev.c pass user-controlled efi_var_ioc::datasize/namesize (size_t) to kmalloc(M_TEMP, M_WAITOK) with no upper bound (efidev.c:105,106,140,165,166). A single EFIIOC_VAR_SET as root with datasize=2^62 panics the stock kernel deterministically (kmem_slab_alloc(): kernel_map ran out of space!, kern_slaballoc.c:1721-1724, because M_WAITOK was given without M_NULLOK); with datasize=SIZE_MAX, round_page() wraps to 0 (kern_slaballoc.c:945), kmem_slab_alloc(0) inserts a zero-length duplicate entry into kernel_map and the next vm_map op panics (vm_map_entry_link: dup addr map). Both reproduced on a BIOS-booted DragonFly 6.5-DEVELOPMENT guest (kldload efirt tolerates efirts ENXIO init failure but the co-packaged efidev module still creates /dev/efi; allocations precede efi_enter). Root-only device (0700 root:wheel) so impact is privileged-user kernel panic / allocator-invariant corruption, same calibration as DF-2121 (known, not re-reported). fix.diff (1 MiB caps + namesize>=sizeof(efi_char)) validated: patched module returns EINVAL for both modes, control ENXIO unchanged, guest survives.", "reproduced": true, "impact": "panic", "confidence": "certain", "exploit_chain": "open(\"/dev/efi\", O_RDWR) [root] -> ioctl(EFIIOC_VAR_SET, {datasize: 0x4000000000000000, name: L\"\", namesize: 4, data: valid}) -> efidev.c:165 kmalloc(2^62, M_WAITOK) -> _kmalloc large path -> kmem_slab_alloc -> vm_map_findspace fails -> panic (variant A); datasize: 0xFFFFFFFFFFFFFFFF -> round_page wraps to 0 -> zero-length vm_map_insert -> vm_map_set_wired_quick -> dup-addr panic (variant B). No uid0 chain: device is root-only; corruption manifests as deterministic kernel-map invariant panic, not a controllable primitive.", "evidence": "panic.m1.txt: panic: kmem_slab_alloc(): kernel_map ran out of space! trace _kmalloc<-efidev_ioctl<-dev_dioctl; panic.m2.txt: panic: vm_map_entry_link: dup addr map ... trace vm_map_set_wired_quick<-kmem_slab_alloc<-_kmalloc; run.log full transcript incl. mode-0 ENXIO control; fix_run.log patched module -> EINVAL x2 + ENXIO control, guest alive; fix.diff the validated bounds check.", "kernel_refs": [ "sys/dev/misc/efirt/efidev.c:105", "sys/dev/misc/efirt/efidev.c:106", "sys/dev/misc/efirt/efidev.c:140", "sys/dev/misc/efirt/efidev.c:165", "sys/dev/misc/efirt/efidev.c:166", "sys/kern/kern_slaballoc.c:942", "sys/kern/kern_slaballoc.c:945", "sys/kern/kern_slaballoc.c:1721", "sys/kern/kern_slaballoc.c:1475", "sys/kern/kern_module.c:110" ], "poc_changes": "Wrote from scratch for this finding (no seed): single C file, three modes (0 control / 1 huge / 2 wrap); struct efi_var_ioc from <sys/efiio.h>; EFIIOC_VAR_SET used because it allocates datasize first; name kept valid (L\"\", namesize=4) so DF-2121s known underflow is not what triggers. Guest /tmp is wiped by vm.sh reset, so re-scp + rebuild between runs is required.", "attempts": 3, "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", "runtime_sec": 1500, "guest_dirty": 0, "build_cmd": "cc -O -o /tmp/efi_sz /tmp/efi_sz.c (guest; then: kldload efirt)", "run_cmd": "/tmp/efi_sz 0 # control ENXIO; /tmp/efi_sz 1 # panic A; (reset+kldload) /tmp/efi_sz 2 # panic B", "code_hash": "676a749ee7c544e99bf3bdeeb45f7aa34fabc7032692124818f5f1a92d96bb89", "notes": "Reachable on BIOS boots: allocations precede efi_enter(); module_register_init (kern_module.c:110-116) tolerates efirts MOD_LOAD ENXIO while co-packaged efidev still creates /dev/efi. Same root-only precondition as DF-2121; generic lesson: any DragonFly ioctl that kmalloc(M_WAITOK)s a user size inherits the panic-or-wrap behavior of _kmalloc.", "recommended_fix": "Validate sizes before allocation in all three VAR ioctls: reject datasize/namesize > 1 MiB and namesize < sizeof(efi_char) with EINVAL (see fix.diff); alternatively make _kmallocs large path fail with NULL instead of panicking when M_NULLOK was not requested.", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff applied to guest /usr/src/sys/dev/misc/efirt/efidev.c, module rebuilt with -Werror clean and loaded as efirt-patched.ko: mode 1 and mode 2 both return EINVAL (errno 22) with the guest surviving and uptime advancing; mode 0 control still returns ENXIO so legitimate size-query semantics are preserved. Baseline panics were reproduced minutes earlier on the stock module in the same session.", "fix_evidence": "fix_build.log (patched efidev.c compiles, links efirt.ko); fix_run.log (mode1 EINVAL, mode2 EINVAL, mode0 ENXIO, guest still alive, efirt-patched.ko in kldstat); compare run.log + panic.m1.txt/panic.m2.txt for baseline." } |