]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
ipvs: fix races around the conn_lfactor and svc_lfactor sysctl vars
authorJulian Anastasov <ja@ssi.bg>
Thu, 30 Apr 2026 07:44:14 +0000 (10:44 +0300)
committerPablo Neira Ayuso <pablo@netfilter.org>
Mon, 4 May 2026 23:52:55 +0000 (01:52 +0200)
Sashiko warns that the new sysctls vars can be changed
after the hash tables are destroyed and their respective
resizing works canceled, leading to mod_delayed_work()
being called for canceled works.

Solve this in different ways. conn_tab can be present even
without services and is destroyed only on netns exit, so use
disable_delayed_work_sync() to disable the work instead of
adding more synchronization mechanisms.

As for the svc_table, it is destroyed when the services
are deleted, so we must be sure that netns exit is not
called yet (the check for 'enable') and the work is
not canceled by checking all under same mutex lock.

Also, use WRITE_ONCE when updating the sysctl vars as we
already read them with READ_ONCE.

Link: https://sashiko.dev/#/patchset/20260410112352.23599-1-fw%40strlen.de
Fixes: 8d7de5477e47 ("ipvs: add conn_lfactor and svc_lfactor sysctl vars")
Signed-off-by: Julian Anastasov <ja@ssi.bg>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
net/netfilter/ipvs/ip_vs_conn.c
net/netfilter/ipvs/ip_vs_ctl.c

index 2082bfb2d93cdf41eb67681854da9a74e3a682a7..84a4921a7865a91decebccfe958e5232cccf13a1 100644 (file)
@@ -1835,7 +1835,7 @@ static void ip_vs_conn_flush(struct netns_ipvs *ipvs)
 
        if (!rcu_dereference_protected(ipvs->conn_tab, 1))
                return;
-       cancel_delayed_work_sync(&ipvs->conn_resize_work);
+       disable_delayed_work_sync(&ipvs->conn_resize_work);
        if (!atomic_read(&ipvs->conn_count))
                goto unreg;
 
index 27e50afe9a545dbbcc55d6627f7b94d8dae47349..caec516856e9534aa3edc27922a36fec36f2ba63 100644 (file)
@@ -2469,7 +2469,7 @@ static int ipvs_proc_conn_lfactor(const struct ctl_table *table, int write,
                if (val < -8 || val > 8) {
                        ret = -EINVAL;
                } else {
-                       *valp = val;
+                       WRITE_ONCE(*valp, val);
                        if (rcu_access_pointer(ipvs->conn_tab))
                                mod_delayed_work(system_unbound_wq,
                                                 &ipvs->conn_resize_work, 0);
@@ -2496,10 +2496,16 @@ static int ipvs_proc_svc_lfactor(const struct ctl_table *table, int write,
                if (val < -8 || val > 8) {
                        ret = -EINVAL;
                } else {
-                       *valp = val;
-                       if (rcu_access_pointer(ipvs->svc_table))
+                       mutex_lock(&ipvs->service_mutex);
+                       WRITE_ONCE(*valp, val);
+                       /* Make sure the services are present */
+                       if (rcu_access_pointer(ipvs->svc_table) &&
+                           READ_ONCE(ipvs->enable) &&
+                           !test_bit(IP_VS_WORK_SVC_NORESIZE,
+                                     &ipvs->work_flags))
                                mod_delayed_work(system_unbound_wq,
                                                 &ipvs->svc_resize_work, 0);
+                       mutex_unlock(&ipvs->service_mutex);
                }
        }
        return ret;