DF-0026 / df26_harness.c
/* * 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); |