โฌข DragonFlyBSD Kernel Audit
DF-0026 / df26_harness.c
โ† back to finding โ†“ download raw
/*
 * DF-0026 harness: kernel module that demonstrates the divide-by-zero at
 * sys/kern/subr_disk.c:1376 (bioq->reorder % bioq_reorder_minor_interval)
 * when the sysctl is set to 0 by root.
 *
 * This module constructs the exact bioq state the bug requires:
 *   1. A WRITE bio queued first (sets bioq->transition != NULL)
 *   2. A READ bio queued second (enters bioqdisksort's READ+transition branch)
 *
 * With bioq_reorder_minor_interval == 0 (set via sysctl by root prior to
 * loading), the modulus at subr_disk.c:1376 evaluates `reorder % 0` and
 * triggers an integer divide fault (FPE_INTDIV) -> kernel panic.
 *
 * The module is a HARNESS โ€” it proves the code path panics when the sysctl
 * is 0. The sysctl is set from userspace (root). The finding is root-only by
 * design (SYSCAP_NOSYSCTL_WR gates the sysctl write).
 *
 * Build:  make  (uses /usr/src/sys Makefile)
 * Load:   kldload ./df26.ko   (root; AFTER sysctl kern.bioq_reorder_minor_interval=0)
 * Expect: immediate kernel panic (integer divide fault in bioqdisksort)
 */
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/malloc.h>
#include <sys/buf.h>
#include <sys/buf2.h>
#include <sys/diskslice.h>

static MALLOC_DEFINE(M_DF26, "df26", "DF-0026 harness");

static int
df26_modevent(module_t mod, int type, void *data)
{
	struct bio_queue_head bq;
	struct bio *wbio, *rbio;
	struct buf *wbuf, *rbuf;

	switch (type) {
	case MOD_LOAD:
		kprintf("DF-0026: harness loaded. bioq_reorder_minor_interval=%d\n",
			bioq_reorder_minor_interval);

		if (bioq_reorder_minor_interval != 0) {
			kprintf("DF-0026: interval is non-zero (%d); "
				"set kern.bioq_reorder_minor_interval=0 first.\n",
				bioq_reorder_minor_interval);
			return 0;
		}

		/* Allocate a fake WRITE bio + buf and a fake READ bio + buf. */
		wbio = kmalloc(sizeof(*wbio), M_DF26, M_WAITOK | M_ZERO);
		rbio = kmalloc(sizeof(*rbio), M_DF26, M_WAITOK | M_ZERO);
		wbuf = kmalloc(sizeof(*wbuf), M_DF26, M_WAITOK | M_ZERO);
		rbuf = kmalloc(sizeof(*rbuf), M_DF26, M_WAITOK | M_ZERO);

		wbio->bio_buf = wbuf;
		wbuf->b_cmd = BUF_CMD_WRITE;
		wbuf->b_bcount = DEV_BSIZE;
		wbio->bio_offset = 0;

		rbio->bio_buf = rbuf;
		rbuf->b_cmd = BUF_CMD_READ;
		rbuf->b_bcount = DEV_BSIZE;
		rbio->bio_offset = DEV_BSIZE;

		/* Initialize bioq and queue the WRITE first (sets transition). */
		bioq_init(&bq);
		kprintf("DF-0026: queuing WRITE (transition before=%p)\n",
			bq.transition);
		bioqdisksort(&bq, wbio);   /* WRITE -> transition = wbio */
		kprintf("DF-0026: queued WRITE, transition=%p. Now queuing READ -> DIV0\n",
			bq.transition);

		/* Queue the READ โ€” bioqdisksort will hit `reorder % 0` here. */
		bioqdisksort(&bq, rbio);   /* READ, transition!=NULL -> DIV0 */

		/* NOTREACHED โ€” should have panicked above */
		kprintf("DF-0026: BUG NOT TRIGGERED (unexpected!)\n");
		return 0;

	case MOD_UNLOAD:
		return 0;
	default:
		return EOPNOTSUPP;
	}
}

static moduledata_t df26_mod = {
	"df26",
	df26_modevent,
	NULL
};
DECLARE_MODULE(df26, df26_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);