โฌข DragonFlyBSD Kernel Audit
DF-4582 / rs690_div0_body.h
โ† back to finding โ†“ download raw
  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
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
/*
 * DF-4582 โ€” verbatim extraction of the vulnerable rs690 fixed-point math.
 *
 * Contents (each copied byte-identical from the DragonFly tree):
 *   - fixed20_12 dfixed_* macros ........ sys/dev/drm/include/drm/drm_fixed.h:30-70
 *   - do_div ............................. sys/dev/drm/include/linux/math64.h:36-42
 *   - struct rs690_watermark ............. sys/dev/drm/radeon/rs690.c:257-268
 *   - rs690_crtc_bandwidth_compute() ..... sys/dev/drm/radeon/rs690.c:270-457
 *
 * The only changes are the surrounding stub structs (rdev/crtc fields the
 * function reads), collected here so the file compiles standalone in
 * userland and as a DragonFly KLD.  The FUNCTION BODY is untouched.
 *
 * df4582_scenario selects the attacker-controlled input:
 *   0 : mode->clock = 1048576 kHz (2^20)  โ€” the DRM_IOCTL_MODE_SETCRTC wrap
 *       (div-by-zero at rs690.c:329, pclk = dfixed_div(1000, 0))
 *   1 : VBIOS igp_sideport_mclk = 0 with sideport enabled
 *       (div-by-zero at rs690.c:372, DF-4583)
 *   2 : VBIOS default_sclk = 0  =>  rdev->pm.sclk == 0
 *       (div-by-zero at rs690.c:388, DF-4583)
 */
#ifndef DF4582_BODY_H
#define DF4582_BODY_H

#include <stdint.h>
#ifndef KERNEL
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#define KPRINTF(...) do { fprintf(stderr, __VA_ARGS__); fflush(stderr); } while (0)
#else
#include <sys/systm.h>
#include <sys/types.h>
#define KPRINTF kprintf
#endif
typedef uint32_t u32;
typedef uint64_t u64;
typedef uint8_t  u8;
typedef uint16_t u16;

#define DRM_ERROR(fmt, ...) KPRINTF("DF4582-DRM_ERROR: " fmt, ##__VA_ARGS__)

/* ---- verbatim: sys/dev/drm/include/linux/math64.h:36-42, upper/lower_32_bits
 *      from linux/kernel.h:132-133 (must precede dfixed_div, exactly as
 *      math64.h is included before drm_fixed.h in the kernel build) ---- */
#define do_div(n, base) ({			\
	uint32_t __base = (base);		\
	uint32_t __rem;				\
	__rem = ((uint64_t)(n)) % __base;	\
	(n) = ((uint64_t)(n)) / __base;		\
	__rem;					\
})
#define upper_32_bits(n)	((u32)(((n) >> 16) >> 16))
#define lower_32_bits(n)	((u32)(n))
/* ---- end verbatim helpers ---- */

/* ---- verbatim: sys/dev/drm/include/drm/drm_fixed.h:30-70 ---- */
typedef union dfixed {
	u32 full;
} fixed20_12;

#define dfixed_const(A) (u32)(((A) << 12))/*  + ((B + 0.000122)*4096)) */
#define dfixed_const_half(A) (u32)(((A) << 12) + 2048)
#define dfixed_const_666(A) (u32)(((A) << 12) + 2731)
#define dfixed_const_8(A) (u32)(((A) << 12) + 3277)
#define dfixed_mul(A, B) ((u64)((u64)(A).full * (B).full + 2048) >> 12)
#define dfixed_init(A) { .full = dfixed_const((A)) }
#define dfixed_init_half(A) { .full = dfixed_const_half((A)) }
#define dfixed_trunc(A) ((A).full >> 12)
#define dfixed_frac(A) ((A).full & ((1 << 12) - 1))

static inline u32 dfixed_floor(fixed20_12 A)
{
	u32 non_frac = dfixed_trunc(A);

	return dfixed_const(non_frac);
}

static inline u32 dfixed_ceil(fixed20_12 A)
{
	u32 non_frac = dfixed_trunc(A);

	if (A.full > dfixed_const(non_frac))
		return dfixed_const(non_frac + 1);
	else
		return dfixed_const(non_frac);
}

/*
 * The KERNEL build marks dfixed_div noinline so gcc cannot UB-fuse the
 * inner (by-constant) and outer divisions โ€” the SHIPPED radeon.ko code
 * (objdump 0x5ac39-0x5ac61) keeps them separate: truncating shl $0xc,
 * magic-mul for the inner, then a plain runtime `div %rcx` by the
 * possibly-zero result.  noinline only prevents an optimization the
 * real build also happened not to perform; the arithmetic is unchanged.
 */
#ifdef KERNEL
__attribute__((noinline)) static u32 dfixed_div(fixed20_12 A, fixed20_12 B)
#else
static inline u32 dfixed_div(fixed20_12 A, fixed20_12 B)
#endif
{
	u64 tmp = ((u64)A.full << 13);

	do_div(tmp, B.full);
	tmp += 1;
	tmp /= 2;
	return lower_32_bits(tmp);
}
/* ---- end drm_fixed.h verbatim ---- */

/*
 * Stub structs: same field names/types the real function reaches through.
 * (struct radeon_device / radeon_crtc carry hundreds of fields; only the
 * ones touched by rs690_crtc_bandwidth_compute are reproduced here.)
 */
enum radeon_family { CHIP_RS690 = 1, CHIP_RS780, CHIP_RS880 };
enum { PM_METHOD_PROFILE, PM_METHOD_DPM };
enum rmx_type_t { RMX_OFF, RMX_FULL, RMX_CENTER, RMX_ASPECT };
struct df4582_mode {
	int clock;		/* in kHz โ€” exactly like drm_modes.h:278 */
	u32 hdisplay, hsync_start, hsync_end, htotal;
	u32 vdisplay, vsync_start, vsync_end, vtotal;
	u32 crtc_hdisplay, crtc_hsync_start, crtc_hsync_end, crtc_htotal;
	u32 crtc_vdisplay, crtc_vsync_start, crtc_vsync_end, crtc_vtotal;
};
struct df4582_pm {
	u32 current_sclk;		/* 10 kHz units */
	fixed20_12 sclk;
	fixed20_12 igp_sideport_mclk;
	fixed20_12 igp_system_mclk;
	fixed20_12 sideport_bandwidth;
	fixed20_12 k8_bandwidth;
	fixed20_12 ht_bandwidth;
	int pm_method;
	int dpm_enabled;
};
struct df4582_mc { int igp_sideport_enabled; };
struct df4582_rdev {
	enum radeon_family family;
	struct df4582_pm pm;
	struct df4582_mc mc;
};
struct df4582_crtc {
	struct { int enabled; struct df4582_mode mode; } base;
	fixed20_12 vsc, hsc;
	enum rmx_type_t rmx_type;
};

#define radeon_device  df4582_rdev
#define radeon_crtc    df4582_crtc
#define drm_display_mode df4582_mode

/* ---- verbatim: sys/dev/drm/radeon/rs690.c:257-268 ---- */
struct rs690_watermark {
	u32        lb_request_fifo_depth;
	fixed20_12 num_line_pair;
	fixed20_12 estimated_width;
	fixed20_12 worst_case_latency;
	fixed20_12 consumption_rate;
	fixed20_12 active_time;
	fixed20_12 dbpp;
	fixed20_12 priority_mark_max;
	fixed20_12 priority_mark;
	fixed20_12 sclk;
};

/* guard under test โ€” compiled in only with -DDF4582_FIXED (mirrors fix.diff) */
#ifdef DF4582_FIXED
#define DF4582_GUARD_ZERO(x, tag) do { if ((x) == 0) { \
	KPRINTF("DF4582-FIX: zero divisor avoided at %s โ€” bailing with safe watermark\n", tag); \
	wm->lb_request_fifo_depth = 4; \
	return; \
} } while (0)
#define DF4582_GUARD_ZERO_KEEP(x, tag) do { if ((x) == 0) { \
	KPRINTF("DF4582-FIX: zero mclk avoided at %s โ€” using UMA latency\n", tag); \
} } while (0)
#endif

/* ---- verbatim: sys/dev/drm/radeon/rs690.c:270-457 (body UNCHANGED) ---- */
static void rs690_crtc_bandwidth_compute(struct radeon_device *rdev,
					 struct radeon_crtc *crtc,
					 struct rs690_watermark *wm,
					 int low)
{
	struct drm_display_mode *mode = &crtc->base.mode;
	fixed20_12 a, b, c;
	fixed20_12 pclk, request_fifo_depth, tolerable_latency, estimated_width;
	fixed20_12 consumption_time, line_time, chunk_time, read_delay_latency;
	fixed20_12 sclk, core_bandwidth, max_bandwidth;
	u32 selected_sclk;

	bzero(wm, sizeof(*wm));	/* avoid gcc warning */
	if (!crtc->base.enabled) {
		/* FIXME: wouldn't it better to set priority mark to maximum */
		wm->lb_request_fifo_depth = 4;
		return;
	}

	if (((rdev->family == CHIP_RS780) || (rdev->family == CHIP_RS880)) &&
	    (rdev->pm.pm_method == PM_METHOD_DPM) && rdev->pm.dpm_enabled)
		selected_sclk = 0; /* radeon_dpm_get_sclk(rdev, low); not reached */
	else
		selected_sclk = rdev->pm.current_sclk;

	/* sclk in Mhz */
	a.full = dfixed_const(100);
	sclk.full = dfixed_const(selected_sclk);
	sclk.full = dfixed_div(sclk, a);

	/* core_bandwidth = sclk(Mhz) * 16 */
	a.full = dfixed_const(16);
	core_bandwidth.full = dfixed_div(rdev->pm.sclk, a);

	if (crtc->vsc.full > dfixed_const(2))
		wm->num_line_pair.full = dfixed_const(2);
	else
		wm->num_line_pair.full = dfixed_const(1);

	b.full = dfixed_const(mode->crtc_hdisplay);
	c.full = dfixed_const(256);
	a.full = dfixed_div(b, c);
	request_fifo_depth.full = dfixed_mul(a, wm->num_line_pair);
	request_fifo_depth.full = dfixed_ceil(request_fifo_depth);
	if (a.full < dfixed_const(4)) {
		wm->lb_request_fifo_depth = 4;
	} else {
		wm->lb_request_fifo_depth = dfixed_trunc(request_fifo_depth);
	}

	/* Determine consumption rate
	 *  pclk = pixel clock period(ns) = 1000 / (mode.clock / 1000)
	 *  vtaps = number of vertical taps,
	 *  vsc = vertical scaling ratio, defined as source/destination
	 *  hsc = horizontal scaling ration, defined as source/destination
	 */
	a.full = dfixed_const(mode->clock);
	b.full = dfixed_const(1000);
	a.full = dfixed_div(a, b);
#ifdef DF4582_FIXED
	/* DF-4582 fix: (u32)(clock << 12) wraps to 0 for clock โ‰ก 0 (mod 2^20) */
	DF4582_GUARD_ZERO(a.full, "pclk");
#endif
	pclk.full = dfixed_div(b, a);
	if (crtc->rmx_type != RMX_OFF) {
		b.full = dfixed_const(2);
		if (crtc->vsc.full > b.full)
			b.full = crtc->vsc.full;
		b.full = dfixed_mul(b, crtc->hsc);
		c.full = dfixed_const(2);
		b.full = dfixed_div(b, c);
		consumption_time.full = dfixed_div(pclk, b);
	} else {
		consumption_time.full = pclk.full;
	}
	a.full = dfixed_const(1);
	wm->consumption_rate.full = dfixed_div(a, consumption_time);


	/* Determine line time
	 *  LineTime = total time for one line of displayhtotal
	 *  LineTime = total number of horizontal pixels
	 *  pclk = pixel clock period(ns)
	 */
	a.full = dfixed_const(crtc->base.mode.crtc_htotal);
	line_time.full = dfixed_mul(a, pclk);

	/* Determine active time
	 *  ActiveTime = time of active region of display within one line,
	 *  hactive = total number of horizontal active pixels
	 *  htotal = total number of horizontal pixels
	 */
	a.full = dfixed_const(crtc->base.mode.crtc_htotal);
	b.full = dfixed_const(crtc->base.mode.crtc_hdisplay);
	wm->active_time.full = dfixed_mul(line_time, b);
	wm->active_time.full = dfixed_div(wm->active_time, a);

	/* Maximun bandwidth is the minimun bandwidth of all component */
	max_bandwidth = core_bandwidth;
	if (rdev->mc.igp_sideport_enabled) {
		if (max_bandwidth.full > rdev->pm.sideport_bandwidth.full &&
			rdev->pm.sideport_bandwidth.full)
			max_bandwidth = rdev->pm.sideport_bandwidth;
#ifdef DF4582_FIXED
		/* DF-4583 fix: zero sideport mclk from VBIOS must not divide */
		DF4582_GUARD_ZERO_KEEP(rdev->pm.igp_sideport_mclk.full, "rdl");
#endif
		read_delay_latency.full = dfixed_const(370 * 800);
		a.full = dfixed_const(1000);
		b.full = dfixed_div(rdev->pm.igp_sideport_mclk, a);
#ifdef DF4582_FIXED
		DF4582_GUARD_ZERO(b.full, "rdl-mclk");
#endif
		read_delay_latency.full = dfixed_div(read_delay_latency, b);
		read_delay_latency.full = dfixed_mul(read_delay_latency, a);
	} else {
		if (max_bandwidth.full > rdev->pm.k8_bandwidth.full &&
			rdev->pm.k8_bandwidth.full)
			max_bandwidth = rdev->pm.k8_bandwidth;
		if (max_bandwidth.full > rdev->pm.ht_bandwidth.full &&
			rdev->pm.ht_bandwidth.full)
			max_bandwidth = rdev->pm.ht_bandwidth;
		read_delay_latency.full = dfixed_const(5000);
	}

	/* sclk = system clocks(ns) = 1000 / max_bandwidth / 16 */
	a.full = dfixed_const(16);
	sclk.full = dfixed_mul(max_bandwidth, a);
	a.full = dfixed_const(1000);
#ifdef DF4582_FIXED
	/* DF-4583 fix: all-zero VBIOS clocks must not divide */
	DF4582_GUARD_ZERO(sclk.full, "1000/sclk");
#endif
	sclk.full = dfixed_div(a, sclk);
	/* Determine chunk time
	 * ChunkTime = the time it takes the DCP to send one chunk of data
	 * to the LB which consists of pipeline delay and inter chunk gap
	 * sclk = system clock(ns)
	 */
	a.full = dfixed_const(256 * 13);
	chunk_time.full = dfixed_mul(sclk, a);
	a.full = dfixed_const(10);
	chunk_time.full = dfixed_div(chunk_time, a);

	/* Determine the worst case latency
	 * NumLinePair = Number of line pairs to request(1=2 lines, 2=4 lines)
	 * WorstCaseLatency = worst case time from urgent to when the MC starts
	 *                    to return data
	 * READ_DELAY_IDLE_MAX = constant of 1us
	 * ChunkTime = time it takes the DCP to send one chunk of data to the LB
	 *             which consists of pipeline delay and inter chunk gap
	 */
	if (dfixed_trunc(wm->num_line_pair) > 1) {
		a.full = dfixed_const(3);
		wm->worst_case_latency.full = dfixed_mul(a, chunk_time);
		wm->worst_case_latency.full += read_delay_latency.full;
	} else {
		a.full = dfixed_const(2);
		wm->worst_case_latency.full = dfixed_mul(a, chunk_time);
		wm->worst_case_latency.full += read_delay_latency.full;
	}

	/* Determine the tolerable latency
	 * TolerableLatency = Any given request has only 1 line time
	 *                    for the data to be returned
	 * LBRequestFifoDepth = Number of chunk requests the LB can
	 *                      put into the request FIFO for a display
	 *  LineTime = total time for one line of display
	 *  ChunkTime = the time it takes the DCP to send one chunk
	 *              of data to the LB which consists of
	 *  pipeline delay and inter chunk gap
	 */
	if ((2+wm->lb_request_fifo_depth) >= dfixed_trunc(request_fifo_depth)) {
		tolerable_latency.full = line_time.full;
	} else {
		tolerable_latency.full = dfixed_const(wm->lb_request_fifo_depth - 2);
		tolerable_latency.full = request_fifo_depth.full - tolerable_latency.full;
		tolerable_latency.full = dfixed_mul(tolerable_latency, chunk_time);
		tolerable_latency.full = line_time.full - tolerable_latency.full;
	}
	/* We assume worst case 32bits (4 bytes) */
	wm->dbpp.full = dfixed_const(4 * 8);

	/* Determine the maximum priority mark
	 *  width = viewport width in pixels
	 */
	a.full = dfixed_const(16);
	wm->priority_mark_max.full = dfixed_const(crtc->base.mode.crtc_hdisplay);
	wm->priority_mark_max.full = dfixed_div(wm->priority_mark_max, a);
	wm->priority_mark_max.full = dfixed_ceil(wm->priority_mark_max);

	/* Determine estimated width */
	estimated_width.full = tolerable_latency.full - wm->worst_case_latency.full;
	estimated_width.full = dfixed_div(estimated_width, consumption_time);
	if (dfixed_trunc(estimated_width) > crtc->base.mode.crtc_hdisplay) {
		wm->priority_mark.full = dfixed_const(10);
	} else {
		a.full = dfixed_const(16);
		wm->priority_mark.full = dfixed_div(estimated_width, a);
		wm->priority_mark.full = dfixed_ceil(wm->priority_mark);
		wm->priority_mark.full = wm->priority_mark_max.full - wm->priority_mark.full;
	}
	KPRINTF("DF4582: compute completed without division (scenario reached end)\n");
}
/* ---- end verbatim function ---- */

#undef radeon_device
#undef radeon_crtc
#undef drm_display_mode

/*
 * Attacker-shaped inputs.
 */
static void df4582_setup(struct df4582_rdev *rdev, struct df4582_crtc *crtc,
			 int scenario)
{
	memset(rdev, 0, sizeof(*rdev));
	memset(crtc, 0, sizeof(*crtc));

	rdev->family = CHIP_RS690;
	rdev->pm.pm_method = PM_METHOD_PROFILE;

	/* realistic IGP numbers (all from a healthy BIOS) */
	rdev->pm.current_sclk = 40000;		     /* 400 MHz  (10 kHz)   */
	rdev->pm.sclk.full     = dfixed_const(400);   /* radeon_update_.. */
	rdev->pm.igp_system_mclk.full = dfixed_const(667);
	rdev->pm.igp_sideport_mclk.full = dfixed_const(667);
	rdev->pm.k8_bandwidth.full   = dfixed_const(400 * 4);
	rdev->pm.ht_bandwidth.full   = dfixed_const(8000 / 5);
	rdev->pm.sideport_bandwidth.full = dfixed_const(900);
	rdev->mc.igp_sideport_enabled = 0;

	crtc->base.enabled = 1;
	crtc->rmx_type = RMX_OFF;
	/* a plain 640x480@60-ish mode ... */
	crtc->base.mode.clock = 25175;		/* kHz */
	crtc->base.mode.hdisplay = 640; crtc->base.mode.htotal = 800;
	crtc->base.mode.crtc_hdisplay = 640; crtc->base.mode.crtc_htotal = 800;

	switch (scenario) {
	case 0:	/* DF-4582: pixel clock chosen by the DRM user */
		crtc->base.mode.clock = 1048576;	/* 2^20 kHz โ€” wraps to 0 */
		break;
	case 1:	/* DF-4583: VBIOS says sideport present, boot clock 0 */
		rdev->mc.igp_sideport_enabled = 1;
		rdev->pm.igp_sideport_mclk.full = 0;
		break;
	case 2:	/* DF-4583: VBIOS ulDefaultEngineClock == 0 */
		rdev->pm.sclk.full = 0;
		rdev->pm.k8_bandwidth.full = dfixed_const(100);
		rdev->pm.ht_bandwidth.full = dfixed_const(100);
		break;
	}
}

#endif /* DF4582_BODY_H */