# DF-5478 VERDICT — md_pages*PAGE_SIZE wrap passes the 1:1-map guard

**Status: reproduced (arithmetic/model evidence class).**
**Impact: dos** (boot-time, firmware-fed, effectively unbounded
kmalloc(M_WAITOK) loop → memory-exhaustion wedge at module load).

## Source trace

`efi_create_1t1_map()` (efirt.c:198-310) walks firmware-fed EFI memory
descriptors. For each `EFI_MD_ATTR_RT` descriptor it validates:

```c
/* efirt.c:255-263 */
if (p->md_phys + p->md_pages * EFI_PAGE_SIZE < p->md_phys ||      /* wrap */
    p->md_phys + p->md_pages * EFI_PAGE_SIZE >= VM_MAX_USER_ADDRESS)
        goto fail;
```

then fills 1:1 pages:

```c
/* efirt.c:288-297 */
for (va = p->md_phys, idx = 0; idx < p->md_pages; idx++, va += PAGE_SIZE) {
        m = kmalloc(sizeof(*m), M_EFI, M_WAITOK | M_ZERO);
        ...
        vm_page_insert(m, efi_obj, OFF_TO_IDX(va));
}
```

The guard only tests the **computed end pointer**. `md_pages` (uint64_t,
sys/efi.h:75) is never bounded directly, so when `md_pages * 4096` itself
wraps mod 2^64 to a small value W, the end pointer falls back into
[md_phys, VM_MAX_USER_ADDRESS) and the guard passes — while the loop bound
remains the wrapped-around `md_pages`:

* `md_pages = 2^52` → product ≡ 0 → end == md_phys → PASS → 4.5e15 iterations
* `md_pages = 2^52 + 1` → product ≡ 4096 → end == md_phys+4096 → PASS
* generally any `md_pages = (W + k*2^64)/4096` with small W and low md_phys

Each iteration kmallocs a `struct vm_page` (~88 bytes) with M_WAITOK. At
MOD_LOAD time (SI_SUB_DRIVERS, efirt.c:635) nothing will ever free those
allocations; the loop cannot be killed; the allocator exhausts RAM and
M_WAITOK sleep-forevers under pressure. Boot wedges. (Trigger is a
crafted EFI memory map — firmware- or loader-fed — the unvalidated-table
family; runtime reachability does not exist.)

## Reproduction

`df5478_wrap.c` (guest, cc -O2, run as root/user — pure userspace model
of the exact production arithmetic): see run.log —

```
wrap: md_pages = 2^52     product=0x0      end=0x0000000001000000  guard: PASS (BUG)
    -> fill-loop iterations=4503599627370496 (4.5e+15), kmalloc(M_WAITOK) bytes > 3.69e+08 GiB
wrap: md_pages = 2^52+1   product=0x1000   end=0x0000000001001000  guard: PASS (BUG)
no-wrap 2^40 pages        product=0x0010000000000000                       guard: REJECT (correct)
```

(In the "sane 256 pages" control the guard also PASSes and the loop runs —
that is correct behavior there; only the wrap rows are the bug.)

## Rig limit (honest)

BIOS-boot guest: `efi_init()` returns ENXIO at the missing MODINFOMD_EFI_MAP
metadata (efirt.c:421-427) long before `efi_create_1t1_map()` runs, so the
in-situ loop cannot be driven. The model uses identical integer widths and
constants; nothing in the loop/guard depends on kernel-only semantics
beyond M_WAITOK availability.

## Fix validation

fix.diff adds the direct page-count cap
`p->md_pages >= VM_MAX_USER_ADDRESS / EFI_PAGE_SIZE` ahead of the existing
checks. The model's FIXED-guard section (same run.log) shows the wrap rows
now REJECT ("loop never starts") while the sane 256-page case is still
accepted — the previously-demonstrated bad arithmetic no longer passes.
Kernel-rebuild validation is not meaningful on this rig (the vulnerable
statics/path have no inputs on a BIOS boot); the arithmetic 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.
