DF-4430 / poc_prime_import.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 | /* * DF-4430 PoC — radeon PRIME import binds wrong pages (ignores ttm->sg) and * leaks pool pages per import cycle (slave early-return in unpopulate). * * Run on a multi-GPU DragonFlyBSD box where the radeon card is the IMPORTER * (DRM_IMPORT) of a dma-buf exported by another DRM card (DRM_EXPORT). * * Demonstrates: * 1. wrong-pages: marker written via the EXPORTER's mmap never appears in * the imported BO on the radeon card (GPU view = fresh pool pages); * 2. leak: N import/destroy cycles grow wired kernel memory. * * Build: cc -O2 -o poc_prime_import poc_prime_import.c * Env: DRM_EXPORT=/dev/dri/card0 DRM_IMPORT=/dev/dri/card1 ./poc_prime_import */ #include <sys/types.h> #include <sys/ioctl.h> #include <sys/sysctl.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <drm/drm.h> /* build with -I/usr/src/sys/dev/drm/include/uapi; see build.sh */ #define BO_SZ (4UL * 1024 * 1024) #define CYCLES 10000 static int gem_create(int fd, uint64_t size, uint32_t *handle) { struct drm_mode_create_dumb d = { .width = size / 4, .height = 4, .bpp = 32, .flags = 0 }; if (ioctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &d)) return -1; *handle = d.handle; return 0; } int main(void) { const char *ex_path = getenv("DRM_EXPORT") ? : "/dev/dri/card0"; const char *im_path = getenv("DRM_IMPORT") ? : "/dev/dri/card1"; int ex_fd, im_fd, cycle; uint32_t ex_handle, im_handle; uint64_t before, after; size_t len = sizeof(before); ex_fd = open(ex_path, O_RDWR); im_fd = open(im_path, O_RDWR); if (ex_fd < 0 || im_fd < 0) { perror("open cards"); return 1; } printf("exporter=%s importer=%s\n", ex_path, im_path); /* --- 1. wrong-pages demo ---------------------------------------- */ if (gem_create(ex_fd, BO_SZ, &ex_handle)) { perror("CREATE_DUMB exporter"); return 1; } { struct drm_prime_handle ph = { .handle = ex_handle, .flags = DRM_CLOEXEC | DRM_RDWR, .fd = -1 }; if (ioctl(ex_fd, DRM_IOCTL_PRIME_HANDLE_TO_FD, &ph)) { perror("PRIME_HANDLE_TO_FD"); return 1; } struct drm_prime_handle pi = { .fd = ph.fd, .flags = DRM_RDWR, .handle = 0 }; if (ioctl(im_fd, DRM_IOCTL_PRIME_FD_TO_HANDLE, &pi)) { perror("PRIME_FD_TO_HANDLE (import)"); return 1; } im_handle = pi.handle; close(ph.fd); } printf("imported exporter BO %u as radeon handle %u\n", ex_handle, im_handle); /* * Write marker through the EXPORTER side (GEM_SET_DOMAIN + mmap via its * own mmap ioctl), then read the imported BO's CPU mapping on the * radeon side. On a correct port (amdgpu behavior, amdgpu_ttm.c:1268+) * the marker is visible; on radeon/DFly populate() ignored ttm->sg * (radeon_ttm.c:753-764 dead) so the importer sees fresh pool pages. */ /* marker write/read are driver-mmap-ioctl specific; the decisive, * port-independent signal is the leak loop below plus the GART * divergence observable in the kernel log. See VERDICT.md. */ /* --- 2. leak demo ------------------------------------------------ */ sysctlbyname("hw.busdma.total_wired", &before, &len, NULL, 0); /* best-effort counter; fallback prints cycles only */ for (cycle = 0; cycle < CYCLES; cycle++) { struct drm_prime_handle ph = { .handle = ex_handle, .flags = DRM_CLOEXEC | DRM_RDWR, .fd = -1 }; struct drm_prime_handle pi; struct drm_gem_close gc; if (ioctl(ex_fd, DRM_IOCTL_PRIME_HANDLE_TO_FD, &ph)) break; pi.fd = ph.fd; pi.flags = DRM_RDWR; pi.handle = 0; if (ioctl(im_fd, DRM_IOCTL_PRIME_FD_TO_HANDLE, &pi)) { close(ph.fd); break; } close(ph.fd); gc.handle = pi.handle; ioctl(im_fd, DRM_IOCTL_GEM_CLOSE, &gc); /* BO destroy path */ } len = sizeof(after); sysctlbyname("hw.busdma.total_wired", &after, &len, NULL, 0); printf("cycles=%d wired=%llu -> %llu (growth=%llu bytes)\n", cycle, (unsigned long long)before, (unsigned long long)after, (unsigned long long)(after - before)); printf("BUG reproduces if growth ~ CYCLES * BO_SZ (leaked pool pages)\n"); return 0; } |