netdev.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v3 0/4] netlink: bridge: fix nf_bridge->physindev use after free
@ 2024-01-11 15:06 Pavel Tikhomirov
  2024-01-11 15:06 ` [PATCH v3 1/4] netfilter: nfnetlink_log: use proper helper for fetching physinif Pavel Tikhomirov
                   ` (4 more replies)
  0 siblings, 5 replies; 10+ messages in thread
From: Pavel Tikhomirov @ 2024-01-11 15:06 UTC (permalink / raw)
  To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Florian Westphal, David Ahern, Jozsef Kadlecsik, Kees Cook,
	Nikolay Aleksandrov, Pablo Neira Ayuso, Roopa Prabhu
  Cc: netdev, linux-kernel, netfilter-devel, coreteam, bridge, kernel,
	Pavel Tikhomirov

Code processing skb from neigh->arp_queue can access its
nf_bridge->physindev, which can already be freed, leading to crash.

So, as Florian suggests, we can put physinif on nf_bridge and peek into
the original device with dev_get_by_index_rcu(), so that we can be sure
that device is not freed under us.

This is a second attempt to fix this issue, first attempt:

"neighbour: purge nf_bridged skb from foreign device neigh"
https://lore.kernel.org/netdev/20240108085232.95437-1-ptikhomirov@virtuozzo.com/

v3: resend it to proper lists and recipients

Pavel Tikhomirov (4):
  netfilter: nfnetlink_log: use proper helper for fetching physinif
  netfilter: nf_queue: remove excess nf_bridge variable
  netfilter: propagate net to nf_bridge_get_physindev
  netfilter: bridge: replace physindev with physinif in nf_bridge_info

 include/linux/netfilter_bridge.h           |  6 ++--
 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 ++++--
 net/netfilter/ipset/ip_set_hash_netiface.c |  8 ++---
 net/netfilter/nf_log_syslog.c              | 13 +++----
 net/netfilter/nf_queue.c                   |  6 ++--
 net/netfilter/nfnetlink_log.c              |  8 ++---
 net/netfilter/xt_physdev.c                 |  2 +-
 11 files changed, 80 insertions(+), 41 deletions(-)

-- 
2.43.0


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v3 1/4] netfilter: nfnetlink_log: use proper helper for fetching physinif
  2024-01-11 15:06 [PATCH v3 0/4] netlink: bridge: fix nf_bridge->physindev use after free Pavel Tikhomirov
@ 2024-01-11 15:06 ` Pavel Tikhomirov
  2024-01-15 10:51   ` Simon Horman
  2024-01-11 15:06 ` [PATCH v3 2/4] netfilter: nf_queue: remove excess nf_bridge variable Pavel Tikhomirov
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 10+ messages in thread
From: Pavel Tikhomirov @ 2024-01-11 15:06 UTC (permalink / raw)
  To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Florian Westphal, David Ahern, Jozsef Kadlecsik, Kees Cook,
	Nikolay Aleksandrov, Pablo Neira Ayuso, Roopa Prabhu
  Cc: netdev, linux-kernel, netfilter-devel, coreteam, bridge, kernel,
	Pavel Tikhomirov

We don't use physindev in __build_packet_message except for getting
physinif from it. So let's switch to nf_bridge_get_physinif to get what
we want directly.

Signed-off-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>
---
 net/netfilter/nfnetlink_log.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/net/netfilter/nfnetlink_log.c b/net/netfilter/nfnetlink_log.c
index f03f4d4d7d889..134e05d31061e 100644
--- a/net/netfilter/nfnetlink_log.c
+++ b/net/netfilter/nfnetlink_log.c
@@ -508,7 +508,7 @@ __build_packet_message(struct nfnl_log_net *log,
 					 htonl(br_port_get_rcu(indev)->br->dev->ifindex)))
 				goto nla_put_failure;
 		} else {
-			struct net_device *physindev;
+			int physinif;
 
 			/* Case 2: indev is bridge group, we need to look for
 			 * physical device (when called from ipv4) */
@@ -516,10 +516,10 @@ __build_packet_message(struct nfnl_log_net *log,
 					 htonl(indev->ifindex)))
 				goto nla_put_failure;
 
-			physindev = nf_bridge_get_physindev(skb);
-			if (physindev &&
+			physinif = nf_bridge_get_physinif(skb);
+			if (physinif &&
 			    nla_put_be32(inst->skb, NFULA_IFINDEX_PHYSINDEV,
-					 htonl(physindev->ifindex)))
+					 htonl(physinif)))
 				goto nla_put_failure;
 		}
 #endif
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH v3 2/4] netfilter: nf_queue: remove excess nf_bridge variable
  2024-01-11 15:06 [PATCH v3 0/4] netlink: bridge: fix nf_bridge->physindev use after free Pavel Tikhomirov
  2024-01-11 15:06 ` [PATCH v3 1/4] netfilter: nfnetlink_log: use proper helper for fetching physinif Pavel Tikhomirov
@ 2024-01-11 15:06 ` Pavel Tikhomirov
  2024-01-15 10:52   ` Simon Horman
  2024-01-11 15:06 ` [PATCH v3 3/4] netfilter: propagate net to nf_bridge_get_physindev Pavel Tikhomirov
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 10+ messages in thread
From: Pavel Tikhomirov @ 2024-01-11 15:06 UTC (permalink / raw)
  To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Florian Westphal, David Ahern, Jozsef Kadlecsik, Kees Cook,
	Nikolay Aleksandrov, Pablo Neira Ayuso, Roopa Prabhu
  Cc: netdev, linux-kernel, netfilter-devel, coreteam, bridge, kernel,
	Pavel Tikhomirov

We don't really need nf_bridge variable here. And nf_bridge_info_exists
is better replacement for nf_bridge_info_get in case we are only
checking for existence.

Signed-off-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>
---
v3: fix spelling in commit message
---
 net/netfilter/nf_queue.c | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/net/netfilter/nf_queue.c b/net/netfilter/nf_queue.c
index 63d1516816b1f..3dfcb3ac5cb44 100644
--- a/net/netfilter/nf_queue.c
+++ b/net/netfilter/nf_queue.c
@@ -82,10 +82,8 @@ static void __nf_queue_entry_init_physdevs(struct nf_queue_entry *entry)
 {
 #if IS_ENABLED(CONFIG_BRIDGE_NETFILTER)
 	const struct sk_buff *skb = entry->skb;
-	struct nf_bridge_info *nf_bridge;
 
-	nf_bridge = nf_bridge_info_get(skb);
-	if (nf_bridge) {
+	if (nf_bridge_info_exists(skb)) {
 		entry->physin = nf_bridge_get_physindev(skb);
 		entry->physout = nf_bridge_get_physoutdev(skb);
 	} else {
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH v3 3/4] netfilter: propagate net to nf_bridge_get_physindev
  2024-01-11 15:06 [PATCH v3 0/4] netlink: bridge: fix nf_bridge->physindev use after free Pavel Tikhomirov
  2024-01-11 15:06 ` [PATCH v3 1/4] netfilter: nfnetlink_log: use proper helper for fetching physinif Pavel Tikhomirov
  2024-01-11 15:06 ` [PATCH v3 2/4] netfilter: nf_queue: remove excess nf_bridge variable Pavel Tikhomirov
@ 2024-01-11 15:06 ` Pavel Tikhomirov
  2024-01-15 10:52   ` Simon Horman
  2024-01-11 15:06 ` [PATCH v3 4/4] netfilter: bridge: replace physindev with physinif in nf_bridge_info Pavel Tikhomirov
  2024-01-17 12:43 ` [PATCH v3 0/4] netlink: bridge: fix nf_bridge->physindev use after free Pablo Neira Ayuso
  4 siblings, 1 reply; 10+ messages in thread
From: Pavel Tikhomirov @ 2024-01-11 15:06 UTC (permalink / raw)
  To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Florian Westphal, David Ahern, Jozsef Kadlecsik, Kees Cook,
	Nikolay Aleksandrov, Pablo Neira Ayuso, Roopa Prabhu
  Cc: netdev, linux-kernel, netfilter-devel, coreteam, bridge, kernel,
	Pavel Tikhomirov

This is a preparation patch for replacing physindev with physinif on
nf_bridge_info structure. We will use dev_get_by_index_rcu to resolve
device, when needed, and it requires net to be available.

Signed-off-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>
---
v2: remove leftover net propagation to __build_packet_message
---
 include/linux/netfilter_bridge.h           |  2 +-
 net/ipv4/netfilter/nf_reject_ipv4.c        |  2 +-
 net/ipv6/netfilter/nf_reject_ipv6.c        |  2 +-
 net/netfilter/ipset/ip_set_hash_netiface.c |  8 ++++----
 net/netfilter/nf_log_syslog.c              | 13 +++++++------
 net/netfilter/nf_queue.c                   |  2 +-
 net/netfilter/xt_physdev.c                 |  2 +-
 7 files changed, 16 insertions(+), 15 deletions(-)

diff --git a/include/linux/netfilter_bridge.h b/include/linux/netfilter_bridge.h
index f980edfdd2783..e927b9a15a556 100644
--- a/include/linux/netfilter_bridge.h
+++ b/include/linux/netfilter_bridge.h
@@ -56,7 +56,7 @@ static inline int nf_bridge_get_physoutif(const struct sk_buff *skb)
 }
 
 static inline struct net_device *
-nf_bridge_get_physindev(const struct sk_buff *skb)
+nf_bridge_get_physindev(const struct sk_buff *skb, struct net *net)
 {
 	const struct nf_bridge_info *nf_bridge = nf_bridge_info_get(skb);
 
diff --git a/net/ipv4/netfilter/nf_reject_ipv4.c b/net/ipv4/netfilter/nf_reject_ipv4.c
index f01b038fc1cda..86e7d390671af 100644
--- a/net/ipv4/netfilter/nf_reject_ipv4.c
+++ b/net/ipv4/netfilter/nf_reject_ipv4.c
@@ -289,7 +289,7 @@ 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);
+	br_indev = nf_bridge_get_physindev(oldskb, net);
 	if (br_indev) {
 		struct ethhdr *oeth = eth_hdr(oldskb);
 
diff --git a/net/ipv6/netfilter/nf_reject_ipv6.c b/net/ipv6/netfilter/nf_reject_ipv6.c
index d45bc54b7ea55..27b2164f4c439 100644
--- a/net/ipv6/netfilter/nf_reject_ipv6.c
+++ b/net/ipv6/netfilter/nf_reject_ipv6.c
@@ -354,7 +354,7 @@ 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);
+	br_indev = nf_bridge_get_physindev(oldskb, net);
 	if (br_indev) {
 		struct ethhdr *oeth = eth_hdr(oldskb);
 
diff --git a/net/netfilter/ipset/ip_set_hash_netiface.c b/net/netfilter/ipset/ip_set_hash_netiface.c
index 95aeb31c60e0d..30a655e5c4fdc 100644
--- a/net/netfilter/ipset/ip_set_hash_netiface.c
+++ b/net/netfilter/ipset/ip_set_hash_netiface.c
@@ -138,9 +138,9 @@ hash_netiface4_data_next(struct hash_netiface4_elem *next,
 #include "ip_set_hash_gen.h"
 
 #if IS_ENABLED(CONFIG_BRIDGE_NETFILTER)
-static const char *get_physindev_name(const struct sk_buff *skb)
+static const char *get_physindev_name(const struct sk_buff *skb, struct net *net)
 {
-	struct net_device *dev = nf_bridge_get_physindev(skb);
+	struct net_device *dev = nf_bridge_get_physindev(skb, net);
 
 	return dev ? dev->name : NULL;
 }
@@ -177,7 +177,7 @@ hash_netiface4_kadt(struct ip_set *set, const struct sk_buff *skb,
 
 	if (opt->cmdflags & IPSET_FLAG_PHYSDEV) {
 #if IS_ENABLED(CONFIG_BRIDGE_NETFILTER)
-		const char *eiface = SRCDIR ? get_physindev_name(skb) :
+		const char *eiface = SRCDIR ? get_physindev_name(skb, xt_net(par)) :
 					      get_physoutdev_name(skb);
 
 		if (!eiface)
@@ -395,7 +395,7 @@ hash_netiface6_kadt(struct ip_set *set, const struct sk_buff *skb,
 
 	if (opt->cmdflags & IPSET_FLAG_PHYSDEV) {
 #if IS_ENABLED(CONFIG_BRIDGE_NETFILTER)
-		const char *eiface = SRCDIR ? get_physindev_name(skb) :
+		const char *eiface = SRCDIR ? get_physindev_name(skb, xt_net(par)) :
 					      get_physoutdev_name(skb);
 
 		if (!eiface)
diff --git a/net/netfilter/nf_log_syslog.c b/net/netfilter/nf_log_syslog.c
index c66689ad2b491..58402226045e8 100644
--- a/net/netfilter/nf_log_syslog.c
+++ b/net/netfilter/nf_log_syslog.c
@@ -111,7 +111,8 @@ nf_log_dump_packet_common(struct nf_log_buf *m, u8 pf,
 			  unsigned int hooknum, const struct sk_buff *skb,
 			  const struct net_device *in,
 			  const struct net_device *out,
-			  const struct nf_loginfo *loginfo, const char *prefix)
+			  const struct nf_loginfo *loginfo, const char *prefix,
+			  struct net *net)
 {
 	const struct net_device *physoutdev __maybe_unused;
 	const struct net_device *physindev __maybe_unused;
@@ -121,7 +122,7 @@ nf_log_dump_packet_common(struct nf_log_buf *m, u8 pf,
 			in ? in->name : "",
 			out ? out->name : "");
 #if IS_ENABLED(CONFIG_BRIDGE_NETFILTER)
-	physindev = nf_bridge_get_physindev(skb);
+	physindev = nf_bridge_get_physindev(skb, net);
 	if (physindev && in != physindev)
 		nf_log_buf_add(m, "PHYSIN=%s ", physindev->name);
 	physoutdev = nf_bridge_get_physoutdev(skb);
@@ -148,7 +149,7 @@ static void nf_log_arp_packet(struct net *net, u_int8_t pf,
 		loginfo = &default_loginfo;
 
 	nf_log_dump_packet_common(m, pf, hooknum, skb, in, out, loginfo,
-				  prefix);
+				  prefix, net);
 	dump_arp_packet(m, loginfo, skb, skb_network_offset(skb));
 
 	nf_log_buf_close(m);
@@ -845,7 +846,7 @@ static void nf_log_ip_packet(struct net *net, u_int8_t pf,
 		loginfo = &default_loginfo;
 
 	nf_log_dump_packet_common(m, pf, hooknum, skb, in,
-				  out, loginfo, prefix);
+				  out, loginfo, prefix, net);
 
 	if (in)
 		dump_mac_header(m, loginfo, skb);
@@ -880,7 +881,7 @@ static void nf_log_ip6_packet(struct net *net, u_int8_t pf,
 		loginfo = &default_loginfo;
 
 	nf_log_dump_packet_common(m, pf, hooknum, skb, in, out,
-				  loginfo, prefix);
+				  loginfo, prefix, net);
 
 	if (in)
 		dump_mac_header(m, loginfo, skb);
@@ -916,7 +917,7 @@ static void nf_log_unknown_packet(struct net *net, u_int8_t pf,
 		loginfo = &default_loginfo;
 
 	nf_log_dump_packet_common(m, pf, hooknum, skb, in, out, loginfo,
-				  prefix);
+				  prefix, net);
 
 	dump_mac_header(m, loginfo, skb);
 
diff --git a/net/netfilter/nf_queue.c b/net/netfilter/nf_queue.c
index 3dfcb3ac5cb44..e2f334f70281f 100644
--- a/net/netfilter/nf_queue.c
+++ b/net/netfilter/nf_queue.c
@@ -84,7 +84,7 @@ static void __nf_queue_entry_init_physdevs(struct nf_queue_entry *entry)
 	const struct sk_buff *skb = entry->skb;
 
 	if (nf_bridge_info_exists(skb)) {
-		entry->physin = nf_bridge_get_physindev(skb);
+		entry->physin = nf_bridge_get_physindev(skb, entry->state.net);
 		entry->physout = nf_bridge_get_physoutdev(skb);
 	} else {
 		entry->physin = NULL;
diff --git a/net/netfilter/xt_physdev.c b/net/netfilter/xt_physdev.c
index ec6ed6fda96c5..343e65f377d44 100644
--- a/net/netfilter/xt_physdev.c
+++ b/net/netfilter/xt_physdev.c
@@ -59,7 +59,7 @@ physdev_mt(const struct sk_buff *skb, struct xt_action_param *par)
 	    (!!outdev ^ !(info->invert & XT_PHYSDEV_OP_BRIDGED)))
 		return false;
 
-	physdev = nf_bridge_get_physindev(skb);
+	physdev = nf_bridge_get_physindev(skb, xt_net(par));
 	indev = physdev ? physdev->name : NULL;
 
 	if ((info->bitmask & XT_PHYSDEV_OP_ISIN &&
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH v3 4/4] netfilter: bridge: replace physindev with physinif in nf_bridge_info
  2024-01-11 15:06 [PATCH v3 0/4] netlink: bridge: fix nf_bridge->physindev use after free Pavel Tikhomirov
                   ` (2 preceding siblings ...)
  2024-01-11 15:06 ` [PATCH v3 3/4] netfilter: propagate net to nf_bridge_get_physindev Pavel Tikhomirov
@ 2024-01-11 15:06 ` Pavel Tikhomirov
  2024-01-17 12:43 ` [PATCH v3 0/4] netlink: bridge: fix nf_bridge->physindev use after free Pablo Neira Ayuso
  4 siblings, 0 replies; 10+ messages in thread
From: Pavel Tikhomirov @ 2024-01-11 15:06 UTC (permalink / raw)
  To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Florian Westphal, David Ahern, Jozsef Kadlecsik, Kees Cook,
	Nikolay Aleksandrov, Pablo Neira Ayuso, Roopa Prabhu
  Cc: netdev, linux-kernel, netfilter-devel, coreteam, bridge, kernel,
	Pavel Tikhomirov

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.

Suggested-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>
---
I'm not fully sure, but likely it:
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
---
 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 a5ae952454c89..2dde34c29203b 100644
--- a/include/linux/skbuff.h
+++ b/include/linux/skbuff.h
@@ -295,7 +295,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 6adcb45bca75d..ed17208907578 100644
--- a/net/bridge/br_netfilter_hooks.c
+++ b/net/bridge/br_netfilter_hooks.c
@@ -279,8 +279,17 @@ int br_nf_pre_routing_finish_bridge(struct net *net, struct sock *sk, struct sk_
 
 		if ((READ_ONCE(neigh->nud_state) & NUD_CONNECTED) &&
 		    READ_ONCE(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
@@ -352,12 +361,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) {
@@ -397,7 +412,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,
@@ -410,7 +425,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;
@@ -419,7 +434,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,
@@ -456,7 +471,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))
@@ -553,7 +568,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;
@@ -899,6 +918,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;
@@ -908,7 +934,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 2e24a743f9173..e0421eaa3abc7 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 86e7d390671af..04504b2b51df5 100644
--- a/net/ipv4/netfilter/nf_reject_ipv4.c
+++ b/net/ipv4/netfilter/nf_reject_ipv4.c
@@ -239,7 +239,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;
@@ -289,9 +288,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 27b2164f4c439..196dd4ecb5e21 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;
@@ -354,9 +353,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


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* Re: [PATCH v3 1/4] netfilter: nfnetlink_log: use proper helper for fetching physinif
  2024-01-11 15:06 ` [PATCH v3 1/4] netfilter: nfnetlink_log: use proper helper for fetching physinif Pavel Tikhomirov
@ 2024-01-15 10:51   ` Simon Horman
  0 siblings, 0 replies; 10+ messages in thread
From: Simon Horman @ 2024-01-15 10:51 UTC (permalink / raw)
  To: Pavel Tikhomirov
  Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Florian Westphal, David Ahern, Jozsef Kadlecsik, Kees Cook,
	Nikolay Aleksandrov, Pablo Neira Ayuso, Roopa Prabhu, netdev,
	linux-kernel, netfilter-devel, coreteam, bridge, kernel

On Thu, Jan 11, 2024 at 11:06:37PM +0800, Pavel Tikhomirov wrote:
> We don't use physindev in __build_packet_message except for getting
> physinif from it. So let's switch to nf_bridge_get_physinif to get what
> we want directly.
> 
> Signed-off-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>

Reviewed-by: Simon Horman <horms@kernel.org>

...

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v3 2/4] netfilter: nf_queue: remove excess nf_bridge variable
  2024-01-11 15:06 ` [PATCH v3 2/4] netfilter: nf_queue: remove excess nf_bridge variable Pavel Tikhomirov
@ 2024-01-15 10:52   ` Simon Horman
  0 siblings, 0 replies; 10+ messages in thread
From: Simon Horman @ 2024-01-15 10:52 UTC (permalink / raw)
  To: Pavel Tikhomirov
  Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Florian Westphal, David Ahern, Jozsef Kadlecsik, Kees Cook,
	Nikolay Aleksandrov, Pablo Neira Ayuso, Roopa Prabhu, netdev,
	linux-kernel, netfilter-devel, coreteam, bridge, kernel

On Thu, Jan 11, 2024 at 11:06:38PM +0800, Pavel Tikhomirov wrote:
> We don't really need nf_bridge variable here. And nf_bridge_info_exists
> is better replacement for nf_bridge_info_get in case we are only
> checking for existence.
> 
> Signed-off-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>

Reviewed-by: Simon Horman <horms@kernel.org>


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v3 3/4] netfilter: propagate net to nf_bridge_get_physindev
  2024-01-11 15:06 ` [PATCH v3 3/4] netfilter: propagate net to nf_bridge_get_physindev Pavel Tikhomirov
@ 2024-01-15 10:52   ` Simon Horman
  0 siblings, 0 replies; 10+ messages in thread
From: Simon Horman @ 2024-01-15 10:52 UTC (permalink / raw)
  To: Pavel Tikhomirov
  Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Florian Westphal, David Ahern, Jozsef Kadlecsik, Kees Cook,
	Nikolay Aleksandrov, Pablo Neira Ayuso, Roopa Prabhu, netdev,
	linux-kernel, netfilter-devel, coreteam, bridge, kernel

On Thu, Jan 11, 2024 at 11:06:39PM +0800, Pavel Tikhomirov wrote:
> This is a preparation patch for replacing physindev with physinif on
> nf_bridge_info structure. We will use dev_get_by_index_rcu to resolve
> device, when needed, and it requires net to be available.
> 
> Signed-off-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>

Reviewed-by: Simon Horman <horms@kernel.org>


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v3 0/4] netlink: bridge: fix nf_bridge->physindev use after free
  2024-01-11 15:06 [PATCH v3 0/4] netlink: bridge: fix nf_bridge->physindev use after free Pavel Tikhomirov
                   ` (3 preceding siblings ...)
  2024-01-11 15:06 ` [PATCH v3 4/4] netfilter: bridge: replace physindev with physinif in nf_bridge_info Pavel Tikhomirov
@ 2024-01-17 12:43 ` Pablo Neira Ayuso
  2024-01-17 14:15   ` Florian Westphal
  4 siblings, 1 reply; 10+ messages in thread
From: Pablo Neira Ayuso @ 2024-01-17 12:43 UTC (permalink / raw)
  To: Pavel Tikhomirov
  Cc: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Florian Westphal, David Ahern, Jozsef Kadlecsik, Kees Cook,
	Nikolay Aleksandrov, Roopa Prabhu, netdev, linux-kernel,
	netfilter-devel, coreteam, bridge, kernel

On Thu, Jan 11, 2024 at 11:06:36PM +0800, Pavel Tikhomirov wrote:
> Code processing skb from neigh->arp_queue can access its
> nf_bridge->physindev, which can already be freed, leading to crash.
> 
> So, as Florian suggests, we can put physinif on nf_bridge and peek into
> the original device with dev_get_by_index_rcu(), so that we can be sure
> that device is not freed under us.
> 
> This is a second attempt to fix this issue, first attempt:
> 
> "neighbour: purge nf_bridged skb from foreign device neigh"
> https://lore.kernel.org/netdev/20240108085232.95437-1-ptikhomirov@virtuozzo.com/

I have applied this series to nf.git

I have added a Fixed: tag sufficiently old to the patch fix so it can
reach -stable at some point.

My understanding is that this problem has been always there for
br_netfilter.

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH v3 0/4] netlink: bridge: fix nf_bridge->physindev use after free
  2024-01-17 12:43 ` [PATCH v3 0/4] netlink: bridge: fix nf_bridge->physindev use after free Pablo Neira Ayuso
@ 2024-01-17 14:15   ` Florian Westphal
  0 siblings, 0 replies; 10+ messages in thread
From: Florian Westphal @ 2024-01-17 14:15 UTC (permalink / raw)
  To: Pablo Neira Ayuso
  Cc: Pavel Tikhomirov, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Florian Westphal, David Ahern, Jozsef Kadlecsik,
	Kees Cook, Nikolay Aleksandrov, Roopa Prabhu, netdev,
	linux-kernel, netfilter-devel, coreteam, bridge, kernel

Pablo Neira Ayuso <pablo@netfilter.org> wrote:
> My understanding is that this problem has been always there for
> br_netfilter.

Right.

^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2024-01-17 14:15 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-01-11 15:06 [PATCH v3 0/4] netlink: bridge: fix nf_bridge->physindev use after free Pavel Tikhomirov
2024-01-11 15:06 ` [PATCH v3 1/4] netfilter: nfnetlink_log: use proper helper for fetching physinif Pavel Tikhomirov
2024-01-15 10:51   ` Simon Horman
2024-01-11 15:06 ` [PATCH v3 2/4] netfilter: nf_queue: remove excess nf_bridge variable Pavel Tikhomirov
2024-01-15 10:52   ` Simon Horman
2024-01-11 15:06 ` [PATCH v3 3/4] netfilter: propagate net to nf_bridge_get_physindev Pavel Tikhomirov
2024-01-15 10:52   ` Simon Horman
2024-01-11 15:06 ` [PATCH v3 4/4] netfilter: bridge: replace physindev with physinif in nf_bridge_info Pavel Tikhomirov
2024-01-17 12:43 ` [PATCH v3 0/4] netlink: bridge: fix nf_bridge->physindev use after free Pablo Neira Ayuso
2024-01-17 14:15   ` Florian Westphal

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).