/*
 * DF-0825 harness — kernel module that calls nfs_getnickauth() directly
 * to prove the post-increment bug at nfs_syscalls.c:1115-1119.
 *
 * Bug: *nickp++ advances nickp to P+4, then *auth_str=(char*)nickp
 * returns P+4 instead of the allocation base P.
 * Consequences:
 *   - nfsm_rpchead bcopy(auth_len=8 from P+4) reads 4 bytes OOB heap
 *   - nfs_request kfree(auth_str==P+4) frees non-base pointer
 *
 * This module sets up a minimal fake nfsmount + nickname entry and
 * calls the REAL nfs_getnickauth (kernel symbol 0xffffffff80809570),
 * then inspects where auth_str actually points.
 */

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/proc.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/mbuf.h>
#include <sys/mount.h>
#include <sys/vnode.h>

#include <vfs/nfs/xdr_subs.h>
#include <vfs/nfs/rpcv2.h>
#include <vfs/nfs/nfsproto.h>
#include <vfs/nfs/nfs.h>
#include <vfs/nfs/nfsm_subs.h>
#include <vfs/nfs/nfsmount.h>

static char verfbuf[RPCX_NICKVERF + 16];

static int
df0825_harness_modevent(module_t mod, int type, void *data)
{
	struct nfsmount *nmp;
	struct nfsuid *nuidp;
	struct ucred *cred;
	char *auth_str;
	int auth_len;
	int error;
	u_int32_t *p;
	u_int32_t test_nickname;
	int i;

	switch (type) {
	case MOD_LOAD:
		break;
	case MOD_UNLOAD:
		return 0;
	default:
		return EOPNOTSUPP;
	}

	kprintf("DF-0825: harness start\n");

	/* Allocate and zero a fake nfsmount — big enough for the struct. */
	nmp = kmalloc(sizeof(struct nfsmount), M_TEMP, M_WAITOK | M_ZERO);
	if (nmp == NULL) {
		kprintf("DF-0825: FAIL kmalloc nfsmount\n");
		return ENOMEM;
	}
	TAILQ_INIT(&nmp->nm_uidlruhead);

	/* Create a nickname entry for uid 1001. */
	nuidp = kmalloc(sizeof(struct nfsuid), M_TEMP, M_WAITOK | M_ZERO);
	if (nuidp == NULL) {
		kfree(nmp, M_TEMP);
		kprintf("DF-0825: FAIL kmalloc nfsuid\n");
		return ENOMEM;
	}
	test_nickname = 0xDEADBEEF;
	nuidp->nu_cr.cr_uid = 1001;
	nuidp->nu_nickname = test_nickname;
	nuidp->nu_expire = time_uptime + 3600; /* not expired */

	/* Insert into the uid hash bucket for uid 1001. */
	LIST_INSERT_HEAD(NMUIDHASH(nmp, 1001), nuidp, nu_hash);
	TAILQ_INSERT_TAIL(&nmp->nm_uidlruhead, nuidp, nu_lru);

	/* Build a credential for uid 1001. */
	cred = crget();
	cred->cr_uid = 1001;

	/* Prepare verifier buffer. */
	memset(verfbuf, 0xAA, sizeof(verfbuf));

	kprintf("DF-0825: calling nfs_getnickauth(nmp, cred uid=%d, ...)\n",
		cred->cr_uid);
	kprintf("DF-0825: nickname planted = 0x%08x\n", test_nickname);
	kprintf("DF-0825: RPCAKN_NICKNAME = %d, txdr = 0x%08x\n",
		RPCAKN_NICKNAME, txdr_unsigned(RPCAKN_NICKNAME));

	auth_str = NULL;
	auth_len = 0;
	error = nfs_getnickauth(nmp, cred, &auth_str, &auth_len,
				verfbuf, RPCX_NICKVERF);

	kprintf("DF-0825: nfs_getnickauth returned error=%d\n", error);
	kprintf("DF-0825: auth_str = %p, auth_len = %d\n",
		(void *)auth_str, auth_len);

	if (error != 0 || auth_str == NULL) {
		kprintf("DF-0825: FAIL — nfs_getnickauth did not succeed\n");
		goto cleanup;
	}

	/*
	 * The CORRECT result: auth_str[0..3] = txdr_unsigned(RPCAKN_NICKNAME)
	 *                     auth_str[4..7] = txdr_unsigned(nickname)
	 * The BUGGY result:   auth_str[0..3] = txdr_unsigned(nickname)
	 *                     auth_str[4..7] = 4 bytes OOB past allocation
	 */
	p = (u_int32_t *)(void *)auth_str;
	kprintf("DF-0825: auth_str[0..3]  = 0x%08x\n", ntohl(p[0]));
	kprintf("DF-0825: auth_str[4..7]  = 0x%08x  (<= OOB heap if bug present)\n",
		ntohl(p[1]));
	kprintf("DF-0825: expected[0..3]  = 0x%08x (RPCAKN_NICKNAME)\n",
		RPCAKN_NICKNAME);
	kprintf("DF-0825: expected[4..7]  = 0x%08x (nickname)\n",
		test_nickname);

	/* Dump raw 16 bytes at auth_str for visual inspection. */
	kprintf("DF-0825: raw bytes at auth_str:");
	{
		unsigned char *c = (unsigned char *)auth_str;
		for (i = 0; i < 16; i++) {
			if ((i % 4) == 0) kprintf(" ");
			kprintf("%02x", c[i]);
		}
	}
	kprintf("\n");

	if (ntohl(p[0]) == test_nickname) {
		kprintf("DF-0825: *** BUG CONFIRMED ***\n");
		kprintf("DF-0825:   auth_str[0] = nickname 0x%08x, NOT RPCAKN_NICKNAME\n",
			test_nickname);
		kprintf("DF-0825:   => auth_str points 4 bytes past allocation base\n");
		kprintf("DF-0825:   => auth_str[4..7] = 0x%08x is OOB heap read\n",
			ntohl(p[1]));
		kprintf("DF-0825:   => caller kfree(auth_str) frees non-base ptr => slab corruption\n");
	} else if (ntohl(p[0]) == RPCAKN_NICKNAME) {
		kprintf("DF-0825: OK — auth_str points at allocation base (bug fixed)\n");
	} else {
		kprintf("DF-0825: UNEXPECTED auth_str[0] = 0x%08x\n", ntohl(p[0]));
	}

	/*
	 * Demonstrate the kfree-of-non-base-pointer consequence.
	 * Under INVARIANTS (GENERIC default), kfree(P+4) trips the slab
	 * allocator's chunk-alignment assertion and panics.
	 * We do this LAST so all diagnostic output is captured first.
	 */
	kprintf("DF-0825: now calling kfree(auth_str) to demonstrate slab corruption...\n");
	kfree(auth_str, M_TEMP);
	kprintf("DF-0825: kfree returned (no panic => INVARIANTS may be off or slab was lucky)\n");

cleanup:
	crfree(cred);
	LIST_REMOVE(nuidp, nu_hash);
	TAILQ_REMOVE(&nmp->nm_uidlruhead, nuidp, nu_lru);
	kfree(nuidp, M_TEMP);
	kfree(nmp, M_TEMP);
	kprintf("DF-0825: harness done\n");
	return 0;
}

static moduledata_t df0825_mod = {
	"df0825_harness",
	df0825_harness_modevent,
	NULL
};

DECLARE_MODULE(df0825_harness, df0825_mod, SI_SUB_PROTO_END, SI_ORDER_ANY);
MODULE_DEPEND(df0825_harness, nfs, 1, 1, 1);
