⬢ DragonFlyBSD Kernel Audit
DF-3881 / mst_lct_model.c
← back to finding ↓ download raw
/* DF-3881 model: unbounded MST topology depth (LCT) -> OOB writes.
 *
 * Faithful arithmetic model of the DragonFlyBSD kernel code paths in
 * sys/dev/drm/drm_dp_mst_topology.c:
 *
 *   drm_dp_port_setup_pdt()   line ~1074:  u8 rad[6];
 *   drm_dp_calculate_rad()    line ~1052:  idx = (parent_lct - 1) / 2;
 *                                          memcpy(rad, port->parent->rad, idx + 1);
 *                                          rad[idx] |= port->port_num << shift;
 *                                          return parent_lct + 1;
 *   drm_dp_add_mst_branch_device() line ~840: memcpy(mstb->rad, rad, lct / 2);
 *                                          struct drm_dp_mst_branch { u8 rad[8]; ... }
 *   set_hdr_from_dst_qlock()  line ~1484:  memcpy(hdr->rad, mstb->rad, mstb->lct / 2);
 *                                          struct drm_dp_sideband_msg_hdr { u8 rad[8]; ... }
 *   drm_dp_mst_dump_mstb()    line ~2954:  int tabs = mstb->lct; char prefix[10];
 *                                          for (i = 0; i < tabs; i++) prefix[i] = '\t';
 *
 * parent_lct grows by exactly +1 per nested MST branch device reported by the
 * hub in its LINK_ADDRESS reply (add_port -> port_setup_pdt -> +1 -> recurse
 * via drm_dp_send_link_address), and NOTHING in the file bounds it.
 *
 * This model reproduces the offset arithmetic (which kernel buffer index is
 * written out of bounds at which depth). It cannot drive the kernel path on
 * this guest because the guest has no DisplayPort MST hardware (no /dev/dri).
 */
#include <stdio.h>
#include <string.h>
#include <stdint.h>

struct mstb_model {            /* mirrors struct drm_dp_mst_branch */
	uint8_t rad[8];
	uint8_t lct;
};

#define RAD_STACK 6            /* u8 rad[6] in drm_dp_port_setup_pdt */
#define RAD_HEAP  8            /* u8 rad[8] in struct drm_dp_mst_branch */
#define PREFIX    10           /* char prefix[10] in drm_dp_mst_dump_mstb */

int main(void)
{
	struct mstb_model parent;
	int parent_lct;
	int first_stack_oob = -1, first_heap_oob = -1, first_hdr_oob = -1;
	int first_prefix_oob = -1;

	memset(&parent, 0, sizeof(parent));
	parent.lct = 1;        /* mgr->mst_primary is created with LCT 1 */

	printf("modeling chain of nested MST branch devices (parent_lct = depth):\n");
	for (parent_lct = 1; parent_lct <= 32; parent_lct++) {
		int idx = (parent_lct - 1) / 2;
		int shift = (parent_lct % 2) ? 4 : 0;
		int lct = parent_lct + 1;
		int stack_oob = 0, heap_oob = 0, hdr_oob = 0, prefix_oob = 0;

		/* drm_dp_calculate_rad() */
		if (parent_lct > 1 && (idx + 1) > RAD_STACK) {
			stack_oob = 1;
			if (first_stack_oob < 0) first_stack_oob = parent_lct;
		}
		if (idx >= RAD_STACK) {
			stack_oob = 1;
			if (first_stack_oob < 0) first_stack_oob = parent_lct;
		}

		/* drm_dp_add_mst_branch_device(lct, rad) */
		if (lct > 1 && (lct / 2) > RAD_HEAP) {
			heap_oob = 1;
			if (first_heap_oob < 0) first_heap_oob = parent_lct;
		}

		/* set_hdr_from_dst_qlock(): mstb->lct == lct */
		if (lct > 1 && (lct / 2) > 8) {
			hdr_oob = 1;
			if (first_hdr_oob < 0) first_hdr_oob = parent_lct;
		}

		/* drm_dp_mst_dump_mstb(): tabs = mstb->lct == lct */
		if (lct >= PREFIX) {
			prefix_oob = 1;
			if (first_prefix_oob < 0) first_prefix_oob = parent_lct;
		}

		printf("parent_lct=%2d child_lct=%2d rad_idx=%2d "
		       "stack_rad6:%-3s heap_rad8:%-3s hdr_rad8:%-3s prefix10:%-3s\n",
		       parent_lct, lct, idx,
		       stack_oob ? "OOB" : "ok",
		       heap_oob  ? "OOB" : "ok",
		       hdr_oob   ? "OOB" : "ok",
		       prefix_oob? "OOB" : "ok");
	}

	printf("\nfirst OOB per sink:\n");
	printf("  drm_dp_calculate_rad stack rad[6]   : parent_lct=%d (chain of %d hubs)\n",
	       first_stack_oob, first_stack_oob - 1);
	printf("  drm_dp_add_mst_branch_device rad[8] : parent_lct=%d\n", first_heap_oob);
	printf("  set_hdr_from_dst_qlock hdr rad[8]   : parent_lct=%d\n", first_hdr_oob);
	printf("  drm_dp_mst_dump_mstb prefix[10]     : parent_lct=%d (debugfs, root)\n",
	       first_prefix_oob);

	/*
	 * The recursion itself: drm_dp_send_link_address() reply loop calls
	 * drm_dp_add_port() which calls drm_dp_send_link_address() for the
	 * child. One nested kernel frame-set per level, no depth limit.
	 * DFBSD kernel thread stack = 16KB (KSTACK_PAGES=4, unless configured
	 * otherwise). Frame-set (send_link_address + add_port + setup_pdt +
	 * build helpers) is on the order of 200-400 bytes per level.
	 */
	printf("\nrecursion: ~300 bytes of kernel stack per topology level;\n");
	printf("16KB kernel stack exhausts at roughly depth %d (%d hub levels deep).\n",
	       16384 / 300, 16384 / 300 - 1);
	return 0;
}