⬢ DragonFlyBSD Kernel Audit
DF-5721 / probe_dbregs.c
← back to finding ↓ download raw
/* DF-5721 probe 3: (a) PT_SETDBREGS with DR0 = kernel address must be
 * rejected (set_dbregs, machdep.c:3190-3211); (b) a legal user-space hardware
 * watchpoint whose buffer the kernel writes during a syscall (read(2) copyout)
 * must be swallowed by user_dbreg_trap() (trap.c:735-742) without entering
 * DDB or wedging. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <machine/reg.h>

#define WATCH_ADDR	0x10000000UL

int main(void)
{
	pid_t pid;
	int st, pfd[2];
	struct dbreg d;
	long r;

	pipe(pfd);
	pid = fork();
	if (pid == 0) {
		char *buf = mmap((void *)WATCH_ADDR, 4096, PROT_READ|PROT_WRITE,
				 MAP_FIXED|MAP_PRIVATE|MAP_ANON, -1, 0);
		if (buf != (char *)WATCH_ADDR)
			_exit(10);
		ptrace(PT_TRACE_ME, 0, (caddr_t)1, 0);
		kill(getpid(), SIGTRAP);	/* stop #1 */
		/* kernel writes 4 bytes to WATCH_ADDR via read() copyout;
		 * DR0 watchpoint is armed on it */
		read(pfd[0], buf, 4);
		int3_marker:	__asm__ __volatile__("int3");
		_exit(0);
	}
	write(pfd[1], "AAAA", 4);
	close(pfd[1]);

	wait(&st);
	printf("dbregs: initial stop sig=%d\n", WSTOPSIG(st));

	/* (a) kernel address must fail */
	memset(&d, 0, sizeof(d));
	d.dr[0] = 0xffffffff80100000UL;		/* kernel text */
	d.dr[7] = 0xD0001;			/* L0, RW=write, LEN=4 */
	errno = 0;
	r = ptrace(PT_SETDBREGS, pid, (caddr_t)&d, 0);
	printf("dbregs: SETDBREGS(kernel addr 0xffffffff80100000) = %ld errno=%d (%s)\n",
	       r, errno, r < 0 ? strerror(errno) : "ACCEPTED -- BAD");
	if (r == 0) {
		printf("dbregs: !!! KERNEL-ADDRESS WATCHPOINT ARMED -- filter test skipped\n");
		memset(&d, 0, sizeof(d));
	}

	/* (b) legal user address; kernel write via syscall must be swallowed */
	memset(&d, 0, sizeof(d));
	d.dr[0] = WATCH_ADDR;
	d.dr[7] = 0xD0001;
	errno = 0;
	r = ptrace(PT_SETDBREGS, pid, (caddr_t)&d, 0);
	printf("dbregs: SETDBREGS(user addr %p) = %ld errno=%d (%s)\n",
	       (void *)WATCH_ADDR, r, errno, r < 0 ? strerror(errno) : "ok");
	if (r < 0) {
		printf("dbregs: cannot arm user watchpoint; filter test inconclusive\n");
		kill(pid, SIGKILL);
		return 0;
	}
	/* let the child run read() -> kernel copyout hits DR0 */
	ptrace(PT_CONTINUE, pid, (caddr_t)1, 0);
	wait(&st);
	printf("dbregs: after syscall-write-to-watched-buffer: stopped=%d sig=%d%s\n",
	       WIFSTOPPED(st), WIFSTOPPED(st) ? WSTOPSIG(st) : 0,
	       WIFSTOPPED(st) && WSTOPSIG(st) == SIGTRAP ?
	       " (watchpoint delivered to user: also fine)" :
	       WIFSIGNALED(st) ? " (CHILD DIED -- BAD)" : "");
	ptrace(PT_CONTINUE, pid, (caddr_t)1, 0);
	wait(&st);
	printf("dbregs: final status=%d\n", st);
	/* disarm before exit */
	memset(&d, 0, sizeof(d));
	ptrace(PT_SETDBREGS, pid, (caddr_t)&d, 0);
	return 0;
}