DF-3882 / txmsg_uaf_race_model.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 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | /* DF-3882 model: drm_dp_mst_handle_down_rep() txmsg use-after-free race. * * Kernel interleave (sys/dev/drm/drm_dp_mst_topology.c): * * HPD/IRQ context (drm_dp_mst_handle_down_rep, :2336-2364): * lock(qlock) * txmsg = mstb->tx_slots[slot] // :2337 * unlock(qlock) // :2339 * drm_dp_sideband_parse_reply(&down_rep_recv, &txmsg->reply) // :2353 * (writes up to ~700 hub-controlled bytes into txmsg->reply) * memset(down_rep_recv); put_mstb // :2358-2359 * lock(qlock) * txmsg->state = DRM_DP_SIDEBAND_TX_RX; // :2362 <-- writes into txmsg * mstb->tx_slots[slot] = NULL; // :2363 * unlock(qlock) * * sender thread (drm_dp_mst_wait_tx_reply, :802-838), same instant: * wait_event_timeout(..., 4*HZ) expired // ret == 0 * lock(qlock) * if (state == SENT) mstb->tx_slots[seqno] = NULL // :829-832 * unlock(qlock) * return -EIO // caller does kfree(txmsg) * // e.g. drm_dp_send_link_address:1649 * * If the sender's 4s timeout expires between the handler's unlock(:2339) and * its re-lock(:2361), the handler performs the ~700-byte parse-reply write * AND the state write into kfree'd memory. The hub controls when the reply * arrives (it can park it 3.99s and release it at the deadline), so it can * retry the race indefinitely. * * This model reproduces the interleaving with two pthreads, using * mmap/munmap as alloc/free so that the post-free write actually SEGVs -- * deterministic proof of the use-after-free (not of the kernel bug itself: * the guest has no DP MST hardware, see env.txt). */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <pthread.h> #include <unistd.h> #include <signal.h> #include <sys/mman.h> #include <time.h> #define TX_SENT 2 #define TX_RX 3 struct txmsg_model { /* relevant slice of drm_dp_sideband_msg_tx */ int seqno; int state; /* written by BOTH sides -- the raced field */ uint8_t reply[700]; /* drm_dp_sideband_msg_reply_body */ }; static pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER; static struct txmsg_model *txmsg; /* == mstb->tx_slots[0] */ static struct txmsg_model *slot0; static uint64_t deadline_ns; static volatile int race_hit; static uint64_t now_ns(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (uint64_t)ts.tv_sec * 1000000000ull + ts.tv_nsec; } static void segv_handler(int sig) { static const char msg[] = " SIGSEGV: handler wrote into txmsg AFTER sender kfree'd it" " -- use-after-free demonstrated\n"; (void)sig; race_hit = 1; if (write(2, msg, sizeof(msg) - 1)) { /* stderr, unbuffered */ } _exit(42); } /* sender: drm_dp_send_link_address + drm_dp_mst_wait_tx_reply */ static void *sender(void *arg) { (void)arg; uint64_t ret; /* wait_event_timeout(tx_waitq, state==RX||TIMEOUT, 4*HZ) -> expires */ do { ret = deadline_ns - now_ns(); } while ((int64_t)ret > 0); /* timeout path under qlock */ pthread_mutex_lock(&qlock); if (txmsg->state == TX_SENT) { printf("sender: timeout, state==SENT -> tx_slots[0] = NULL\n"); slot0 = NULL; /* :831 */ } pthread_mutex_unlock(&qlock); /* caller: kfree(txmsg) (drm_dp_send_link_address:1649) */ munmap(txmsg, sizeof(struct txmsg_model)); printf("sender: kfree(txmsg) done\n"); return NULL; } /* handler: drm_dp_mst_handle_down_rep */ static void *irq_handler(void *arg) { struct txmsg_model *tx; (void)arg; /* hub timed the DOWN_REP_MSG_RDY to land 1ms before the deadline */ while ((int64_t)(deadline_ns - 1000000ull - now_ns()) > 0) ; pthread_mutex_lock(&qlock); tx = slot0; /* :2337 read the slot */ pthread_mutex_unlock(&qlock); /* :2339 */ printf("handler: read txmsg=%p from slot, dropped qlock, parsing...\n", (void *)tx); /* drm_dp_sideband_parse_reply writes ~700 bytes into tx->reply */ memset(tx->reply, 0xA5, sizeof(tx->reply)); usleep(2000); /* :2362 -- the 4-byte state write after the unlocked window */ tx->state = TX_RX; printf("handler: txmsg->state = TX_RX (no fault: sender had not freed)\n"); return NULL; } int main(void) { pthread_t th_sender, th_irq; signal(SIGSEGV, segv_handler); txmsg = mmap(NULL, sizeof(struct txmsg_model), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); if (txmsg == MAP_FAILED) { perror("mmap"); return 1; } txmsg->seqno = 0; txmsg->state = TX_SENT; /* fully sent, parked in tx_slots[0] */ slot0 = txmsg; deadline_ns = now_ns() + 200 * 1000000ull; /* '4 HZ' compressed to 200ms */ pthread_create(&th_sender, NULL, sender, NULL); pthread_create(&th_irq, NULL, irq_handler, NULL); pthread_join(th_irq, NULL); pthread_join(th_sender, NULL); printf("interleaving not hit this run (handler finished before kfree)\n"); return 0; } |