2024-01-11 15:17:05

by Pavel Tikhomirov

[permalink] [raw]
Subject: [PATCH v3 0/4] netlink: bridge: fix nf_bridge->physindev use after free

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/[email protected]/

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



2024-01-11 15:17:45

by Pavel Tikhomirov

[permalink] [raw]
Subject: [PATCH v3 3/4] netfilter: propagate net to nf_bridge_get_physindev

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 <[email protected]>
---
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


2024-01-15 10:52:45

by Simon Horman

[permalink] [raw]
Subject: Re: [PATCH v3 3/4] netfilter: propagate net to nf_bridge_get_physindev

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 <[email protected]>

Reviewed-by: Simon Horman <[email protected]>


2024-01-17 12:44:18

by Pablo Neira Ayuso

[permalink] [raw]
Subject: Re: [PATCH v3 0/4] netlink: bridge: fix nf_bridge->physindev use after free

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/[email protected]/

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.

2024-01-17 14:20:24

by Florian Westphal

[permalink] [raw]
Subject: Re: [PATCH v3 0/4] netlink: bridge: fix nf_bridge->physindev use after free

Pablo Neira Ayuso <[email protected]> wrote:
> My understanding is that this problem has been always there for
> br_netfilter.

Right.