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.

As skb->dev can be reset back to nf_bridge->physindev and used, and as
there is no explicit mechanism that prevents this physindev from been
freed under us (for instance neigh_flush_dev doesn't cleanup skbs from
different device's neigh queue) we can 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

Let's use plain ifindex instead of net_device link. To peek into the
original net_device we will use dev_get_by_index_rcu(). Thus either we
get device and are safe to use it or we don't get it and drop skb.

Fixes: c4e70a87d975 ("netfilter: bridge: rename br_netfilter.c to 
br_netfilter_hooks.c")
Suggested-by: Florian Westphal <f...@strlen.de>
Signed-off-by: Pavel Tikhomirov <ptikhomi...@virtuozzo.com>
Signed-off-by: Pablo Neira Ayuso <pa...@netfilter.org>

(cherry picked from commit 9874808878d9eed407e3977fd11fee49de1e1d86)
https://virtuozzo.atlassian.net/browse/PSBM-153269
Signed-off-by: Pavel Tikhomirov <ptikhomi...@virtuozzo.com>
---
 include/linux/netfilter_bridge.h    |  4 +--
 include/linux/skbuff.h              |  2 +-
 net/bridge/br_netfilter_hooks.c     | 42 +++++++++++++++++++++++------
 net/bridge/br_netfilter_ipv6.c      | 14 +++++++---
 net/ipv4/netfilter/nf_reject_ipv4.c |  9 ++++---
 net/ipv6/netfilter/nf_reject_ipv6.c | 11 +++++---
 6 files changed, 61 insertions(+), 21 deletions(-)

diff --git a/include/linux/netfilter_bridge.h b/include/linux/netfilter_bridge.h
index e927b9a15a556..743475ca7e9d5 100644
--- a/include/linux/netfilter_bridge.h
+++ b/include/linux/netfilter_bridge.h
@@ -42,7 +42,7 @@ static inline int nf_bridge_get_physinif(const struct sk_buff 
*skb)
        if (!nf_bridge)
                return 0;
 
-       return nf_bridge->physindev ? nf_bridge->physindev->ifindex : 0;
+       return nf_bridge->physinif;
 }
 
 static inline int nf_bridge_get_physoutif(const struct sk_buff *skb)
@@ -60,7 +60,7 @@ nf_bridge_get_physindev(const struct sk_buff *skb, struct net 
*net)
 {
        const struct nf_bridge_info *nf_bridge = nf_bridge_info_get(skb);
 
-       return nf_bridge ? nf_bridge->physindev : NULL;
+       return nf_bridge ? dev_get_by_index_rcu(net, nf_bridge->physinif) : 
NULL;
 }
 
 static inline struct net_device *
diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h
index a6d26a08a2557..4714ce0afffb3 100644
--- a/include/linux/skbuff.h
+++ b/include/linux/skbuff.h
@@ -263,7 +263,7 @@ struct nf_bridge_info {
        u8                      bridged_dnat:1;
        u8                      sabotage_in_done:1;
        __u16                   frag_max_size;
-       struct net_device       *physindev;
+       int                     physinif;
 
        /* always valid & non-NULL from FORWARD on, for physdev match */
        struct net_device       *physoutdev;
diff --git a/net/bridge/br_netfilter_hooks.c b/net/bridge/br_netfilter_hooks.c
index 4bc6761517bbc..98598672f9382 100644
--- a/net/bridge/br_netfilter_hooks.c
+++ b/net/bridge/br_netfilter_hooks.c
@@ -278,8 +278,17 @@ int br_nf_pre_routing_finish_bridge(struct net *net, 
struct sock *sk, struct sk_
                int ret;
 
                if ((neigh->nud_state & NUD_CONNECTED) && neigh->hh.hh_len) {
+                       struct net_device *br_indev;
+
+                       br_indev = nf_bridge_get_physindev(skb, net);
+                       if (!br_indev) {
+                               neigh_release(neigh);
+                               goto free_skb;
+                       }
+
                        neigh_hh_bridge(&neigh->hh, skb);
-                       skb->dev = nf_bridge->physindev;
+                       skb->dev = br_indev;
+
                        ret = br_handle_frame_finish(net, sk, skb);
                } else {
                        /* the neighbour function below overwrites the complete
@@ -351,12 +360,18 @@ br_nf_ipv4_daddr_was_changed(const struct sk_buff *skb,
  */
 static int br_nf_pre_routing_finish(struct net *net, struct sock *sk, struct 
sk_buff *skb)
 {
-       struct net_device *dev = skb->dev;
+       struct net_device *dev = skb->dev, *br_indev;
        struct iphdr *iph = ip_hdr(skb);
        struct nf_bridge_info *nf_bridge = nf_bridge_info_get(skb);
        struct rtable *rt;
        int err;
 
+       br_indev = nf_bridge_get_physindev(skb, net);
+       if (!br_indev) {
+               kfree_skb(skb);
+               return 0;
+       }
+
        nf_bridge->frag_max_size = IPCB(skb)->frag_max_size;
 
        if (nf_bridge->pkt_otherhost) {
@@ -396,7 +411,7 @@ static int br_nf_pre_routing_finish(struct net *net, struct 
sock *sk, struct sk_
                } else {
                        if (skb_dst(skb)->dev == dev) {
 bridged_dnat:
-                               skb->dev = nf_bridge->physindev;
+                               skb->dev = br_indev;
                                nf_bridge_update_protocol(skb);
                                nf_bridge_push_encap_header(skb);
                                br_nf_hook_thresh(NF_BR_PRE_ROUTING,
@@ -409,7 +424,7 @@ static int br_nf_pre_routing_finish(struct net *net, struct 
sock *sk, struct sk_
                        skb->pkt_type = PACKET_HOST;
                }
        } else {
-               rt = bridge_parent_rtable(nf_bridge->physindev);
+               rt = bridge_parent_rtable(br_indev);
                if (!rt) {
                        kfree_skb(skb);
                        return 0;
@@ -418,7 +433,7 @@ static int br_nf_pre_routing_finish(struct net *net, struct 
sock *sk, struct sk_
                skb_dst_set_noref(skb, &rt->dst);
        }
 
-       skb->dev = nf_bridge->physindev;
+       skb->dev = br_indev;
        nf_bridge_update_protocol(skb);
        nf_bridge_push_encap_header(skb);
        br_nf_hook_thresh(NF_BR_PRE_ROUTING, net, sk, skb, skb->dev, NULL,
@@ -455,7 +470,7 @@ struct net_device *setup_pre_routing(struct sk_buff *skb, 
const struct net *net)
        }
 
        nf_bridge->in_prerouting = 1;
-       nf_bridge->physindev = skb->dev;
+       nf_bridge->physinif = skb->dev->ifindex;
        skb->dev = brnf_get_logical_dev(skb, skb->dev, net);
 
        if (skb->protocol == htons(ETH_P_8021Q))
@@ -552,7 +567,11 @@ static int br_nf_forward_finish(struct net *net, struct 
sock *sk, struct sk_buff
                if (skb->protocol == htons(ETH_P_IPV6))
                        nf_bridge->frag_max_size = IP6CB(skb)->frag_max_size;
 
-               in = nf_bridge->physindev;
+               in = nf_bridge_get_physindev(skb, net);
+               if (!in) {
+                       kfree_skb(skb);
+                       return 0;
+               }
                if (nf_bridge->pkt_otherhost) {
                        skb->pkt_type = PACKET_OTHERHOST;
                        nf_bridge->pkt_otherhost = false;
@@ -896,6 +915,13 @@ static unsigned int ip_sabotage_in(void *priv,
 static void br_nf_pre_routing_finish_bridge_slow(struct sk_buff *skb)
 {
        struct nf_bridge_info *nf_bridge = nf_bridge_info_get(skb);
+       struct net_device *br_indev;
+
+       br_indev = nf_bridge_get_physindev(skb, dev_net(skb->dev));
+       if (!br_indev) {
+               kfree_skb(skb);
+               return;
+       }
 
        skb_pull(skb, ETH_HLEN);
        nf_bridge->bridged_dnat = 0;
@@ -905,7 +931,7 @@ static void br_nf_pre_routing_finish_bridge_slow(struct 
sk_buff *skb)
        skb_copy_to_linear_data_offset(skb, -(ETH_HLEN - ETH_ALEN),
                                       nf_bridge->neigh_header,
                                       ETH_HLEN - ETH_ALEN);
-       skb->dev = nf_bridge->physindev;
+       skb->dev = br_indev;
 
        nf_bridge->physoutdev = NULL;
        br_handle_frame_finish(dev_net(skb->dev), NULL, skb);
diff --git a/net/bridge/br_netfilter_ipv6.c b/net/bridge/br_netfilter_ipv6.c
index 550039dfc31a9..ad268bd19d5b0 100644
--- a/net/bridge/br_netfilter_ipv6.c
+++ b/net/bridge/br_netfilter_ipv6.c
@@ -102,9 +102,15 @@ static int br_nf_pre_routing_finish_ipv6(struct net *net, 
struct sock *sk, struc
 {
        struct nf_bridge_info *nf_bridge = nf_bridge_info_get(skb);
        struct rtable *rt;
-       struct net_device *dev = skb->dev;
+       struct net_device *dev = skb->dev, *br_indev;
        const struct nf_ipv6_ops *v6ops = nf_get_ipv6_ops();
 
+       br_indev = nf_bridge_get_physindev(skb, net);
+       if (!br_indev) {
+               kfree_skb(skb);
+               return 0;
+       }
+
        nf_bridge->frag_max_size = IP6CB(skb)->frag_max_size;
 
        if (nf_bridge->pkt_otherhost) {
@@ -122,7 +128,7 @@ static int br_nf_pre_routing_finish_ipv6(struct net *net, 
struct sock *sk, struc
                }
 
                if (skb_dst(skb)->dev == dev) {
-                       skb->dev = nf_bridge->physindev;
+                       skb->dev = br_indev;
                        nf_bridge_update_protocol(skb);
                        nf_bridge_push_encap_header(skb);
                        br_nf_hook_thresh(NF_BR_PRE_ROUTING,
@@ -133,7 +139,7 @@ static int br_nf_pre_routing_finish_ipv6(struct net *net, 
struct sock *sk, struc
                ether_addr_copy(eth_hdr(skb)->h_dest, dev->dev_addr);
                skb->pkt_type = PACKET_HOST;
        } else {
-               rt = bridge_parent_rtable(nf_bridge->physindev);
+               rt = bridge_parent_rtable(br_indev);
                if (!rt) {
                        kfree_skb(skb);
                        return 0;
@@ -142,7 +148,7 @@ static int br_nf_pre_routing_finish_ipv6(struct net *net, 
struct sock *sk, struc
                skb_dst_set_noref(skb, &rt->dst);
        }
 
-       skb->dev = nf_bridge->physindev;
+       skb->dev = br_indev;
        nf_bridge_update_protocol(skb);
        nf_bridge_push_encap_header(skb);
        br_nf_hook_thresh(NF_BR_PRE_ROUTING, net, sk, skb,
diff --git a/net/ipv4/netfilter/nf_reject_ipv4.c 
b/net/ipv4/netfilter/nf_reject_ipv4.c
index 63c91aa2375fb..4aac392f98916 100644
--- a/net/ipv4/netfilter/nf_reject_ipv4.c
+++ b/net/ipv4/netfilter/nf_reject_ipv4.c
@@ -237,7 +237,6 @@ static int nf_reject_fill_skb_dst(struct sk_buff *skb_in)
 void nf_send_reset(struct net *net, struct sock *sk, struct sk_buff *oldskb,
                   int hook)
 {
-       struct net_device *br_indev __maybe_unused;
        struct sk_buff *nskb;
        struct iphdr *niph;
        const struct tcphdr *oth;
@@ -286,9 +285,13 @@ void nf_send_reset(struct net *net, struct sock *sk, 
struct sk_buff *oldskb,
         * build the eth header using the original destination's MAC as the
         * source, and send the RST packet directly.
         */
-       br_indev = nf_bridge_get_physindev(oldskb, net);
-       if (br_indev) {
+       if (nf_bridge_info_exists(oldskb)) {
                struct ethhdr *oeth = eth_hdr(oldskb);
+               struct net_device *br_indev;
+
+               br_indev = nf_bridge_get_physindev(oldskb, net);
+               if (!br_indev)
+                       goto free_nskb;
 
                nskb->dev = br_indev;
                niph->tot_len = htons(nskb->len);
diff --git a/net/ipv6/netfilter/nf_reject_ipv6.c 
b/net/ipv6/netfilter/nf_reject_ipv6.c
index b138ee4ebc6a2..cda37265bd394 100644
--- a/net/ipv6/netfilter/nf_reject_ipv6.c
+++ b/net/ipv6/netfilter/nf_reject_ipv6.c
@@ -278,7 +278,6 @@ static int nf_reject6_fill_skb_dst(struct sk_buff *skb_in)
 void nf_send_reset6(struct net *net, struct sock *sk, struct sk_buff *oldskb,
                    int hook)
 {
-       struct net_device *br_indev __maybe_unused;
        struct sk_buff *nskb;
        struct tcphdr _otcph;
        const struct tcphdr *otcph;
@@ -353,9 +352,15 @@ void nf_send_reset6(struct net *net, struct sock *sk, 
struct sk_buff *oldskb,
         * build the eth header using the original destination's MAC as the
         * source, and send the RST packet directly.
         */
-       br_indev = nf_bridge_get_physindev(oldskb, net);
-       if (br_indev) {
+       if (nf_bridge_info_exists(oldskb)) {
                struct ethhdr *oeth = eth_hdr(oldskb);
+               struct net_device *br_indev;
+
+               br_indev = nf_bridge_get_physindev(oldskb, net);
+               if (!br_indev) {
+                       kfree_skb(nskb);
+                       return;
+               }
 
                nskb->dev = br_indev;
                nskb->protocol = htons(ETH_P_IPV6);
-- 
2.43.0

_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel

Reply via email to