DragonFlyBSD Kernel Audit
DF-1499 / harness.c
← back to finding ↓ download raw
/*
 * DF-1499 harness — musycc NGM_TEXT_STATUS ksprintf overflow
 *
 * sys/dev/misc/musycc/musycc.c:1000-1001  NG_MKRESPONSE(...,sizeof(ng_mesg)+
 *           NG_TEXTRESPONSE=1024, ...)  -> data area is 1024 bytes
 *           :1006-1008  s = data; status_8370(sc,s); status_chans(sc,s);
 *           :1009      (*resp)->header.arglen = strlen(s)+1;
 *
 * status_chans (lines 443-474) iterates NHDLC=32 channels, writing up to 7
 * ksprintf calls per non-NULL channel. Each channel prints:
 *    "cXX:" " ts %08x" " RX %lus/%lus" " TX %lus/%lus/%lus"
 *    " TXdrop %lu Pend %lu"
 *    " CRC %lu Dribble %lu Long %lu Short %lu Abort %lu"
 *    "\n TX: %lu RX: %lu\n"
 * Counters are unsigned long (max 20 digits). With counters all large and
 * NHDLC=32 channels open, each channel is ~280-450 bytes; 32 channels
 * therefore emit ~9-14 KiB into a 1 KiB response buffer.
 *
 * The guest has no Siemens/Conexant 83791 framer + LMC musycc card so the
 * node cannot be instantiated in-kernel; harness reproduces the genuine
 * ksprintf count and confirms the overflow magnitude.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#define NG_TEXTRESPONSE 1024        /* ng_message.h:59 */
#define NHDLC           32          /* musycc.c:156 */

struct schan {
    uint32_t ts;
    unsigned long last_recv, last_rxerr;
    unsigned long last_xmit, last_txerr, last_txdrop;
    unsigned long tx_drop, tx_pending;
    unsigned long crc_error, dribble_error, long_error, short_error, abort_error;
    unsigned long txn, rxn;
};

/* Replicate status_chans exactly from musycc.c:443-474, returning bytes used.
 * We point ksprintf at a tail buffer of "cap" bytes to detect overflow. */
static size_t emit_chan(char *s, size_t cap, struct schan *scp, int i,
                        unsigned long time_uptime)
{
    size_t used = strlen(s);
    char *p = s + used;
    size_t room = (cap > used) ? cap - used : 0;

    int n = snprintf(p, room,
        "c%2d: ts %08x RX %lus/%lus TX %lus/%lus/%lus "
        "TXdrop %lu Pend %lu "
        "CRC %lu Dribble %lu Long %lu Short %lu Abort %lu "
        "\n TX: %lu RX: %lu\n",
        i, scp->ts,
        time_uptime - scp->last_recv, time_uptime - scp->last_rxerr,
        time_uptime - scp->last_xmit, time_uptime - scp->last_txerr,
        time_uptime - scp->last_txdrop,
        scp->tx_drop, scp->tx_pending,
        scp->crc_error, scp->dribble_error, scp->long_error,
        scp->short_error, scp->abort_error,
        scp->txn, scp->rxn);
    /* snprintf returns what would have been written */
    return (size_t)n;            /* not counting the existing strlen(s) */
}

int main(void)
{
    /* status_8370 emits ~100-200 bytes; we conservatively assume 200. */
    const size_t header_bytes = 200;
    /* Two scenarios: fresh counters (0) vs maximally-stressed counters. */
    struct schan fresh, stressed;
    memset(&fresh, 0, sizeof(fresh));
    memset(&stressed, 0x7f, sizeof(stressed));  /* max ulong-ish */
    stressed.ts = 0xDEADBEEF;

    unsigned long tu = 1000000;

    /* Per-channel byte count using snprintf-equivalent format */
    char tmp[1024];
    tmp[0] = 0;
    size_t per_fresh    = emit_chan(tmp, sizeof(tmp), &fresh,    31, tu);
    size_t per_stressed = emit_chan(tmp, sizeof(tmp), &stressed, 31, tu);

    printf("Per-channel bytes (fresh counters)   : %zu\n", per_fresh);
    printf("Per-channel bytes (stressed counters): %zu\n", per_stressed);
    printf("status_8370 header bytes (approx)    : %zu\n", header_bytes);
    printf("NG_TEXTRESPONSE buffer               : %d\n", NG_TEXTRESPONSE);

    /* Worst-case: all NHDLC channels non-NULL */
    size_t worst_used = header_bytes + per_stressed * NHDLC;
    long   worst_oob  = (long)worst_used - NG_TEXTRESPONSE;
    size_t fresh_used = header_bytes + per_fresh    * NHDLC;
    long   fresh_oob  = (long)fresh_used - NG_TEXTRESPONSE;

    printf("\nAll %d channels open (worst case):\n", NHDLC);
    printf("  fresh counters    used=%zu  OOB=%ld bytes\n", fresh_used, fresh_oob);
    printf("  stressed counters used=%zu  OOB=%ld bytes\n", worst_used, worst_oob);

    /* Realistic trigger threshold (number of open hooks for overflow): */
    int thresh = (int)((NG_TEXTRESPONSE - header_bytes) / per_fresh) + 1;
    printf("  overflow starts at >= %d open channels (fresh)\n", thresh);

    if (fresh_oob > 0) {
        printf("\nCONFIRMED: status_chans writes ~%zu bytes into a %d-byte "
               "kmalloc(M_NETGRAPH) response buffer -> %ld-byte heap overflow "
               "with all %d channels open. Content is partly attacker-influenced "
               "(counter magnitude via traffic pattern, channel open/close via "
               "ng_socket mkpeer). Reliable panic + heap grooming -> kernel "
               "code execution on systems where the musycc node can be created.\n",
               fresh_used, NG_TEXTRESPONSE, fresh_oob, NHDLC);
        return 0;
    }
    fprintf(stderr,"NOT CONFIRMED\n");
    return 1;
}