/*
 * DF-0877 — ext2_dx_csum OOB heap read: deterministic harness.
 *
 * Transcribes ext2_dx_csum (sys/vfs/ext2fs/ext2_csum.c:240-266) and
 * ext2_dx_csum_verify (ext2_csum.c:268-293) verbatim, in particular:
 *
 *   ext2_csum.c:280-281  limit/count read from disk (attacker-controlled)
 *   ext2_csum.c:282-284  limit*8 validated against bsize-tail  -- BUT count is NOT
 *   ext2_csum.c:287      ext2_dx_csum(ip, ep, count_offset, count, tp)
 *   ext2_csum.c:253      size = count_offset + count * sizeof(htree_entry)
 *   ext2_csum.c:261      crc = calculate_crc32c(crc, buf, size)  <-- OOB read
 *
 * With count=65535, sizeof(htree_entry)=8:
 *   size = 32 + 65535*8 = 524312 bytes
 * The buf is a single directory block (bsize=1024), so the read walks
 * 523288 bytes (~511 KB) past the buffer into adjacent kernel heap.
 *
 * This harness mirrors that read against a poison-padded buffer placed at
 * a page boundary with a PROT_NONE guard page, proving:
 *   - the read length when count=65535 is exactly 524312 bytes,
 *   - the read goes 523288 bytes past the 1024-byte buffer,
 *   - on a "production" mapping (where pages past the buffer are mapped),
 *     this leaks whatever bytes are adjacent in memory.
 *
 * Build: cc -O2 -o harness harness.c
 * Run  : ./harness
 */
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/endian.h>

/* On-disk htree structures (sys/vfs/ext2fs/htree.h) */
struct ext2fs_htree_count {
	uint16_t h_entries_max;   /* limit */
	uint16_t h_entries_num;   /* count */
};
struct ext2fs_htree_entry {
	uint32_t h_hash;
	uint32_t h_blk;
};
struct ext2fs_htree_tail {
	uint32_t ht_reserved;
	uint32_t ht_checksum;
};

#define HTREE_ENTRY_SIZE sizeof(struct ext2fs_htree_entry)   /* 8 */
#define HTREE_TAIL_SIZE  sizeof(struct ext2fs_htree_tail)    /* 8 */

/* ---- CRC32C (Castagnoli), non-inverting, identical to libkern/icrc32.c ---- */
static uint32_t crc32c_table[256];
static void crc32c_init(void) {
	uint32_t i, j, crc;
	const uint32_t POLY = 0x82F63B78; /* reflected */
	for (i = 0; i < 256; i++) {
		crc = i;
		for (j = 0; j < 8; j++)
			crc = (crc >> 1) ^ (POLY & (-(int32_t)(crc & 1)));
		crc32c_table[i] = crc;
	}
}
static uint32_t calculate_crc32c(uint32_t crc, const uint8_t *buf, size_t len) {
	while (len--)
		crc = (crc >> 8) ^ crc32c_table[(crc ^ *buf++) & 0xff];
	return crc;
}

/* SIGSEGV handler so the OOB read becomes a clean diagnostic. */
static volatile sig_atomic_t g_faulted = 0;
static void *g_fault_addr = NULL;
static void sig_segv(int sig, siginfo_t *si, void *uc) {
	(void)sig; (void)uc;
	g_fault_addr = si->si_addr;
	g_faulted = 1;
	char buf[128];
	size_t n = 0;
	const char msg[] = "\n[!] SIGSEGV during dx_csum OOB read at addr ";
	memcpy(buf, msg, sizeof(msg)-1); n += sizeof(msg)-1;
	unsigned long addr = (unsigned long)(uintptr_t)si->si_addr;
	const char hex[] = "0123456789abcdef";
	buf[n++] = '0'; buf[n++] = 'x';
	for (int shift = 60; shift >= 0; shift -= 4)
		buf[n++] = hex[(addr >> shift) & 0xf];
	buf[n++] = '\n';
	write(2, buf, n);
	_exit(133);
}

/*
 * Verbatim transcription of ext2_dx_csum (ext2_csum.c:240-266).
 * The only difference: we compute size but cap the actual read for the
 * "safe" variant. The "faulting" variant does the full uncapped read.
 */
static uint32_t
ext2_dx_csum_safe(int count_offset, int count,
    const uint8_t *buf, size_t buf_len,
    const struct ext2fs_htree_tail *tp,
    uint32_t csum_seed, uint32_t inum, uint32_t gen,
    size_t *out_size, size_t *out_oob)
{
	uint32_t crc;
	int size;

	/* ext2_csum.c:253 — THE BUG: unvalidated count drives the read length */
	size = count_offset + (count * (int)HTREE_ENTRY_SIZE);
	*out_size = (size_t)size;
	*out_oob = (size > (int)buf_len) ? (size_t)size - buf_len : 0;

	/* For the safe variant, cap the read to buf_len so we don't SIGSEGV. */
	size_t safe = (size > (int)buf_len) ? buf_len : (size_t)size;

	inum = htole32(inum);
	gen  = htole32(gen);
	crc = calculate_crc32c(csum_seed, (const uint8_t *)&inum, sizeof(inum));
	crc = calculate_crc32c(crc, (const uint8_t *)&gen, sizeof(gen));
	crc = calculate_crc32c(crc, buf, safe);
	crc = calculate_crc32c(crc, (const uint8_t *)tp, sizeof(*tp));
	return crc;
}

/* Faulting variant: does the FULL ext2_csum.c:261 read (will SIGSEGV past guard). */
static uint32_t
ext2_dx_csum_faulting(int count_offset, int count,
    const uint8_t *buf,
    const struct ext2fs_htree_tail *tp,
    uint32_t csum_seed, uint32_t inum, uint32_t gen)
{
	uint32_t crc;
	int size;

	size = count_offset + (count * (int)HTREE_ENTRY_SIZE);   /* ext2_csum.c:253 */

	inum = htole32(inum);
	gen  = htole32(gen);
	crc = calculate_crc32c(csum_seed, (const uint8_t *)&inum, sizeof(inum));
	crc = calculate_crc32c(crc, (const uint8_t *)&gen, sizeof(gen));
	crc = calculate_crc32c(crc, buf, size);   /* ext2_csum.c:261 — THE OOB READ */
	crc = calculate_crc32c(crc, (const uint8_t *)tp, sizeof(*tp));
	return crc;
}

/*
 * Verbatim transcription of ext2_dx_csum_verify (ext2_csum.c:268-293)
 * to show limit is validated but count is not.
 */
static int
ext2_dx_csum_verify_harness(int bsize, int count_offset,
    struct ext2fs_htree_count *cp,
    const uint8_t *buf, size_t buf_len,
    uint32_t csum_seed, uint32_t inum, uint32_t gen,
    size_t *out_size, size_t *out_oob)
{
	struct ext2fs_htree_tail *tp;
	int limit, count;

	/* ext2_csum.c:280-281 */
	limit = le16toh(cp->h_entries_max);
	count = le16toh(cp->h_entries_num);

	/* ext2_csum.c:282-284 — limit IS validated */
	if (count_offset + (limit * (int)HTREE_ENTRY_SIZE) >
	    bsize - (int)HTREE_TAIL_SIZE) {
		printf("    [limit check FAILED — would return EIO]\n");
		return (-1);
	}
	/* ext2_csum.c:286 */
	tp = (struct ext2fs_htree_tail *)(((struct ext2fs_htree_entry *)cp) + limit);

	/* ext2_csum.c:287 -> ext2_dx_csum with UNVALIDATED count */
	ext2_dx_csum_safe(count_offset, count, buf, buf_len, tp,
	    csum_seed, inum, gen, out_size, out_oob);
	return (0);
}

int main(void)
{
	setvbuf(stdout, NULL, _IONBF, 0);
	setvbuf(stderr, NULL, _IONBF, 0);

	struct sigaction sa;
	memset(&sa, 0, sizeof(sa));
	sa.sa_sigaction = sig_segv;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = SA_SIGINFO;
	sigaction(SIGSEGV, &sa, NULL);

	crc32c_init();

	const int BSIZE = 1024;          /* directory block size */
	const int COUNT_OFFSET = 32;     /* htree root: after dot/dotdot/root_info */

	printf("[*] sizeof(struct ext2fs_htree_entry) = %zu\n", HTREE_ENTRY_SIZE);
	printf("[*] sizeof(struct ext2fs_htree_tail)  = %zu\n", HTREE_TAIL_SIZE);
	printf("[*] bsize (directory block)           = %d\n", BSIZE);
	printf("[*] count_offset (htree root)         = %d\n", COUNT_OFFSET);

	/*
	 * Simulate the buffer-cache layout: a 1024-byte directory block placed
	 * at the end of a page, with the next page PROT_NONE (mirroring the
	 * kernel where the OOB read eventually hits an unmapped page).
	 */
	const size_t PAGE = 4096;
	uint8_t *base = mmap(NULL, 3 * PAGE, PROT_READ | PROT_WRITE,
	    MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
	if (base == MAP_FAILED) { perror("mmap"); return 1; }
	if (mprotect(base + PAGE, PAGE, PROT_NONE) != 0) { perror("mprotect"); return 1; }

	/* Place the dir block at the END of the first page so buf+1024 is the
	 * start of page 2... no: place it at the start of page 2 so that the
	 * PROT_NONE guard starts right at buf + PAGE. Actually we want the
	 * block to have some room before the guard so the csum read into the
	 * guard region faults predictably. Place block at base+PAGE-BSIZE. */
	uint8_t *buf = base + PAGE - BSIZE;
	memset(buf, 0, BSIZE);
	/* Mark the legitimate block content. */
	buf[0] = 0xAA;

	/* count header at buf+32 */
	struct ext2fs_htree_count *cp = (struct ext2fs_htree_count *)(buf + COUNT_OFFSET);

	/* tail at cp + limit*8. For limit=1: buf+40 */
	int limit = 1;
	struct ext2fs_htree_tail *tp =
	    (struct ext2fs_htree_tail *)((uint8_t *)cp + limit * HTREE_ENTRY_SIZE);
	tp->ht_checksum = 0;

	/* -- CASE A: legitimate count=1, limit=1 -- no OOB -- */
	{
		printf("\n[A] count=1 limit=1 (legitimate):\n");
		cp->h_entries_max = 1;
		cp->h_entries_num = 1;
		size_t sz=0, oob=0;
		int rc = ext2_dx_csum_verify_harness(BSIZE, COUNT_OFFSET, cp,
		    buf, BSIZE, 0xCAFEF00D, 12, 0x1234, &sz, &oob);
		if (rc == 0) {
			printf("    size=%zu  oob=%zu  (expected size=40, oob=0)\n", sz, oob);
			if (sz != 40 || oob != 0) { printf("    !! UNEXPECTED\n"); return 1; }
		}
	}

	/* -- CASE B: attacker count=65535, limit=1 -- OOB -- */
	{
		printf("\n[B] count=65535 limit=1 (attacker-controlled):\n");
		cp->h_entries_max = 1;     /* passes limit check: 32+8=40 <= 1024-8=1016 */
		cp->h_entries_num = 65535; /* POISON — NOT validated */
		size_t sz=0, oob=0;
		int rc = ext2_dx_csum_verify_harness(BSIZE, COUNT_OFFSET, cp,
		    buf, BSIZE, 0xCAFEF00D, 12, 0x1234, &sz, &oob);
		if (rc == 0) {
			printf("    limit check PASSED (limit=1: 32+8=40 <= 1016)\n");
			printf("    ext2_dx_csum size = %zu bytes\n", sz);
			printf("    buffer size       = %d bytes\n", BSIZE);
			printf("    OOB read          = %zu bytes (%zu KB) past the buffer\n",
			    oob, oob / 1024);
			if (sz != 524312) {
				printf("    !! UNEXPECTED size %zu (expected 524312)\n", sz);
				return 1;
			}
			if (oob != 523288) {
				printf("    !! UNEXPECTED oob %zu (expected 523288)\n", oob);
				return 1;
			}
		}
	}

	/* -- CASE C: invoke the faulting variant — full ext2_csum.c:261 read -- */
	{
		printf("\n[C] Invoking ext2_dx_csum_faulting (full ext2_csum.c:261 read):\n");
		cp->h_entries_max = 1;
		cp->h_entries_num = 65535;
		printf("    buf at %p (end of page 1); PROT_NONE guard at %p\n",
		    (void *)buf, (void *)(base + PAGE));
		printf("    Expecting SIGSEGV near %p (start of guard page).\n",
		    (void *)(base + PAGE));
		fflush(stdout);
		volatile uint32_t crc = ext2_dx_csum_faulting(COUNT_OFFSET, 65535,
		    buf, tp, 0xCAFEF00D, 12, 0x1234);
		(void)crc;
		printf("\n[!] Harness did NOT fault -- PROT_NONE guard ineffective.\n");
	}

	printf("\n=== DF-0877 VERDICT ===\n");
	printf("ext2_csum.c:282-284 validates limit*8 <= bsize-tail but NEVER\n");
	printf("checks count <= limit. ext2_csum.c:253 computes\n");
	printf("    size = count_offset + count * sizeof(htree_entry)\n");
	printf("and ext2_csum.c:261 reads `size` bytes from the bsize-byte dir\n");
	printf("block buffer. With count=65535, size=524312, reading 523288 bytes\n");
	printf("(511 KB) past the 1024-byte buffer. In-kernel this is a heap\n");
	printf("over-read / info leak; on GENERIC it page-faults and panics.\n");

	munmap(base, 3 * PAGE);
	return 0;
}
