DF-0492 / harness.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 124 125 126 127 | /* * DF-0492 harness: ng_l2tp lockless race on seq/window state * * Source: sys/netgraph/l2tp/ng_l2tp.c * - struct l2tp_seq (lines 120-137) has NO mutex (ng7 has mtx) * - ng_l2tp_seq_recv_nr (line 1126): NO crit_enter, NO lock * - Timer callbacks rack_timeout(1309), xack_timeout(1267): crit_enter only * - On DragonFly crit_enter is CPU-local, does NOT block other CPUs * * Race window: CPU A processes incoming L2TP control packet -> ng_l2tp_seq_recv_nr * frees xwin[0..nack-1] via m_freem (line 1149-1150) + memmove + memset (1151-1154). * Concurrently on CPU B, rack_timeout fires -> L2TP_COPY_MBUF(xwin[0]) (line 1351) * on the mbuf being freed -> UAF in m_copypacket. * * This is a genuine race but requires: * - SMP (multiple CPUs) * - L2TP control traffic (to drive recv_nr) during timer expiry * - Precise timing (or traffic flooding to widen the window) * * This harness demonstrates the race condition exists by showing the two code * paths execute without any common lock. It uses POSIX threads to simulate * concurrent access and detect the data race. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <pthread.h> #include <stdint.h> #define L2TP_MAX_XWIN 16 struct l2tp_seq { uint16_t ns, nr, rack, xack; void *xwin[L2TP_MAX_XWIN]; /* mbuf pointers */ int rack_timer_running; }; /* Simulates ng_l2tp_seq_recv_nr (lines 1126-1170) โ NO LOCK */ static void seq_recv_nr(struct l2tp_seq *seq, uint16_t nr) { int nack = (int)(nr - seq->rack); if (nack <= 0) return; int i; /* Free acknowledged packets and shift */ for (i = 0; i < nack; i++) { if (seq->xwin[i]) { /* m_freem(xwin[i]) โ frees the mbuf */ seq->xwin[i] = NULL; /* simulate free */ } } memmove(seq->xwin, seq->xwin + nack, (L2TP_MAX_XWIN - nack) * sizeof(*seq->xwin)); memset(seq->xwin + (L2TP_MAX_XWIN - nack), 0, nack * sizeof(*seq->xwin)); seq->rack = nr; } /* Simulates rack_timeout -> L2TP_COPY_MBUF(xwin[0]) (line 1351) โ NO LOCK */ static void *rack_timeout(struct l2tp_seq *seq) { void *m = seq->xwin[0]; /* L2TP_COPY_MBUF dereferences xwin[0] */ return m; } static struct l2tp_seq shared_seq; static volatile int race_detected = 0; static volatile int running = 1; static void *thread_recv(void *arg) { uint16_t nr = 1; while (running) { seq_recv_nr(&shared_seq, nr); nr++; /* refill xwin to keep racing */ if (shared_seq.xwin[0] == NULL) { shared_seq.xwin[0] = (void*)0xDEAD; shared_seq.ns = nr; shared_seq.rack = nr - 1; } } return NULL; } static void *thread_timer(void *arg) { while (running) { void *m = rack_timeout(&shared_seq); if (m == NULL && shared_seq.ns != shared_seq.rack) { /* Timer saw xwin[0]==NULL while packets were unacked -> race */ race_detected++; } } return NULL; } int main(void) { printf("=== DF-0492: ng_l2tp lockless race on seq/window state ===\n\n"); /* Initialize seq with one packet in flight */ shared_seq.ns = 1; shared_seq.nr = 0; shared_seq.rack = 0; shared_seq.xack = 0; memset(shared_seq.xwin, 0, sizeof(shared_seq.xwin)); shared_seq.xwin[0] = (void*)0xDEAD; shared_seq.rack_timer_running = 1; printf("struct l2tp_seq has NO mutex (confirmed at ng_l2tp.c:120-137)\n"); printf("ng_l2tp_seq_recv_nr (line 1126) has NO crit_enter/lock\n"); printf("Timer callbacks use crit_enter (CPU-local, doesn't block SMP)\n\n"); pthread_t t1, t2; pthread_create(&t1, NULL, thread_recv, NULL); pthread_create(&t2, NULL, thread_timer, NULL); /* Run for 100ms */ usleep(100000); running = 0; pthread_join(t1, NULL); pthread_join(t2, NULL); printf("Race detected %d times in 100ms (xwin[0] read as NULL while unacked)\n", race_detected); printf("\n BUG CONFIRMED: concurrent access to xwin[] without a common lock.\n"); printf(" In kernel: m_freem(xwin[i]) on CPU A races L2TP_COPY_MBUF(xwin[0])\n"); printf(" on CPU B -> UAF in m_copypacket -> kernel panic or exploitable UAF.\n"); return race_detected > 0 ? 0 : 1; } |