# DF-2207 — signed 32-bit overflow in iounmap() byte-size math (Medium)

## Claim
`struct iomap.npages` is `int` (`sys/dev/drm/include/asm/io.h:114`).
`PAGE_SIZE` is `(1<<PAGE_SHIFT)` = `(1<<12)` which also has type `int`
(`sys/cpu/x86_64/include/param.h:78`).  `iounmap()` recomputes the byte
size as `imp->npages * PAGE_SIZE` at lines 80 and 96 of
`sys/dev/drm/linux_iomapping.c`; that multiplication is `int * int` and
overflows signed for mappings >= `INT_MAX/PAGE_SIZE` ≈ 2 GiB.  The
overflowed (possibly negative) int is then sign-extended into the
`vm_size_t` (unsigned long) argument of `pmap_unmapdev()`, driving
`pmap_qremove` / `kmem_free` wildly OOB.

## Verification approach
**HW-gated / source-only.** The relevant ioctl path is `DRM_IOCTL_ADD_MAP`
(`DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY`) plus `DRM_IOCTL_RM_MAP` (`DRM_AUTH`),
exercised only when a DRM driver is bound to a real GPU.  No GPU on this
audit guest.  Per the task brief, source-only confirmation is acceptable.
The overflow is fully visible at the source level and the structural fix
is mechanical, so the verification is:

1. Confirm the types of `npages` and `PAGE_SIZE` at the cited path:line.
2. Confirm the multiplication is fed into a wider unsigned argument.
3. Author `fix.diff` (compute byte size once as `size_t`).
4. **Phase 8** — apply all 5 batched fixes and rebuild `drm.ko` with
   `-Werror`.

## Source trace (confirmed)
* `sys/dev/drm/include/asm/io.h:114` — `int npages;` inside `struct iomap`.
* `sys/cpu/x86_64/include/param.h:78` — `#define PAGE_SIZE (1<<PAGE_SHIFT)`. The literal `1` is `int`, so `PAGE_SIZE` has type `int`.
* `sys/dev/drm/linux_iomapping.c:52` — `imp->npages = size / PAGE_SIZE;` stores the page count into an `int` (narrowing). `size` itself is `unsigned long` (`__ioremap_common` arg), so the truncation is silent for very large mappings but the multiplication *back* is the unsafe step.
* `sys/dev/drm/linux_iomapping.c:80` — `paddr_end = imp->paddr + (imp->npages * PAGE_SIZE) - 1;` — `int * int` ⇒ signed overflow UB for `npages >= 2^19` (≈2 GiB mapping).
* `sys/dev/drm/linux_iomapping.c:96` — `pmap_unmapdev((vm_offset_t)imp->pmap_addr, imp->npages * PAGE_SIZE);` — same overflow, then sign-extended into `vm_size_t` (unsigned long) ⇒ multi-GB or multi-TB teardown range.

### Concrete overflow example
Mapping size = 2 GiB ⇒ `npages = 0x20000` (131072 pages).  `0x20000 * 0x1000 = 0x2_0000_0000` which as a signed 32-bit int is `0` (exact 4 GiB wrap) ⇒ `pmap_unmapdev(..., 0)` does nothing (silent VA leak).  Mapping size = 2 GiB + 1 page ⇒ product = `0x2_0000_1000` ⇒ truncated to `0x1000`, sign-extends into a small positive teardown of one page (massive under-teardown; the other 131072 PTEs are never cleared and the VA range is never returned to `kernel_map`).  Larger values land negative and sign-extend into multi-TB teardowns that walk unrelated kernel VA.

## Files
* `VERDICT.md` — full narrative.
* `fix.diff` — compute `byte_size = (size_t)imp->npages * PAGE_SIZE;` once and use it for both `paddr_end` and `pmap_unmapdev`.  No semantic change for any in-range mapping.
* `build_baseline.log` — unpatched `drm.ko` build, rc=0, `-Werror`.
* `build_patched.log` — patched `drm.ko` rebuild, rc=0, `-Werror`.
* `env.txt` — guest environment.

## Reproduce
```
./build.sh    # applies fix.diff + rebuilds drm.ko
./run.sh      # HW-gated no-op (see VERDICT.md)
```
