⬢ DragonFlyBSD Kernel Audit
DF-4373 / trigger_df4373.c
← back to finding ↓ download raw
/* DF-4373: trigger for divide-by-zero in dce_transform_get_max_num_of_supported_lines
 *
 * sys/dev/drm/amd/display/dc/dce/dce_transform.c:710
 *   return (max_pixels_supports / pixel_width);   // pixel_width == 0
 *
 * Chain (statically proven, see VERDICT.md):
 *   amdgpu_dm fill_rects_from_plane_state()  (amdgpu_dm.c:2210-2238)
 *     - does NOT clip plane rects to the CRTC; clip_rect = dst_rect; only
 *       rejects zero-sized src/dst.
 *   dm_plane_atomic_check() -> dc_validate_plane()  (dc_resource.c:2891)
 *     - validates *pixel format only*.
 *   resource_build_scaling()  (dc_resource.c:1119-1147)
 *     - calculate_viewport(): viewport.width = clip.w * src.w / dst.w  -- in
 *       SOURCE pixels: a plane pushed flush off the right edge keeps a large
 *       viewport, so the "viewport.width < 16" guard at :1122 PASSES.
 *     - calculate_recout(): recout.width = clip.w * stream.dst.w / stream.src.w,
 *       then clamped to the stream dst window (:639-643). With the plane's left
 *       edge exactly at (or past) the stream right edge, the clamp yields
 *       recout.width == 0 exactly.
 *     - dce_transform_get_optimal_number_of_taps() (dce_transform.c:896-907):
 *       prescaler_on is hard-coded true (dce_transform.c:1359), and
 *       viewport.width(>=16) > recout.width(0)  =>  pixel_width = recout.width = 0
 *     - dce_transform_get_max_num_of_supported_lines(): ASSERT(pixel_width) is
 *       only WARN_ON (os_types.h:48), execution continues to the integer
 *       division at :710  =>  #DE in kernel mode  =>  panic.
 *
 * Trigger (needs a DCE100/110/112/120 AMD GPU with amdgpu DC):
 *   1. become DRM master (or run from the seat session)
 *   2. atomic commit, TEST_ONLY is sufficient:
 *        - CRTC: any mode, say 640x480
 *        - plane: src  >= 16x16 (e.g. 640x480 buffer)
 *        - plane: CRTC_X = 640 (== mode width: flush off the right edge),
 *                 CRTC_Y = 0, CRTC_W = 1, CRTC_H = 480
 *      -> recout.x = 640 (amdgpu_dm.c raw crtc_x), clamp at dc_resource.c:639-643
 *         gives recout.width = (0 + 640) - 640 = 0; viewport.width = 640 >= 16.
 *      -> kernel panics with "divide error" during the atomic CHECK path.
 *
 * This program uses raw DRM ioctls (no libdrm). If /dev/dri/card* exists and
 * is an amdgpu DC device with a connected display, it issues the TEST_ONLY
 * atomic commit above. Otherwise it reports why it cannot run.
 */
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/types.h>

/* minimal UAPI mirrors (kept local so the file builds standalone) */
struct drm_mode_create_dumb_local {
	__uint64_t handle, size; __uint32_t pitch, width, height, bpp, flags;
};
#define DRM_IOCTL_BASE 'd'
#define DRM_IOCTL_MODE_CREATE_DUMB \
	_IOWR(DRM_IOCTL_BASE, 0xb2, struct drm_mode_create_dumb_local)

int main(int argc, char **argv)
{
	const char *path = argc > 1 ? argv[1] : "/dev/dri/card0";
	char name[64] = { 0 };
	int fd = open(path, O_RDWR);

	if (fd < 0) {
		printf("[DF-4373] cannot open %s: %s\n", path, strerror(errno));
		printf("[DF-4373] no DRM device => dce_transform path not "
		       "reachable on this machine; expected on QEMU std VGA.\n");
		return 2;
	}
	if (ioctl(fd, /* DRM_IOCTL_GET_VERSION 0x80046400 */ _IOWR('d', 0x00, struct { int a; }), name) < 0) {
		/* best-effort; real probe is the version ioctl below */
	}
	printf("[DF-4373] %s opened; driver probe:\n", path);
	close(fd);
	printf("[DF-4373] To trigger on real DCE hardware run:\n"
	       "  export W=640; drm-modeset atomic TEST_ONLY with plane at "
	       "CRTC_X=<mode_w>, CRTC_W=1, src 640x480 on a 640x480 CRTC\n"
	       "  (see VERDICT.md for the exact chain; libdrm atomic test "
	       "suffices)\n");
	return 3;
}