Missing cleanup on agp_via_attach failure leaks /dev/agpgart with freed softc -> NULL-deref kernel panic
- File:
sys/dev/agp/agp_via.c - Lines: 175β183 (leaked cleanup at 180β183)
- Severity: Low
- CVSS 3.1:
CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U:C:N/I:N/A:H - CWE: CWE-416 Use After Free, CWE-476 NULL Pointer Dereference
- Confidence: likely
- Status: new
Summary
agp_via_attach calls agp_generic_attach (which creates the /dev/agpgart
cdev and allocates the aperture resource) but if the subsequent
initial_aperture==0 check fails, it returns ENXIO without calling
agp_generic_detach.
The new-bus framework then frees the softc (subr_bus.c:1944), while the
/dev/agpgart device node persists with si_drv1 pointing at the device whose
softc is now NULL. Any process that opens /dev/agpgart triggers a NULL
pointer dereference in agp_open (agp.c:781: sc->as_isopen) β kernel
panic.
Root cause
In agp_via_attach (agp_via.c:175-183):
error = agp_generic_attach(dev); /* line 175: creates /dev/agpgart via
* make_dev (agp.c:240), allocates aperture
* resource (agp.c:209) */
if (error)
return error; /* line 177: OK -- generic_attach failed,
* nothing leaked */
sc->initial_aperture = AGP_GET_APERTURE(dev); /* line 179 */
if (sc->initial_aperture == 0) { /* line 180 */
device_printf(dev, "bad initial aperture size, disabling\n");
return ENXIO; /* line 182: BUG -- returns
* without agp_generic_detach() */
}
agp_generic_attach (agp.c:195-245) has already:
- (a) bus_alloc_resource_any'd the aperture (agp.c:209),
- (b) make_dev'd /dev/agpgart with si_drv1=dev (agp.c:240-242).
Neither is cleaned up. The other failure path β the for(;;) GATT loop at
lines 185-197 β correctly calls agp_generic_detach(dev) before returning
(line 195), proving the omission at line 182 is an oversight.
After agp_via_attach returns ENXIO, device_doattach
(subr_bus.c:2108-2124) calls device_set_driver(dev, NULL) at line 2121,
which in turn calls kfree(dev->softc) and sets dev->softc = NULL
(subr_bus.c:1943-1945).
The /dev/agpgart cdev is never destroyed (destroy_dev /
dev_ops_remove_minor only happens in agp_free_cdev, which was never
called).
When any process subsequently opens /dev/agpgart, agp_open
(agp.c:775-787) does:
struct agp_softc *sc = device_get_softc(dev); /* returns NULL -- softc freed */
if (!sc->as_isopen) { /* NULL deref -> page fault -> panic */
Trigger precondition
The initial_aperture==0 precondition is reached for AGP 3.0 (v3) VIA
chipsets when the APSIZE register (agp_via.c:255) has a value not in the
switch table (any value other than 0x800/0xc00/0xe00/0xf00/0xf20/0xf30/
0xf38/0xf3c/0xf3e/0xf3f). The default case at agp_via.c:277-280 returns 0.
For v2 chipsets, get_aperture always returns β₯1MB, so this path is v3-only.
Threat model
Attacker position: any local user with read/write access to /dev/agpgart
(root:wheel 0600 by default per agp.c:241; may be relaxed via devfs rules
for video group on systems running Xorg/DRM, which is the intended use case
for AGP hardware).
Precondition: a VIA AGP 3.0 host bridge whose APSIZE register value is
unrecognized by the driver's switch table β this is a hardware/BIOS condition
that persists for the lifetime of the system once triggered at boot; the
attacker cannot create it but inherits it.
Once the precondition exists, the attack is trivial and deterministic: a
single open("/dev/agpgart", O_RDWR) call triggers a NULL pointer dereference
in kernel context, causing an immediate kernel panic (system-wide DoS).
No race, no grooming, no special privileges beyond device-node access.
Impact: local denial of service (kernel panic). Not privilege escalation or info leak.
Proof of concept
/* poc_open_agpgart.c
* Build: cc -o poc_open_agpgart poc_open_agpgart.c
* Run: ./poc_open_agpgart
*
* Triggers NULL-deref panic on systems where VIA AGP 3.0 attach failed
* (check dmesg for "bad initial aperture size, disabling" before running).
*
* agp_open dereferences device_get_softc(dev) which is NULL after
* device_doattach freed the softc via device_set_driver(dev, NULL).
*
* Expected: kernel panic "Fatal trap 12: page fault while in kernel mode"
* on address 0x0 (or small offset for as_isopen field).
*/
#include <fcntl.h>
#include <unistd.h>
int main(void) {
int fd = open("/dev/agpgart", O_RDWR);
if (fd >= 0) close(fd);
return 0;
}
Success: immediate kernel panic (page fault at NULL). The dmesg/boot log
will show the earlier "agp0: Invalid aperture setting 0x..." and
"bad initial aperture size, disabling" messages from the failed attach,
followed by the panic stack trace through agp_open.
The precondition (failed attach) can be verified pre-exploit by checking
dmesg for the "bad initial aperture size" message and confirming
/dev/agpgart exists (ls -la /dev/agpgart) despite the AGP driver not being
attached.
Recommended fix
Call agp_generic_detach(dev) before returning ENXIO, matching the cleanup
pattern already used in the for(;;) loop failure path at line 195.
This destroys the leaked /dev/agpgart cdev (via agp_free_cdev) and
releases the aperture resource (via agp_free_res).
--- a/sys/dev/agp/agp_via.c
+++ b/sys/dev/agp/agp_via.c
@@ -179,6 +179,7 @@
sc->initial_aperture = AGP_GET_APERTURE(dev);
if (sc->initial_aperture == 0) {
device_printf(dev, "bad initial aperture size, disabling\n");
+ agp_generic_detach(dev);
return ENXIO;
}
agp_generic_detach (agp.c:263-269) calls agp_free_cdev (removes the
/dev/agpgart device node) and agp_free_res (releases the aperture bus
resource), ensuring no dangling cdev persists after the failed attach.
References
sys/dev/agp/agp_via.c:175-183β the leaked error pathsys/dev/agp/agp_via.c:194-196β the correct error-path pattern (for comparison)sys/dev/agp/agp.c:195-245βagp_generic_attach(cdev + aperture alloc)sys/dev/agp/agp.c:240-242βmake_devof/dev/agpgartwithsi_drv1=devsys/dev/agp/agp.c:263-269βagp_generic_detach(proper cleanup)sys/dev/agp/agp.c:775-787βagp_openNULL-deref sitesys/kern/subr_bus.c:1943-1945,2108-2124β softc freed after attach failuresys/dev/agp/agp_via.c:255-280βget_aperturev3 switch table (default returns 0)
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1986 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| VERDICT.md | verdict | Source verification narrative | 1.1 KB | β raw |
| fix.diff | suggested-fix | Fix: Call agp_generic_detach(dev) before returning ENXIO. | 334 B | view raw |
| build.sh | build-script | Build/validation instructions | 366 B | view raw |
| run.sh | run-script | Run instructions (HW-gated, source-only) | 184 B | view raw |
| env.txt | environment | Guest environment | 404 B | view raw |
DF-1986 - Source Verification
Verdict: REPRODUCED (source-only confirmation)
Finding: sys/dev/agp/agp_via.c:180-182
Mechanism: agp_via_attach returns ENXIO on initial_aperture==0 without calling agp_generic_detach (unlike the for-loop path at L195). Leaks /dev/agpgart cdev + freed softc access.
Hardware dependency: Requires VIA AGP bridge with zero initial aperture.
Fix: Call agp_generic_detach(dev) before returning ENXIO.
Verification method
Source-only confirmation. The cited code path was traced line-by-line in the audited sys/ tree. The bug exists exactly as described. This is a HW-gated driver finding β the vulnerable code path requires specific hardware (GPU, controller, PHY, TPM, etc.) not present in the QEMU audit guest. Runtime reproduction on this guest is not possible without the hardware.
Fix validation
fix.diff authored and applied to guest source. All 40 fixes in this batch
compile cleanly in a single combined kernel build: make -j6 nativekernel
KERNCONF=X86_64_GENERIC β rc=0, zero -Werror violations.
Kernel: DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026
Fix verification
not_testablenot_testable: HW-gated. fix.diff applies + compiles in batch build (rc=0 -Werror). Source trace confirms fix closes the path.
Batch build: 40 fix.diffs applied, make nativekernel β rc=0 -Werror. Bug at sys/dev/agp/agp_via.c:180-182 source-confirmed.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- a
- g
- p
- /
- a
- g
- p
- _
- v
- i
- a
- .
- c
- :
- 1
- 8
- 0
- -
- 1
- 8
- 2
Detail
Exploit chain
none
Evidence (decisive lines)
Source trace sys/dev/agp/agp_via.c:180-182. HW-gated (no HW in QEMU). Fix compiles in batch build rc=0.
PoC changes
Evidence pack: VERDICT.md, fix.diff, manifest.json. Fix: ENXIO without agp_generic_detach β leaks cdev + freed softc. Call detach.
Verified recommended fix
See fix.diff. ENXIO without agp_generic_detach β leaks cdev + freed softc. Call detach.
Verdict
REPRODUCED (source-only). sys/dev/agp/agp_via.c:180-182: ENXIO without agp_generic_detach β leaks cdev + freed softc. Call detach.
No comments yet.