Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

neigh: fix possible DoS due to net iface start/stop loop

Normal processing of ARP request (usually this is Ethernet broadcast
packet) coming to the host is looking like the following:
* the packet comes to arp_process() call and is passed through routing
procedure
* the request is put into the queue using pneigh_enqueue() if
corresponding ARP record is not local (common case for container
records on the host)
* the request is processed by timer (within 80 jiffies by default) and
ARP reply is sent from the same arp_process() using
NEIGH_CB(skb)->flags & LOCALLY_ENQUEUED condition (flag is set inside
pneigh_enqueue())

And here the problem comes. Linux kernel calls pneigh_queue_purge()
which destroys the whole queue of ARP requests on ANY network interface
start/stop event through __neigh_ifdown().

This is actually not a problem within the original world as network
interface start/stop was accessible to the host 'root' only, which
could do more destructive things. But the world is changed and there
are Linux containers available. Here container 'root' has an access
to this API and could be considered as untrusted user in the hosting
(container's) world.

Thus there is an attack vector to other containers on node when
container's root will endlessly start/stop interfaces. We have observed
similar situation on a real production node when docker container was
doing such activity and thus other containers on the node become not
accessible.

The patch proposed doing very simple thing. It drops only packets from
the same namespace in the pneigh_queue_purge() where network interface
state change is detected. This is enough to prevent the problem for the
whole node preserving original semantics of the code.

v2:
- do del_timer_sync() if queue is empty after pneigh_queue_purge()
v3:
- rebase to net tree

Cc: "David S. Miller" <davem@davemloft.net>
Cc: Eric Dumazet <edumazet@google.com>
Cc: Jakub Kicinski <kuba@kernel.org>
Cc: Paolo Abeni <pabeni@redhat.com>
Cc: Daniel Borkmann <daniel@iogearbox.net>
Cc: David Ahern <dsahern@kernel.org>
Cc: Yajun Deng <yajun.deng@linux.dev>
Cc: Roopa Prabhu <roopa@nvidia.com>
Cc: Christian Brauner <brauner@kernel.org>
Cc: netdev@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: Alexey Kuznetsov <kuznet@ms2.inr.ac.ru>
Cc: Alexander Mikhalitsyn <alexander.mikhalitsyn@virtuozzo.com>
Cc: Konstantin Khorenko <khorenko@virtuozzo.com>
Cc: kernel@openvz.org
Cc: devel@openvz.org
Investigated-by: Alexander Mikhalitsyn <alexander.mikhalitsyn@virtuozzo.com>
Signed-off-by: Denis V. Lunev <den@openvz.org>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Denis V. Lunev and committed by
David S. Miller
66ba215c 68a838b8

+17 -8
+17 -8
net/core/neighbour.c
··· 307 307 return 0; 308 308 } 309 309 310 - static void pneigh_queue_purge(struct sk_buff_head *list) 310 + static void pneigh_queue_purge(struct sk_buff_head *list, struct net *net) 311 311 { 312 + unsigned long flags; 312 313 struct sk_buff *skb; 313 314 314 - while ((skb = skb_dequeue(list)) != NULL) { 315 - dev_put(skb->dev); 316 - kfree_skb(skb); 315 + spin_lock_irqsave(&list->lock, flags); 316 + skb = skb_peek(list); 317 + while (skb != NULL) { 318 + struct sk_buff *skb_next = skb_peek_next(skb, list); 319 + if (net == NULL || net_eq(dev_net(skb->dev), net)) { 320 + __skb_unlink(skb, list); 321 + dev_put(skb->dev); 322 + kfree_skb(skb); 323 + } 324 + skb = skb_next; 317 325 } 326 + spin_unlock_irqrestore(&list->lock, flags); 318 327 } 319 328 320 329 static void neigh_flush_dev(struct neigh_table *tbl, struct net_device *dev, ··· 394 385 write_lock_bh(&tbl->lock); 395 386 neigh_flush_dev(tbl, dev, skip_perm); 396 387 pneigh_ifdown_and_unlock(tbl, dev); 397 - 398 - del_timer_sync(&tbl->proxy_timer); 399 - pneigh_queue_purge(&tbl->proxy_queue); 388 + pneigh_queue_purge(&tbl->proxy_queue, dev_net(dev)); 389 + if (skb_queue_empty_lockless(&tbl->proxy_queue)) 390 + del_timer_sync(&tbl->proxy_timer); 400 391 return 0; 401 392 } 402 393 ··· 1796 1787 cancel_delayed_work_sync(&tbl->managed_work); 1797 1788 cancel_delayed_work_sync(&tbl->gc_work); 1798 1789 del_timer_sync(&tbl->proxy_timer); 1799 - pneigh_queue_purge(&tbl->proxy_queue); 1790 + pneigh_queue_purge(&tbl->proxy_queue, NULL); 1800 1791 neigh_ifdown(tbl, NULL); 1801 1792 if (atomic_read(&tbl->entries)) 1802 1793 pr_crit("neighbour leakage\n");