No upstream commit exists for this patch.
The file ipt_CLUSTERIP.c was deleted in commit 9db5d918e2c0 ("netfilter: ip_tables: remove clusterip target"), but races still exist in stable branches.
Thread A in clusterip_config_entry_put() decrements refcount and removes config from the list but doesn't call proc_remove(). Thread B searches the config, doesn't find it, re-adds it to the list, and calls proc_create_data() with an IP that still exists in the tree.
|A| refcount_dec_and_lock() |A| list_del_rcu() === Must be here |A| proc_remove() === |B| __clusterip_config_find() |B| list_add_rcu() |B| proc_create_data() |B| WARN()
As a fix, also cover with mutex the places of interaction with the list of configs before proc_remove() and proc_create_data() functions.
------------[ cut here ]------------ proc_dir_entry 'ipt_CLUSTERIP/100.1.1.2' already registered WARNING: CPU: 0 PID: 2597 at fs/proc/generic.c:381 proc_register+0x517/0x6e0 fs/proc/generic.c:381 [...] Call Trace: proc_create_data+0x130/0x1a0 fs/proc/generic.c:583 clusterip_config_init net/ipv4/netfilter/ipt_CLUSTERIP.c:281 [inline] clusterip_tg_check+0xb8d/0x1380 net/ipv4/netfilter/ipt_CLUSTERIP.c:502 xt_check_target+0x27c/0xa00 net/netfilter/x_tables.c:1018 check_target net/ipv4/netfilter/ip_tables.c:511 [inline] find_check_entry.constprop.0+0x7b0/0x9b0 net/ipv4/netfilter/ip_tables.c:553 translate_table+0xc6a/0x16a0 net/ipv4/netfilter/ip_tables.c:717 do_replace net/ipv4/netfilter/ip_tables.c:1138 [inline] do_ipt_set_ctl+0x54e/0xb00 net/ipv4/netfilter/ip_tables.c:1636 nf_setsockopt+0x88/0xf0 net/netfilter/nf_sockopt.c:101 ip_setsockopt+0xe4d/0x3d10 net/ipv4/ip_sockglue.c:1442 sctp_setsockopt+0x135/0xa390 net/sctp/socket.c:4475 __sys_setsockopt+0x234/0x5a0 net/socket.c:2145 __do_sys_setsockopt net/socket.c:2156 [inline] __se_sys_setsockopt net/socket.c:2153 [inline] __x64_sys_setsockopt+0xb9/0x150 net/socket.c:2153 do_syscall_64+0x30/0x40 arch/x86/entry/common.c:46 entry_SYSCALL_64_after_hwframe+0x67/0xd1
Found by Linux Verification Center (linuxtesting.org) with Syzkaller.
Fixes: 2a61d8b883bb ("netfilter: ipt_CLUSTERIP: fix sleep-in-atomic bug in clusterip_config_entry_put()") Cc: stable@vger.kernel.org # v5.4+ Suggested-by: Fedor Pchelkin pchelkin@ispras.ru Suggested-by: Alexey Khoroshilov khoroshilov@ispras.ru Signed-off-by: Evgeny Pimenov pimenoveu12@gmail.com --- net/ipv4/netfilter/ipt_CLUSTERIP.c | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-)
diff --git a/net/ipv4/netfilter/ipt_CLUSTERIP.c b/net/ipv4/netfilter/ipt_CLUSTERIP.c index 77e3b67e8790..61ccfd97841f 100644 --- a/net/ipv4/netfilter/ipt_CLUSTERIP.c +++ b/net/ipv4/netfilter/ipt_CLUSTERIP.c @@ -79,6 +79,22 @@ static inline struct clusterip_net *clusterip_pernet(struct net *net) return net_generic(net, clusterip_net_id); }
+static inline void +clusterip_net_lock(struct clusterip_net *cn) +{ +#ifdef CONFIG_PROC_FS + mutex_lock(&cn->mutex); +#endif +}; + +static inline void +clusterip_net_unlock(struct clusterip_net *cn) +{ +#ifdef CONFIG_PROC_FS + mutex_unlock(&cn->mutex); +#endif +}; + static inline void clusterip_config_get(struct clusterip_config *c) { @@ -114,6 +130,7 @@ clusterip_config_entry_put(struct clusterip_config *c) { struct clusterip_net *cn = clusterip_pernet(c->net);
+ clusterip_net_lock(cn); local_bh_disable(); if (refcount_dec_and_lock(&c->entries, &cn->lock)) { list_del_rcu(&c->list); @@ -123,14 +140,14 @@ clusterip_config_entry_put(struct clusterip_config *c) * functions are also incrementing the refcount on their own, * so it's safe to remove the entry even if it's in use. */ #ifdef CONFIG_PROC_FS - mutex_lock(&cn->mutex); if (cn->procdir) proc_remove(c->pde); - mutex_unlock(&cn->mutex); #endif + clusterip_net_unlock(cn); return; } local_bh_enable(); + clusterip_net_unlock(cn); }
static struct clusterip_config * @@ -262,6 +279,7 @@ clusterip_config_init(struct net *net, const struct ipt_clusterip_tgt_info *i, c->net = net; refcount_set(&c->refcount, 1);
+ clusterip_net_lock(cn); spin_lock_bh(&cn->lock); if (__clusterip_config_find(net, ip)) { err = -EBUSY; @@ -277,11 +295,9 @@ clusterip_config_init(struct net *net, const struct ipt_clusterip_tgt_info *i,
/* create proc dir entry */ sprintf(buffer, "%pI4", &ip); - mutex_lock(&cn->mutex); c->pde = proc_create_data(buffer, 0600, cn->procdir, &clusterip_proc_ops, c); - mutex_unlock(&cn->mutex); if (!c->pde) { err = -ENOMEM; goto err; @@ -290,6 +306,7 @@ clusterip_config_init(struct net *net, const struct ipt_clusterip_tgt_info *i, #endif
refcount_set(&c->entries, 1); + clusterip_net_unlock(cn); return c;
#ifdef CONFIG_PROC_FS @@ -300,6 +317,7 @@ clusterip_config_init(struct net *net, const struct ipt_clusterip_tgt_info *i, out_config_put: spin_unlock_bh(&cn->lock); clusterip_config_put(c); + clusterip_net_unlock(cn); return ERR_PTR(err); }
@@ -848,10 +866,10 @@ static void clusterip_net_exit(struct net *net) #ifdef CONFIG_PROC_FS struct clusterip_net *cn = clusterip_pernet(net);
- mutex_lock(&cn->mutex); + clusterip_net_lock(cn); proc_remove(cn->procdir); cn->procdir = NULL; - mutex_unlock(&cn->mutex); + clusterip_net_unlock(cn); #endif nf_unregister_net_hook(net, &cip_arp_ops); }