/*
 * DF-2858 v2 - corrected detector.
 * The parent's attacher thread attaches the SAME segment at a FIXED
 * address (0x50000000) in a shmat/shmdt loop.  A fork can land:
 *   (a) outside the window: child inherits either nothing (cap=shmseg,
 *       no inherited mapping) or the live mapping (cap=shmseg-1 WITH
 *       mapping at 0x50000000) - both normal;
 *   (b) inside sys_shmat's reserved-but-unmapped window: child inherits
 *       reserved=1/shmid=-1 and NO mapping.  On a vulnerable kernel the
 *       slot can never be cleared -> child capacity < shmseg with NO
 *       inherited mapping = BURNED SLOT.  With the shmfork fix the
 *       inherited reserved flag is cleared -> cap == shmseg, no mapping.
 * Child detection: attach loop count + probe whether 0x50000000 is
 * mapped (mincore).
 */
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#define FIXADDR ((void *)0x50000000UL)
#define SHMSEG_TEST 16

static int shmid;
static volatile int *stop;

static void *
attacher(void *arg __unused)
{
	while (!*stop) {
		void *p = shmat(shmid, FIXADDR, 0);
		if (p == (void *)-1) {
			usleep(200);
			continue;
		}
		usleep(50);
		if (shmdt(p) < 0)
			fprintf(stderr, "[!] shmdt: %s\n", strerror(errno));
	}
	return NULL;
}

static int
addr_mapped(void *a)
{
	char vec[1];
	return mincore(a, 4096, vec) == 0 ? 1 : 0;
}

int
main(int argc, char **argv)
{
	int forks = argc > 1 ? atoi(argv[1]) : 3000;
	int shmseg = SHMSEG_TEST;

	stop = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE,
	    MAP_ANON | MAP_SHARED, -1, 0);
	*stop = 0;

	if ((shmid = shmget(IPC_PRIVATE, 256 * 1024, IPC_CREAT | 0666)) < 0)
		err(1, "shmget");
	fprintf(stderr, "[*] shmseg test cap=%d\n", shmseg);

	pthread_t th;
	if (pthread_create(&th, NULL, attacher, NULL))
		err(1, "pthread_create");

	long burns = 0, normal = 0, clean = 0, total = 0;
	for (int i = 0; i < forks; i++) {
		pid_t p = fork();
		if (p == 0) {
			int n = 0;
			void *last[64];
			memset(last, 0, sizeof(last));
			while (n < shmseg) {
				void *q = shmat(shmid, NULL, 0);
				if (q == (void *)-1)
					break;
				last[n++] = q;
			}
			int inherited = addr_mapped(FIXADDR);
			for (int k = 0; k < n; k++)
				shmdt(last[k]);
			if (!inherited && n < shmseg) {
				printf("BURN pid=%d cap=%d inherited=0\n",
				    getpid(), n);
				fflush(stdout);
				_exit(42);
			}
			_exit(0);
		}
		int st;
		waitpid(p, &st, 0);
		total++;
		if (WIFEXITED(st) && WEXITSTATUS(st) == 42)
			burns++;
		else {
			/* classify */
			;
		}
		if ((i % 500) == 0)
			fprintf(stderr, "[*] %d forks, %ld burns\n", i, burns);
	}
	*stop = 1;
	fprintf(stderr, "[*] DONE forks=%ld burned-slot children=%ld\n",
	    total, burns);
	if (burns > 0) {
		fprintf(stderr, "[*] REPRODUCED: fork landed in sys_shmat's "
		    "reservation window and the child inherited a permanently "
		    "unusable shmseg slot\n");
		return 2;
	}
	fprintf(stderr, "[*] no burned slots observed this run\n");
	return 0;
}
