/*
 * 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);
