/*
 * DF-1174 — userspace harness for ata_raid_attach divide-by-zero.
 *
 * Reconstructs the rounddown() macro from sys/sys/param.h:400 and the
 * ata_raid_attach logic from sys/dev/disk/nata/ata-raid.c:155-160 to
 * demonstrate the kernel #DE (divide error) panic when a crafted RAID
 * metadata block supplies width==0 or interleave==0 for a RAID0/01/05
 * array.
 *
 * Build:  cc -O2 -o harness harness.c
 * Run:    ./harness
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <signal.h>
#include <setjmp.h>
#include <string.h>

/* sys/sys/param.h:400 */
#define rounddown(x, y) (((x)/(y))*(y))

#define AR_T_RAID0  0x01
#define AR_T_RAID1  0x02
#define AR_T_RAID01 0x03
#define AR_T_RAID5  0x05

struct ar_softc {
	int type;
	uint64_t total_sectors;
	unsigned int width;
	unsigned int interleave;
};

/* ata_raid_attach excerpt — sys/dev/disk/nata/ata-raid.c:154-160.
 * BUG: rounddown(total_sectors, interleave*width) divides by interleave*width;
 *      if either is 0 from crafted metadata -> kernel #DE panic. */
static uint64_t ata_raid_attach_rounddown(struct ar_softc *rdp)
{
	rdp->total_sectors = rounddown(rdp->total_sectors,
	    rdp->interleave * rdp->width);
	return rdp->total_sectors;
}

static sigjmp_buf jb;
static volatile int got_sig;
static void handler(int s) { got_sig = s; siglongjmp(jb, 1); }

int main(void)
{
	struct sigaction sa;
	memset(&sa, 0, sizeof(sa));
	sa.sa_handler = handler;
	sigemptyset(&sa.sa_mask);
	sigaction(SIGFPE, &sa, NULL);

	struct {
		const char *desc;
		unsigned interleave;
		unsigned width;
	} cases[] = {
		{ "nVidia meta stripe_sectors=0, array_width=2",     0,  2 },
		{ "Intel meta stripe_sectors=64, array_width=0",    64,  0 },
		{ "ITE meta stripe_sectors=0, array_width=0",        0,  0 },
	};
	unsigned i;
	for (i = 0; i < sizeof(cases)/sizeof(cases[0]); i++) {
		struct ar_softc rdp = {
			.type = AR_T_RAID0,
			.total_sectors = 1000000ULL,
			.interleave = cases[i].interleave,
			.width = cases[i].width,
		};
		printf("[case %u] %s -> rounddown(total_sectors=%llu, %u*%u)\n",
		       i + 1, cases[i].desc,
		       (unsigned long long)rdp.total_sectors,
		       rdp.interleave, rdp.width);
		fflush(stdout);

		got_sig = 0;
		if (sigsetjmp(jb, 1) == 0) {
			ata_raid_attach_rounddown(&rdp);
			printf("    result: total_sectors=%llu (no div0 -- "
			       "not the bug case)\n",
			       (unsigned long long)rdp.total_sectors);
		} else {
			printf("    BUG: integer divide-by-zero -> SIGFPE %d "
			       "(kernel equivalent: #DE trap -> panic in "
			       "ata_raid_attach at ata-raid.c:157-158)\n",
			       got_sig);
		}
	}

	/* IOCATARAIDSTATUS divide-by-zero at ata-raid.c:1028
	 * status->progress = 100 * rdp->rebuild_lba / rdp->total_sectors; */
	puts("");
	puts("[case 4] IOCATARAIDSTATUS: total_sectors=0 (crafted metadata)");
	uint64_t rebuild_lba = 12345ULL;
	uint64_t total_sectors_zero = 0ULL;
	volatile unsigned progress;
	got_sig = 0;
	if (sigsetjmp(jb, 1) == 0) {
		progress = 100 * rebuild_lba / total_sectors_zero;
		printf("    progress=%u (no div0)\n", progress);
	} else {
		printf("    BUG: integer divide-by-zero -> SIGFPE %d "
		       "(kernel equivalent: #DE trap -> panic in "
		       "ata_raid_status at ata-raid.c:1028)\n", got_sig);
	}

	/* ata_raid_create at ata-raid.c:1255 also divides by (type&...? 2:1)
	 * -- not attacker-reachable so we skip it. */

	puts("");
	puts("DF-1174 harness: all div-by-zero cases demonstrated.");
	return 0;
}
