/*
 * DF-1881 source-confirmation harness (dma_fence_get_rcu returns fence
 * unconditionally -> RCU readers' retry logic is dead code -> UAF/double-free).
 *
 * sys/dev/drm/include/linux/dma-fence.h:97-103:
 *   dma_fence_get_rcu(struct dma_fence *fence) {
 *     if (fence) kref_get(&fence->refcount);   // ALWAYS increments
 *     return fence;                            // NEVER returns NULL
 *   }
 *
 * The correct Linux implementation uses kref_get_unless_zero() (defined
 * at sys/dev/drm/include/linux/kref.h:85) so it fails when refcount is
 * already 0 and the fence is mid-teardown.  All RCU reader retry sites
 * test the return: linux_reservation.c:357/384/444/470/511
 *   if (!dma_fence_get_rcu(fence)) goto retry;
 * With the buggy unconditional kref_get, the retry is dead code: a reader
 * can take a ref on a fence whose refcount is already 0 (release fired).
 * Its later dma_fence_put drives 1->0 a SECOND time, re-firing release
 * -> double-free.  Compounded by dma_fence_free (linux_fence.c:339-343)
 * using kfree, not kfree_rcu, so memory may be reused while an
 * rcu_read_lock reader still dereferences it.
 *
 * Needs drm/amdgpu/radeon/i915 HW (not on this guest).  The harness
 * models the refcount arithmetic.
 *
 * Build:  cc -O2 -o harness harness.c
 * Run:    ./harness
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdatomic.h>

typedef struct { _Atomic int refs; } kref_t;
typedef struct dma_fence { kref_t refcount; int freed; const char *name; } dma_fence;

static void dma_fence_release(dma_fence *f) { f->freed = 1; /* kfree(f) */ }

/* BUGGY dma_fence_get_rcu (dma-fence.h:97-103): unconditional kref_get */
static dma_fence *dma_fence_get_rcu_BUGGY(dma_fence *f) {
    if (f) atomic_fetch_add(&f->refcount.refs, 1);   /* kref_get — always increments */
    return f;
}
/* FIXED dma_fence_get_rcu: kref_get_unless_zero */
static dma_fence *dma_fence_get_rcu_FIXED(dma_fence *f) {
    if (f) {
        int cur;
        do { cur = atomic_load(&f->refcount.refs); if (cur == 0) return NULL; }
        while (!atomic_compare_exchange_weak(&f->refcount.refs, &cur, cur + 1));
    }
    return f;
}
static void dma_fence_put(dma_fence *f) {
    if (atomic_fetch_sub(&f->refcount.refs, 1) == 1) dma_fence_release(f);
}

int main(void) {
    printf("DF-1881: dma_fence_get_rcu (dma-fence.h:97-103)\n\n");

    /* Scenario: writer has already driven refcount 1->0 -> release fired.
     * RCU reader in the retry loop tries to acquire. */
    dma_fence f = { .refcount = {0}, .freed = 0, .name = "F" };
    printf("initial state: refcount=%d freed=%d (release has fired)\n",
           atomic_load(&f.refcount.refs), f.freed);

    /* BUGGY path */
    f.refcount.refs = 0; f.freed = 0; dma_fence_release(&f);
    dma_fence *got = dma_fence_get_rcu_BUGGY(&f);
    printf("BUGGY dma_fence_get_rcu returns %p (NULL=%d) — got ref on freed fence, "
           "retry check if(!got) is %s\n", (void*)got, got==NULL,
           got ? "DEAD CODE (won't retry)" : "would retry");
    if (got) {
        printf("         later dma_fence_put drives refs %d->", atomic_load(&f.refcount.refs));
        dma_fence_put(got);
        printf("%d, re-firing release -> DOUBLE FREE (freed=%d)\n",
               atomic_load(&f.refcount.refs), f.freed);
    }

    /* FIXED path */
    f.refcount.refs = 0; f.freed = 0;
    dma_fence *got2 = dma_fence_get_rcu_FIXED(&f);
    printf("FIXED dma_fence_get_rcu returns NULL=%d — reader retries (correct)\n",
           got2 == NULL);
    return 0;
}
