# DF-4855 — agp_amd64: attach error paths leak GATT/resources and leave a live /dev/agpgart on an unattached device

## What

`agp_amd64_attach()` (sys/dev/agp/agp_amd64.c:159-243) has three error exits
that fire **after** `agp_generic_attach()` succeeded at :183:

```c
183:	if ((error = agp_generic_attach(dev)))
184:		return (error);
...
204:	switch (pci_get_vendor(dev)) {
205:	case 0x10b9:	/* ULi */
206:		agp_amd64_uli_init(dev);
207:		if (agp_amd64_uli_set_aperture(dev, sc->initial_aperture))
208:			return (ENXIO);            /* <-- no unwind */
...
211:	case 0x10de:	/* nVidia */
212:		agp_amd64_nvidia_init(dev);
213:		if (agp_amd64_nvidia_set_aperture(dev, sc->initial_aperture))
214:			return (ENXIO);            /* <-- no unwind */
...
217:	case 0x1106:	/* VIA */
218:		sc->via_agp = agp_amd64_via_match();
219:		if (sc->via_agp) {
220:			agp_amd64_via_init(dev);
221:			if (agp_amd64_via_set_aperture(dev, sc->initial_aperture))
222:				return (ENXIO);        /* <-- no unwind */
```

`agp_generic_attach()` (agp.c:196-245) has already:
- bus_alloc_resource'd the aperture BAR (agp.c:209),
- `make_dev(&agp_ops, 0, UID_ROOT, GID_WHEEL, 0600, "agpgart")` with
  `si_drv1 = dev` (agp.c:240-242).

And `sc->gatt` was already allocated via `agp_alloc_gatt()` (agp_amd64.c:189,
stored at :202; up to `entries * 4` = 2 MiB of `contigmalloc`, agp.c:146).

When these `return (ENXIO)` fire, **newbus never calls `agp_amd64_detach()`**
(detach is only invoked for successfully-attached devices), so nothing ever
runs `agp_free_cdev` / `agp_free_gatt` / `agp_free_res`.

## Consequences

1. **Leaked kernel memory**: the GATT (contiguous, up to 2 MiB) plus the
   aperture bus resource leak on every failed attach.
2. **Orphaned live cdev**: `/dev/agpgart` (0600 root:wheel) remains
   registered pointing at a half-initialized, *unattached* device:
   `agp_open`/`agp_ioctl` never check `device_is_attached()`. Root holding
   the node can AGPIOC_ALLOCATE + AGPIOC_BIND; the bind lands in
   `sc->gatt` — which **is** set (allocated at :202 before the failing
   switch) — but the GART was never installed: the ATTBASE/APCTRL-GARTEN
   config-write loop at agp_amd64.c:229-238 never ran, so the hardware
   ignores every entry written. (No memory-safety violation occurs in the
   driver itself; the GATT writes are in-bounds — `ag_entries` is
   `u_int32_t`, agppriv.h:83, so the `ag_entries << AGP_PAGE_SHIFT` bound at
   :333/:347 promotes correctly to 64-bit.)
3. Same defect class as DF-1986 (agp_via.c:180-183, filed Low) — this is the
   agp_amd64.c instance.

## Trigger (hardware-gated)

`sc->initial_aperture` comes from the AMD64 GART APCTRL register
(agp_amd64.c:186, table at :265-273: 32M..2G). The vendor sub-tables are
strictly smaller:
- ULi M1689 accepts 32/64/128/256 MB (agp_amd64.c:398-406) → BIOS aperture
  ≥ 512 MB ⇒ EINVAL ⇒ ENXIO.
- nForce3 accepts 32..512 MB (agp_amd64.c:433-441) → BIOS aperture
  ≥ 1 GB ⇒ EINVAL ⇒ ENXIO.
- VIA: aperture math (agp_amd64.c:470-472) validates every power-of-two, so
  the VIA exit needs an actual config-write anomaly.

So: boot or `kldload agp` on an AMD-8151/SiS-75x/ULi-M1689/nForce3/VIA-K8
board whose BIOS programmed a larger-than-vendor-max aperture. Requires
matching 2003-2005-era hardware; the cdev is 0600, so the ioctl surface is
root-only regardless.

## Guest verification (honest negative)

The QEMU/i440FX guest has exactly one host bridge, `0x12378086`
(Intel 82441) — no id in `agp_amd64_match()`'s table
(agp_amd64.c:87-112) can match, `agp_amd64_probe` returns ENXIO for every
device, `/dev/agpgart` does not exist, and the trigger program (built clean
on the guest) gets ENOENT from both root and `nobody`. The failure path
cannot execute on this guest; the defect is proven statically by the line
trace above. Verdict: `not_reproduced` (hardware absent), confidence
`certain` in the code defect.

## Fix

Unwind in the error paths (authoritative `fix.diff` next to this file):

```diff
--- a/sys/dev/agp/agp_amd64.c
+++ b/sys/dev/agp/agp_amd64.c
@@ -204,10 +204,13 @@ agp_amd64_attach(device_t dev)
 	switch (pci_get_vendor(dev)) {
 	case 0x10b9:	/* ULi */
 		agp_amd64_uli_init(dev);
-		if (agp_amd64_uli_set_aperture(dev, sc->initial_aperture))
-			return (ENXIO);
+		if (agp_amd64_uli_set_aperture(dev, sc->initial_aperture)) {
+			error = ENXIO;
+			goto err;
+		}
 		break;
```
(with matching `goto err` at the nVidia and VIA sites, and
`err: agp_free_gatt(sc->gatt); agp_generic_detach(dev); return (error);`
after the switch.)

Alternatively (defense in depth, fixes every chipset driver at once):
move the `make_dev()` out of `agp_generic_attach()` into a post-attach step,
or make `agp_open`/`agp_ioctl` reject unattached devices.

## 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.
