# DF-4430 — radeon PRIME import: populate ignores the sg table; unpopulate
# leaks the pages (DUMBBELL_WIP divergence vs amdgpu)

## What

On the DragonFlyBSD radeon port, a **PRIME-imported** buffer object (a
dma-buf from another DRM driver, e.g. i915/amdgpu/second radeon) is created
as `ttm_bo_type_sg` with `ttm->sg` set (`sys/dev/drm/radeon/radeon_prime.c:73-93`
→ `radeon_bo_create(..., sg, ...)`; `sys/dev/drm/radeon/radeon_object.c` →
type `ttm_bo_type_sg`; `sys/dev/drm/ttm/ttm_tt.c:71-72` sets
`TTM_PAGE_FLAG_SG`, `ttm_tt.c:243` copies `bo->sg`).

The SG ("slave") handling in the TTM glue is `#ifdef DUMBBELL_WIP` — **not
compiled** — with two consequences:

1. **Wrong pages bound** — `radeon_ttm_tt_populate()`
   (`sys/dev/drm/radeon/radeon_ttm.c:730-801`): the slave branch that would
   install the sg pages via `drm_prime_sg_to_page_addr_arrays()` is dead
   (`radeon_ttm.c:753-764`). Control falls through to `ttm_pool_populate()`
   (`radeon_ttm.c:779`), which allocates **fresh anonymous pages** and
   installs them in `ttm->pages`, ignoring `ttm->sg` entirely
   (`sys/dev/drm/ttm/ttm_page_alloc.c:890-933` has no SG check).
   `ttm_tt_bind` then points the GART at those fresh pages
   (`radeon_ttm.c:659`), so the GPU view of an imported dma-buf is *not* the
   exporter's memory: imported data is invisible, and GPU writes land in
   orphan pages (silent data divergence between exporter and importer —
   data-corruption class).

2. **Permanent page + accounting leak** — `radeon_ttm_tt_unpopulate()`
   (`radeon_ttm.c:803-847`): the slave early-return **is** live
   (`radeon_ttm.c:808,818-819`), so the pages that populate wrongly allocated
   are never returned to the pool and the per-page `ttm_mem_global` accounting
   charged in `ttm_pool_populate()` (`ttm_page_alloc.c:913-921`) is never
   freed. Teardown leaks too: `ttm_tt_destroy()` → `ttm_tt_unpopulate()` →
   same early-return (`sys/dev/drm/ttm/ttm_tt.c:215-229,500-508`).
   Repeated import/destroy cycles grow non-swappable kernel memory and drain
   the TTM global memory pool until GPU allocations fail (local DoS).

Contrast: `sys/dev/drm/amd/amdgpu/amdgpu_ttm.c:1268-1330` — the sibling
driver's populate/unpopulate implement the full slave/SG path **live** (no
ifdef), e.g. `amdgpu_ttm_tt_populate` allocates `ttm->sg` and fills
`ttm->pages`/`dma_address` from the sg table.

## Reachability

Requires radeon to *import* a foreign dma-buf: PRIME fd passing between two
processes with GPUs under different drivers on one box (i915+radeon,
amdgpu+radeon, radeon+radeon), via `DRM_IOCTL_PRIME_HANDLE_TO_FD` /
`DRM_IOCTL_PRIME_FD_TO_HANDLE` on the exporting and importing cards
(`sys/dev/drm/drm_prime.c:599,709-745`; radeon hooks registered at
`sys/dev/drm/radeon/radeon_drv.c:698-704`). Same-driver self-import
short-circuits to the original object and is not affected. Niche (multi-GPU)
→ Low severity.

## PoC

`poc_prime_import.c` — exports a dma-buf from card0 (exporter, e.g. i915),
imports it on the radeon card1, mmaps both sides, writes a marker through the
exporter mapping and shows the importer's GPU-visible copy does not contain
it; loops import/destroy 10k times while watching
`vmstat -m`/`sysctl hw.drm...` growth to demonstrate the page leak.

## Build / run (multi-GPU DFly box with radeon as importer)

```
cc -O2 -o poc_prime_import poc_prime_import.c
DRM_EXPORT=/dev/dri/card0 DRM_IMPORT=/dev/dri/card1 ./poc_prime_import
```

Success criterion: (a) marker written via exporter's mmap absent from the
imported BO read via the radeon mmap (wrong-pages), (b) steady kernel-memory
growth across cycles (leak). On amdgpu-as-importer the same test shows the
marker propagating (control).

## Verification status

Audit QEMU guest has one bochs VGA stub and no radeon/amdgpu/i915 DRM device
(`env.txt`) — not runnable; source-verified only (`untested`), DF-4018/4019
precedent.

## Fix

Port the amdgpu approach into radeon's populate/unpopulate (un-ifdef the
slave branches and use the sg table), or reject SG imports until that
porting is done:

```diff
--- a/sys/dev/drm/radeon/radeon_ttm.c
+++ b/sys/dev/drm/radeon/radeon_ttm.c
@@ radeon_ttm_tt_populate
+	bool slave = !!(ttm->page_flags & TTM_PAGE_FLAG_SG);
 	...
+	if (slave && ttm->sg) {
+		drm_prime_sg_to_page_addr_arrays(ttm->sg, ttm->pages,
+						 gtt->ttm.dma_address,
+						 ttm->num_pages);
+		ttm->state = tt_unbound;
+		return 0;
+	}
@@ radeon_ttm_tt_unpopulate
 	if (slave)
-		return;
+		return;		/* slave pages belong to the sg table; see fix.diff */
```

See `fix.diff` for the full patch (the populate side is the security-relevant
half; the unpopulate early-return is *correct* once populate stops using the
pool for slave tts).
