]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
ipvs: stop estimator after disabled calc phase
authorZhiling Zou <zhilinz@nebusec.ai>
Wed, 29 Jul 2026 13:56:59 +0000 (21:56 +0800)
committerPablo Neira Ayuso <pablo@netfilter.org>
Fri, 31 Jul 2026 13:56:23 +0000 (15:56 +0200)
IPVS estimator kthread 0 starts with zeroed chain and tick limits until
its initial calculation phase completes. If network namespace teardown
clears ipvs->enable during that phase, ip_vs_est_calc_phase() can return
without installing positive limits.

The kthread can then continue into its main loop and drain
est_temp_list with zero chain_max, tick_max and est_max_count values.
Each enqueue consumes one available tick row, but est_count never
reaches the zero est_max_count value. After all rows are consumed, the
row lookup returns IPVS_EST_NTICKS and ip_vs_enqueue_estimator() writes
past the ticks and tick_len arrays.

Exit kthread 0 after the calculation phase if the kthread is stopping or
IPVS has been disabled. That keeps temporary estimators from being
drained after the limits failed to initialize.

Estimator kthreads can now self-exit before teardown or reload stops
kd->task. Keep an extra task reference after creation and release it
with kthread_stop_put(), so kd->task remains valid until the stop paths
consume that reference.

Fixes: 705dd3444081 ("ipvs: use kthreads for stats estimation")
Cc: stable@vger.kernel.org
Reported-by: Vega <vega@nebusec.ai>
Signed-off-by: Zhiling Zou <zhilinz@nebusec.ai>
Acked-by: Julian Anastasov <ja@ssi.bg>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
net/netfilter/ipvs/ip_vs_est.c

index ab09f5182951224d9fe143f6f290fc96beb0cb55..05a216a47b45e3da0c54830517e26e10d4c289a1 100644 (file)
@@ -191,8 +191,11 @@ static int ip_vs_estimation_kthread(void *data)
                }
 
                /* kthread 0 will handle the calc phase */
-               if (ipvs->est_calc_phase)
+               if (ipvs->est_calc_phase) {
                        ip_vs_est_calc_phase(ipvs);
+                       if (kthread_should_stop() || !READ_ONCE(ipvs->enable))
+                               return 0;
+               }
        }
 
        while (1) {
@@ -270,6 +273,7 @@ int ip_vs_est_kthread_start(struct netns_ipvs *ipvs,
                kd->task = NULL;
                goto out;
        }
+       get_task_struct(kd->task);
 
        set_user_nice(kd->task, sysctl_est_nice(ipvs));
        if (sysctl_est_preferred_cpulist(ipvs))
@@ -286,7 +290,7 @@ void ip_vs_est_kthread_stop(struct ip_vs_est_kt_data *kd)
 {
        if (kd->task) {
                pr_info("stopping estimator thread %d...\n", kd->id);
-               kthread_stop(kd->task);
+               kthread_stop_put(kd->task);
                kd->task = NULL;
        }
 }
@@ -526,7 +530,7 @@ static void ip_vs_est_kthread_destroy(struct ip_vs_est_kt_data *kd)
        if (kd) {
                if (kd->task) {
                        pr_info("stop unused estimator thread %d...\n", kd->id);
-                       kthread_stop(kd->task);
+                       kthread_stop_put(kd->task);
                }
                ip_vs_stats_free(kd->calc_stats);
                kfree(kd);