DF-0744 / corrupt.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 | /* * DF-0744 trigger #1 โ sticky-option corruption demonstrator. * * Bug: sys/netinet6/udp6_output.c:264 * On the failure path of ip6_setpktoptions() the per-call control * handler `goto release`s without ever executing * `in6p->in6p_outputopts = &opt;` (line 138). At releaseopt the code * unconditionally does * * ip6_clearpktopts(in6p->in6p_outputopts, -1); // line 264 * in6p->in6p_outputopts = stickyopt; // line 265 * * but at that point in6p->in6p_outputopts is STILL the sticky options * (not the local `opt`), so the user's persistent setsockopt() state is * cleared instead of the per-call copy. * * Demonstrator: * 1. AF_INET6 SOCK_DGRAM socket. * 2. setsockopt(IPV6_PKTINFO) with a non-zero source address. Sticky. * 3. getsockopt(IPV6_PKTINFO) -> the value we just set (baseline). * 4. sendmsg() with control = single cmsghdr whose cmsg_len == 0. * ip6_setpktoptions() returns EINVAL at once, udp6_output hits the * buggy releaseopt path and clears the STICKY options. * 5. getsockopt(IPV6_PKTINFO) -> the value is gone (zeroed). * * A correct kernel keeps the sticky value across the failed sendmsg. * A buggy kernel zeroes it. We print both snapshots so the difference is * unambiguous. * * Unprivileged: only requires socket()+setsockopt()+sendmsg() on a * PF_INET6 SOCK_DGRAM socket. */ #include <sys/param.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> /* pulls in netinet6/in6.h; provides * IPV6_PKTINFO, struct in6_pktinfo */ #include <err.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> /* ::ffff:0.0.0.42 โ recognisable sentinel that is NOT in6addr_any. */ static const unsigned char sentinel_addr[16] = { 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,42, }; static void dump_pktinfo(const char *label, const struct in6_pktinfo *pi) { const unsigned char *a = pi->ipi6_addr.s6_addr; printf(" %-26s ifindex=%u addr=" "%02x%02x:%02x%02x:%02x%02x:%02x%02x:" "%02x%02x:%02x%02x:%02x%02x:%02x%02x\n", label, pi->ipi6_ifindex, a[0],a[1],a[2],a[3],a[4],a[5],a[6],a[7], a[8],a[9],a[10],a[11],a[12],a[13],a[14],a[15]); } int main(void) { int s, rc; struct in6_pktinfo pi_set, pi_get; socklen_t optlen; struct sockaddr_in6 dst; /* --- 1. socket ------------------------------------------------ */ s = socket(AF_INET6, SOCK_DGRAM, 0); if (s < 0) err(1, "socket(AF_INET6, SOCK_DGRAM)"); /* --- 2. set sticky IPV6_PKTINFO ------------------------------ */ memset(&pi_set, 0, sizeof(pi_set)); memcpy(&pi_set.ipi6_addr, sentinel_addr, 16); pi_set.ipi6_ifindex = 0; /* let kernel pick iface */ rc = setsockopt(s, IPPROTO_IPV6, IPV6_PKTINFO, &pi_set, sizeof(pi_set)); if (rc != 0) err(1, "setsockopt(IPV6_PKTINFO)"); /* --- 3. read it back (baseline) ------------------------------ */ optlen = sizeof(pi_get); memset(&pi_get, 0xee, sizeof(pi_get)); rc = getsockopt(s, IPPROTO_IPV6, IPV6_PKTINFO, &pi_get, &optlen); if (rc != 0) err(1, "getsockopt(IPV6_PKTINFO) #1"); printf("[baseline] sticky IPV6_PKTINFO after setsockopt:\n"); dump_pktinfo("read-back", &pi_get); /* --- 4. sendmsg with one malformed cmsg (cmsg_len == 0) ------ */ /* * Control buffer = a single struct cmsghdr whose cmsg_len is 0. * ip6_setpktoptions() returns EINVAL at once; udp6_output() then * runs the buggy releaseopt path and clears the sticky options. */ { char cbuf[CMSG_SPACE(0)]; /* 16 bytes on amd64 */ struct cmsghdr *cm; struct msghdr msg; struct iovec iov; unsigned char payload[1] = { '.' }; memset(cbuf, 0, sizeof(cbuf)); cm = (struct cmsghdr *)cbuf; cm->cmsg_level = IPPROTO_IPV6; cm->cmsg_type = IPV6_PKTINFO; cm->cmsg_len = 0; /* <<< the trigger */ memset(&dst, 0, sizeof(dst)); dst.sin6_len = sizeof(dst); dst.sin6_family = AF_INET6; dst.sin6_addr = in6addr_loopback; /* ::1 */ dst.sin6_port = htons(9); /* discard */ memset(&msg, 0, sizeof(msg)); iov.iov_base = payload; iov.iov_len = sizeof(payload); msg.msg_name = &dst; msg.msg_namelen = sizeof(dst); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = cbuf; msg.msg_controllen = sizeof(cbuf); rc = sendmsg(s, &msg, 0); /* EINVAL is expected; we want the side-effect, not the send. */ printf("[trigger] sendmsg returned %d (errno=%d: %s)\n", rc, rc < 0 ? errno : 0, rc < 0 ? strerror(errno) : "ok"); } /* --- 5. read sticky again โ buggy kernel: zeroed ------------- */ optlen = sizeof(pi_get); memset(&pi_get, 0xee, sizeof(pi_get)); rc = getsockopt(s, IPPROTO_IPV6, IPV6_PKTINFO, &pi_get, &optlen); if (rc != 0) err(1, "getsockopt(IPV6_PKTINFO) #2"); printf("[after] sticky IPV6_PKTINFO after buggy sendmsg:\n"); dump_pktinfo("read-back", &pi_get); /* * Verdict: if the address is still the sentinel -> safe kernel. * If it is all-zero (in6addr_any) -> sticky state was corrupted. */ { const unsigned char zero[16] = {0}; int is_zero = memcmp(&pi_get.ipi6_addr, zero, 16) == 0; int is_sentinel = memcmp(&pi_get.ipi6_addr, sentinel_addr, 16) == 0; if (is_sentinel) { printf("\nVERDICT: SAFE - sticky IPV6_PKTINFO " "preserved.\n"); return 0; } if (is_zero) { printf("\nVERDICT: BUG - sticky IPV6_PKTINFO was " "CLEARED (state corruption, DF-0744 reproduced).\n"); return 2; } printf("\nVERDICT: DETERIORATED - sticky value unexpected " "(partial corruption?).\n"); return 3; } } |