An skb can be added to a neigh->arp_queue while waiting for an arp reply. Where original skb's skb->dev can be different to neigh's neigh->dev. For instance in case of bridging dnated skb from one veth to another, the skb would be added to a neigh->arp_queue of the bridge.
There is no explicit mechanism that prevents the original skb->dev link of such skb from being freed under us. For instance neigh_flush_dev does not cleanup skbs from different device's neigh queue. But that original link can be used and lead to crash on e.g. this stack: arp_process neigh_update skb = __skb_dequeue(&neigh->arp_queue) neigh_resolve_output(..., skb) ... br_nf_dev_xmit br_nf_pre_routing_finish_bridge_slow skb->dev = nf_bridge->physindev br_handle_frame_finish So let's improve neigh_flush_dev to also purge skbs when device equal to their skb->nf_bridge->physindev gets destroyed. https://virtuozzo.atlassian.net/browse/PSBM-151735 Signed-off-by: Pavel Tikhomirov <ptikhomi...@virtuozzo.com> --- v2: take neigh->lock for queue modification --- net/core/neighbour.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/net/core/neighbour.c b/net/core/neighbour.c index 7f197a63c780f..2aa1c23a7f44d 100644 --- a/net/core/neighbour.c +++ b/net/core/neighbour.c @@ -43,6 +43,10 @@ #include <net/addrconf.h> #include <bc/beancounter.h> +#include <linux/skbuff.h> +#include <linux/netfilter.h> +#include <net/netfilter/br_netfilter.h> + #define DEBUG #define NEIGH_DEBUG 1 #define neigh_dbg(level, fmt, ...) \ @@ -257,6 +261,27 @@ static void pneigh_queue_purge(struct sk_buff_head *list, struct net *net) } } +static void neigh_purge_nf_bridge_dev(struct neighbour *neigh, struct net_device *dev) +{ + struct sk_buff_head *list = &neigh->arp_queue; + struct sk_buff *skb = skb_peek(list), *next; + struct nf_bridge_info *nf_bridge; + + write_lock(&neigh->lock); + while (skb) { + nf_bridge = nf_bridge_info_get(skb); + + next = skb_peek_next(skb, list); + if (nf_bridge && nf_bridge->physindev == dev) { + __skb_unlink(skb, list); + neigh->arp_queue_len_bytes -= skb->truesize; + kfree_skb(skb); + } + skb = next; + } + write_unlock(&neigh->lock); +} + static void neigh_flush_dev(struct neigh_table *tbl, struct net_device *dev) { int i; @@ -272,6 +297,7 @@ static void neigh_flush_dev(struct neigh_table *tbl, struct net_device *dev) while ((n = rcu_dereference_protected(*np, lockdep_is_held(&tbl->lock))) != NULL) { if (dev && n->dev != dev) { + neigh_purge_nf_bridge_dev(n, dev); np = &n->next; continue; } -- 2.43.0 _______________________________________________ Devel mailing list Devel@openvz.org https://lists.openvz.org/mailman/listinfo/devel