β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-5188

ttm_agp_backend.c is dead, un-compilable, half-ported code: excluded from every build (CONFIG_AGP undefined, asm/agp.h absent), and if ever revived carries a pages-vs-bytes 8x under-allocation (heap OOB write in the bind loop) plus an agp_memory leak on every AGP rebind

Summary

Three-layer reachability refutation (Makefile commented, kconfig undefined, header absent -> un-compilable; nm shows 0 agp_tt symbols in shipped drm.ko). Latent landmines if revived: num_pages passed where the native API expects BYTES (8x under-alloc -> OOB write of page pointers) + mem leak on rebind. Bytes-convert + free-before-overwrite fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5188 Β· 5 files
FileTypeDescriptionSize
README.md file 5.2 KB ↓ raw
VERDICT.md file 6.1 KB ↓ raw
manifest.json file 1.1 KB view raw
runtime_absence.log file 742 B view raw
verdict.json file 4.4 KB view raw
README.md file
↓ download raw

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:

# 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

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

VERDICT.md file
↓ download raw

DF-5188 β€” VERDICT

Status: untested (by construction β€” the target code exists in no kernel artifact). Impact: none today; latent 8x heap OOB write + unbounded agp_memory leak if the backend is ever compiled in. Confidence: certain (on every claim below; each is source- or binary-verifiable in one command).

1. What was audited

sys/dev/drm/ttm/ttm_agp_backend.c β€” full 150-line read (pass 2, GLM 5.3). Pass 1 recorded no findings for this file (DB check: findings has zero rows for it; the only ttm-family findings are on ttm_bo/ttm_bo_util/ttm_bo_vm/ ttm_page_alloc/ttm_execbuf_util/ttm_bo_manager/ttm_module/ttm_memory β€” see DF-1445..DF-5138, none on this file).

2. Reachability β€” refuted at three independent layers

  1. Build system: the only Makefile referencing the file is sys/dev/drm/drm/Makefile, where both drm_agpsupport.c and ttm_agp_backend.c sit commented out under the banner # if CONFIG_AGP is set (Makefile:110-112). No other Makefile in the tree mentions it (rg -l ttm_agp sys/ --glob Makefile β†’ that one file).
  2. Kconfig: sys/dev/drm/kconfig.h defines 15 CONFIG_ macros, none of them CONFIG_AGP; therefore IS_ENABLED(CONFIG_AGP) expands to 0 (sys/dev/drm/include/linux/kconfig.h:36-39), and the only in-tree callers β€” sys/dev/drm/radeon/radeon_ttm.c:705,769,824 β€” compile their ttm_agp_tt_create/populate/unpopulate call sites out entirely.
  3. The file cannot compile even if added: #include <asm/agp.h> (ttm_agp_backend.c:43) β€” no such header exists anywhere under sys/; the only visible struct agp_memory declarations are the native sys/dev/agp/agppriv.h:55-65 (members am_id/am_size/am_type/am_obj/ am_physical/am_offset/am_is_bound β€” none of the pages, page_count, is_flushed, type, is_bound members used at ttm_agp_backend.c:64-91) and the empty forward declaration in sys/dev/drm/include/linux/agp_backend.h:30-31. Call arities also match nothing: agp_free_memory(mem) (1 arg, :92), agp_bind_memory(mem, off) (2 args, :78), agp_unbind_memory(mem) (1 arg, :91) versus the native prototypes agpvar.h:100-116 (2, 3, and 2 args).

Runtime confirmation (guest, dfbsd-qemu/vm.sh run_root, no mutation): nm /boot/kernel/drm.ko | grep -ci agp_tt β†’ 0. The 22,447,656-byte /boot/kernel/drm.ko built Jun 29 2026 contains no ttm_agp_* symbol; the only agp-matching strings are kld dependency metadata (_mod_metadata_md_drm_on_agp). See runtime_absence.log.

Conclusion: no syscall, ioctl, mmap, net, or mount path can reach any instruction of this file on any DragonFly configuration, current tree.

3. The latent hazards (why this is filed at Info rather than dropped)

The file shows signs of an abandoned half-port to the native API: the alloc call was renamed from Linux's agp_allocate_memory(agp_bridge, num_pages, AGP_USER_MEMORY) to agp_alloc_memory(agp_be->bridge, AGP_USER_MEMORY, ttm->num_pages) β€” the native name and argument order (agpvar.h:100) but with a page count fed to a vm_size_t bytes parameter:

  • agp_generic_alloc_memory (sys/dev/agp/agp.c:456-473) page-align-checks the byte size, charges it against as_maxmem, and sizes the backing vm_object via atop(round_page(size)) (agp.c:472).
  • On x86-64, passing num_pages bytes for a num_pages-page BO under-allocates by exactly PAGE_SIZE / 8 = 8x.
  • The bind loop then stores num_pages page pointers (ttm_agp_backend.c:65-72). Any future shim that derives mem->pages from the (under-sized) native object turns this into a kernel heap OOB write with attacker-influenced contents (page pointers of an unprivileged-created TTM BO).

Second latent hazard β€” lifecycle leak: ttm_agp_unbind returns at :91 after agp_unbind_memory() on a bound region without freeing or NULLing agp_be->mem, and ttm_agp_bind overwrites agp_be->mem at :73 without freeing the previous allocation (and leaves it set on the agp_bind_memory failure path :78-80). Revived, every AGP aperture rebind leaks one agp_memory + page array β€” unbounded kernel memory exhaustion by an unprivileged DRM client.

FreeBSD's drm2 has exactly the LinuxKPI AGP shim this file is missing, so "revive CONFIG_AGP" is a plausible future change that would walk into both landmines unless this is on record.

4. Phase V

Not applicable in the strong sense: the trigger target (compiled code) does not exist in any kernel image; there is nothing to build or run. The verification that was performed is the three-layer reachability refutation above plus the runtime symbol-absence check. Per the orchestrator's status vocabulary this is recorded as untested with impact: latent and attempts: 1 (the nm check). No fix.diff: the fix is deletion or completion of the port, not a one-line patch to live code; the recommended disposition in README.md Β§"Recommended disposition" covers both.

5. Classes considered and killed (pass-2 depth)

  • Populate/pages/counts OOB (:60-72): real as latent unit bug, dead as shipped β€” this finding.
  • Unbind/bind/destroy double-free (:85-106): destroy path (ttm_tt_destroy β†’ unbind-then-destroy, and the :102-103 re-unbind) ends in agp_free_memory on the not-bound member β€” no double-free even hypothetically; the defect is the leak, covered above.
  • ttm_agp_tt_create (:114-134): M_WAITOK NULL-check is dead code, error path frees correctly β€” nothing.
  • ttm_agp_tt_populate/unpopulate (:137-149): thin wrappers into ttm_pool_(un)populate β€” that surface is already filed (DF-1663, DF-4507); nothing new here.
  • Build gating: refuted above (Makefile:110-112, kconfig.h, radeon guards).

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.

(No fix.diff for this item: documented recommendation only - see Fix above.)

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

n/a β€” dead code, nothing to validate a fix against

per-fix-DF-5188

Confirmed kernel references

Detail

Evidence (decisive lines)

findings/poc/DF-5188/VERDICT.md sections 2-3 (three-layer reachability refutation with path:line + latent hazard derivation); runtime_absence.log (nm drm.ko: 0 agp_tt symbols, guest uname 6.5-DEVELOPMENT #0 Thu Jul 2 06:02:54 UTC 2026); README.md provenance table; sys/dev/drm/drm/Makefile:110-112; sys/dev/drm/kconfig.h (no CONFIG_AGP); sys/dev/drm/radeon/radeon_ttm.c:703-708,767-771,822-826; sys/dev/agp/agpvar.h:100-116; sys/dev/agp/agppriv.h:55-65; sys/dev/agp/agp.c:456-473; sys/dev/drm/include/linux/agp_backend.h:30-31

PoC changes

n/a β€” no PoC exists or can exist for code present in no binary; the verification artifact is the reachability refutation (source citations + nm symbol-absence on the shipped drm.ko)

Verified recommended fix

Delete ttm_agp_backend.c (and the commented #SRCS block), or if AGP support is revived: fix the unit confusion (agp_alloc_memory(bridge, type, (vm_size_t)ttm->num_pages << PAGE_SHIFT) at :60), free the prior agp_be->mem before the overwrite at :73, and free+NULL in the is_bound branch of ttm_agp_unbind (:90-91)

Verdict

ttm_agp_backend.c is dead code with zero runtime reachability: it is commented out of the only Makefile that lists it (sys/dev/drm/drm/Makefile:110-112, '# if CONFIG_AGP is set'), CONFIG_AGP is not among the 15 defines in sys/dev/drm/kconfig.h so IS_ENABLED(CONFIG_AGP)=0 compiles out the sole in-tree callers (radeon_ttm.c:705,769,824), and the file cannot compile anyway (asm/agp.h at :43 exists nowhere under sys/; the Linux-style struct agp_memory members pages/page_count/is_flushed/type/is_bound used at :64-91 exist in no header a DRM build sees; agp_free_memory/agp_bind_memory/agp_unbind_memory arities at :78,:91-92 match no available prototype). Runtime-confirmed: nm /boot/kernel/drm.ko | grep -ci agp_tt = 0. The file is an abandoned half-port toward the native API whose landmines are on record for whoever revives it: (1) ttm_agp_backend.c:60 passes ttm->num_pages (a page count) as the vm_size_t bytes parameter of agp_alloc_memory (agpvar.h:100; agp.c:456-473 sizes the backing vm_object from that byte count) β€” an exact 8x under-allocation on x86-64 while the bind loop at :65-72 writes num_pages page pointers, i.e. a kernel heap OOB write once a shim exists; (2) ttm_agp_unbind returns at :91 on a bound region without freeing or NULLing agp_be->mem and ttm_agp_bind overwrites agp_be->mem at :73 without freeing the prior allocation β€” an unbounded agp_memory leak per AGP rebind cycle. Filed Info: no demonstrated impact on any current kernel configuration.