FreeBSD IPv6 remote ding-dong attack (kernel panic) reddit / BSD ---------------------------------------------------------------------- TheGrandSchlonging · 2019-06-24 09:35 FreeBSD IPv6 remote ding-dong attack (kernel panic) **INTRODUCTION** A bug in `m_pulldown()` was reported to and publicly acknowledged by FreeBSD in 2006 but ultimately never fixed. OpenBSD, NetBSD, and XNU (Apple) have all committed the one-line fix. Although it's not possible to target FreeBSD the same way the original research targeted OpenBSD, i.e., by spewing relatively simple IPv6 fragments with a stress-testing tool and waiting for a crash, the bug is nevertheless present in FreeBSD and can be triggered with Internet-routable ICMPv6 MLD packets under some conditions. **BUG REACHABILITY** `m_pulldown()` is reachable primarily through the macro `IP6_EXTHDR_GET()`, where the impact of the bug is a mere kernel panic. Other, potentially more serious calls to `m_pulldown()` have uninvestigated impact: in ipfilter, in pfsync (which for a synchronization interface presupposes trusted traffic), and in the `PF_KEY` code (which is being phased out of GENERIC kernels into a KLD as a result of the `IPSEC_SUPPORT` replacement for `IPSEC`). The `IP6_EXTHDR_GET()` macro in turn is reachable in only a small number of places when `PULLDOWN_TEST` isn't defined: SCTP, IPsec AH, IPsec ESP, ICMPv6 NI, ICMPv6 ECHO REQUEST, and ICMPv6 MLD. SCTP doesn't require an open SCTP socket, for the attack occurs after the protocol switch but before the search of the PCBs. The problem is that sixteen bytes comprising an SCTP header and SCTP chunk header must span three fragments such that the first two fragments' byte counts are nonzero multiples of eight, and there's simply nothing available for the third fragment. IPsec AH and ESP are also saved by too-small length requests. ICMPv6 NI (in particular, `ICMP6_NI_SUBJ_FQDN`) offers large subject lengths in `ni6_input()` but is stymied by the way the `m_copym()` call in `icmp6_input()` reuses clusters, with the bumped reference counts causing the `M_WRITABLE()` component of the writability check in `m_pulldown()` to fail. Moreover, the NI packets must be sourced from a loopback or link-local address in the default configuration, so these packets should be dropped the instant they hit a conforming router or an anti-spoofing packet filter. ICMPv6 ECHO REQUEST (ping query) is affected by `m_copym()` non- writability as well, but the `IP6_EXTHDR_GET()` call here is perfectly safe in any event. That leaves ICMPv6 MLD, which is doable. Note that the `ip6_hlim == 1` requirement presents no real obstacle, and the kernel panic occurs before address checking, making the attack Internet-routable in principle. **ATTACK DESIGN** The attack is based on four IPv6 fragments: [Fragment #1] IPv6 header (40) - must reach target with hop limit == 1 HBH header, 2 x pad1 opt, router-alert opt (8) FRAG header (8) DSTOPT header, 1222 x pad1 opt (1224) - options continue into next fragment [Fragment #2] IPv6 header (40) HBH header, 2 x pad1 opt, router-alert opt (8) FRAG header (8) - fragment can be redesigned as 1280 bytes total if having a smaller fragment with the IP6F_MORE_FRAG bit set raises red flags $n x pad1 opt ($n) - $n is calculated as (760 - L2_preamble) & ~7. L2_preamble is the leading cluster space in fragment #1 and results from some combination of... ... driver displacement compensating for h/w rx bugs ... alignment (e.g., ETHER_ALIGN) ... headers (e.g., ether_header, ether_vlan_header) ... other L2_preamble is indeterminate remotely, but preambles group so that a zero-length preamble is covered by one round of four fragments, one- to eight-length preambles are covered by a second round of four fragments, nine- to sixteen-length preambles are covered by a third round of four fragments, etc. ICMPv6 header (8) - type = 130 (MLD_LISTENER_QUERY) - only type and checksum fields matter; everything else in the MLDv2 query header can be zeroed junk [Fragment #3] IPv6 header (40) HBH header, 2 x pad1 opt, router-alert opt (8) DSTOPT header, 1214 x pad1 opt (1216) FRAG header (8) JUNK (8) [Fragment #4] IPv6 header (40) HBH header, 2 x pad1 opt, router-alert opt (8) FRAG header (8) JUNK (1224) **ATTACK CONDITIONS** (1) There must be no prohibitive filtering anywhere of the router- alert HBH option. Note that the router-alert HBH option is different from the widely maligned routing header and has a security profile defined mainly in terms of performance degradation and slow-path congestion. (2) There must be nothing anywhere enforcing the RFC 8200 IPv6 requirement that an upper-layer header appear in the offset-zero fragment. This requirement is a fairly new change from RFC 2460 and isn't enforced by the kernel itself or by a lot of networking gear. If ipfw, for one, is enabled anywhere, the fragment in question will cause a pull-up failure if processed, regardless of otherwise lax filtering rules. (3) In general, there must be no filtering/normalization/offloading intelligence anywhere that messes with the four carefully designed IPv6 fragments. (4) `m_pulldown()` must be handed a cluster-carrying mbuf such that `m_ext.ext_type` is `EXT_CLUSTER`. This means the network driver mustn't directly or indirectly obtain an mbuf from the UMA packet zone, for such an mbuf entails a pre-attached cluster from the cluster zone but with an `m_ext.ext_type` override of `EXT_PACKET`. Potentially favorable drivers include those still obtaining a cluster via `MCLGET()`; the cxgbe driver, which can obtain a cluster from the cluster zone directly via `uma_zalloc()`, with the cluster being attached via `m_cljset()` with the `m_gettype(MCLBYTES)`-derived type of `EXT_CLUSTER`; and any drivers using the new iflib routines, a case similar to the cxgbe case, except that `m_cljget()` is used instead of `uma_zalloc()`. **ATTACK ANALYSIS** The complexity of the attack design results from the need to spread only twenty-eight logically successive bytes across three 2048-byte clusters without exceeding the lowest-bound IPv6 MTU of 1280 and while foiling the consolidation efforts of `m_cat()` during fragment reassembly in `frag6_input()`. `m_cat()` will find enough trailing space in the first cluster to consolidate rather than link the second fragment's data. There'll then be at most seven bytes of trailing space at the end of the first cluster, which is too little space for the eight bytes of junk in the third fragment, forcing `m_cat()` to link mbufs in this case. Likewise, `m_cat()` will find insufficient space in the third fragment's cluster for the 1224 bytes of junk from the fourth fragment and will therefore link a second time. The end result will be an mbuf chain consisting of three cluster-carrying mbufs. The mbuf chain will be protocol-switched into `dest6_input()` to process the in-band destination options, and a side-effect of the `m_cat()` processing is that the contiguity constraint on `dstoptlen` is now satisfied. A protocol switch into `icmp6_input()` then occurs, followed by a call to `mld_input()`: /* Pullup to appropriate size. */ mld = (struct mld_hdr *)(mtod(m, uint8_t *) + off); if (mld->mld_type == MLD_LISTENER_QUERY && icmp6len >= sizeof(struct mldv2_query)) { mldlen = sizeof(struct mldv2_query); } else { mldlen = sizeof(struct mld_hdr); } IP6_EXTHDR_GET(mld, struct mld_hdr *, m, off, mldlen); This results in `IP6_EXTHDR_GET(..., m, offset_of_ICMPv6_header, 28);` Because the twenty-eight bytes requested are spread across three mbuf clusters, the following check in `IP6_EXTHDR_GET()` fails: if ((m)->m_len >= (off) + (len)) And therefore `m_pulldown()` is invoked: t = m_pulldown((m), (off), (len), &tmp); `m_pulldown()` uses the following writability test: writable = 0; if ((n->m_flags & M_EXT) == 0 || (n->m_ext.ext_type == EXT_CLUSTER && M_WRITABLE(n))) writable = 1; This is where the `EXT_CLUSTER` condition described earlier arises. It's worth noting that this writability test can also be satisfied if one targets a driver willing to allocate a plain mbuf and if one doesn't mind somewhat pathologically sized IPv6 fragments. Next `m_pulldown()` runs through a series of special cases: if ((off == 0 || offp) && len <= n->m_len - off) [...] if (len <= n->m_len - off) These cases aren't applicable -- the twenty-eight bytes requested can't be found at the end of the first cluster. hlen = n->m_len - off; tlen = len - hlen; Here `hlen == 8`, and `tlen == 20`. if ((off == 0 || offp) && M_TRAILINGSPACE(n) >= tlen && writable) This case isn't applicable either -- there's not enough room for twenty bytes at the end of the first cluster (as mentioned earlier, there's room for at most seven bytes). if ((off == 0 || offp) && M_LEADINGSPACE(n->m_next) >= hlen && writable) { n->m_next->m_data -= hlen; n->m_next->m_len += hlen; bcopy(mtod(n, caddr_t) + off, mtod(n->m_next, caddr_t), hlen); n->m_len -= hlen; n = n->m_next; off = 0; goto ok; } This is the buggy case targeted. There's guaranteed to be sufficient leading space in the second cluster to store `hlen` (eight) bytes because `frag6_input()` calls `m_adj()` to shift the data frame of reference past unneeded headers in non-offset-zero fragments. Unfortunately, FreeBSD is missing an additional `if` check that other OSes have committed: OpenBSD/NetBSD: n->m_next->m_len >= tlen XNU: (n->m_next->m_len + hlen) >= len What's returned to `IP6_EXTHDR_GET()` is a two-node mbuf chain in which only the first sixteen bytes of the twenty-eight requested are contiguous. `IP6_EXTHDR_GET()` catches this and panics: if (t) { if (t->m_len < tmp + (len)) panic("m_pulldown malfunction"); Ding dong. ---------------------------------------------------------------------- [deleted] · 2019-06-24 10:48 [deleted] ---------------------------------------------------------------------- TheGrandSchlonging · 2019-06-24 11:16 >Soo are you gonna report it Did you even read the post? "A bug in `m_pulldown()` **was reported to and publicly acknowledged by FreeBSD in 2006** but ultimately never fixed. OpenBSD, NetBSD, and XNU (Apple) have all committed the one-line fix." >or pull a [bitch move](https://zerodium.com)? Why would you imagine this bug has any monetary value? >Reddit is the wrong platform for this. Says who? ---------------------------------------------------------------------- icantthinkofone · 2019-06-24 12:10 It would be more helpful and insightful to tell us **why** the bug has not been fixed and not information already known. Is this the reason why? > it's not possible to target FreeBSD the same way the original research targeted OpenBSD You can't reach the FreeBSD with this attack at all? Therefore, FreeBSD is not susceptible to the problem? ---------------------------------------------------------------------- TheGrandSchlonging · 2019-06-24 12:25 > It would be more helpful and insightful to tell us why the bug has not been fixed Laziness, a cat dying, an asteroid attack -- what exactly do you expect me to say? >You can't reach the FreeBSD with this attack at all? Therefore, FreeBSD is not susceptible to the problem? Sigh. Third sentence of the post: "**the bug is nevertheless present in FreeBSD and can be triggered with Internet-routable ICMPv6 MLD packets under some conditions**." Also notice the title of the post and the length of the post, from which you may be able to infer a few things. ---------------------------------------------------------------------- icantthinkofone · 2019-06-24 12:55 Ok. I missed that but the more important question still is why it's not been fixed. I am still betting that it's something related to the statement > it's not possible to target FreeBSD the same way the original research targeted OpenBSD Serious issues are fixed all the time in FreeBSD so there **must** be reason why this one isn't. ---------------------------------------------------------------------- knz · 2019-06-24 13:07 Please provide a link to the freebsd bug report. ---------------------------------------------------------------------- knz · 2019-06-24 13:08 Also please provide a link to the "one-line fix" performed in openbsd/etc. ---------------------------------------------------------------------- TheGrandSchlonging · 2019-06-24 13:36 The original research was done under the aegis of FreeBSD in its 2006 Summer of Code program: >Student: Clement Lecigne >Summary: IPv6 stack vulnerabilities >Mentor: George Neville-Neil It was publicly acknowledged in a FreeBSD quarterly status report: >The focus of this project was to review past vulnerabilities, create vulnerability testing tools and to discover new vulnerabilities in the FreeBSD IPv6 stack which is derived from the KAME project code. During the summer Clement took two libraries, the popular libnet, and his mentor's Packet Construction Set (PCS) and created tools to find security problems in the IPv6 code. Several issues were found, bugs filed, and patches created. At the moment Clement and George are editing a 50 page paper that describes the project which will be submitted for conference publication. >All of the code from the project, including the tools, is online and is described in the paper. >By all measures, this was a successful project. Both student and mentor gained valuable insight into a previously externally maintained set of code. In addition to the new tools development in this effort, the FreeBSD Project has gained a new developer to help work on the code. ---------------------------------------------------------------------- TheGrandSchlonging · 2019-06-24 13:42 The fix is mentioned in the post. I'm not sure what your struggle is with confirming it in the OpenBSD/NetBSD/XNU trees. ---------------------------------------------------------------------- TheGrandSchlonging · 2019-06-24 14:12 Let's imagine the most extreme case of nothing whatsoever calling `m_pulldown()` in the 2006 tree. You'd still have a dangerously buggy mbuf utility function in a sensitive part of the kernel awaiting future use -- hardly a model WONTFIX case. I'd love to be able to claim that this is some new bug, but it's really not. It's old. ---------------------------------------------------------------------- knz · 2019-06-24 14:48 yes sorry I missed it. thank you ---------------------------------------------------------------------- knz · 2019-06-24 14:50 That's not a link to the freebsd bug report. Can you confirm the FreeBSD devs actually know about the specific issue and the one-line bug fix? It's possible that the person who read the 50-page paper was not a developer, and did not confirm that an issue was created for every issue found. Perhaps that specific issue was actually misfiled/not filed? ---------------------------------------------------------------------- TheGrandSchlonging · 2019-06-24 15:27 >Can you confirm the FreeBSD devs actually know about the specific issue and the one-line bug fix? It was a FreeBSD dev who found the bug, and a more established FreeBSD dev helped the first FreeBSD dev write the paper in which FreeBSD was mentioned as vulnerable. If you mean each and every FreeBSD dev, I consider that unlikely. >Perhaps that specific issue was actually misfiled/not filed? It appears an additional bug in `ip6_nexthdr()` was also not fixed in either FreeBSD or XNU, at least not at the site of the problem as in OpenBSD and NetBSD, though I've only started looking at the actual impact of that. Let me make something very clear: I occasionally post bugs to this sub because I love using the BSDs, not because I stick penguins up my ass or anything like that. I just refuse to deal with official bug- reporting channels nowadays. It's never been a pleasant experience for me, whether dealing with developers who are outright assholes (e.g., glibc, ffmpeg); developers who simply refuse to cut the bullshit even at the feature level, to the point that it's understandable why people sell bugs instead of revisiting history every two weeks (e.g., Mozilla); developers who are insular when it comes to real-world attacks happening every day and who treat you like some tinfoil- hatter; developers who have some kind of impenetrable gated community going on when it comes to code commits; and developers who reserve attentiveness, promptness, and vigor for appeasing privileged white American SJWs with First-World problems. Reddit is great for bug reporting, imo. You get to fix typos, add some formatting, or post updates without having to run a blog. You get more like-minded folks looking at a problem, ready to take the reins if you get hit by a bus or don't want to spend six months shooting off messages. ---------------------------------------------------------------------- TotesMessenger · 2019-06-25 15:07 I'm a bot, *bleep*, *bloop*. Someone has linked to this thread from another place on reddit: - [/r/freebsd] [FreeBSD Ipv6 remote ding song attack](https://www.redd it.com/r/freebsd/comments/c59vpq/freebsd_ipv6_remote_ding_song_attack/ )  *^(If you follow any of the above links, please respect the rules of reddit and don't vote in the other threads.) ^\([Info](/r/TotesMessenger) ^/ ^[Contact](/message/compose?to=/r/TotesMessenger))* ---------------------------------------------------------------------- lildergs · 2019-06-27 06:01 If there is an active bug report, please provide the link. If there isn't, could you submit? I'd appreciate it: seems like you know what you're doing. (I have read the whole thread, and didn't see either take place). Thanks! ---------------------------------------------------------------------- TheGrandSchlonging · 2019-06-30 17:23 I don't know whether anyone has acted on this since my post. The `icmp_error()` buffer underwrite I found and posted about here last year (CVE-2018-17156) was fixed in record time, though that could be because the bug was heading into 12.0-RELEASE. FreeBSD-EN-18:13.icmp linked to my thread, so obviously posting to Reddit can work better than other options. ---------------------------------------------------------------------- urielii · 2019-07-12 13:56 *hugs* Hehehe ---------------------------------------------------------------------- emaste · 2019-08-13 20:55 Fixed in [FreeBSD r350815](https://svnweb.freebsd.org/changeset/base/350815) and related MFCs.