DF-4891 / df4891_recttest.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 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | /* * DF-4891 โ in-kernel reproduction of the clip_scaled() source-rectangle * corruption caused by DragonFly's DIV_ROUND_DOWN_ULL() compat shim. * * sys/dev/drm/include/linux/kernel.h:61 defines * #define DIV_ROUND_DOWN_ULL(x, n) (((unsigned long long)(x) / (n)) * (n)) * but Linux semantics are floor((x)/(n)) (do_div truncation). The only * in-tree user is clip_scaled() in sys/dev/drm/drm_rect.c:64, whose * "round down" (downscale / 1:1) branch therefore returns a value ~dst times * too large, truncated to uint32_t โ i.e. an essentially arbitrary * attacker-influenced new_src_w that is then written into the source * rectangle AFTER drm_atomic_plane_check() already validated src against * the framebuffer (drm_atomic.c:573-586 / drm_plane.c:623-636). * * This module loads the real drm.ko, looks up the real exported * drm_rect_clip_scaled() and calls it with the geometry of a fully * unprivileged SetPlane/atomic update (VLV/CHV sprite check path, * intel_sprite.c:1195 vlv_sprite_check -> drm_atomic_helper_check_plane_state * -> drm_rect_clip_scaled), then prints the resulting source rectangle next * to what upstream Linux semantics produce. * * Case C additionally demonstrates the cleanest leak shape: the mangled * source WIDTH (12288 px on a 1920 px framebuffer) that VLV/CHV sprites * never re-validate (i9xx_check_plane_surface, intel_display.c:3312 has no * src bounds check) before folding src.x1 into the DSPSURF offset * (intel_compute_aligned_offset, intel_display.c:2410: offset = y*pitch + x*cpp). */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/types.h> struct drm_rect { int x1, y1, x2, y2; }; extern bool drm_rect_clip_scaled(struct drm_rect *src, struct drm_rect *dst, const struct drm_rect *clip); /* upstream-Linux-semantics reference implementation of clip_scaled() */ static uint32_t ref_clip_scaled(uint32_t src, uint32_t dst, uint32_t clip) { uint64_t tmp = (uint64_t)src * (uint32_t)(dst - clip); if (src < (dst << 16)) return (uint32_t)((tmp + dst - 1) / dst); /* DIV_ROUND_UP_ULL */ else return (uint32_t)(tmp / dst); /* DIV_ROUND_DOWN_ULL (Linux: do_div) */ } /* upstream-Linux-semantics reference of drm_rect_clip_scaled() */ static bool ref_clip_scaled_rect(struct drm_rect *src, struct drm_rect *dst, const struct drm_rect *clip) { uint32_t new_src_w, new_src_h; int diff; diff = clip->x1 - dst->x1; if (diff > 0) { new_src_w = ref_clip_scaled(src->x2 - src->x1, dst->x2 - dst->x1, diff); src->x1 = (int)(int64_t)((uint64_t)(uint32_t)src->x2 - new_src_w); if (src->x1 > 0x7fffffff) src->x1 = 0x7fffffff; dst->x1 = clip->x1; } diff = clip->y1 - dst->y1; if (diff > 0) { new_src_h = ref_clip_scaled(src->y2 - src->y1, dst->y2 - dst->y1, diff); src->y1 = (int)(int64_t)((uint64_t)(uint32_t)src->y2 - new_src_h); if (src->y1 > 0x7fffffff) src->y1 = 0x7fffffff; dst->y1 = clip->y1; } diff = dst->x2 - clip->x2; if (diff > 0) { new_src_w = ref_clip_scaled(src->x2 - src->x1, dst->x2 - dst->x1, diff); src->x2 = src->x1 + (int)new_src_w; /* <= INT_MAX in our cases */ dst->x2 = clip->x2; } diff = dst->y2 - clip->y2; if (diff > 0) { new_src_h = ref_clip_scaled(src->y2 - src->y1, dst->y2 - dst->y1, diff); src->y2 = src->y1 + (int)new_src_h; dst->y2 = clip->y2; } return (dst->x2 - dst->x1) > 0 && (dst->y2 - dst->y1) > 0; } static void run_case(const char *name, struct drm_rect src_in, struct drm_rect dst_in, struct drm_rect clip) { struct drm_rect s_k, d_k, s_r, d_r; bool vk, vr; s_k = src_in; d_k = dst_in; s_r = src_in; d_r = dst_in; kprintf("DF4891[%s] INPUT src=[%d,%d,%d,%d] dst=[%d,%d,%d,%d] clip=[%d,%d,%d,%d]\n", name, src_in.x1, src_in.y1, src_in.x2, src_in.y2, dst_in.x1, dst_in.y1, dst_in.x2, dst_in.y2, clip.x1, clip.y1, clip.x2, clip.y2); vk = drm_rect_clip_scaled(&s_k, &d_k, &clip); vr = ref_clip_scaled_rect(&s_r, &d_r, &clip); kprintf("DF4891[%s] KERNEL visible=%d src=[%d,%d,%d,%d] w=%d(%dpx) h=%d(%dpx) dst=[%d,%d,%d,%d]\n", name, vk, s_k.x1, s_k.y1, s_k.x2, s_k.y2, s_k.x2 - s_k.x1, (s_k.x2 - s_k.x1) >> 16, s_k.y2 - s_k.y1, (s_k.y2 - s_k.y1) >> 16, d_k.x1, d_k.y1, d_k.x2, d_k.y2); kprintf("DF4891[%s] LINUX visible=%d src=[%d,%d,%d,%d] w=%d(%dpx) h=%d(%dpx) dst=[%d,%d,%d,%d]\n", name, vr, s_r.x1, s_r.y1, s_r.x2, s_r.y2, s_r.x2 - s_r.x1, (s_r.x2 - s_r.x1) >> 16, s_r.y2 - s_r.y1, (s_r.y2 - s_r.y1) >> 16, d_r.x1, d_r.y1, d_r.x2, d_r.y2); if (s_k.x1 != s_r.x1 || s_k.y1 != s_r.y1 || s_k.x2 != s_r.x2 || s_k.y2 != s_r.y2) kprintf("DF4891[%s] DIVERGENCE: kernel source rect differs from Linux semantics -> POST-VALIDATION SRC CORRUPTION\n", name); else kprintf("DF4891[%s] no divergence\n", name); } static int df4891_modevent(module_t mod, int type, void *arg) { switch (type) { case MOD_LOAD: /* Case A: 1:1 (NO_SCALING) VLV/CHV sprite, top-left of dst * off-screen. 1920x1080 fb, 1920x1080 plane, positioned at * (-480,-270) so the CRTC clip (0,0,1920,1080) clips both * left and top. */ run_case("A-1to1-topleft", (struct drm_rect){0, 0, 1920 << 16, 1080 << 16}, (struct drm_rect){-480, -270, 1440, 810}, (struct drm_rect){0, 0, 1920, 1080}); /* Case C: right+bottom edge clip (1:1 plane positioned at * (480,270), hanging off the bottom-right of the 1920x1080 * CRTC). Correct result shrinks the src to 1440x810 inside * the fb; buggy result inflates the src WIDTH to 12288 px * (0x30000000) and HEIGHT to 22839 px on a 1920x1080 fb โ * the shape VLV/CHV sprites (vlv_sprite_check) never * re-validate before computing the DSPSURF offset. */ run_case("C-1to1-bottomright", (struct drm_rect){0, 0, 1920 << 16, 1080 << 16}, (struct drm_rect){480, 270, 2400, 1350}, (struct drm_rect){0, 0, 1920, 1080}); /* Case D: genuine 2:1 downscale, left clip (g4x-class sprite, * min_scale=1 max=16<<16). */ run_case("D-down2-left", (struct drm_rect){0, 0, 1920 << 16, 1080 << 16}, (struct drm_rect){-480, 0, 480, 540}, (struct drm_rect){0, 0, 960, 540}); return 0; case MOD_UNLOAD: kprintf("DF4891: unloaded\n"); return 0; } return 0; } static moduledata_t df4891_mod = { "df4891", df4891_modevent, NULL }; DECLARE_MODULE(df4891, df4891_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); MODULE_DEPEND(df4891, drm, 1, 1, 1); |