From nobody Sun Feb 8 07:58:07 2026 Received: from mail-07.mail-europe.com (mail-0701.mail-europe.com [51.83.17.38]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6AAE0410D0F for ; Thu, 5 Feb 2026 15:10:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=51.83.17.38 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770304253; cv=none; b=pRuQEnkZNsTipDViQ0VM9JiySNre8eC8SPpkiJX8s084uZKKwXsmNJP3u7X5a0rVjlhMJmKAtJxEmQuSthfLeZNtGYRWOQ3XYcoGV8G7NeapMFYF7uIzGO3BGi0ozAsWX6Z6SGeYFoLhSC1DHda66CZHaXhKYNAmrRKQopGJ5H4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770304253; c=relaxed/simple; bh=1ZI8PXcbopErwpROotj6cgRwxst5oQWaFvOmqEXxAj8=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=YSZsvufXom/65PT31QE0TV3b0m4Anne8LGneW4Y9fV2iOx9QyxXmfO1V6yqsii1AYqdZDwdJqx+Ue8OfdD6G34Ms8yIbnZz6pstUIZAgVy9MUCqKzeoQoVvrA6xt9R7zzKQbaPCAMWI/knc1CfjUzD5CB2akWeVd1S8NkODS+y8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=1g4.org; spf=fail smtp.mailfrom=1g4.org; dkim=pass (2048-bit key) header.d=1g4.org header.i=@1g4.org header.b=YuFDusG3; arc=none smtp.client-ip=51.83.17.38 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=1g4.org Authentication-Results: smtp.subspace.kernel.org; spf=fail smtp.mailfrom=1g4.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=1g4.org header.i=@1g4.org header.b="YuFDusG3" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1g4.org; s=protonmail2; t=1770304236; x=1770563436; bh=+gmsEdMi/ENpyGWI2zFCC94ey4BsmPCY9p3jP0IbOnw=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: Feedback-ID:From:To:Cc:Date:Subject:Reply-To:Feedback-ID: Message-ID:BIMI-Selector; b=YuFDusG3U9oWn3SZhqz/Ml55JVbN7UpjdPaTbnU3Vef1pY3mj60WSFt87Oaq+28XG NlB7G0q+kbai5KU/bJglpuqnzfdt+ntHd1WBZbHAMJofa30foN55q4iiCxFmdlrzEt xIKgwZ49rHYe7cgquTyukus56yhO0u65cx+RQC28Ed+o4tcKx5tVy44HT7YURRyIES ceU64HwIbd02jbS/OMQ7btNvYQE+gq9b1JPOejM6NHqko4d1hpha4ULkh+kaHFaBQp Nvc46anQ7xTHR6kRY/2+5yOVxgNgoA3pn4dPsauV/lwibb4RDy8viQsGBiJFrJl5er lnllHMYKuSrtg== Date: Thu, 05 Feb 2026 15:10:29 +0000 To: Victor Nogueira , Jamal Hadi Salim , Cong Wang , Jiri Pirko From: Paul Moses Cc: "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , netdev@vger.kernel.org, linux-kernel@vger.kernel.org, Paul Moses , stable@vger.kernel.org Subject: [PATCH net v5 1/1] net/sched: act_gate: snapshot parameters with RCU on replace Message-ID: <20260205150958.412278-2-p@1g4.org> In-Reply-To: <20260205150958.412278-1-p@1g4.org> References: <20260205150958.412278-1-p@1g4.org> Feedback-ID: 8253658:user:proton X-Pm-Message-ID: 7df58eefb53ff899aa961e6a97f82f477f111abe Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The gate action can be replaced while the hrtimer callback or dump path is walking the schedule list. Convert the parameters to an RCU-protected snapshot and swap updates under tcf_lock, freeing the previous snapshot via call_rcu(). When REPLACE omits the entry list, preserve the existing schedule so the effective state is unchanged. Fixes: a51c328df310 ("net: qos: introduce a gate control flow action") Cc: stable@vger.kernel.org Signed-off-by: Paul Moses --- include/net/tc_act/tc_gate.h | 33 ++++- net/sched/act_gate.c | 266 ++++++++++++++++++++++++++--------- 2 files changed, 228 insertions(+), 71 deletions(-) diff --git a/include/net/tc_act/tc_gate.h b/include/net/tc_act/tc_gate.h index c1a67149c6b62..006111b978eda 100644 --- a/include/net/tc_act/tc_gate.h +++ b/include/net/tc_act/tc_gate.h @@ -32,6 +32,7 @@ struct tcf_gate_params { s32 tcfg_clockid; size_t num_entries; struct list_head entries; + struct rcu_head rcu; }; =20 #define GATE_ACT_GATE_OPEN BIT(0) @@ -39,7 +40,7 @@ struct tcf_gate_params { =20 struct tcf_gate { struct tc_action common; - struct tcf_gate_params param; + struct tcf_gate_params __rcu *param; u8 current_gate_status; ktime_t current_close_time; u32 current_entry_octets; @@ -51,47 +52,65 @@ struct tcf_gate { =20 #define to_gate(a) ((struct tcf_gate *)a) =20 +static inline struct tcf_gate_params *tcf_gate_params(const struct tc_acti= on *a) +{ + struct tcf_gate *gact =3D to_gate(a); + + return rcu_dereference_protected(gact->param, + lockdep_is_held(&gact->tcf_lock)); +} + static inline s32 tcf_gate_prio(const struct tc_action *a) { + struct tcf_gate_params *p; s32 tcfg_prio; =20 - tcfg_prio =3D to_gate(a)->param.tcfg_priority; + p =3D tcf_gate_params(a); + tcfg_prio =3D p->tcfg_priority; =20 return tcfg_prio; } =20 static inline u64 tcf_gate_basetime(const struct tc_action *a) { + struct tcf_gate_params *p; u64 tcfg_basetime; =20 - tcfg_basetime =3D to_gate(a)->param.tcfg_basetime; + p =3D tcf_gate_params(a); + tcfg_basetime =3D p->tcfg_basetime; =20 return tcfg_basetime; } =20 static inline u64 tcf_gate_cycletime(const struct tc_action *a) { + struct tcf_gate_params *p; u64 tcfg_cycletime; =20 - tcfg_cycletime =3D to_gate(a)->param.tcfg_cycletime; + p =3D tcf_gate_params(a); + tcfg_cycletime =3D p->tcfg_cycletime; =20 return tcfg_cycletime; } =20 static inline u64 tcf_gate_cycletimeext(const struct tc_action *a) { + struct tcf_gate_params *p; u64 tcfg_cycletimeext; =20 - tcfg_cycletimeext =3D to_gate(a)->param.tcfg_cycletime_ext; + p =3D tcf_gate_params(a); + tcfg_cycletimeext =3D p->tcfg_cycletime_ext; =20 return tcfg_cycletimeext; } =20 static inline u32 tcf_gate_num_entries(const struct tc_action *a) { + struct tcf_gate_params *p; u32 num_entries; =20 - num_entries =3D to_gate(a)->param.num_entries; + p =3D tcf_gate_params(a); + num_entries =3D p->num_entries; =20 return num_entries; } @@ -105,7 +124,7 @@ static inline struct action_gate_entry u32 num_entries; int i =3D 0; =20 - p =3D &to_gate(a)->param; + p =3D tcf_gate_params(a); num_entries =3D p->num_entries; =20 list_for_each_entry(entry, &p->entries, list) diff --git a/net/sched/act_gate.c b/net/sched/act_gate.c index c1f75f2727576..4a1a10bfe3e62 100644 --- a/net/sched/act_gate.c +++ b/net/sched/act_gate.c @@ -32,9 +32,12 @@ static ktime_t gate_get_time(struct tcf_gate *gact) return KTIME_MAX; } =20 -static void gate_get_start_time(struct tcf_gate *gact, ktime_t *start) +static void tcf_gate_params_free_rcu(struct rcu_head *head); + +static void gate_get_start_time(struct tcf_gate *gact, + const struct tcf_gate_params *param, + ktime_t *start) { - struct tcf_gate_params *param =3D &gact->param; ktime_t now, base, cycle; u64 n; =20 @@ -56,11 +59,10 @@ static void gate_start_timer(struct tcf_gate *gact, kti= me_t start) { ktime_t expires; =20 - expires =3D hrtimer_get_expires(&gact->hitimer); - if (expires =3D=3D 0) - expires =3D KTIME_MAX; - - start =3D min_t(ktime_t, start, expires); + if (hrtimer_active(&gact->hitimer)) { + expires =3D hrtimer_get_expires(&gact->hitimer); + start =3D min_t(ktime_t, start, expires); + } =20 hrtimer_start(&gact->hitimer, start, HRTIMER_MODE_ABS_SOFT); } @@ -69,12 +71,14 @@ static enum hrtimer_restart gate_timer_func(struct hrti= mer *timer) { struct tcf_gate *gact =3D container_of(timer, struct tcf_gate, hitimer); - struct tcf_gate_params *p =3D &gact->param; struct tcfg_gate_entry *next; + struct tcf_gate_params *p; ktime_t close_time, now; =20 spin_lock(&gact->tcf_lock); =20 + p =3D rcu_dereference_protected(gact->param, + lockdep_is_held(&gact->tcf_lock)); next =3D gact->next_entry; =20 /* cycle start, clear pending bit, clear total octets */ @@ -225,6 +229,37 @@ static void release_entry_list(struct list_head *entri= es) } } =20 +static int tcf_gate_copy_entries(struct tcf_gate_params *dst, + const struct tcf_gate_params *src, + struct netlink_ext_ack *extack) +{ + struct tcfg_gate_entry *entry; + int i =3D 0; + + list_for_each_entry(entry, &src->entries, list) { + struct tcfg_gate_entry *new; + + new =3D kzalloc(sizeof(*new), GFP_ATOMIC); + if (!new) { + NL_SET_ERR_MSG(extack, "Not enough memory for entry"); + return -ENOMEM; + } + + new->index =3D entry->index; + new->gate_state =3D entry->gate_state; + new->interval =3D entry->interval; + new->ipv =3D entry->ipv; + new->maxoctets =3D entry->maxoctets; + INIT_LIST_HEAD(&new->list); + list_add_tail(&new->list, &dst->entries); + i++; + } + + dst->num_entries =3D i; + + return 0; +} + static int parse_gate_list(struct nlattr *list_attr, struct tcf_gate_params *sched, struct netlink_ext_ack *extack) @@ -270,22 +305,27 @@ static int parse_gate_list(struct nlattr *list_attr, return err; } =20 -static void gate_setup_timer(struct tcf_gate *gact, u64 basetime, - enum tk_offsets tko, s32 clockid, - bool do_init) +static bool gate_timer_needs_cancel(u64 basetime, u64 old_basetime, + enum tk_offsets tko, + enum tk_offsets old_tko, + s32 clockid, s32 old_clockid, bool do_init) { - if (!do_init) { - if (basetime =3D=3D gact->param.tcfg_basetime && - tko =3D=3D gact->tk_offset && - clockid =3D=3D gact->param.tcfg_clockid) - return; + if (do_init) + return false; =20 - spin_unlock_bh(&gact->tcf_lock); - hrtimer_cancel(&gact->hitimer); - spin_lock_bh(&gact->tcf_lock); - } - gact->param.tcfg_basetime =3D basetime; - gact->param.tcfg_clockid =3D clockid; + if (basetime !=3D old_basetime) + return true; + if (clockid !=3D old_clockid) + return true; + if (tko !=3D old_tko) + return true; + + return false; +} + +static void gate_timer_setup(struct tcf_gate *gact, s32 clockid, + enum tk_offsets tko) +{ gact->tk_offset =3D tko; hrtimer_setup(&gact->hitimer, gate_timer_func, clockid, HRTIMER_MODE_ABS_= SOFT); } @@ -296,15 +336,22 @@ static int tcf_gate_init(struct net *net, struct nlat= tr *nla, struct netlink_ext_ack *extack) { struct tc_action_net *tn =3D net_generic(net, act_gate_ops.net_id); + u64 cycletime =3D 0, basetime =3D 0, cycletime_ext =3D 0; + struct tcf_gate_params *p =3D NULL, *old_p =3D NULL; + enum tk_offsets old_tk_offset =3D TK_OFFS_TAI; enum tk_offsets tk_offset =3D TK_OFFS_TAI; bool bind =3D flags & TCA_ACT_FLAGS_BIND; struct nlattr *tb[TCA_GATE_MAX + 1]; struct tcf_chain *goto_ch =3D NULL; - u64 cycletime =3D 0, basetime =3D 0; - struct tcf_gate_params *p; + bool clockid_provided =3D false; + bool use_old_entries =3D false; + s32 old_clockid =3D CLOCK_TAI; + bool need_cancel =3D false; s32 clockid =3D CLOCK_TAI; struct tcf_gate *gact; + bool do_init =3D false; struct tc_gate *parm; + u64 old_basetime =3D 0; int ret =3D 0, err; u32 gflags =3D 0; s32 prio =3D -1; @@ -323,20 +370,11 @@ static int tcf_gate_init(struct net *net, struct nlat= tr *nla, =20 if (tb[TCA_GATE_CLOCKID]) { clockid =3D nla_get_s32(tb[TCA_GATE_CLOCKID]); - switch (clockid) { - case CLOCK_REALTIME: - tk_offset =3D TK_OFFS_REAL; - break; - case CLOCK_MONOTONIC: - tk_offset =3D TK_OFFS_MAX; - break; - case CLOCK_BOOTTIME: - tk_offset =3D TK_OFFS_BOOT; - break; - case CLOCK_TAI: - tk_offset =3D TK_OFFS_TAI; - break; - default: + clockid_provided =3D true; + if (clockid !=3D CLOCK_REALTIME && + clockid !=3D CLOCK_MONOTONIC && + clockid !=3D CLOCK_BOOTTIME && + clockid !=3D CLOCK_TAI) { NL_SET_ERR_MSG(extack, "Invalid 'clockid'"); return -EINVAL; } @@ -366,6 +404,37 @@ static int tcf_gate_init(struct net *net, struct nlatt= r *nla, return -EEXIST; } =20 + gact =3D to_gate(*a); + err =3D tcf_action_check_ctrlact(parm->action, tp, &goto_ch, extack); + if (err < 0) + goto release_idr; + + if (ret !=3D ACT_P_CREATED) { + rcu_read_lock(); + old_p =3D rcu_dereference(gact->param); + if (old_p) { + basetime =3D old_p->tcfg_basetime; + prio =3D old_p->tcfg_priority; + gflags =3D old_p->tcfg_flags; + if (!clockid_provided) + clockid =3D old_p->tcfg_clockid; + cycletime_ext =3D old_p->tcfg_cycletime_ext; + old_basetime =3D old_p->tcfg_basetime; + old_clockid =3D old_p->tcfg_clockid; + } + do_init =3D !old_p; + old_tk_offset =3D READ_ONCE(gact->tk_offset); + rcu_read_unlock(); + old_p =3D NULL; + } + + p =3D kzalloc(sizeof(*p), GFP_KERNEL); + if (!p) { + err =3D -ENOMEM; + goto release_idr; + } + INIT_LIST_HEAD(&p->entries); + if (tb[TCA_GATE_PRIORITY]) prio =3D nla_get_s32(tb[TCA_GATE_PRIORITY]); =20 @@ -375,24 +444,71 @@ static int tcf_gate_init(struct net *net, struct nlat= tr *nla, if (tb[TCA_GATE_FLAGS]) gflags =3D nla_get_u32(tb[TCA_GATE_FLAGS]); =20 - gact =3D to_gate(*a); - if (ret =3D=3D ACT_P_CREATED) - INIT_LIST_HEAD(&gact->param.entries); + if (tb[TCA_GATE_CYCLE_TIME]) + cycletime =3D nla_get_u64(tb[TCA_GATE_CYCLE_TIME]); =20 - err =3D tcf_action_check_ctrlact(parm->action, tp, &goto_ch, extack); - if (err < 0) - goto release_idr; + if (tb[TCA_GATE_CYCLE_TIME_EXT]) + cycletime_ext =3D nla_get_u64(tb[TCA_GATE_CYCLE_TIME_EXT]); + + switch (clockid) { + case CLOCK_REALTIME: + tk_offset =3D TK_OFFS_REAL; + break; + case CLOCK_MONOTONIC: + tk_offset =3D TK_OFFS_MAX; + break; + case CLOCK_BOOTTIME: + tk_offset =3D TK_OFFS_BOOT; + break; + case CLOCK_TAI: + tk_offset =3D TK_OFFS_TAI; + break; + default: + NL_SET_ERR_MSG(extack, "Invalid 'clockid'"); + err =3D -EINVAL; + goto err_free; + } =20 - spin_lock_bh(&gact->tcf_lock); - p =3D &gact->param; + need_cancel =3D gate_timer_needs_cancel(basetime, old_basetime, + tk_offset, old_tk_offset, + clockid, old_clockid, + ret =3D=3D ACT_P_CREATED || do_init); =20 - if (tb[TCA_GATE_CYCLE_TIME]) - cycletime =3D nla_get_u64(tb[TCA_GATE_CYCLE_TIME]); + if (need_cancel) + hrtimer_cancel(&gact->hitimer); + + spin_lock_bh(&gact->tcf_lock); + if (ret !=3D ACT_P_CREATED) + old_p =3D rcu_dereference_protected(gact->param, + lockdep_is_held(&gact->tcf_lock)); =20 if (tb[TCA_GATE_ENTRY_LIST]) { err =3D parse_gate_list(tb[TCA_GATE_ENTRY_LIST], p, extack); if (err < 0) - goto chain_put; + goto unlock; + if (!err) { + if (ret =3D=3D ACT_P_CREATED) { + NL_SET_ERR_MSG(extack, "The entry list is empty"); + err =3D -EINVAL; + goto unlock; + } + use_old_entries =3D true; + } + } else if (ret =3D=3D ACT_P_CREATED) { + NL_SET_ERR_MSG(extack, "The entry list is empty"); + err =3D -EINVAL; + goto unlock; + } else { + use_old_entries =3D true; + } + + if (use_old_entries) { + err =3D tcf_gate_copy_entries(p, old_p, extack); + if (err) + goto unlock; + + if (!tb[TCA_GATE_CYCLE_TIME]) + cycletime =3D old_p->tcfg_cycletime; } =20 if (!cycletime) { @@ -404,20 +520,22 @@ static int tcf_gate_init(struct net *net, struct nlat= tr *nla, cycletime =3D cycle; if (!cycletime) { err =3D -EINVAL; - goto chain_put; + goto unlock; } } p->tcfg_cycletime =3D cycletime; + p->tcfg_cycletime_ext =3D cycletime_ext; =20 - if (tb[TCA_GATE_CYCLE_TIME_EXT]) - p->tcfg_cycletime_ext =3D - nla_get_u64(tb[TCA_GATE_CYCLE_TIME_EXT]); - - gate_setup_timer(gact, basetime, tk_offset, clockid, - ret =3D=3D ACT_P_CREATED); + if (need_cancel || ret =3D=3D ACT_P_CREATED) + gate_timer_setup(gact, clockid, tk_offset); p->tcfg_priority =3D prio; p->tcfg_flags =3D gflags; - gate_get_start_time(gact, &start); + p->tcfg_basetime =3D basetime; + p->tcfg_clockid =3D clockid; + gate_get_start_time(gact, p, &start); + + old_p =3D rcu_replace_pointer(gact->param, p, + lockdep_is_held(&gact->tcf_lock)); =20 gact->current_close_time =3D start; gact->current_gate_status =3D GATE_ACT_GATE_OPEN | GATE_ACT_PENDING; @@ -434,23 +552,41 @@ static int tcf_gate_init(struct net *net, struct nlat= tr *nla, if (goto_ch) tcf_chain_put_by_act(goto_ch); =20 + if (old_p) + call_rcu(&old_p->rcu, tcf_gate_params_free_rcu); + return ret; =20 -chain_put: +unlock: spin_unlock_bh(&gact->tcf_lock); =20 if (goto_ch) tcf_chain_put_by_act(goto_ch); + release_entry_list(&p->entries); + kfree(p); release_idr: /* action is not inserted in any list: it's safe to init hitimer * without taking tcf_lock. */ if (ret =3D=3D ACT_P_CREATED) - gate_setup_timer(gact, gact->param.tcfg_basetime, - gact->tk_offset, gact->param.tcfg_clockid, - true); + gate_timer_setup(gact, clockid, tk_offset); tcf_idr_release(*a, bind); return err; + +err_free: + if (goto_ch) + tcf_chain_put_by_act(goto_ch); + release_entry_list(&p->entries); + kfree(p); + goto release_idr; +} + +static void tcf_gate_params_free_rcu(struct rcu_head *head) +{ + struct tcf_gate_params *p =3D container_of(head, struct tcf_gate_params, = rcu); + + release_entry_list(&p->entries); + kfree(p); } =20 static void tcf_gate_cleanup(struct tc_action *a) @@ -458,9 +594,10 @@ static void tcf_gate_cleanup(struct tc_action *a) struct tcf_gate *gact =3D to_gate(a); struct tcf_gate_params *p; =20 - p =3D &gact->param; hrtimer_cancel(&gact->hitimer); - release_entry_list(&p->entries); + p =3D rcu_replace_pointer(gact->param, NULL, 1); + if (p) + call_rcu(&p->rcu, tcf_gate_params_free_rcu); } =20 static int dumping_entry(struct sk_buff *skb, @@ -512,7 +649,8 @@ static int tcf_gate_dump(struct sk_buff *skb, struct tc= _action *a, spin_lock_bh(&gact->tcf_lock); opt.action =3D gact->tcf_action; =20 - p =3D &gact->param; + p =3D rcu_dereference_protected(gact->param, + lockdep_is_held(&gact->tcf_lock)); =20 if (nla_put(skb, TCA_GATE_PARMS, sizeof(opt), &opt)) goto nla_put_failure; --=20 2.52.GIT