/*
 * DF-5801 KLD rig: vm_page_bits() / vm_page_set_valid*() have NO bounds
 * validation in production kernels (the only guard is a KASSERT, which is
 * compiled out unless INVARIANTS is set - sys/sys/systm.h:118).
 *
 * This rig demonstrates, against the running production kernel:
 *   (1) vm_page_bits() accepts base/size outside the page and returns
 *       masks with bits >= 8 set (and UB shifts for negative/huge args);
 *   (2) vm_page_set_valid() fed an out-of-page (base,size) silently marks
 *       the WHOLE page valid in its uint8_t m->valid, over-validating
 *       fragments that were never populated - the stale-data / info-leak
 *       direction if any caller ever miscalculates the range;
 *   (3) the phantom bit-8 result is visible to int-sized consumers
 *       (e.g. the m->dirty & vm_page_bits(...) test in vfs_bio.c).
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/module.h>
#include <vm/vm.h>
#include <vm/vm_page.h>
#include <vm/vm_page2.h>
#include <vm/vm_extern.h>
#include <machine/pmap.h>

static int
rig_loader(struct module *m, int what, void *arg)
{
	vm_page_t pg;
	char *p;
	int r_in, r_oob_size, r_oob_base, r_ub;

	switch (what) {
	case MOD_LOAD:
		/* ---- (1) mask function: no production bounds check ---- */
		r_in       = vm_page_bits(0, PAGE_SIZE);		/* legal: 0xff   */
		r_oob_size = vm_page_bits(0, PAGE_SIZE * 2);	/* size > page   */
		r_oob_base = vm_page_bits(PAGE_SIZE, 1);		/* base > page   */
		kprintf("RIG bits(0,%d)=0x%x (in-page, legal)\n",
			PAGE_SIZE, r_in);
		kprintf("RIG bits(0,%d)=0x%x (size beyond page, unchecked)\n",
			PAGE_SIZE * 2, r_oob_size);
		kprintf("RIG bits(%d,1)=0x%x (base beyond page -> phantom bit 8,"
			" visible to int consumers)\n", PAGE_SIZE, r_oob_base);
		r_ub = vm_page_bits(-512, 1024);			/* UB: 1 << -1  */
		kprintf("RIG bits(-512,1024)=0x%x (negative base/size ->"
			" shift UB, unguarded)\n", (unsigned)r_ub);

		/* ---- (2) setter over-validation on a real vm_page ---- */
		pg = vm_page_alloczwq(0, VM_ALLOC_NORMAL |
					 VM_ALLOC_SYSTEM | VM_ALLOC_ZERO);
		if (pg == NULL) {
			kprintf("RIG alloczwq failed\n");
			return (0);
		}
		p = (char *)PHYS_TO_DMAP(VM_PAGE_TO_PHYS(pg));
		memset(p, 0x41, PAGE_SIZE);	/* simulate stale kernel data */
		pg->valid = 0x0f;		/* only first 4 fragments valid */

		vm_page_busy_wait(pg, FALSE, "rigb");
		/* caller asks to validate 4.5KB starting at 0 (range crosses
		 * the page end).  bits(0, PAGE_SIZE+512) = (2<<8)-1 = 0x1ff,
		 * m->valid |= 0x1ff -> uint8_t truncates -> whole page (0xff)
		 * marked valid, including the never-read last fragment. */
		vm_page_set_valid(pg, 0, PAGE_SIZE + 512);
		kprintf("RIG set_valid(0,%d) on page w/ valid=0x0f ->"
			" m->valid=0x%02x (whole page 'valid', stale 0x41 tail"
			" now indistinguishable from validated data)\n",
			PAGE_SIZE + 512, pg->valid);
		kprintf("RIG tail fragment content still 0x41 (never zeroed:"
			" _vm_page_zero_valid only touches DEV_BSIZE"
			" boundaries)\n");
		vm_page_wakeup(pg);
		vm_page_freezwq(pg);
		kprintf("RIG done\n");
		return (0);
	default:
		return (0);
	}
}

static moduledata_t rig_mod = { "pqb_rig", rig_loader, NULL };
DECLARE_MODULE(pqb_rig, rig_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
MODULE_VERSION(pqb_rig, 1);
