DF-1881 / harness.c
/* * 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; } |