/*
 * DF-0811 — deterministic harness.
 *
 * Transcribes the EXACT kernel math from:
 *   sys/vfs/ext2fs/ext2_alloc.c:856-868  (ext2_get_group_number / ext2_block_in_group)
 *   sys/vfs/ext2fs/ext2_alloc.c:870-925  (ext2_cg_block_bitmap_init, the setbit path)
 *   sys/sys/param.h:390                  (setbit macro semantics)
 *
 * The harness proves that the divisor bug (/ e2fs_bsize instead of / e2fs_bpg,
 * which mount enforces == e2fs_bsize*8) lets ext2_block_in_group() return TRUE
 * for a block bitmap pointer that is physically in a DIFFERENT group, so the
 *   setbit(bp->b_data, tmp - start)
 * index wraps a uint64 subtraction to ~2^64 and the resulting byte offset
 * (index / NBBY) is a wild out-of-bounds kernel heap write.
 *
 * The "bp->b_data" stand-in is an mmap'd page with a guard page immediately
 * after it, so the wild write faults (SIGSEGV) — proving the OOB.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <signal.h>
#include <setjmp.h>
#include <unistd.h>
#include <sys/mman.h>

#define NBBY 8            /* sys/sys/param.h */

/* ---- verbatim transcription of ext2_alloc.c:855-868 ------------------------- */

/* ext2_alloc.c:855 */
static int
ext2_get_group_number(uint32_t e2fs_first_dblock, uint32_t e2fs_bsize,
    uint32_t e2fs_bpg, uint64_t block)
{
	/*
	 * KERNEL CODE (ext2_alloc.c:859-860):
	 *   return ((block - le32toh(fs->e2fs->e2fs_first_dblock)) /
	 *       fs->e2fs_bsize);
	 *
	 * BUG: divides by e2fs_bsize (block size in BYTES).  Mount enforces
	 *      e2fs_bpg == e2fs_bsize * 8 (ext2_vfsops.c:568), so the divisor is
	 *      wrong by a factor of 8.  Correct divisor is e2fs_bpg.
	 */
	return (int)((block - e2fs_first_dblock) / e2fs_bsize);   /* BUGGY */
}

/* ext2_alloc.c:863 */
static int
ext2_block_in_group(uint32_t first_dblock, uint32_t bsize, uint32_t bpg,
    uint64_t block, int cg)
{
	return ((ext2_get_group_number(first_dblock, bsize, bpg, block) == cg)
	    ? 1 : 0);
}

/* what the CORRECT implementation would do (for comparison) */
static int
ext2_block_in_group_fixed(uint32_t first_dblock, uint32_t bpg,
    uint64_t block, int cg)
{
	return ((((block - first_dblock) / bpg) == cg) ? 1 : 0);
}

/* ---- verbatim transcription of setbit (sys/sys/param.h:390) ----------------- */
/* #define setbit(a,i)  ((a)[(i)/NBBY] |= 1<<((i)%NBBY)) */
static unsigned long
setbit_byte_index(uint64_t i)
{
	return (unsigned long)(i / NBBY);
}

/* ---- harness state for the guard-page trap --------------------------------- */
static sigjmp_buf jb;
static volatile int faulted;
static void *fault_addr;

static void
segv_handler(int sig, siginfo_t *si, void *uc)
{
	(void)sig; (void)uc;
	faulted = 1;
	fault_addr = si->si_addr;
	siglongjmp(jb, 1);
}

int
main(void)
{
	/*
	 * Filesystem geometry matching a default mke2fs -b 4096 image:
	 *   bsize = 4096, first_dblock = 0, bpg = 32768 (= bsize*8).
	 * Mount invariant (ext2_vfsops.c:568): e2fs_bpg == e2fs_bsize * 8.
	 */
	const uint32_t bsize = 4096;
	const uint32_t first_dblock = 0;
	const uint32_t bpg = bsize * 8;          /* 32768, enforced at mount */
	const int cg = 1;
	const int has_flex_bg = 1;               /* EXT2F_INCOMPAT_FLEX_BG */

	/*
	 * Attacker-controlled group-1 descriptor: block bitmap pointer.
	 * Under FLEX_BG this may live anywhere in the fs that passes mount
	 * validation (ext2_vfsops.c:400-409 uses CORRECT bounds), so we point
	 * it at a block physically in GROUP 0 but whose buggy group-number
	 * computes to 1.
	 *
	 *   buggy ext2_get_group_number(tmp) = (tmp - 0)/4096 == 1  =>  tmp in
	 *   [4096, 8191].  Pick tmp = 4096 (first data block of group 0 after
	 *   the metadata area).  This block is inside group 0 (blocks 0..32767).
	 */
	const uint64_t b_bitmap_attacker = 4096;

	printf("=== DF-0811 divisor-bug harness ===\n");
	printf("geometry: bsize=%u first_dblock=%u bpg=%u (== bsize*8 mount invariant)\n",
	    bsize, first_dblock, bpg);
	printf("target cg=%d, FLEX_BG=%d\n", cg, has_flex_bg);
	printf("attacker group-%d b_bitmap pointer = %llu (physically in group 0)\n",
	    cg, (unsigned long long)b_bitmap_attacker);

	/* --- the buggy gate (ext2_alloc.c:893-894) --- */
	int gate_buggy = ext2_block_in_group(first_dblock, bsize, bpg,
	    b_bitmap_attacker, cg);
	int gate_fixed = ext2_block_in_group_fixed(first_dblock, bpg,
	    b_bitmap_attacker, cg);
	printf("\nBUGGY  ext2_block_in_group(tmp=%llu, cg=%d) = %d\n",
	    (unsigned long long)b_bitmap_attacker, cg, gate_buggy);
	printf("  (buggy ext2_get_group_number = (%llu-%u)/%u = %llu)\n",
	    (unsigned long long)b_bitmap_attacker, first_dblock, bsize,
	    (unsigned long long)((b_bitmap_attacker - first_dblock) / bsize));
	printf("CORRECT ext2_block_in_group(tmp=%llu, cg=%d) = %d\n",
	    (unsigned long long)b_bitmap_attacker, cg, gate_fixed);
	printf("  (correct group number        = (%llu-%u)/%u = %llu)\n",
	    (unsigned long long)b_bitmap_attacker, first_dblock, bpg,
	    (unsigned long long)((b_bitmap_attacker - first_dblock) / bpg));

	/* --- the wrap (ext2_alloc.c:888-895) --- */
	uint64_t start = (uint64_t)cg * bpg + first_dblock;   /* line 888-889 */
	uint64_t bit_index = b_bitmap_attacker - start;       /* line 895 arg */
	unsigned long byte_index = setbit_byte_index(bit_index);

	printf("\n--- setbit index math (ext2_alloc.c:888-895) ---\n");
	printf("start           = cg*bpg + first_dblock = %u*%u + %u = %llu\n",
	    cg, bpg, first_dblock, (unsigned long long)start);
	printf("bit_index       = tmp - start           = %llu - %llu = %llu (0x%llx)\n",
	    (unsigned long long)b_bitmap_attacker, (unsigned long long)start,
	    (unsigned long long)bit_index, (unsigned long long)bit_index);
	printf("byte_index      = bit_index / NBBY      = %lu (0x%lx)\n",
	    byte_index, byte_index);
	printf("bp->b_data size = bsize                 = %u bytes\n", bsize);

	if (gate_buggy && byte_index >= bsize) {
		printf("\n*** BUG TRIGGERED: setbit(bp->b_data[+%lu], ...) writes %lu bytes"
		    "\n    PAST the %u-byte block-bitmap buffer => wild kernel heap write.\n",
		    byte_index, byte_index - bsize, bsize);
	} else if (!gate_buggy) {
		printf("\n(no trigger: buggy gate returned FALSE)\n");
		return 0;
	}

	/* --- guard-page demonstration: actually do the wild write --- */
	/* mmap one page then an unmapped guard page.  bsize (4096) == one page. */
	long pgsz = sysconf(_SC_PAGESIZE);
	char *base = mmap(NULL, pgsz * 2, PROT_READ | PROT_WRITE,
	    MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
	if (base == MAP_FAILED) { perror("mmap"); return 1; }
	/* unmap the second page so any write >= pgsz faults */
	if (munmap(base + pgsz, pgsz) != 0) { perror("munmap"); return 1; }
	char *bp_data = base;                 /* stand-in for bp->b_data */

	struct sigaction sa;
	sa.sa_sigaction = segv_handler;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = SA_SIGINFO;
	sigaction(SIGSEGV, &sa, NULL);
	sigaction(SIGBUS, &sa, NULL);

	printf("\n--- demonstrating the wild write into a guard-paged buffer ---\n");
	printf("bp->b_data stand-in = %p (%ld bytes valid, guard page at %p)\n",
	    bp_data, pgsz, bp_data + pgsz);

	faulted = 0;
	if (sigsetjmp(jb, 1) == 0) {
		/* the verbatim setbit, exactly as the kernel would do it */
		bp_data[byte_index] |= (1u << (bit_index % NBBY));
		printf("WTF: wild write did NOT fault — harness logic error\n");
		return 2;
	}
	printf("RESULT: write to bp->b_data[+%lu] (addr %p) FAULTED (SIGSEGV/SIGBUS)\n",
	    byte_index, fault_addr);
	printf("        => the kernel write is %lu bytes beyond the %u-byte buffer.\n",
	    byte_index - bsize, bsize);
	printf("        => confirmed out-of-bounds kernel heap write primitive.\n");

	munmap(base, pgsz);
	return 0;
}
