DF-4007 / model_df4007.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | /* * DF-4007 model harness โ i915_gem_execbuffer.c eb_move_to_gpu() error path * double-unreserve via eb_release_vmas(). * * Replicates, with mock refcounts, the exact control flow of: * eb_move_to_gpu() second loop (sys/dev/drm/i915/i915_gem_execbuffer.c:1860-1876) * eb_release_vmas() (sys/dev/drm/i915/i915_gem_execbuffer.c:857-879) * __eb_unreserve_vma() (:427-435 -> __i915_vma_unpin = vma->flags--; i915_vma.h:323-326) * i915_vma_put() (i915_vma.h:222 -> i915_gem_object_put) * * Trigger: i915_vma_move_to_active() (:1864) returns -ENOMEM mid-loop when * active_instance()'s rbtree kmalloc fails (i915_vma.c:981-989) โ reachable * when the vma is active from a second context (last_active cache miss) under * DRM-slab exhaustion. For every entry already processed by the loop, * eb->flags[i] still has __EXEC_OBJECT_HAS_PIN/HAS_REF set (they are never * cleared โ the unreserve took `flags` BY VALUE), so the ioctl error path * (i915_gem_do_execbuffer :2442-2444 -> eb_release_vmas) unreserves and * i915_vma_put()s those vmas A SECOND TIME. */ #include <stdio.h> #include <stdint.h> #define __EXEC_OBJECT_HAS_REF (1u<<31) #define __EXEC_OBJECT_HAS_PIN (1u<<30) #define __EXEC_OBJECT_HAS_FENCE (1u<<29) #define __EXEC_OBJECT_RESERVED (__EXEC_OBJECT_HAS_PIN | __EXEC_OBJECT_HAS_FENCE) struct mock_vma { const char *name; int pin_count; /* vma->flags pin bits */ int fence_pin; /* vma->fence->pin_count */ int obj_refcount; /* drm_i915_gem_object refcount */ int obj_floor; /* refs that must remain after correct unwind */ unsigned int *exec_flags; /* vma->exec_flags -> &eb.flags[i] */ }; static void __eb_unreserve_vma(struct mock_vma *vma, unsigned int flags) { if (flags & __EXEC_OBJECT_HAS_FENCE) vma->fence_pin--; /* __i915_vma_unpin_fence: vma->fence->pin_count-- */ vma->pin_count--; /* __i915_vma_unpin: vma->flags-- */ } static void eb_unreserve_vma_fixed(struct mock_vma *vma, unsigned int *flags) { if (!(*flags & __EXEC_OBJECT_HAS_PIN)) return; __eb_unreserve_vma(vma, *flags); *flags &= ~__EXEC_OBJECT_RESERVED; /* the missing step in the kernel code */ } static void i915_vma_put(struct mock_vma *vma) { vma->obj_refcount--; } /* eb_move_to_gpu second loop; fail_at = index where move_to_active returns -ENOMEM (<0 = never) */ static int eb_move_to_gpu(struct mock_vma **vma, unsigned int *flags, int count, int fail_at, int fixed) { for (int i = 0; i < count; i++) { if (i == fail_at) return -12; /* -ENOMEM from active_instance() */ if (fixed) { /* fix.diff: by-ref unreserve clears PIN|FENCE; HAS_REF cleared after the put */ eb_unreserve_vma_fixed(vma[i], &flags[i]); vma[i]->exec_flags = NULL; if (flags[i] & __EXEC_OBJECT_HAS_REF) { i915_vma_put(vma[i]); flags[i] &= ~__EXEC_OBJECT_HAS_REF; } } else { /* kernel as shipped: flags taken BY VALUE, eb->flags[i] never cleared */ __eb_unreserve_vma(vma[i], flags[i]); vma[i]->exec_flags = NULL; if (flags[i] & __EXEC_OBJECT_HAS_REF) i915_vma_put(vma[i]); /* shadow batch only */ } } return 0; } /* eb_release_vmas โ verbatim semantics of i915_gem_execbuffer.c:857-879 */ static void eb_release_vmas(struct mock_vma **vma, unsigned int *flags, int count) { for (int i = 0; i < count; i++) { if (!vma[i]) break; vma[i]->exec_flags = NULL; if (flags[i] & __EXEC_OBJECT_HAS_PIN) __eb_unreserve_vma(vma[i], flags[i]); if (flags[i] & __EXEC_OBJECT_HAS_REF) i915_vma_put(vma[i]); } } static void run(const char *tag, int fail_at, int fixed) { /* a/c: 1 pin, 1 fence pin, 2 obj refs (create+ctx) that must survive. b (cmdparser shadow, eb_parse :1963): 1 pin, no fence, 3 obj refs (pool + i915_vma_get); execbuf must return exactly its 1 -> floor 2 */ struct mock_vma a = {"vma[0] objA", 1, 1, 2, 2, NULL}; struct mock_vma b = {"vma[1] shadow", 1, 0, 3, 2, NULL}; struct mock_vma c = {"vma[2] objC", 1, 1, 2, 2, NULL}; struct mock_vma *vmas[3] = { &a, &b, &c }; unsigned int flags[3]; int count = 3; for (int i = 0; i < count; i++) flags[i] = __EXEC_OBJECT_HAS_PIN | __EXEC_OBJECT_HAS_FENCE; flags[1] = __EXEC_OBJECT_HAS_PIN | __EXEC_OBJECT_HAS_REF; /* eb_parse :1964-1965: shadow has NO fence */ for (int i = 0; i < count; i++) vmas[i]->exec_flags = &flags[i]; int err = eb_move_to_gpu(vmas, flags, count, fail_at, fixed); if (err) eb_release_vmas(vmas, flags, count); /* err_vma: :2442-2444 */ printf("[%s] move_to_active fails at index %d%s\n", tag, fail_at, fixed ? " (PATCHED)" : " (BASELINE)"); for (int i = 0; i < count; i++) { const char *bad = ""; if (vmas[i]->pin_count < 0) bad = " <-- PIN UNDERFLOW (vma->flags borrow: vma unbinds while execbuf believes it pinned)"; if (vmas[i]->obj_refcount < vmas[i]->obj_floor) bad = " <-- OBJ REF UNDERFLOW (premature GEM object free -> UAF)"; if (vmas[i]->fence_pin < 0) bad = " <-- FENCE PIN UNDERFLOW (fence freed while vma still fenced)"; printf(" %-12s pin=%d fence=%d objrefs=%d%s\n", vmas[i]->name, vmas[i]->pin_count, vmas[i]->fence_pin, vmas[i]->obj_refcount, bad); } printf("\n"); } int main(void) { run("DF-4007 BASELINE", 1, 0); /* bug as shipped: entry 1 ENOMEM, entry 0 double-released (pin+fence) */ run("DF-4007 BASELINE", 2, 0); /* bug as shipped: entry 2 ENOMEM, shadow batch double-put (obj refcount) */ run("DF-4007 control", -1, 0); /* no failure: perfectly balanced */ run("DF-4007 PATCHED ", 2, 1); /* fix.diff applied: balanced even on failure */ return 0; } |