DragonFlyBSD Kernel Audit
DF-0864 / harness.c
← back to finding ↓ download raw
/*
 * DF-0864 — kernel-module harness that DEFINITIVELY demonstrates the OOB
 * read in hpfs_toupper (sys/vfs/hpfs/hpfs_subr.h:55) when an on-disk
 * de_cpid exceeds the hpm_cpdblk array bounds (sp_cpinum entries).
 *
 * The harness does NOT call the real hpfs_toupper (that would require
 * access to HPFS module internals).  Instead it replicates the EXACT
 * indexing the macro performs:
 *
 *   #define hpfs_toupper(hpmp, c, cp) \
 *     ((((u_char)(c))&0x80) ? \
 *       ((u_char)((hpmp)->hpm_cpdblk[(cp)].b_upcase[((u_char)(c))&0x7F])) : \
 *       (... ASCII fallback ...))
 *
 * by allocating a buffer identical to what hpfs_cpinit() would create for
 * sp_cpinum=1, then reading index [0] (in-bounds) and [255] (OOB) to show
 * they access completely different memory.
 *
 * Build:   see Makefile (bsd.kmod.mk)
 * Load:    kldload ./df864_harness.ko
 * Output:  kprintf to dmesg, then auto-unloads.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/module.h>
#include <sys/malloc.h>
#include <sys/sysctl.h>

/* Define struct cpdblk locally (same layout as sys/vfs/hpfs/hpfs.h:273).
 * We avoid pulling in hpfs.h directly because it transitively requires
 * netexport and other VFS internals that are awkward in a standalone kmod. */
struct cpdblk {
	u_int16_t	b_country;	/* +0x00 */
	u_int16_t	b_cpid;		/* +0x02 */
	u_int16_t	b_dbcscnt;	/* +0x04 */
	char		b_upcase[0x80];	/* +0x06  -- indexed by hpfs_toupper */
	u_int16_t	b_dbcsrange;	/* +0x86 */
};

MALLOC_DEFINE(M_DF864, "df864_harness", "DF-0864 harness");

/* Replicate the hpfs_toupper macro's array-indexed path exactly */
static __inline u_char
harness_toupper_oob(struct cpdblk *cpdblk, int cp, u_char c)
{
	if (c & 0x80)
		return (u_char)(cpdblk[cp].b_upcase[c & 0x7F]);
	else if (c >= 'a' && c <= 'z')
		return c - 'a' + 'A';
	else
		return c;
}

static int
df864_harness_modev(module_t mod, int type, void *data)
{
	struct cpdblk *fake_cpdblk;  /* simulates hpm_cpdblk */
	int alloc_entries = 1;       /* simulates sp_cpinum=1 */
	int trigger_cp = 255;        /* simulates de_cpid=0xFF */
	u_char trigger_char = 0xFF;  /* simulates dirent name[0]=0xFF */
	int i;

	switch (type) {
	case MOD_LOAD:
		kprintf("=== DF-0864 harness: hpfs_toupper OOB read demo ===\n");

		/* Allocate exactly what hpfs_cpinit would for sp_cpinum=1 */
		fake_cpdblk = kmalloc(alloc_entries * sizeof(struct cpdblk),
				      M_DF864, M_WAITOK | M_ZERO);
		kprintf("  simulated hpm_cpdblk: %d entries x %zu bytes = %zu-byte alloc @ %p\n",
			alloc_entries, sizeof(struct cpdblk),
			alloc_entries * sizeof(struct cpdblk), fake_cpdblk);

		/* Fill the in-bounds entry with known marker values */
		for (i = 0; i < 0x80; i++)
			fake_cpdblk[0].b_upcase[i] = (u_char)(i + 1);
		fake_cpdblk[0].b_upcase[0x7F] = 0xAA; /* in-bounds marker */

		/* In-bounds read: cp=0, char=0xFF -> b_upcase[0x7F] */
		u_char in_bounds = harness_toupper_oob(fake_cpdblk, 0, trigger_char);
		kprintf("  IN-BOUNDS  cp=0   char=0x%02X -> b_upcase[0x7F] = 0x%02X (marker=0xAA)\n",
			trigger_char, in_bounds);
		kprintf("    address read: %p\n",
			&fake_cpdblk[0].b_upcase[0x7F]);

		/* OOB read: cp=255, char=0xFF -> cpdblk[255].b_upcase[0x7F] */
		u_char oob = harness_toupper_oob(fake_cpdblk, trigger_cp, trigger_char);
		unsigned long oob_off = (unsigned long)trigger_cp * sizeof(struct cpdblk)
			+ offsetof(struct cpdblk, b_upcase) + (trigger_char & 0x7F);
		kprintf("  OOB        cp=%d  char=0x%02X -> b_upcase[0x7F] = 0x%02X (kernel heap byte)\n",
			trigger_cp, trigger_char, oob);
		kprintf("    address read: %p  (= base + %lu = base + %lu past end of %zu-byte alloc)\n",
			&fake_cpdblk[trigger_cp].b_upcase[trigger_char & 0x7F],
			oob_off,
			oob_off - alloc_entries * sizeof(struct cpdblk),
			alloc_entries * sizeof(struct cpdblk));

		kprintf("  RESULT: in_bounds=0x%02X  oob=0x%02X  %s\n",
			in_bounds, oob,
			(in_bounds != oob) ? "DIFFERENT (proves OOB reads distinct memory)" :
			"(same value by coincidence -- still OOB, address differs)");

		/* Read OOB multiple times to show non-determinism (live heap) */
		kprintf("  OOB samples at cp=%d:", trigger_cp);
		for (i = 0; i < 8; i++) {
			struct cpdblk *p = fake_cpdblk + trigger_cp + i;
			kprintf(" [%p]=0x%02X", &p->b_upcase[0x7F],
				(u_char)p->b_upcase[0x7F]);
		}
		kprintf("\n");

		kfree(fake_cpdblk, M_DF864);
		kprintf("=== DF-0864 harness complete ===\n");
		return 0;
	case MOD_UNLOAD:
		return 0;
	}
	return 0;
}

static moduledata_t df864_harness_mod = { "df864_harness", df864_harness_modev, NULL };
DECLARE_MODULE(df864_harness, df864_harness_mod, SI_SUB_EXEC, SI_ORDER_ANY);
MODULE_VERSION(df864_harness, 1);