From nobody Sun Feb 8 07:14:32 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 9EB20283FDD for ; Wed, 4 Feb 2026 13:25:12 +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=1770211513; cv=none; b=Bwo4BySx8EQ+RgW8UQNoSroOcEOAZyitK7uUEfyQBpq8iMiRd2qaVuAQ1a52icsg1Leg3F2IbA04nPYpf7FAQo3i7iVn2W4mSelv1k152ay2506kN9UL+gJBjlLhS8HXNEJwaiSBLbkPkHZxHIEjXF74jNosqunIjPeJkbJebOI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770211513; c=relaxed/simple; bh=KV0CbcjqjCo0Ob9YhLNc58d7FjNioB9tS11V3MkHbqk=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=MHNWXYrQjeMZVwIF7jBjFdjsEN7XTO0iGvlyraJXS+OuZwEKQZtfEKjUcE4TaxcY+S4KrG+C9uEpbWD47GosYHDuabvOpflaeu2/swcZyk6h35UERnlle/NHpvXeGSBH981oER4qwdnQrPWpXEr20oCzCqOZtm7JtbbUjDJa2IE= 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=CJny7VRR; 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="CJny7VRR" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1g4.org; s=protonmail2; t=1770211497; x=1770470697; bh=k8JHG7Yfy12K/FHVi/TvyMV5PKtsYDKSsR1JOZ0H4yk=; 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=CJny7VRRIkzMjK/8YuiNx8EfLJd+aemTxR1D33QmgX3SIkoDlT9XVq5E9nQ47cFEQ 8TDVRa/l+IqOvfRwVyqgda40H9+/V0MllMjq7fv2RURLMxSKLO357cymnplMP+855t REmHlT+ASVVwZdJ9erypnZltezOS6WugBnVB+FzMxbFtbET4O9YSFx6P2kbtiJOM9F 5GBXc3LmuYWp63iOdSxDg+q6fd267+y7DNm12hQozG+wiBiL3k96cXBRL47c3hLg2z /bLpc+dT0sD8fo3+gPwv75sNN8JHDa3L4gL7V33lmEJIeX+TMG9XCoLU02j4mmra2F s+YEyXIsSRFlA== Date: Wed, 04 Feb 2026 13:24:53 +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 v4 1/1] net/sched: act_gate: protect parameters with RCU on replace Message-ID: <20260204132428.224465-2-p@1g4.org> In-Reply-To: <20260204132428.224465-1-p@1g4.org> References: <20260204132428.224465-1-p@1g4.org> Feedback-ID: 8253658:user:proton X-Pm-Message-ID: 74d262b01d1b9b65f8b16d77ed67c7a6061460ef 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" Convert act_gate parameters to an RCU protected snapshot. Allocate a new snapshot on CREATE and REPLACE, swap it under tcf_lock, and free the old snapshot via call_rcu() to avoid races with the hrtimer callback and the dump path. 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 | 29 +++- net/sched/act_gate.c | 300 ++++++++++++++++++++++++++--------- 2 files changed, 246 insertions(+), 83 deletions(-) diff --git a/include/net/tc_act/tc_gate.h b/include/net/tc_act/tc_gate.h index c1a67149c6b62..5f4fc407f7bf6 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,60 @@ struct tcf_gate { =20 #define to_gate(a) ((struct tcf_gate *)a) =20 +static inline struct tcf_gate_params *tcf_gate_params_locked(const struct = tc_action *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 =3D tcf_gate_params_locked(a); s32 tcfg_prio; =20 - tcfg_prio =3D to_gate(a)->param.tcfg_priority; + 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 =3D tcf_gate_params_locked(a); u64 tcfg_basetime; =20 - tcfg_basetime =3D to_gate(a)->param.tcfg_basetime; + 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 =3D tcf_gate_params_locked(a); u64 tcfg_cycletime; =20 - tcfg_cycletime =3D to_gate(a)->param.tcfg_cycletime; + 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 =3D tcf_gate_params_locked(a); u64 tcfg_cycletimeext; =20 - tcfg_cycletimeext =3D to_gate(a)->param.tcfg_cycletime_ext; + 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 =3D tcf_gate_params_locked(a); u32 num_entries; =20 - num_entries =3D to_gate(a)->param.num_entries; + num_entries =3D p->num_entries; =20 return num_entries; } @@ -100,12 +114,11 @@ static inline struct action_gate_entry *tcf_gate_get_list(const struct tc_action *a) { struct action_gate_entry *oe; - struct tcf_gate_params *p; + struct tcf_gate_params *p =3D tcf_gate_params_locked(a); struct tcfg_gate_entry *entry; u32 num_entries; int i =3D 0; =20 - p =3D &to_gate(a)->param; 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..e93b77edd0694 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 @@ -69,12 +72,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 tcf_gate_params *p; struct tcfg_gate_entry *next; 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 +230,42 @@ 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, *new; + int i =3D 0; + + list_for_each_entry(entry, &src->entries, list) { + new =3D kzalloc(sizeof(*new), GFP_KERNEL); + if (!new) { + NL_SET_ERR_MSG(extack, "Not enough memory for entry"); + goto err_free; + } + + 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; + + list_add_tail(&new->list, &dst->entries); + i++; + } + + dst->num_entries =3D i; + return 0; + +err_free: + list_for_each_entry_safe(new, entry, &dst->entries, list) { + list_del(&new->list); + kfree(new); + } + dst->num_entries =3D 0; + return -ENOMEM; +} + static int parse_gate_list(struct nlattr *list_attr, struct tcf_gate_params *sched, struct netlink_ext_ack *extack) @@ -243,7 +284,7 @@ static int parse_gate_list(struct nlattr *list_attr, continue; } =20 - entry =3D kzalloc(sizeof(*entry), GFP_ATOMIC); + entry =3D kzalloc(sizeof(*entry), GFP_KERNEL); if (!entry) { NL_SET_ERR_MSG(extack, "Not enough memory for entry"); err =3D -ENOMEM; @@ -266,6 +307,7 @@ static int parse_gate_list(struct nlattr *list_attr, =20 release_list: release_entry_list(&sched->entries); + sched->num_entries =3D 0; =20 return err; } @@ -274,36 +316,64 @@ static void gate_setup_timer(struct tcf_gate *gact, u= 64 basetime, enum tk_offsets tko, s32 clockid, bool do_init) { + struct tcf_gate_params *p; + if (!do_init) { - if (basetime =3D=3D gact->param.tcfg_basetime && + p =3D rcu_dereference_protected(gact->param, + lockdep_is_held(&gact->tcf_lock)); + if (basetime =3D=3D p->tcfg_basetime && tko =3D=3D gact->tk_offset && - clockid =3D=3D gact->param.tcfg_clockid) + clockid =3D=3D p->tcfg_clockid) return; - - 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; gact->tk_offset =3D tko; hrtimer_setup(&gact->hitimer, gate_timer_func, clockid, HRTIMER_MODE_ABS_= SOFT); } =20 +static int gate_clockid_to_offset(s32 clockid, enum tk_offsets *off, + struct netlink_ext_ack *extack) +{ + switch (clockid) { + case CLOCK_REALTIME: + *off =3D TK_OFFS_REAL; + break; + case CLOCK_MONOTONIC: + *off =3D TK_OFFS_MAX; + break; + case CLOCK_BOOTTIME: + *off =3D TK_OFFS_BOOT; + break; + case CLOCK_TAI: + *off =3D TK_OFFS_TAI; + break; + default: + NL_SET_ERR_MSG(extack, "Invalid 'clockid'"); + return -EINVAL; + } + + return 0; +} + static int tcf_gate_init(struct net *net, struct nlattr *nla, struct nlattr *est, struct tc_action **a, struct tcf_proto *tp, u32 flags, struct netlink_ext_ack *extack) { struct tc_action_net *tn =3D net_generic(net, act_gate_ops.net_id); + const struct tcf_gate_params *cur_p =3D NULL; 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_gate_params *old_p, *p; struct tcf_chain *goto_ch =3D NULL; u64 cycletime =3D 0, basetime =3D 0; - struct tcf_gate_params *p; + bool clockid_changed =3D false; + bool use_old_entries =3D false; + bool list_provided =3D false; s32 clockid =3D CLOCK_TAI; struct tcf_gate *gact; + u64 cycletime_ext =3D 0; + int parsed =3D -ENOENT; struct tc_gate *parm; int ret =3D 0, err; u32 gflags =3D 0; @@ -323,23 +393,9 @@ static int tcf_gate_init(struct net *net, struct nlatt= r *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: - NL_SET_ERR_MSG(extack, "Invalid 'clockid'"); - return -EINVAL; - } + err =3D gate_clockid_to_offset(clockid, &tk_offset, extack); + if (err) + return err; } =20 parm =3D nla_data(tb[TCA_GATE_PARMS]); @@ -376,48 +432,128 @@ static int tcf_gate_init(struct net *net, struct nla= ttr *nla, 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); - - err =3D tcf_action_check_ctrlact(parm->action, tp, &goto_ch, extack); - if (err < 0) + p =3D kzalloc(sizeof(*p), GFP_KERNEL); + if (!p) { + err =3D -ENOMEM; goto release_idr; + } + INIT_LIST_HEAD(&p->entries); =20 - spin_lock_bh(&gact->tcf_lock); - p =3D &gact->param; + list_provided =3D !!tb[TCA_GATE_ENTRY_LIST]; =20 - if (tb[TCA_GATE_CYCLE_TIME]) - cycletime =3D nla_get_u64(tb[TCA_GATE_CYCLE_TIME]); + if (list_provided) { + parsed =3D parse_gate_list(tb[TCA_GATE_ENTRY_LIST], p, extack); + if (parsed < 0) { + err =3D parsed; + goto release_mem; + } + } =20 - if (tb[TCA_GATE_ENTRY_LIST]) { - err =3D parse_gate_list(tb[TCA_GATE_ENTRY_LIST], p, extack); + if (!list_provided || parsed =3D=3D 0) { + if (ret =3D=3D ACT_P_CREATED) { + NL_SET_ERR_MSG(extack, "The entry list is empty"); + err =3D -EINVAL; + goto release_mem; + } + use_old_entries =3D true; + } + + if (use_old_entries) { + cur_p =3D rcu_dereference_protected(gact->param, + lockdep_rtnl_is_held()); + if (!cur_p) { + NL_SET_ERR_MSG(extack, "Missing schedule entries"); + err =3D -EINVAL; + goto release_mem; + } + + if (!tb[TCA_GATE_PRIORITY]) + prio =3D cur_p->tcfg_priority; + + if (!tb[TCA_GATE_BASE_TIME]) + basetime =3D cur_p->tcfg_basetime; + + if (!tb[TCA_GATE_FLAGS]) + gflags =3D cur_p->tcfg_flags; + + if (!tb[TCA_GATE_CLOCKID]) { + clockid =3D cur_p->tcfg_clockid; + err =3D gate_clockid_to_offset(clockid, &tk_offset, extack); + if (err) + goto release_mem; + } + + if (!tb[TCA_GATE_CYCLE_TIME]) + cycletime =3D cur_p->tcfg_cycletime; + + if (!tb[TCA_GATE_CYCLE_TIME_EXT]) + cycletime_ext =3D cur_p->tcfg_cycletime_ext; + + err =3D tcf_gate_copy_entries(p, cur_p, extack); if (err < 0) - goto chain_put; + goto release_mem; } =20 + p->tcfg_priority =3D prio; + p->tcfg_flags =3D gflags; + + if (tb[TCA_GATE_CYCLE_TIME]) + cycletime =3D nla_get_u64(tb[TCA_GATE_CYCLE_TIME]); + if (!cycletime) { struct tcfg_gate_entry *entry; ktime_t cycle =3D 0; =20 - list_for_each_entry(entry, &p->entries, list) - cycle =3D ktime_add_ns(cycle, entry->interval); - cycletime =3D cycle; + if (list_provided && !use_old_entries) { + list_for_each_entry(entry, &p->entries, list) + cycle =3D ktime_add_ns(cycle, entry->interval); + cycletime =3D cycle; + } else if (cur_p) { + cycletime =3D cur_p->tcfg_cycletime; + } if (!cycletime) { err =3D -EINVAL; - goto chain_put; + goto release_mem; } } p->tcfg_cycletime =3D cycletime; =20 if (tb[TCA_GATE_CYCLE_TIME_EXT]) - p->tcfg_cycletime_ext =3D - nla_get_u64(tb[TCA_GATE_CYCLE_TIME_EXT]); + cycletime_ext =3D nla_get_u64(tb[TCA_GATE_CYCLE_TIME_EXT]); + p->tcfg_cycletime_ext =3D cycletime_ext; =20 - gate_setup_timer(gact, basetime, tk_offset, clockid, - ret =3D=3D ACT_P_CREATED); - p->tcfg_priority =3D prio; - p->tcfg_flags =3D gflags; - gate_get_start_time(gact, &start); + err =3D tcf_action_check_ctrlact(parm->action, tp, &goto_ch, extack); + if (err < 0) + goto release_mem; + + if (ret !=3D ACT_P_CREATED) { + cur_p =3D rcu_dereference_protected(gact->param, + lockdep_rtnl_is_held()); + if (cur_p && clockid !=3D cur_p->tcfg_clockid) { + hrtimer_cancel(&gact->hitimer); + clockid_changed =3D true; + } + } + + spin_lock_bh(&gact->tcf_lock); + if (ret =3D=3D ACT_P_CREATED) { + gate_setup_timer(gact, basetime, tk_offset, clockid, true); + } else { + cur_p =3D rcu_dereference_protected(gact->param, + lockdep_is_held(&gact->tcf_lock)); + if (!cur_p) { + err =3D -EINVAL; + goto chain_put; + } + if (clockid_changed) + gate_setup_timer(gact, basetime, tk_offset, clockid, false); + } + 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,6 +570,9 @@ static int tcf_gate_init(struct net *net, struct nlattr= *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: @@ -441,26 +580,36 @@ static int tcf_gate_init(struct net *net, struct nlat= tr *nla, =20 if (goto_ch) tcf_chain_put_by_act(goto_ch); +release_mem: + 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_setup_timer(gact, basetime, tk_offset, clockid, true); tcf_idr_release(*a, bind); return err; } =20 +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); +} + 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, lockdep_rtnl_is_held()); + if (p) + call_rcu(&p->rcu, tcf_gate_params_free_rcu); } =20 static int dumping_entry(struct sk_buff *skb, @@ -499,65 +648,66 @@ static int tcf_gate_dump(struct sk_buff *skb, struct = tc_action *a, { unsigned char *b =3D skb_tail_pointer(skb); struct tcf_gate *gact =3D to_gate(a); + struct tcfg_gate_entry *entry; + struct tcf_gate_params *p; + struct nlattr *entry_list; struct tc_gate opt =3D { .index =3D gact->tcf_index, .refcnt =3D refcount_read(&gact->tcf_refcnt) - ref, .bindcnt =3D atomic_read(&gact->tcf_bindcnt) - bind, }; - struct tcfg_gate_entry *entry; - struct tcf_gate_params *p; - struct nlattr *entry_list; struct tcf_t t; =20 - spin_lock_bh(&gact->tcf_lock); - opt.action =3D gact->tcf_action; - - p =3D &gact->param; + opt.action =3D READ_ONCE(gact->tcf_action); =20 if (nla_put(skb, TCA_GATE_PARMS, sizeof(opt), &opt)) goto nla_put_failure; =20 + rcu_read_lock(); + p =3D rcu_dereference(gact->param); + if (nla_put_u64_64bit(skb, TCA_GATE_BASE_TIME, p->tcfg_basetime, TCA_GATE_PAD)) - goto nla_put_failure; + goto nla_put_failure_rcu; =20 if (nla_put_u64_64bit(skb, TCA_GATE_CYCLE_TIME, p->tcfg_cycletime, TCA_GATE_PAD)) - goto nla_put_failure; + goto nla_put_failure_rcu; =20 if (nla_put_u64_64bit(skb, TCA_GATE_CYCLE_TIME_EXT, p->tcfg_cycletime_ext, TCA_GATE_PAD)) - goto nla_put_failure; + goto nla_put_failure_rcu; =20 if (nla_put_s32(skb, TCA_GATE_CLOCKID, p->tcfg_clockid)) - goto nla_put_failure; + goto nla_put_failure_rcu; =20 if (nla_put_u32(skb, TCA_GATE_FLAGS, p->tcfg_flags)) - goto nla_put_failure; + goto nla_put_failure_rcu; =20 if (nla_put_s32(skb, TCA_GATE_PRIORITY, p->tcfg_priority)) - goto nla_put_failure; + goto nla_put_failure_rcu; =20 entry_list =3D nla_nest_start_noflag(skb, TCA_GATE_ENTRY_LIST); if (!entry_list) - goto nla_put_failure; + goto nla_put_failure_rcu; =20 list_for_each_entry(entry, &p->entries, list) { if (dumping_entry(skb, entry) < 0) - goto nla_put_failure; + goto nla_put_failure_rcu; } =20 nla_nest_end(skb, entry_list); + rcu_read_unlock(); =20 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); =20 return skb->len; =20 +nla_put_failure_rcu: + rcu_read_unlock(); nla_put_failure: - spin_unlock_bh(&gact->tcf_lock); nlmsg_trim(skb, b); return -1; } --=20 2.52.GIT