@@ -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);
}
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(-)