/*
 * DF-0900 - Signed integer overflow in RPC reply verifier length
 *
 * This is a USERSPACE HARNESS that replicates the EXACT arithmetic the kernel
 * performs at sys/vfs/nfs/krpc_subr.c:415-428 in krpc_call()'s `gotreply`
 * path, to demonstrate that an attacker-controlled `authlen` field (read from
 * a network RPC reply) drives a signed `int len` into signed-overflow /
 * undefined-behavior territory, producing an INT_MIN argument to m_adj().
 *
 * Why a harness and not a live trigger?
 * -------------------------------------
 * krpc_call() is invoked ONLY from krpc_portmap() and the nfs_mountrpc
 * mount-rpc helpers, which are themselves reached only from the diskless
 * NFS-root bootstrap path (bootpc_init -> nfs_mountrpc -> krpc_*). The
 * nfs_mountrpc.c file header states verbatim:
 *     "This module is not used by normal operating code because the 'mount'
 *      command has a far more sophisticated implementation."
 * So there is NO userspace syscall on a normally-booted kernel that reaches
 * this code; the attacker model is an on-path network attacker or a
 * malicious NFS server during diskless boot. We cannot stage a diskless
 * NFS boot inside this audit guest, so we reproduce the kernel's own
 * arithmetic here to prove the overflow fires for the cited authlen values.
 *
 * The kernel code under test (verbatim from sys/vfs/nfs/krpc_subr.c):
 *
 *   415:   len = sizeof(*reply);                                   // = 24
 *   416:   if (m->m_len < len) { m = m_pullup(m, len); ... }
 *   423:   reply = mtod(m, struct rpc_reply *);
 *   424:   if (reply->rp_auth.authtype != 0) {
 *   425:       len += fxdr_unsigned(u_int32_t, reply->rp_auth.authlen);
 *   426:       len = (len + 3) & ~3;            // XXX?
 *   427:   }
 *   428:   m_adj(m, len);
 *
 * `fxdr_unsigned(u_int32_t, x)` byte-swaps the on-wire big-endian u_int32_t
 * into host order. `authlen` is the verifier length field the attacker
 * controls in the RPC reply. `len` is a plain signed `int` (declared at
 * krpc_subr.c:200: `int error, rcvflg, timo, secs, len;`).
 *
 * With authlen = 0x7FFFFFE8:  24 + 0x7FFFFFE8 = 0x80000000 = INT_MIN
 *   -> (INT_MIN + 3) & ~3 = INT_MIN
 *   -> m_adj(m, INT_MIN)   // req_len < 0 branch: len = -len; (UB), then
 *                           // m->m_len -= len corrupts m_len.
 *
 * Build:  cc -O2 -Wall -o overflow_demo overflow_demo.c
 * Run:    ./overflow_demo
 */

#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <limits.h>

/* Mirror of the kernel struct layout (sys/vfs/nfs/krpc_subr.c:72-111). */
struct auth_info { uint32_t authtype; uint32_t authlen; };
struct rpc_reply_partial {
    uint32_t rp_xid;        /* 4 */
    int32_t  rp_direction;  /* 4 */
    int32_t  rp_astatus;    /* 4 */
    /* union { u_int32_t errno; struct { auth_info(8) + uint32(4) } rok } */
    uint32_t rok[3];        /* 12 */
};

#define RPCAUTH_MAXSIZ 400   /* sys/vfs/nfs/rpcv2.h:55 */

/* What m_adj() does to a single-mbuf m_len, condensed from
 * sys/kern/uipc_mbuf.c:1859-1924. */
static long m_adj_single_mbuf(int req_len, int m_len_in)
{
    int len = req_len;
    long m_len = m_len_in;
    if (len >= 0) {
        /* head trim */
        if (m_len <= len) m_len = 0;
        else              m_len -= len;
    } else {
        len = -len;                  /* -INT_MIN is UB; compiler leaves INT_MIN */
        if (m_len >= len) m_len -= len;  /* m_len - INT_MIN overflows */
        else              m_len = 0;
    }
    return m_len;
}

int main(void)
{
    int sizeof_reply = (int)sizeof(struct rpc_reply_partial);

    printf("DF-0900 harness: krpc_subr.c:415-428 signed-int overflow demo\n");
    printf("sizeof(struct rpc_reply) = %d  (kernel len initial value)\n",
           sizeof_reply);
    printf("RPCAUTH_MAXSIZ            = %d  (the bound the code lacks)\n\n",
           RPCAUTH_MAXSIZ);

    printf("%-12s %-22s %-22s %-22s %s\n",
           "authlen", "len_after_add", "len_after_align", "m_adj_req_len",
           "single-mbuf m_len");

    struct { uint32_t authlen; int m_len_in; const char *tag; } cases[] = {
        { 0,           100, "benign (no overflow)" },
        { 400,         100, "exactly RPCAUTH_MAXSIZ (would be allowed by fix)" },
        { 401,         100, "just over RPCAUTH_MAXSIZ (fix -> EBADRPC)" },
        { 0x7FFFFFFFu, 100, "authlen = INT_MAX" },
        { 0x7FFFFFE8u, 100, "authlen = 0x7FFFFFE8  (cited overflow trigger)" },
        { 0x80000000u, 100, "authlen = 0x80000000" },
        { 0xFFFFFFFFu, 100, "authlen = 0xFFFFFFFF  (max u32)" },
    };

    for (size_t i = 0; i < sizeof(cases)/sizeof(cases[0]); i++) {
        uint32_t a = cases[i].authlen;
        /* Reproduce the two arithmetic steps separately so the reader sees
         * exactly where signed overflow happens. */
        int len_after_add    = sizeof_reply + (int)a;            /* :425 */
        int len_after_align  = (len_after_add + 3) & ~3;         /* :426 */
        long m_len_after     = m_adj_single_mbuf(len_after_align,
                                                 cases[i].m_len_in);

        printf("0x%08x   %-22d %-22d %-22d %ld   %s\n",
               a, len_after_add, len_after_align, len_after_align,
               m_len_after, cases[i].tag);

        if (a == 0x7FFFFFE8u) {
            printf("  ** BUG FIRES: sizeof_reply(%d) + authlen(0x7FFFFFE8) = "
                   "0x%08x = INT_MIN (signed overflow, UB)\n",
                   sizeof_reply, (unsigned)len_after_add);
            printf("  ** (len+3)&~3 = INT_MIN, passed to m_adj() as req_len.\n");
            if (m_len_after < 0 || m_len_after > cases[i].m_len_in) {
                printf("  ** m_len corrupted: %d -> %ld (OOB on next mbuf op)\n",
                       cases[i].m_len_in, m_len_after);
            }
        }
    }

    printf("\nVerdict: the cited path performs signed `int` arithmetic on an\n");
    printf("attacker-controlled u_int32_t (`authlen`) without any upper bound.\n");
    printf("For authlen >= 0x7FFFFFE8 the sum overflows to INT_MIN/UB.\n");
    printf("The kernel's own NFS socket path (sys/vfs/nfs/nfs_socket.c:2347,2385)\n");
    printf("already guards this with `if (len < 0 || len > RPCAUTH_MAXSIZ)`;\n");
    printf("krpc_subr.c:425 omits that guard.\n");
    return 0;
}
