From: Simon Horman <horms@kernel.org>
To: p@1g4.org
Cc: Simon Horman <horms@kernel.org>,
jiri@resnulli.us, pabeni@redhat.com, victor@mojatatu.com,
jhs@mojatatu.com, davem@davemloft.net, netdev@vger.kernel.org,
stable@vger.kernel.org, edumazet@google.com,
linux-kernel@vger.kernel.org, kuba@kernel.org,
xiyou.wangcong@gmail.com
Subject: Re: [net,v6,1/1] net/sched: act_gate: snapshot parameters with RCU on replace
Date: Fri, 13 Feb 2026 15:55:28 +0000 [thread overview]
Message-ID: <20260213155528.2515066-1-horms@kernel.org> (raw)
In-Reply-To: <20260213113849.136695-2-p@1g4.org>
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
For local reproduction steps see https://netdev-ai.bots.linux.dev/ai-local.html
---
net/sched: act_gate: snapshot parameters with RCU on replace
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.
> diff --git a/include/net/tc_act/tc_gate.h b/include/net/tc_act/tc_gate.h
> index c1a67149c6b6..5223c00279d5 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;
> };
>
> #define GATE_ACT_GATE_OPEN BIT(0)
> @@ -39,7 +40,7 @@ struct tcf_gate_params {
>
> 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 {
>
> #define to_gate(a) ((struct tcf_gate *)a)
>
> +static inline struct tcf_gate_params *tcf_gate_params_locked(const struct tc_action *a)
> +{
> + struct tcf_gate *gact = 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;
>
> - tcfg_prio = to_gate(a)->param.tcfg_priority;
> + p = tcf_gate_params_locked(a);
> + tcfg_prio = p->tcfg_priority;
>
> return tcfg_prio;
> }
[ ... ]
> diff --git a/net/sched/act_gate.c b/net/sched/act_gate.c
> index c1f75f272757..60c80e609ec3 100644
> --- a/net/sched/act_gate.c
> +++ b/net/sched/act_gate.c
[ ... ]
> @@ -509,10 +621,9 @@ static int tcf_gate_dump(struct sk_buff *skb, struct tc_action *a,
> struct nlattr *entry_list;
> struct tcf_t t;
>
> - spin_lock_bh(&gact->tcf_lock);
> - opt.action = gact->tcf_action;
> -
> - p = &gact->param;
> + rcu_read_lock();
> + opt.action = READ_ONCE(gact->tcf_action);
> + p = rcu_dereference(gact->param);
>
> if (nla_put(skb, TCA_GATE_PARMS, sizeof(opt), &opt))
> goto nla_put_failure;
>
> @@ -552,12 +663,12 @@ static int tcf_gate_dump(struct sk_buff *skb, struct tc_action *a,
> tcf_tm_dump(&t, &gact->tcf_tm);
> if (nla_put_64bit(skb, TCA_GATE_TM, sizeof(t), &t, TCA_GATE_PAD))
> goto nla_put_failure;
> - spin_unlock_bh(&gact->tcf_lock);
> + rcu_read_unlock();
>
> return skb->len;
>
> nla_put_failure:
> - spin_unlock_bh(&gact->tcf_lock);
> + rcu_read_unlock();
> nlmsg_trim(skb, b);
> return -1;
> }
Can tcf_gate_dump be called before gact->param is initialized?
After the conversion to RCU pointers, if tcf_gate_dump is called during
action initialization (between tcf_idr_create_from_flags and
rcu_replace_pointer in tcf_gate_init), p will be NULL:
net/sched/act_gate.c:tcf_gate_init() {
tcf_idr_create_from_flags(...);
...
// gact->param still NULL here
...
rcu_replace_pointer(gact->param, p, ...); // line 517
}
net/sched/act_gate.c:tcf_gate_dump() {
rcu_read_lock();
p = rcu_dereference(gact->param); // can be NULL
if (nla_put_u64_64bit(skb, TCA_GATE_BASE_TIME,
p->tcfg_basetime, TCA_GATE_PAD)) // NULL deref
}
Before the patch, param was an embedded struct that was zero-initialized,
so dump would return valid (if meaningless) data. After the patch, this
becomes a NULL pointer dereference.
While the tc action framework may prevent dumping partially initialized
actions through external synchronization, should this code include a NULL
check to make the initialization window safer?
The second issue is in the hardware offload path. Looking at
net/sched/act_gate.c:tcf_gate_offload_act_setup(), the function calls the
accessor helpers without holding tcf_lock:
net/sched/act_gate.c:tcf_gate_offload_act_setup() {
// no lock held here
entry->gate.prio = tcf_gate_prio(act); // line 723
entry->gate.basetime = tcf_gate_basetime(act);
entry->gate.cycletime = tcf_gate_cycletime(act);
...
}
include/net/tc_act/tc_gate.h:tcf_gate_prio() {
p = tcf_gate_params_locked(a); // uses rcu_dereference_protected
...
}
include/net/tc_act/tc_gate.h:tcf_gate_params_locked() {
return rcu_dereference_protected(gact->param,
lockdep_is_held(&gact->tcf_lock));
}
Does tcf_gate_offload_act_setup violate the locking requirements?
The accessor functions all use tcf_gate_params_locked(), which requires
tcf_lock to be held (verified via lockdep_is_held). The offload setup
function doesn't acquire this lock before calling the accessors.
With lockdep enabled, this will trigger warnings. Without lockdep, there's
a race where param can be replaced via rcu_replace_pointer while the
offload function is reading it, potentially causing reads of inconsistent
state.
The dump path uses the correct pattern with rcu_read_lock() and
rcu_dereference(). Should the offload path either acquire tcf_lock or use
a similar RCU-only approach?
next prev parent reply other threads:[~2026-02-13 15:55 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-02-13 11:38 [PATCH net v6 0/1] net/sched: act_gate: snapshot parameters with RCU on replace Paul Moses
2026-02-13 11:39 ` [PATCH net v6 1/1] " Paul Moses
2026-02-13 15:55 ` Simon Horman [this message]
2026-02-13 17:37 ` [net,v6,1/1] " Paul Moses
2026-02-18 13:32 ` Jamal Hadi Salim
2026-02-15 20:45 ` [PATCH net v6 1/1] " Victor Nogueira
2026-02-15 23:59 ` Paul Moses
2026-02-16 11:45 ` Victor Nogueira
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260213155528.2515066-1-horms@kernel.org \
--to=horms@kernel.org \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=jhs@mojatatu.com \
--cc=jiri@resnulli.us \
--cc=kuba@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=netdev@vger.kernel.org \
--cc=p@1g4.org \
--cc=pabeni@redhat.com \
--cc=stable@vger.kernel.org \
--cc=victor@mojatatu.com \
--cc=xiyou.wangcong@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox