Bug 157410 - [ip6] IPv6 Router Advertisements Cause Excessive CPU Use
Summary: [ip6] IPv6 Router Advertisements Cause Excessive CPU Use
Status: In Progress
Alias: None
Product: Base System
Classification: Unclassified
Component: kern (show other bugs)
Version: unspecified
Hardware: Any Any
: Normal Affects Only Me
Assignee: Bjoern A. Zeeb
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2011-05-30 00:20 UTC by Sam Bowne
Modified: 2019-11-20 17:04 UTC (History)
8 users (show)

See Also:


Attachments
updated patch (7.26 KB, patch)
2015-04-08 03:25 UTC, Mark Felder
no flags Details | Diff
ported to 12-CURRENT r327568M (7.99 KB, patch)
2018-01-05 07:04 UTC, Cy Schubert
no flags Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Sam Bowne 2011-05-30 00:20:10 UTC
IPv6 Router Advertisement packets cause a denial of service by CPU consumption.  This is a known  vulnerability in Windows systems, and it works against FreeBSD too.

Here is a screen captures of the attack in action, with a slow attack of 100 packets per second:

http://samsclass.info/ipv6/proj/FreeBSD-100RAps.png

Here is a detailed vulnerability report I wrote about the Windows version:

http://samsclass.info/ipv6/proj/flood-router6a.htm

Thanks to ty Justin Hohner for telling me about this.

Fix: 

Mac OS X and Ubuntu Linux are not vulnerable,  because they ignore all RAs after the first ten or so.
How-To-Repeat: To reproduce it, use Linux and the thc-ipv6 tools from http://www.thc.org/thc-ipv6/ 
If you run 

./flood_router6 eth0

on the attacker, a FreeBSD network on the same LAN will freeze.
Comment 1 Mark Linimon freebsd_committer freebsd_triage 2011-05-30 00:39:36 UTC
Responsible Changed
From-To: freebsd-i386->freebsd-net

reclassify.
Comment 2 sam.bowne 2011-05-31 03:16:20 UTC
OpenBSD is not vulnerable, so it could probably be fixed by porting code
from there.
Comment 3 Eitan Adler freebsd_committer freebsd_triage 2013-10-20 21:04:34 UTC
---------- Forwarded message ----------
From: Loganaden Velvindron <logan@elandsys.com>
Date: Mon, Jul 1, 2013 at 4:30 PM
Subject: Re: kern/157410: [ip6] IPv6 Router Advertisements Cause
Excessive CPU Use
To: freebsd-net@freebsd.org
Cc: bz@freebsd.org


On Mon, Jul 01, 2013 at 12:58:23PM -0700, Loganaden Velvindron wrote:
> Hi I came across this old PR. It appears that it's not fixed in -current.
>
> I attempted to port the diff to our FreeBSD 9.1 release machines which
> have IPv6 connectivity and are affected by RA flooding.
>
> I can report that it mitigates RA_flooding.
>
> Feedback welcomed. I'd be happy to polish it so that it can
> make it to 9.2 and 10.0 :-)
>
> I broke down the diffs into separate ones.
>
>

The last diff (nd6_rtr.diff) was garbled and another diff was missing.
I'm resending the whole patchset.

--- in6.c.orig  2013-06-30 23:07:46.000000000 +0400
+++ in6.c       2013-07-01 19:20:15.000000000 +0400
@@ -2694,6 +2694,8 @@ in6_domifattach(struct ifnet *ifp)
        ext->nd_ifinfo = nd6_ifattach(ifp);
        ext->scope6_id = scope6_ifattach(ifp);
        ext->lltable = lltable_init(ifp, AF_INET6);
+       ext->nprefixes = 0;
+       ext->ndefrouters = 0;
        if (ext->lltable != NULL) {
                ext->lltable->llt_free = in6_lltable_free;
                ext->lltable->llt_prefix_free = in6_lltable_prefix_free;

--- in6_proto.c.orig    2013-06-30 23:07:58.000000000 +0400
+++ in6_proto.c 2013-07-01 21:05:08.000000000 +0400
@@ -413,7 +413,8 @@ VNET_DEFINE(int, ip6_rr_prune) = 5; /* r
                                         * walk list every 5 sec. */
 VNET_DEFINE(int, ip6_mcast_pmtu) = 0;  /* enable pMTU discovery for
multicast? */
 VNET_DEFINE(int, ip6_v6only) = 1;
-
+VNET_DEFINE(int, ip6_maxifprefixes) = 16;
+VNET_DEFINE(int, ip6_maxifdefrouters) = 16;
 VNET_DEFINE(int, ip6_keepfaith) = 0;
 VNET_DEFINE(time_t, ip6_log_time) = (time_t)0L;
 #ifdef IPSTEALTH
@@ -524,6 +525,10 @@ SYSCTL_VNET_STRUCT(_net_inet6_ip6, IPV6C
        &VNET_NAME(ip6stat), ip6stat, "");
 SYSCTL_VNET_INT(_net_inet6_ip6, IPV6CTL_MAXFRAGPACKETS, maxfragpackets,
        CTLFLAG_RW, &VNET_NAME(ip6_maxfragpackets), 0, "");
+SYSCTL_VNET_INT(_net_inet6_ip6, IPV6CTL_MAXIFPREFIXES, maxifprefixes,
+        CTLFLAG_RW, &VNET_NAME(ip6_maxifprefixes), 0, "");
+SYSCTL_VNET_INT(_net_inet6_ip6, IPV6CTL_MAXIFDEFROUTERS, maxifdefrouters,
+        CTLFLAG_RW, &VNET_NAME(ip6_maxifdefrouters), 0, "");
 SYSCTL_VNET_INT(_net_inet6_ip6, IPV6CTL_ACCEPT_RTADV, accept_rtadv,
        CTLFLAG_RW, &VNET_NAME(ip6_accept_rtadv), 0,
        "Default value of per-interface flag for accepting ICMPv6 Router"

--- in6_var.h.orig      2013-06-30 23:08:28.000000000 +0400
+++ in6_var.h   2013-07-01 22:38:03.000000000 +0400
@@ -104,6 +104,8 @@ struct in6_ifextra {
        struct scope6_id *scope6_id;
        struct lltable *lltable;
        struct mld_ifinfo *mld_ifinfo;
+       int nprefixes;
+       int ndefrouters;
 };

 #define        LLTABLE6(ifp)   (((struct in6_ifextra
*)(ifp)->if_afdata[AF_INET6])->lltable)

--- ip6_var.h.orig      2013-06-30 23:09:22.000000000 +0400
+++ ip6_var.h   2013-07-01 20:28:30.000000000 +0400
@@ -315,6 +315,8 @@ VNET_DECLARE(int, ip6_maxfragpackets);      /
                                         * queue */
 VNET_DECLARE(int, ip6_maxfrags);       /* Maximum fragments in reassembly
                                         * queue */
+VNET_DECLARE(int, ip6_maxifprefixes);
+VNET_DECLARE(int, ip6_maxifdefrouters);
 VNET_DECLARE(int, ip6_accept_rtadv);   /* Acts as a host not a router */
 VNET_DECLARE(int, ip6_no_radr);                /* No defroute from RA */
 VNET_DECLARE(int, ip6_norbit_raif);    /* Disable R-bit in NA on RA

--- nd6.h.orig  2013-06-30 23:09:42.000000000 +0400
+++ nd6.h       2013-07-01 22:16:09.000000000 +0400
@@ -277,6 +277,7 @@ struct nd_prefix {
        u_char  ndpr_plen;
        int     ndpr_refcnt;    /* reference couter from addresses */
 };
+#define ndpr_next              ndpr_entry.le_next

 #define ndpr_raf               ndpr_flags
 #define ndpr_raf_onlink                ndpr_flags.onlink

--- in6.h.orig  2013-07-02 00:24:36.000000000 +0400
+++ in6.h       2013-07-01 21:58:04.000000000 +0400
@@ -607,7 +607,6 @@ struct ip6_mtuinfo {
 #define IPV6CTL_ISATAPRTR      43      /* isatap router */
 #endif
 #define IPV6CTL_MCAST_PMTU     44      /* enable pMTU discovery for
multicast? */
-
 /* New entries should be added here from current IPV6CTL_MAXID value. */
 /* to define items, should talk with KAME guys first, for *BSD compatibility */
 #define IPV6CTL_STEALTH                45
@@ -619,6 +618,9 @@ struct ip6_mtuinfo {
 #define        IPV6CTL_RFC6204W3       50      /* Accept defroute
even when forwarding
                                           enabled */
 #define        IPV6CTL_MAXID           51
+#define IPV6CTL_MAXIFPREFIXES   52
+#define IPV6CTL_MAXIFDEFROUTERS 53
+
 #endif /* __BSD_VISIBLE */

 /*

--- nd6_rtr.c.orig      2013-06-30 23:10:24.000000000 +0400
+++ nd6_rtr.c   2013-07-01 22:40:29.000000000 +0400
@@ -83,6 +83,7 @@ static void nd6_rtmsg(int, struct rtentr
 static int in6_init_prefix_ltimes(struct nd_prefix *);
 static void in6_init_address_ltimes __P((struct nd_prefix *,
        struct in6_addrlifetime *));
+static void purge_detached(struct ifnet *);

 static int nd6_prefix_onlink(struct nd_prefix *);
 static int nd6_prefix_offlink(struct nd_prefix *);
@@ -565,6 +566,7 @@ defrtrlist_del(struct nd_defrouter *dr)
 {
        struct nd_defrouter *deldr = NULL;
        struct nd_prefix *pr;
+       struct in6_ifextra *ext = dr->ifp->if_afdata[AF_INET6];

        /*
         * Flush all the routing table entries that use the router
@@ -597,6 +599,12 @@ defrtrlist_del(struct nd_defrouter *dr)
        if (deldr)
                defrouter_select();

+       ext->ndefrouters--;
+       if (ext->ndefrouters < 0) {
+               log(LOG_WARNING, "defrtrlist_del: negative count on %s\n",
+                   dr->ifp->if_xname);
+       }
+
        free(dr, M_IP6NDP);
 }

@@ -734,6 +742,7 @@ static struct nd_defrouter *
 defrtrlist_update(struct nd_defrouter *new)
 {
        struct nd_defrouter *dr, *n;
+       struct in6_ifextra *ext = new->ifp->if_afdata[AF_INET6];
        int s = splnet();

        if ((dr = defrouter_lookup(&new->rtaddr, new->ifp)) != NULL) {
@@ -775,6 +784,12 @@ defrtrlist_update(struct nd_defrouter *n
                splx(s);
                return (dr);
        }
+       /*struct in6_ifextra *ext = new->ifp->if_afdata[AF_INET6];*/
+       if (ip6_maxifdefrouters >= 0 &&
+           ext->ndefrouters >= ip6_maxifdefrouters) {
+               splx(s);
+               return (NULL);
+       }

        /* entry does not exist */
        if (new->rtlifetime == 0) {
@@ -810,6 +825,8 @@ insert:

        defrouter_select();

+       ext->ndefrouters++;
+
        splx(s);

        return (n);
@@ -868,6 +885,44 @@ nd6_prefix_lookup(struct nd_prefixctl *k
        return (search);
 }

+static void
+purge_detached(struct ifnet *ifp)
+{
+       struct nd_prefix *pr, *pr_next;
+       struct in6_ifaddr *ia;
+       struct ifaddr *ifa, *ifa_next;
+
+       for (pr = nd_prefix.lh_first; pr; pr = pr_next) {
+               pr_next = pr->ndpr_next;
+
+               /*
+                * This function is called when we need to make more room for
+                * new prefixes rather than keeping old, possibly stale ones.
+                * Detached prefixes would be a good candidate; if all routers
+                * that advertised the prefix expired, the prefix is also
+                * probably stale.
+                */
+               if (pr->ndpr_ifp != ifp ||
+                   IN6_IS_ADDR_LINKLOCAL(&pr->ndpr_prefix.sin6_addr) ||
+                   ((pr->ndpr_stateflags & NDPRF_DETACHED) == 0 &&
+                   !LIST_EMPTY(&pr->ndpr_advrtrs)))
+                       continue;
+
+               for (ifa = ifp->if_addrlist.tqh_first; ifa; ifa = ifa_next) {
+                       ifa_next = ifa->ifa_list.tqe_next;
+                       if (ifa->ifa_addr->sa_family != AF_INET6)
+                               continue;
+                       ia = (struct in6_ifaddr *)ifa;
+                       if ((ia->ia6_flags & IN6_IFF_AUTOCONF) ==
+                           IN6_IFF_AUTOCONF && ia->ia6_ndpr == pr) {
+                               in6_purgeaddr(ifa);
+                       }
+               }
+               if (pr->ndpr_refcnt == 0)
+                       prelist_remove(pr);
+       }
+}
+
 int
 nd6_prelist_add(struct nd_prefixctl *pr, struct nd_defrouter *dr,
     struct nd_prefix **newp)
@@ -876,6 +931,14 @@ nd6_prelist_add(struct nd_prefixctl *pr,
        int error = 0;
        int i, s;
        char ip6buf[INET6_ADDRSTRLEN];
+       struct in6_ifextra *ext = pr->ndpr_ifp->if_afdata[AF_INET6];
+
+       if (ip6_maxifprefixes >= 0) {
+               if (ext->nprefixes >= ip6_maxifprefixes / 2)
+                       purge_detached(pr->ndpr_ifp);
+               if (ext->nprefixes >= ip6_maxifprefixes)
+                       return ENOMEM;
+       }

        new = (struct nd_prefix *)malloc(sizeof(*new), M_IP6NDP, M_NOWAIT);
        if (new == NULL)
@@ -923,7 +986,7 @@ nd6_prelist_add(struct nd_prefixctl *pr,

        if (dr)
                pfxrtr_add(new, dr);
-
+       ext->nprefixes++;
        return 0;
 }

@@ -933,6 +996,7 @@ prelist_remove(struct nd_prefix *pr)
        struct nd_pfxrouter *pfr, *next;
        int e, s;
        char ip6buf[INET6_ADDRSTRLEN];
+       struct in6_ifextra *ext = pr->ndpr_ifp->if_afdata[AF_INET6];

        /* make sure to invalidate the prefix until it is really freed. */
        pr->ndpr_vltime = 0;
@@ -965,6 +1029,12 @@ prelist_remove(struct nd_prefix *pr)
        LIST_FOREACH_SAFE(pfr, &pr->ndpr_advrtrs, pfr_entry, next) {
                free(pfr, M_IP6NDP);
        }
+
+       ext->nprefixes--;
+       if (ext->nprefixes < 0) {
+               log(LOG_WARNING, "prelist_remove: negative count on %s\n",
+                   pr->ndpr_ifp->if_xname);
+       }
        splx(s);

        free(pr, M_IP6NDP);
_______________________________________________
freebsd-net@freebsd.org mailing list
http://lists.freebsd.org/mailman/listinfo/freebsd-net
To unsubscribe, send any mail to "freebsd-net-unsubscribe@freebsd.org"


-- 
Eitan Adler
Comment 4 logan 2014-12-18 18:44:11 UTC
Hi guys,

I'd be happy to follow-up on my original diff if developers are willing to give me their feedback :-)
Comment 5 Mark Felder freebsd_committer 2015-04-08 03:25:43 UTC
Created attachment 155329 [details]
updated patch

I have forward-ported this patch to head

Read carefully: it compiles, but I have not tested this in any way. I also do not know what I'm touching.

If someone could chime in on whether or not this is worth pursuing it would be greatly appreciated. I've known about this PR for a few years and the recent IPv6 SA prompted me to revisit this.
Comment 6 Cy Schubert freebsd_committer 2018-01-05 03:31:37 UTC
This can be tested by installing the nmap port and running:

nmap -6 --script ipv6-ra-flood.nse --script-args 'interface=lagg0'

(Pick your interface on the attacking machine. I used lagg0.)
Comment 7 Cy Schubert freebsd_committer 2018-01-05 03:32:58 UTC
(In reply to Mark Felder from comment #5)

I don't see how it can compile. The function purge_detached is incomplete.
Comment 8 Cy Schubert freebsd_committer 2018-01-05 07:04:00 UTC
Created attachment 189428 [details]
ported to 12-CURRENT r327568M

Tested. It mitigates the bug.

A couple of notes:

1. purge_detached() was incomplete in the original patch. I'm not sure this is
   the right thing to do anyway. It's been ifdef'd out.

2. The default limit should be higher. NetBSD uses 100.

3. Otherwise it needs a little polishing before commit.

Does anyone want me to take ownership of this PR (not that I'm looking for more work) or can we look at otherwise moving this forward?
Comment 9 Ed Maste freebsd_committer 2019-07-04 13:55:32 UTC
(In reply to Cy Schubert from comment #8)
> Does anyone want me to take ownership of this PR

If you're willing I think that would be great!
Comment 10 Bjoern A. Zeeb freebsd_committer 2019-07-04 18:27:59 UTC
This area is on my TODO for the next months anyway.
I'll try to get this sorted before September.
Comment 11 Bjoern A. Zeeb freebsd_committer 2019-11-19 23:00:16 UTC
A bit later .. and years too late.

I've put a first cut of changes into review:
https://reviews.freebsd.org/D22447

People will notice that the diff differs.  The default router and prefix lists are global structures and limits are counted globally (per-VNET).
At the moment the goal is not to panic.  While per-Interface counters can help mitigate an attack better (only 1 interface blocked, might still get legit information via another) we still have to deal with other bogus entires in the tables and the system wont work too nicely in most cases.  In order to prevent some (a lot) more of that the code needs to be proactively a lot more clever and complicated and I haven't convinced myself yet that is worth it.

On another note, the code also has limits on the neighbour table entries, which I didn't see in the patches here.

Redirects are a lot harder to deal with in FreeBSD currently.  While I prepared the stats the best way for the moment would be to simply disable them in case it still is a problem despite other limits.

There is no expiry of bad entries currently.  Once can however, given the system does not panic, flush tables manually ( which, if the attack continues will also not guarantee a working system afterwards ).

Any feedback on this will be welcome.
Comment 12 Bjoern A. Zeeb freebsd_committer 2019-11-20 17:04:51 UTC
(In reply to Bjoern A. Zeeb from comment #11)

I probably should have added that we already have a way to throw away RAs on a per-interface base in ifconfig.  So if we would have multi-interfaces machines under attack by only one interface we could always only just -no_radr or -accept_rtadv savign a lot more of cpu and memory (at the expense that the "good" one will not be received either) but frankly might often be better than all the other garbage in case of DoS.