# DF-5188 — ttm_agp_backend.c: dead, un-compilable, half-ported AGP TTM backend (latent 8x under-allocation OOB + rebind leak if revived)

File: `sys/dev/drm/ttm/ttm_agp_backend.c` (150 LOC) — pass 2 (GLM 5.3)

## Bottom line

The file is **not part of any kernel or module build** in this tree and
**cannot compile as-is**. It is a Linux TTM source that was *partially*
ported toward the DragonFly native AGP API (function renamed, arguments
reordered) and then abandoned — excluded from the build behind a
never-defined `CONFIG_AGP`. If a future change revives it (e.g. by adding
the missing LinuxKPI AGP shim, as FreeBSD's drm2 did), the code as written
contains:

1. a **pages-vs-bytes unit confusion** at `ttm_agp_backend.c:60` that would
   under-allocate the backing store by exactly `PAGE_SIZE/sizeof(paddr_t)`
   = 8x on x86-64 while the bind loop at `:65-72` writes `num_pages` page
   pointers into it — a kernel heap OOB write; and
2. an **`agp_memory` leak on every rebind** (overwrite at `:73` without
   free; unbind of a bound region returns at `:91` without freeing).

Today: zero reachability. No syscall/ioctl/mmap path reaches a single
instruction of this file on any DragonFly configuration.

## Provenance (why it is dead)

| Fact | Evidence |
|---|---|
| Excluded from the only Makefile that lists it | `sys/dev/drm/drm/Makefile:110-112` — `# if CONFIG_AGP is set` / `#SRCS+= ttm_agp_backend.c` (commented out) |
| `CONFIG_AGP` never defined | `sys/dev/drm/kconfig.h` (15 defines, no `CONFIG_AGP`) → `IS_ENABLED(CONFIG_AGP)` = 0 (`sys/dev/drm/include/linux/kconfig.h:36-39`) |
| Only in-tree callers compiled out | `sys/dev/drm/radeon/radeon_ttm.c:705,769,824` all inside `#if IS_ENABLED(CONFIG_AGP)` |
| Header it includes does not exist | `ttm_agp_backend.c:43` `#include <asm/agp.h>` — no `asm/agp.h` anywhere under `sys/` (only `sys/dev/drm/include/linux/agp_backend.h`, which contains nothing but a forward declaration, lines 30-31) |
| Struct fields it uses exist in no visible type | `mem->pages`, `mem->page_count`, `mem->is_flushed`, `mem->type`, `mem->is_bound` (`:64-76,89-91`) — no definition of `struct agp_memory` with those members is reachable from a DRM build; native `struct agp_memory` (`sys/dev/agp/agppriv.h:55-65`) has `am_*` members only |
| Call arity matches no available prototype | `agp_free_memory(mem)` 1-arg (`:92`), `agp_bind_memory(mem, off)` 2-arg (`:78`), `agp_unbind_memory(mem)` 1-arg (`:91`) vs native `agpvar.h:100-116` which declares 2-, 3-, and 2-arg forms |
| Runtime artifact clean | `nm /boot/kernel/drm.ko | grep -ci agp_tt` → `0` (guest, 22 MB module built Jun 29 2026); only `agp` hits are kld-dependency metadata (`_mod_metadata_md_drm_on_agp`) |
| No local post-import edits | `git log --oneline -- sys/dev/drm/ttm/ttm_agp_backend.c` → single vendor-drop commit `6cc80ee9` |

## The latent unit confusion (the landmine)

- Native API: `void *agp_alloc_memory(device_t dev, int type, vm_size_t bytes)`
  (`sys/dev/agp/agpvar.h:100`). **Bytes.** Enforced page-aligned and
  checked against `as_maxmem` in `agp_generic_alloc_memory`
  (`sys/dev/agp/agp.c:456-464`); backing store is
  `vm_object_allocate(OBJT_DEFAULT, atop(round_page(size)))`
  (`sys/dev/agp/agp.c:472`) — i.e. sized from the byte count.
- The half-ported call: `agp_alloc_memory(agp_be->bridge, AGP_USER_MEMORY,
  ttm->num_pages)` (`ttm_agp_backend.c:60`) — **page count in a bytes
  parameter**. A `num_pages`-page BO would request `num_pages` bytes =
  `num_pages/8` pages of backing on x86-64.
- The consumer: `mem->pages[mem->page_count++] = page;` for
  `i < ttm->num_pages` (`ttm_agp_backend.c:65-72`). Whoever wires the shim
  that provides `mem->pages` from the under-sized object gets an 8x OOB
  write of attacker-influenced page pointers.
- (Side note: `agp_generic_alloc_memory` also rejects `type != 0`
  (`agp.c:468-471`), so `AGP_USER_MEMORY` = 0x10000 would fail there too —
  further proof the port was never finished against the generic layer.)

## The latent rebind leak

- `ttm_agp_unbind` on a *bound* region returns at `:91` right after
  `agp_unbind_memory()` — it neither frees `agp_be->mem` nor NULLs it.
- `ttm_agp_bind` unconditionally overwrites `agp_be->mem = mem` at `:73`
  (and also leaves it set when `agp_bind_memory` fails at `:78-80`).
- Net effect if revived: every unbind→bind cycle (AGP aperture shuffle) or
  failed bind followed by retry leaks one `agp_memory` plus its page array —
  unbounded kernel memory exhaustion from an unprivileged DRM client.

## Reproduce / verify absence (what was actually run)

No PoC can exist for a function that is in no binary. Verification performed:

```sh
# source level (host)
rg -n "ttm_agp" sys/dev/drm --glob Makefile        # only the commented line
rg -n "CONFIG_AGP" sys/dev/drm/kconfig.h           # no match
find sys -name 'agp.h' -o -name 'agp_backend.h     # no asm/agp.h anywhere
# runtime level (guest)
nm /boot/kernel/drm.ko | grep -ci agp_tt           # -> 0
```

## Recommended disposition

Either delete the file (and the `#SRCS+=` comment block), or — if AGP
support is ever revived — finish the port: add a real LinuxKPI AGP shim,
multiply by PAGE_SIZE at `:60`, free the previous `agp_be->mem` at `:73`,
and free+NULL in the bound branch of `ttm_agp_unbind` (`:90-91`).
