/*
 * DF-1603 - virtio_blk divide-by-zero PoC (userspace harness).
 *
 * Bug: sys/dev/virtual/virtio/block/virtio_blk.c:702-709
 *
 *   702: if (virtio_with_feature(sc->vtblk_dev, VIRTIO_BLK_F_BLK_SIZE))
 *   703:     sc->vtblk_sector_size = blkcfg->blk_size;        // NO validation
 *   704: else
 *   705:     sc->vtblk_sector_size = 512;
 *   ...
 *   708: info.d_media_blksize = sc->vtblk_sector_size;
 *   709: info.d_media_blocks = blkcfg->capacity * 512 / info.d_media_blksize;
 *                                                              // DIV#0
 *
 * blkcfg->blk_size is a u32 read verbatim from the virtio PCI config space
 * (host/hypervisor-controlled). The virtio-blk spec allows VIRTIO_BLK_F_BLK_SIZE
 * to be negotiated without constraining the value, so a malicious backend
 * (compromised hypervisor, malicious vhost-user backend, hostile VFIO device)
 * can advertise the feature AND report blk_size = 0. vtblk_attach() -> 
 * vtblk_alloc_disk() executes unconditionally at first boot and divides by
 * info.d_media_blksize on line 709, taking a #DE (divide error) trap that
 * the kernel converts to a panic. Result: guest is dead on first attach;
 * unprivileged in-guest action is irrelevant - the bug fires from the host
 * side before userspace exists.
 *
 * Threat model: this is a host-side DoS of a guest by a malicious backend.
 * On this DragonFly audit guest the virtio-blk device is provided by KVM
 * with a sane 512-byte sector size and the guest has no syscall surface to
 * rewrite PCI config space, so the live kernel path cannot be exercised
 * from inside the guest. This harness reproduces the EXACT arithmetic of
 * vtblk_alloc_disk() lines 702-709 to confirm the primitive: blkcfg.blk_size
 * = 0 produces an integer divide-by-zero (#DE / SIGFPE) where the kernel
 * code does the same division at attach time.
 *
 * Build:  cc -O2 -o df1603_poc df1603_poc.c
 * Run:    ./df1603_poc        ; exits 0 if SIGFPE observed on blk_size=0
 */
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <setjmp.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>

static sigjmp_buf jb;
static volatile sig_atomic_t got_fpe = 0;

static void
fpe_hdl(int sig)
{
	(void)sig;
	got_fpe = 1;
	siglongjmp(jb, 1);
}

/*
 * Mirror virtio_blk.c:702-709 with bit-exact arithmetic.
 *   VIRTIO_BLK_F_BLK_SIZE negotiated  -> sector_size = blkcfg.blk_size
 *   blkcfg.capacity is in 512-byte sectors; the line 709 division is the sink.
 *   feature_negotiated is independent of blk_size's value - a malicious
 *   backend can advertise the feature AND report blk_size = 0.
 */
static int
vtblk_alloc_disk_arith(int feature_negotiated, uint64_t capacity,
    uint32_t blk_size, uint64_t *out_blocks, uint32_t *out_bs)
{
	uint32_t sector_size;

	if (feature_negotiated)		/* virtio_blk.c:702 */
		sector_size = blk_size;	/* virtio_blk.c:703 - NO validation */
	else
		sector_size = 512;	/* virtio_blk.c:705 */

	*out_bs = sector_size;
	/* virtio_blk.c:708-709 - division by sector_size */
	*out_blocks = capacity * 512 / sector_size;
	return 0;
}

int
main(void)
{
	struct sigaction sa;
	uint64_t blocks;
	uint32_t bs;

	memset(&sa, 0, sizeof(sa));
	sa.sa_handler = fpe_hdl;
	sigemptyset(&sa.sa_mask);
	sigaction(SIGFPE, &sa, NULL);

	/* Sanity case: well-formed backend reports blk_size = 512. */
	vtblk_alloc_disk_arith(1, 0x100000ULL, 512, &blocks, &bs);
	printf("[control] capacity=0x%llx blk_size=512 -> %llu blocks x %u bytes\n",
	    (unsigned long long)0x100000ULL, (unsigned long long)blocks, bs);

	/*
	 * Malicious backend: VIRTIO_BLK_F_BLK_SIZE negotiated + blk_size = 0.
	 * SIGFPE here is the userland analogue of the kernel's #DE trap; the
	 * kernel has no SIGFPE handler and converts #DE to a panic at
	 * vtblk_attach -> vtblk_alloc_disk line 709.
	 */
	if (sigsetjmp(jb, 1) == 0) {
		vtblk_alloc_disk_arith(1, 0x100000ULL, 0, &blocks, &bs);
		printf("[FAIL] expected div-by-zero but got blocks=%llu bs=%u\n",
		    (unsigned long long)blocks, bs);
		return 1;
	}
	printf("[OK] blk_size=0 raised SIGFPE (analogue of #DE trap);\n");
	printf("     kernel path: vtblk_attach -> vtblk_alloc_disk line 709\n");
	printf("     result on guest: kernel panic at attach time (boot DoS).\n");
	return 0;
}
