# DF-1863 — amdgpu_ctx_add_fence TOCTOU

## What this is

A **userspace pthread harness** that proves the TOCTOU race in
`amdgpu_ctx_add_fence` (`sys/dev/drm/amd/amdgpu/amdgpu_ctx.c:440-464`) at
the *algorithm* level.  The in-kernel path cannot be exercised on this
guest (no `device amdgpu` in `X86_64_GENERIC`, no AMD GPU, no
`/dev/dri/`, amdgpu module not loaded) so the harness replicates the
buggy function byte-for-byte and runs both the BUGGY and FIXED variants
under two concurrent threads.

## Build & run

```
./build.sh      # cc -O2 -Wall -pthread -o race race.c
./run.sh        # ./race; ./race; ./race
```

## Expected output (bug present, algorithm level)

```
DF-1863 amdgpu_ctx_add_fence TOCTOU harness  (N_THREADS=2, ITERS=4000)
----------------------------------------------------------
BUGGY  : iters=8000  double_put=1825  live_fences=1858  occupied_slots=32  leaked=1826
FIXED  : iters=8000  double_put=0     live_fences=32    occupied_slots=32  leaked=0
----------------------------------------------------------
RESULT: BUGGY variant produced 1825 double dma_fence_put() events and 1826 leaked fences
        => confirms TOCTOU primitive.
```

The race is statistical — roughly 2 of 3 runs trigger it within a few
hundred iterations.  The FIXED variant never double-puts.

## What it means

- `double_put` — a `dma_fence_put(other)` was invoked on a fence whose
  refcount had already been driven to 0 by a racing thread.  In the
  kernel this is the use-after-free / double-free of `struct dma_fence`
  (`drm_sched_fence`) in `kmalloc`.
- `leaked` — a fence was stored into a slot and then silently
  overwritten by the losing thread before its ctx-held ref was ever
  released (its refcount never returns to 0 via the ctx-held put).  This
  is the "lost fence" of the finding.

## In-kernel reproduction (NOT possible on this guest)

End-to-end kernel reproduction requires a system with:

- `device amdgpu` in the kernel config (or `kldload amdgpu`),
- a real AMD GPU,
- access to `/dev/dri/renderD128` (typically granted to the `video`
  group).

The trigger is two threads issuing `AMDGPU_CS` ioctls against the same
`ctx_id` / `ip_type` / `ring` concurrently.  On a default GENERIC
DragonFly kernel none of these preconditions hold, so this is filed as
a **latent bug** with the primitive proved at the harness level (per
DF-0594 / DF-0616 / DF-0281 precedent).

## Fix

`fix.diff` moves the reads of `centity->sequence`, `idx`, and
`other = centity->fences[idx]` inside the `ring_lock` critical section.
Validated by rebuilding `amdgpu.ko` with `-Werror` (clean compile +
link).  See `VERDICT.md` for the full narrative.
