agp_amd64_attach error exits leak the GATT and aperture resource and leave a live /dev/agpgart on an unattached device
Summary
After agp_generic_attach has make_dev'd /dev/agpgart and allocated the GATT, three vendor set_aperture failures return ENXIO with no unwind; newbus never detaches a failed attach - the contigmalloc'd GATT (up to 2MiB), the aperture resource, and a live 0600 cdev pointing at an unattached device all leak, and root holding the orphaned node can ALLOCATE/BIND into a GATT never installed in hardware. Trigger: BIOS aperture larger than the vendor sub-table max (ULi >=512MB, nForce3 >=1GB). Same class as DF-1986 (agp_via). Hardware-gated (i440FX guest never probes; trigger built+ran ENOENT). Unwind fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4855 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| agp_orphan.c | β | 2.4 KB | view raw | |
| build.sh | β | 110 B | view raw | |
| run.sh | β | 119 B | view raw | |
| build.log | β | 267 B | view raw | |
| run.log | β | 905 B | view raw | |
| env.txt | β | 518 B | view raw | |
| VERDICT.md | β | 5.0 KB | β raw | |
| fix.diff | β | 1.4 KB | view raw | |
| manifest.json | β | 1.1 KB | view raw | |
| verdict.json | β | 3.4 KB | view raw |
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:
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
- Leaked kernel memory: the GATT (contiguous, up to 2 MiB) plus the aperture bus resource leak on every failed attach.
- Orphaned live cdev:
/dev/agpgart(0600 root:wheel) remains registered pointing at a half-initialized, unattached device:agp_open/agp_ioctlnever checkdevice_is_attached(). Root holding the node can AGPIOC_ALLOCATE + AGPIOC_BIND; the bind lands insc->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_entriesisu_int32_t, agppriv.h:83, so theag_entries << AGP_PAGE_SHIFTbound at :333/:347 promotes correctly to 64-bit.) - 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):
--- 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.
Fix verification
not_testablefix.diff is a compile-pattern-correct unwind authored against the read-only tree and validated with git apply --check; kernel build validation is hardware-gated (attach cannot be exercised on the QEMU guest with or without the fix)
fix.diff (git apply --check OK)
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md (line-trace + trigger conditions); run.log (guest: ENOENT root+nobody, single host bridge 0x12378086, agp in-kernel but /dev/agpgart absent); fix.diff (git-apply-able unwind, validated with git apply --check)
PoC changes
agp_bind field corrected for DragonFly sys/sys/agpio.h:149-152 (pg_start, not offset); guest heredoc re-formatting
Verified recommended fix
Convert the three vendor set_aperture failures in agp_amd64_attach to goto err; unwind with agp_free_gatt(sc->gatt) + agp_generic_detach(dev); see fix.diff
Verdict
Code defect is certain by line trace: agp_amd64_attach calls agp_generic_attach (agp.c:196) which make_dev()s /dev/agpgart at agp.c:240-242, then three vendor set_aperture failures (agp_amd64.c:207-208 ULi, :213-214 nVidia, :221-223 VIA) return ENXIO with no unwind - newbus never calls detach for a failed attach, so the GATT contigmalloc (up to 2MiB, agp.c:146), the aperture bus resource and the live 0600 /dev/agpgart node on an unattached device all leak. Trigger is hardware-gated (ULi M1689 with >=512MB or nForce3 with >=1GB BIOS-set aperture, since the vendor sub-tables at agp_amd64.c:398-406/:433-441 reject those sizes that the host table :265-273 accepts); the ioctl surface is root-only (0600). Runtime reproduction was impossible on the audit guest: QEMU i440FX presents only host bridge 0x12378086, which matches no id in agp_amd64_match (agp_amd64.c:87-112), so the driver never attaches - trigger builds clean and open() returns ENOENT for root and nobody. No memory-safety violation in the driver itself: the orphaned-node binds write in-bounds into a GATT that is allocated (sc->gatt set at :202) but never installed (ATTBASE/GARTEN loop :229-238 never ran), so impact is resource leak + stale-cdev functional corruption, Low.
No comments yet.