# DF-4685 — `rcu_barrier()` is a no-op: `amdgpu_fence_slab_fini()` permits module unload while `call_rcu` delay-line callbacks into module text are pending → fatal page fault (reproduced)

## Root cause (line-accurate)

`amdgpu_fence_slab_fini` (`sys/dev/drm/amd/amdgpu/amdgpu_fence.c:69-73`) is
the amdgpu module-exit drain:

```c
void amdgpu_fence_slab_fini(void)
{
        rcu_barrier();                        /* amdgpu_fence.c:71 */
        kmem_cache_destroy(amdgpu_fence_slab);
}
```

Every amdgpu fence free goes through
`amdgpu_fence_release` → `call_rcu(&f->rcu, amdgpu_fence_free)`
(`amdgpu_fence.c:646-649`), and `amdgpu_fence_free` — **code that lives in
amdgpu.ko's text** — is executed by the port's per-CPU delay line
(`sys/dev/drm/linux_rcu.c` `rcu_timer`, callbacks fire once an entry is
`>= hz` ticks old).  The `rcu_barrier()` call exists precisely so that no
`amdgpu_fence_free` callback is still queued when the module text is
unmapped.

On this port it does nothing:

```c
/* sys/dev/drm/include/linux/rcutree.h:30 */
static inline void rcu_barrier(void) {}
```

So `kldunload amdgpu` (or module teardown of any consumer of this port's
`call_rcu`) within ~1 s of fence activity leaves armed `elm->func` pointers
into soon-to-be-unmapped module text; when `rcu_timer` sweeps the expired
entry it calls straight into freed text.

## Reproduction (guest, deterministic)

`/root/poc/DF-4684/df4684.ko` mode 2 replicates `amdgpu_fence_slab_fini`'s
contract faithfully with the port's own primitives:

1. `call_rcu(&dummy_rcu, df4684_dummy_cb)` — callback in module text,
   fires in ≥1 s;
2. `rcu_barrier()` — returns instantly (no-op);
3. operator runs `kldunload df4684` — unload completes, text unmapped;
4. ~1 s later the delay line executes `df4684_dummy_cb` at its old address.

`panic.txt` (serial console, verbatim):

```
df4684: call_rcu enqueued (callback 0xffffffff831dd020 fires in >=1s)
df4684: rcu_barrier() returned (no-op on this port) — kldunload NOW; ...
df4684: unloading

Fatal trap 12: page fault while in kernel mode
fault virtual address   = 0xffffffff831dd020      <- the unmapped callback
fault code              = supervisor read instruction, page not present
instruction pointer     = 0x8:0xffffffff831dd020
current process         = Idle
...
Stopped at      iicbus_devclass+0x1e348:         <- ddb nearest-symbol noise
db>
```

Guest dropped to `db>` (down).  That is exactly what `kldunload amdgpu`
after GPU work does on real hardware: `amdgpu_device_fini` →
`amdgpu_fence_driver_fini` puts all slot fences (each
`amdgpu_fence_release` → `call_rcu`), then `amdgpu_fence_slab_fini`'s
no-op barrier "drains" nothing, module unmaps, ≥1 s later
`rcu_timer` calls `amdgpu_fence_free` at a dead address → fatal trap.
(Root-triggered, `kldunload`; severity Low/DoS.  The same broken contract
affects any module using the port's `call_rcu`/`kfree_rcu`, but this file's
`amdgpu_fence_slab_fini` is the instance audited.)

Note: the first two attempts of this demo wedged instead of panicking —
the culprit turned out to be DF's linker not refcounting module
dependencies (`kern_linker.c:574 linker_file_add_dependancy()` never does
`dep->refs++`), so an earlier `MODULE_DEPEND(df4684, drm, ...)` made
`kldunload df4684` recursively tear down `drm.ko`, whose headless exit
blocks forever in a DRM wait_event (wchan `"lwe"`,
`linux/wait.h:133`).  Removing the MODULE_DEPEND (manual `kldload drm`)
made the unload complete and the panic reproduce deterministically.  That
linker refcount gap is a separate latent issue, noted for the record.

## Fix (validated A/B on the guest)

`fix.diff` implements a real `rcu_barrier()` in `linux_rcu.c` (wait until
every per-CPU delay line is drained) and turns the `rcutree.h` stub into
the extern declaration.  Validation on the rebuilt kernel
(`make -j6 nativekernel KERNCONF=X86_64_GENERIC`, RC=0 under `-Werror`,
installed + rebooted):

* baseline (stock kernel): mode 2 + `kldunload` → `Fatal trap 12` at the
  unmapped callback, guest down (`panic.txt`);
* patched kernel + module rebuilt against the patched `rcutree.h`: mode 2's
  `rcu_barrier()` actually blocks (~1 s) until the dummy callback has run
  ("dummy RCU callback executed ... still mapped" printed *before* unload),
  `kldunload` completes, guest survives indefinitely (`run.fixed.log`) —
  the amdgpu unload contract restored.

See `../DF-4684/` for the shared module source and the guest environment.

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