/*
 * DF pass-2 kern_sysctl.c adversarial prober (unprivileged).
 * Exercises: name2oid edges, exact-path oidfmt/oiddescr with sprayed stale
 * stack, oldlenp/oldlen truncation games on multi-SYSCTL_OUT nodes,
 * write gating. Success criterion: no panic, sane errno/length semantics.
 */
#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/param.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>

static int q(const char *name, void *old, size_t *oldlen)
{
	return sysctlbyname(name, old, oldlen, NULL, 0);
}

static void raw(int *name, u_int namelen, void *old, size_t *oldlen,
		void *new, size_t newlen)
{
	int r = sysctl(name, namelen, old, oldlen, new, newlen);
	printf("raw([%d,...],%u) = %d errno=%d (%s) len=%zu\n",
	       name[0], namelen, r, r < 0 ? errno : 0,
	       r < 0 ? strerror(errno) : "ok", oldlen ? *oldlen : 0);
}

int main(void)
{
	char buf[2048];
	size_t len;
	int r, i;

	/* ---- 1. name2oid edge cases ({0,3}) ---- */
	{
		int nm[2] = {0, 3};
		int oid[CTL_MAXNAME];
		size_t oidlen;
		char big[MAXPATHLEN];		/* 1024 */

		/* newlen = 0 */
		oidlen = sizeof(oid);
		r = sysctl(nm, 2, oid, &oidlen, (void *)"", 0);
		printf("name2oid newlen=0        -> %d errno=%d\n", r, errno);

		/* newlen = 1, no NUL needed (len==1 exact) */
		oidlen = sizeof(oid);
		r = sysctl(nm, 2, oid, &oidlen, (void *)"k", 1);
		printf("name2oid newlen=1        -> %d errno=%d\n", r, errno);

		/* newlen = MAXPATHLEN (rejected: ENAMETOOLONG) */
		oidlen = sizeof(oid);
		memset(big, 'x', sizeof(big));
		r = sysctl(nm, 2, oid, &oidlen, big, sizeof(big));
		printf("name2oid newlen=1024     -> %d errno=%d (%s)\n", r, errno,
		       strerror(errno));

		/* newlen = MAXPATHLEN-1, no NUL anywhere */
		oidlen = sizeof(oid);
		r = sysctl(nm, 2, oid, &oidlen, big, sizeof(big) - 1);
		printf("name2oid newlen=1023     -> %d errno=%d (%s)\n", r, errno,
		       strerror(errno));

		/* all dots */
		oidlen = sizeof(oid);
		r = sysctl(nm, 2, oid, &oidlen, (void *)"....", 4);
		printf("name2oid dots            -> %d errno=%d\n", r, errno);

		/* valid + trailing junk past 12 components */
		oidlen = sizeof(oid);
		r = sysctl(nm, 2, oid, &oidlen,
			   (void *)"kern.ipc.shm.info.x.y.z.w", 25);
		printf("name2oid deep            -> %d errno=%d\n", r, errno);
	}

	/* ---- 2. exact-path oidfmt/oiddescr w/ sprayed stale stack ---- */
	for (i = 0; i < 64; i++) {
		/* seed kernel stack frame of __sysctl with a 12-int name */
		int nm[CTL_MAXNAME] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
		size_t pl = 0;
		sysctl(nm, 12, NULL, &pl, NULL, 0);	/* ENOENT expected */

		/* now {0,4} namelen=2: handler re-reads uninitialized name[2] */
		{
			int fm[2] = {0, 4};
			unsigned char ob[64];
			size_t ol = sizeof(ob);
			sysctl(fm, 2, ob, &ol, NULL, 0);
		}
		{
			int ds[2] = {0, 5};
			unsigned char ob[256];
			size_t ol = sizeof(ob);
			sysctl(ds, 2, ob, &ol, NULL, 0);
		}
	}
	printf("oidfmt/oiddescr exact-path stale-stack x64: no panic\n");

	/* ---- 3. truncation games on multi-OUT nodes ---- */
	/* sysctl.name of a real oid = several separate SYSCTL_OUT calls */
	{
		int nm[4] = {0, 1, 1, 99};	/* name of kern.??? unknown */
		size_t pl = 0;
		r = sysctl(nm, 4, NULL, &pl, NULL, 0);
		printf("sysctl.name query len    -> %d errno=%d pl=%zu\n", r,
		       errno, pl);
	}
	{
		int nm[4] = {0, 1, 1, 28};	/* name of kern.bootfile? guess */
		char ob[8];
		size_t ol;
		for (ol = 0; ol <= 8; ol++) {
			size_t l2 = ol;
			memset(ob, 0xAA, sizeof(ob));
			r = sysctl(nm, 4, ob, &l2, NULL, 0);
			printf("sysctl.name oldlen=%zu    -> r=%d errno=%d "
			       "retlen=%zu ob[0..3]=%02x%02x%02x%02x\n",
			       ol, r, errno, l2, ob[0], ob[1], ob[2], ob[3]);
		}
	}
	/* kern.bootfile string with tiny oldlen */
	for (len = 0; len <= 6; len++) {
		size_t l2 = len;
		memset(buf, 0x5A, 16);
		r = q("kern.bootfile", buf, &l2);
		printf("kern.bootfile oldlen=%zu  -> r=%d errno=%d retlen=%zu "
		       "buf0=%02x\n", len, r, errno, l2,
		       (unsigned char)buf[0]);
	}
	/* opaque struct node: kern.clockrate with oldlen=1 */
	{
		size_t l2 = 1;
		memset(buf, 0x5A, sizeof(buf));
		r = q("kern.clockrate", buf, &l2);
		printf("kern.clockrate oldlen=1  -> r=%d errno=%d retlen=%zu\n",
		       r, errno, l2);
	}

	/* ---- 4. write gating ---- */
	{
		int nv = 1;
		r = sysctlbyname("debug.sysctl", NULL, NULL, &nv, sizeof(nv));
		printf("write debug.sysctl       -> %d errno=%d (%s)\n", r, errno,
		       strerror(errno));
		r = sysctlbyname("kern.hostname", NULL, NULL, (void *)"x", 1);
		printf("write kern.hostname      -> %d errno=%d (%s)\n", r, errno,
		       strerror(errno));
	}

	/* ---- 5. adversarial next-walk namelen ---- */
	{
		int nm[12] = {0, 2, 0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,
			      0x7fffffff, 0x7fffffff, 0x7fffffff, 0x7fffffff,
			      0x7fffffff, 0x7fffffff};
		int ob[16];
		size_t ol = sizeof(ob);
		raw(nm, 12, ob, &ol, NULL, 0);
		nm[2] = -1; nm[3] = -1;
		ol = sizeof(ob);
		raw(nm, 12, ob, &ol, NULL, 0);
	}

	printf("PROBER-COMPLETE\n");
	return 0;
}
