]> git.ipfire.org Git - thirdparty/kernel/stable-queue.git/commitdiff
6.19-stable patches
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 12 Mar 2026 16:12:22 +0000 (17:12 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 12 Mar 2026 16:12:22 +0000 (17:12 +0100)
added patches:
net-sched-act_gate-snapshot-parameters-with-rcu-on-replace.patch
net-sched-only-allow-act_ct-to-bind-to-clsact-ingress-qdiscs-and-shared-blocks.patch

queue-6.19/net-sched-act_gate-snapshot-parameters-with-rcu-on-replace.patch [new file with mode: 0644]
queue-6.19/net-sched-only-allow-act_ct-to-bind-to-clsact-ingress-qdiscs-and-shared-blocks.patch [new file with mode: 0644]
queue-6.19/series

diff --git a/queue-6.19/net-sched-act_gate-snapshot-parameters-with-rcu-on-replace.patch b/queue-6.19/net-sched-act_gate-snapshot-parameters-with-rcu-on-replace.patch
new file mode 100644 (file)
index 0000000..dbc5d92
--- /dev/null
@@ -0,0 +1,532 @@
+From 62413a9c3cb183afb9bb6e94dd68caf4e4145f4c Mon Sep 17 00:00:00 2001
+From: Paul Moses <p@1g4.org>
+Date: Mon, 23 Feb 2026 15:05:44 +0000
+Subject: net/sched: act_gate: snapshot parameters with RCU on replace
+
+From: Paul Moses <p@1g4.org>
+
+commit 62413a9c3cb183afb9bb6e94dd68caf4e4145f4c upstream.
+
+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 <p@1g4.org>
+Tested-by: Vladimir Oltean <vladimir.oltean@nxp.com>
+Acked-by: Jamal Hadi Salim <jhs@mojatatu.com>
+Reviewed-by: Victor Nogueira <victor@mojatatu.com>
+Link: https://patch.msgid.link/20260223150512.2251594-2-p@1g4.org
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/net/tc_act/tc_gate.h |   33 ++++-
+ net/sched/act_gate.c         |  269 ++++++++++++++++++++++++++++++-------------
+ 2 files changed, 214 insertions(+), 88 deletions(-)
+
+--- 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;
+ }
+ static inline u64 tcf_gate_basetime(const struct tc_action *a)
+ {
++      struct tcf_gate_params *p;
+       u64 tcfg_basetime;
+-      tcfg_basetime = to_gate(a)->param.tcfg_basetime;
++      p = tcf_gate_params_locked(a);
++      tcfg_basetime = p->tcfg_basetime;
+       return tcfg_basetime;
+ }
+ static inline u64 tcf_gate_cycletime(const struct tc_action *a)
+ {
++      struct tcf_gate_params *p;
+       u64 tcfg_cycletime;
+-      tcfg_cycletime = to_gate(a)->param.tcfg_cycletime;
++      p = tcf_gate_params_locked(a);
++      tcfg_cycletime = p->tcfg_cycletime;
+       return tcfg_cycletime;
+ }
+ static inline u64 tcf_gate_cycletimeext(const struct tc_action *a)
+ {
++      struct tcf_gate_params *p;
+       u64 tcfg_cycletimeext;
+-      tcfg_cycletimeext = to_gate(a)->param.tcfg_cycletime_ext;
++      p = tcf_gate_params_locked(a);
++      tcfg_cycletimeext = p->tcfg_cycletime_ext;
+       return tcfg_cycletimeext;
+ }
+ static inline u32 tcf_gate_num_entries(const struct tc_action *a)
+ {
++      struct tcf_gate_params *p;
+       u32 num_entries;
+-      num_entries = to_gate(a)->param.num_entries;
++      p = tcf_gate_params_locked(a);
++      num_entries = p->num_entries;
+       return num_entries;
+ }
+@@ -105,7 +124,7 @@ static inline struct action_gate_entry
+       u32 num_entries;
+       int i = 0;
+-      p = &to_gate(a)->param;
++      p = tcf_gate_params_locked(a);
+       num_entries = p->num_entries;
+       list_for_each_entry(entry, &p->entries, list)
+--- a/net/sched/act_gate.c
++++ b/net/sched/act_gate.c
+@@ -32,9 +32,12 @@ static ktime_t gate_get_time(struct tcf_
+       return KTIME_MAX;
+ }
+-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 = &gact->param;
+       ktime_t now, base, cycle;
+       u64 n;
+@@ -69,12 +72,14 @@ static enum hrtimer_restart gate_timer_f
+ {
+       struct tcf_gate *gact = container_of(timer, struct tcf_gate,
+                                            hitimer);
+-      struct tcf_gate_params *p = &gact->param;
+       struct tcfg_gate_entry *next;
++      struct tcf_gate_params *p;
+       ktime_t close_time, now;
+       spin_lock(&gact->tcf_lock);
++      p = rcu_dereference_protected(gact->param,
++                                    lockdep_is_held(&gact->tcf_lock));
+       next = gact->next_entry;
+       /* cycle start, clear pending bit, clear total octets */
+@@ -225,6 +230,35 @@ static void release_entry_list(struct li
+       }
+ }
++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 = 0;
++
++      list_for_each_entry(entry, &src->entries, list) {
++              struct tcfg_gate_entry *new;
++
++              new = kzalloc(sizeof(*new), GFP_ATOMIC);
++              if (!new) {
++                      NL_SET_ERR_MSG(extack, "Not enough memory for entry");
++                      return -ENOMEM;
++              }
++
++              new->index      = entry->index;
++              new->gate_state = entry->gate_state;
++              new->interval   = entry->interval;
++              new->ipv        = entry->ipv;
++              new->maxoctets  = entry->maxoctets;
++              list_add_tail(&new->list, &dst->entries);
++              i++;
++      }
++
++      dst->num_entries = i;
++      return 0;
++}
++
+ static int parse_gate_list(struct nlattr *list_attr,
+                          struct tcf_gate_params *sched,
+                          struct netlink_ext_ack *extack)
+@@ -270,24 +304,44 @@ release_list:
+       return err;
+ }
+-static void gate_setup_timer(struct tcf_gate *gact, u64 basetime,
+-                           enum tk_offsets tko, s32 clockid,
+-                           bool do_init)
+-{
+-      if (!do_init) {
+-              if (basetime == gact->param.tcfg_basetime &&
+-                  tko == gact->tk_offset &&
+-                  clockid == gact->param.tcfg_clockid)
+-                      return;
+-
+-              spin_unlock_bh(&gact->tcf_lock);
+-              hrtimer_cancel(&gact->hitimer);
+-              spin_lock_bh(&gact->tcf_lock);
++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)
++{
++      return basetime != old_basetime ||
++             clockid != old_clockid ||
++             tko != old_tko;
++}
++
++static int gate_clock_resolve(s32 clockid, enum tk_offsets *tko,
++                            struct netlink_ext_ack *extack)
++{
++      switch (clockid) {
++      case CLOCK_REALTIME:
++              *tko = TK_OFFS_REAL;
++              return 0;
++      case CLOCK_MONOTONIC:
++              *tko = TK_OFFS_MAX;
++              return 0;
++      case CLOCK_BOOTTIME:
++              *tko = TK_OFFS_BOOT;
++              return 0;
++      case CLOCK_TAI:
++              *tko = TK_OFFS_TAI;
++              return 0;
++      default:
++              NL_SET_ERR_MSG(extack, "Invalid 'clockid'");
++              return -EINVAL;
+       }
+-      gact->param.tcfg_basetime = basetime;
+-      gact->param.tcfg_clockid = clockid;
+-      gact->tk_offset = tko;
+-      hrtimer_setup(&gact->hitimer, gate_timer_func, clockid, HRTIMER_MODE_ABS_SOFT);
++}
++
++static void gate_setup_timer(struct tcf_gate *gact, s32 clockid,
++                           enum tk_offsets tko)
++{
++      WRITE_ONCE(gact->tk_offset, tko);
++      hrtimer_setup(&gact->hitimer, gate_timer_func, clockid,
++                    HRTIMER_MODE_ABS_SOFT);
+ }
+ static int tcf_gate_init(struct net *net, struct nlattr *nla,
+@@ -296,15 +350,22 @@ static int tcf_gate_init(struct net *net
+                        struct netlink_ext_ack *extack)
+ {
+       struct tc_action_net *tn = net_generic(net, act_gate_ops.net_id);
+-      enum tk_offsets tk_offset = TK_OFFS_TAI;
++      u64 cycletime = 0, basetime = 0, cycletime_ext = 0;
++      struct tcf_gate_params *p = NULL, *old_p = NULL;
++      enum tk_offsets old_tk_offset = TK_OFFS_TAI;
++      const struct tcf_gate_params *cur_p = NULL;
+       bool bind = flags & TCA_ACT_FLAGS_BIND;
+       struct nlattr *tb[TCA_GATE_MAX + 1];
++      enum tk_offsets tko = TK_OFFS_TAI;
+       struct tcf_chain *goto_ch = NULL;
+-      u64 cycletime = 0, basetime = 0;
+-      struct tcf_gate_params *p;
++      s32 timer_clockid = CLOCK_TAI;
++      bool use_old_entries = false;
++      s32 old_clockid = CLOCK_TAI;
++      bool need_cancel = false;
+       s32 clockid = CLOCK_TAI;
+       struct tcf_gate *gact;
+       struct tc_gate *parm;
++      u64 old_basetime = 0;
+       int ret = 0, err;
+       u32 gflags = 0;
+       s32 prio = -1;
+@@ -321,26 +382,8 @@ static int tcf_gate_init(struct net *net
+       if (!tb[TCA_GATE_PARMS])
+               return -EINVAL;
+-      if (tb[TCA_GATE_CLOCKID]) {
++      if (tb[TCA_GATE_CLOCKID])
+               clockid = nla_get_s32(tb[TCA_GATE_CLOCKID]);
+-              switch (clockid) {
+-              case CLOCK_REALTIME:
+-                      tk_offset = TK_OFFS_REAL;
+-                      break;
+-              case CLOCK_MONOTONIC:
+-                      tk_offset = TK_OFFS_MAX;
+-                      break;
+-              case CLOCK_BOOTTIME:
+-                      tk_offset = TK_OFFS_BOOT;
+-                      break;
+-              case CLOCK_TAI:
+-                      tk_offset = TK_OFFS_TAI;
+-                      break;
+-              default:
+-                      NL_SET_ERR_MSG(extack, "Invalid 'clockid'");
+-                      return -EINVAL;
+-              }
+-      }
+       parm = nla_data(tb[TCA_GATE_PARMS]);
+       index = parm->index;
+@@ -366,6 +409,60 @@ static int tcf_gate_init(struct net *net
+               return -EEXIST;
+       }
++      gact = to_gate(*a);
++
++      err = tcf_action_check_ctrlact(parm->action, tp, &goto_ch, extack);
++      if (err < 0)
++              goto release_idr;
++
++      p = kzalloc(sizeof(*p), GFP_KERNEL);
++      if (!p) {
++              err = -ENOMEM;
++              goto chain_put;
++      }
++      INIT_LIST_HEAD(&p->entries);
++
++      use_old_entries = !tb[TCA_GATE_ENTRY_LIST];
++      if (!use_old_entries) {
++              err = parse_gate_list(tb[TCA_GATE_ENTRY_LIST], p, extack);
++              if (err < 0)
++                      goto err_free;
++              use_old_entries = !err;
++      }
++
++      if (ret == ACT_P_CREATED && use_old_entries) {
++              NL_SET_ERR_MSG(extack, "The entry list is empty");
++              err = -EINVAL;
++              goto err_free;
++      }
++
++      if (ret != ACT_P_CREATED) {
++              rcu_read_lock();
++              cur_p = rcu_dereference(gact->param);
++
++              old_basetime  = cur_p->tcfg_basetime;
++              old_clockid   = cur_p->tcfg_clockid;
++              old_tk_offset = READ_ONCE(gact->tk_offset);
++
++              basetime      = old_basetime;
++              cycletime_ext = cur_p->tcfg_cycletime_ext;
++              prio          = cur_p->tcfg_priority;
++              gflags        = cur_p->tcfg_flags;
++
++              if (!tb[TCA_GATE_CLOCKID])
++                      clockid = old_clockid;
++
++              err = 0;
++              if (use_old_entries) {
++                      err = tcf_gate_copy_entries(p, cur_p, extack);
++                      if (!err && !tb[TCA_GATE_CYCLE_TIME])
++                              cycletime = cur_p->tcfg_cycletime;
++              }
++              rcu_read_unlock();
++              if (err)
++                      goto err_free;
++      }
++
+       if (tb[TCA_GATE_PRIORITY])
+               prio = nla_get_s32(tb[TCA_GATE_PRIORITY]);
+@@ -375,25 +472,26 @@ static int tcf_gate_init(struct net *net
+       if (tb[TCA_GATE_FLAGS])
+               gflags = nla_get_u32(tb[TCA_GATE_FLAGS]);
+-      gact = to_gate(*a);
+-      if (ret == ACT_P_CREATED)
+-              INIT_LIST_HEAD(&gact->param.entries);
++      if (tb[TCA_GATE_CYCLE_TIME])
++              cycletime = nla_get_u64(tb[TCA_GATE_CYCLE_TIME]);
+-      err = 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 = nla_get_u64(tb[TCA_GATE_CYCLE_TIME_EXT]);
+-      spin_lock_bh(&gact->tcf_lock);
+-      p = &gact->param;
++      err = gate_clock_resolve(clockid, &tko, extack);
++      if (err)
++              goto err_free;
++      timer_clockid = clockid;
++
++      need_cancel = ret != ACT_P_CREATED &&
++                    gate_timer_needs_cancel(basetime, old_basetime,
++                                            tko, old_tk_offset,
++                                            timer_clockid, old_clockid);
+-      if (tb[TCA_GATE_CYCLE_TIME])
+-              cycletime = nla_get_u64(tb[TCA_GATE_CYCLE_TIME]);
++      if (need_cancel)
++              hrtimer_cancel(&gact->hitimer);
+-      if (tb[TCA_GATE_ENTRY_LIST]) {
+-              err = parse_gate_list(tb[TCA_GATE_ENTRY_LIST], p, extack);
+-              if (err < 0)
+-                      goto chain_put;
+-      }
++      spin_lock_bh(&gact->tcf_lock);
+       if (!cycletime) {
+               struct tcfg_gate_entry *entry;
+@@ -402,22 +500,20 @@ static int tcf_gate_init(struct net *net
+               list_for_each_entry(entry, &p->entries, list)
+                       cycle = ktime_add_ns(cycle, entry->interval);
+               cycletime = cycle;
+-              if (!cycletime) {
+-                      err = -EINVAL;
+-                      goto chain_put;
+-              }
+       }
+       p->tcfg_cycletime = cycletime;
++      p->tcfg_cycletime_ext = cycletime_ext;
+-      if (tb[TCA_GATE_CYCLE_TIME_EXT])
+-              p->tcfg_cycletime_ext =
+-                      nla_get_u64(tb[TCA_GATE_CYCLE_TIME_EXT]);
+-
+-      gate_setup_timer(gact, basetime, tk_offset, clockid,
+-                       ret == ACT_P_CREATED);
++      if (need_cancel || ret == ACT_P_CREATED)
++              gate_setup_timer(gact, timer_clockid, tko);
+       p->tcfg_priority = prio;
+       p->tcfg_flags = gflags;
+-      gate_get_start_time(gact, &start);
++      p->tcfg_basetime = basetime;
++      p->tcfg_clockid = timer_clockid;
++      gate_get_start_time(gact, p, &start);
++
++      old_p = rcu_replace_pointer(gact->param, p,
++                                  lockdep_is_held(&gact->tcf_lock));
+       gact->current_close_time = start;
+       gact->current_gate_status = GATE_ACT_GATE_OPEN | GATE_ACT_PENDING;
+@@ -434,11 +530,15 @@ static int tcf_gate_init(struct net *net
+       if (goto_ch)
+               tcf_chain_put_by_act(goto_ch);
++      if (old_p)
++              call_rcu(&old_p->rcu, tcf_gate_params_free_rcu);
++
+       return ret;
++err_free:
++      release_entry_list(&p->entries);
++      kfree(p);
+ chain_put:
+-      spin_unlock_bh(&gact->tcf_lock);
+-
+       if (goto_ch)
+               tcf_chain_put_by_act(goto_ch);
+ release_idr:
+@@ -446,21 +546,29 @@ release_idr:
+        * without taking tcf_lock.
+        */
+       if (ret == ACT_P_CREATED)
+-              gate_setup_timer(gact, gact->param.tcfg_basetime,
+-                               gact->tk_offset, gact->param.tcfg_clockid,
+-                               true);
++              gate_setup_timer(gact, timer_clockid, tko);
++
+       tcf_idr_release(*a, bind);
+       return err;
+ }
++static void tcf_gate_params_free_rcu(struct rcu_head *head)
++{
++      struct tcf_gate_params *p = 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 = to_gate(a);
+       struct tcf_gate_params *p;
+-      p = &gact->param;
+       hrtimer_cancel(&gact->hitimer);
+-      release_entry_list(&p->entries);
++      p = rcu_dereference_protected(gact->param, 1);
++      if (p)
++              call_rcu(&p->rcu, tcf_gate_params_free_rcu);
+ }
+ static int dumping_entry(struct sk_buff *skb,
+@@ -509,10 +617,9 @@ static int tcf_gate_dump(struct sk_buff
+       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 +659,12 @@ static int tcf_gate_dump(struct sk_buff
+       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;
+ }
diff --git a/queue-6.19/net-sched-only-allow-act_ct-to-bind-to-clsact-ingress-qdiscs-and-shared-blocks.patch b/queue-6.19/net-sched-only-allow-act_ct-to-bind-to-clsact-ingress-qdiscs-and-shared-blocks.patch
new file mode 100644 (file)
index 0000000..52901cc
--- /dev/null
@@ -0,0 +1,89 @@
+From 11cb63b0d1a0685e0831ae3c77223e002ef18189 Mon Sep 17 00:00:00 2001
+From: Victor Nogueira <victor@mojatatu.com>
+Date: Wed, 25 Feb 2026 10:43:48 -0300
+Subject: net/sched: Only allow act_ct to bind to clsact/ingress qdiscs and shared blocks
+
+From: Victor Nogueira <victor@mojatatu.com>
+
+commit 11cb63b0d1a0685e0831ae3c77223e002ef18189 upstream.
+
+As Paolo said earlier [1]:
+
+"Since the blamed commit below, classify can return TC_ACT_CONSUMED while
+the current skb being held by the defragmentation engine. As reported by
+GangMin Kim, if such packet is that may cause a UaF when the defrag engine
+later on tries to tuch again such packet."
+
+act_ct was never meant to be used in the egress path, however some users
+are attaching it to egress today [2]. Attempting to reach a middle
+ground, we noticed that, while most qdiscs are not handling
+TC_ACT_CONSUMED, clsact/ingress qdiscs are. With that in mind, we
+address the issue by only allowing act_ct to bind to clsact/ingress
+qdiscs and shared blocks. That way it's still possible to attach act_ct to
+egress (albeit only with clsact).
+
+[1] https://lore.kernel.org/netdev/674b8cbfc385c6f37fb29a1de08d8fe5c2b0fbee.1771321118.git.pabeni@redhat.com/
+[2] https://lore.kernel.org/netdev/cc6bfb4a-4a2b-42d8-b9ce-7ef6644fb22b@ovn.org/
+
+Reported-by: GangMin Kim <km.kim1503@gmail.com>
+Fixes: 3f14b377d01d ("net/sched: act_ct: fix skb leak and crash on ooo frags")
+CC: stable@vger.kernel.org
+Signed-off-by: Victor Nogueira <victor@mojatatu.com>
+Acked-by: Jamal Hadi Salim <jhs@mojatatu.com>
+Link: https://patch.msgid.link/20260225134349.1287037-1-victor@mojatatu.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/net/act_api.h |    1 +
+ net/sched/act_ct.c    |    6 ++++++
+ net/sched/cls_api.c   |    7 +++++++
+ 3 files changed, 14 insertions(+)
+
+--- a/include/net/act_api.h
++++ b/include/net/act_api.h
+@@ -70,6 +70,7 @@ struct tc_action {
+ #define TCA_ACT_FLAGS_REPLACE (1U << (TCA_ACT_FLAGS_USER_BITS + 2))
+ #define TCA_ACT_FLAGS_NO_RTNL (1U << (TCA_ACT_FLAGS_USER_BITS + 3))
+ #define TCA_ACT_FLAGS_AT_INGRESS      (1U << (TCA_ACT_FLAGS_USER_BITS + 4))
++#define TCA_ACT_FLAGS_AT_INGRESS_OR_CLSACT    (1U << (TCA_ACT_FLAGS_USER_BITS + 5))
+ /* Update lastuse only if needed, to avoid dirtying a cache line.
+  * We use a temp variable to avoid fetching jiffies twice.
+--- a/net/sched/act_ct.c
++++ b/net/sched/act_ct.c
+@@ -1358,6 +1358,12 @@ static int tcf_ct_init(struct net *net,
+               return -EINVAL;
+       }
++      if (bind && !(flags & TCA_ACT_FLAGS_AT_INGRESS_OR_CLSACT)) {
++              NL_SET_ERR_MSG_MOD(extack,
++                                 "Attaching ct to a non ingress/clsact qdisc is unsupported");
++              return -EOPNOTSUPP;
++      }
++
+       err = nla_parse_nested(tb, TCA_CT_MAX, nla, ct_policy, extack);
+       if (err < 0)
+               return err;
+--- a/net/sched/cls_api.c
++++ b/net/sched/cls_api.c
+@@ -2228,6 +2228,11 @@ static bool is_qdisc_ingress(__u32 class
+       return (TC_H_MIN(classid) == TC_H_MIN(TC_H_MIN_INGRESS));
+ }
++static bool is_ingress_or_clsact(struct tcf_block *block, struct Qdisc *q)
++{
++      return tcf_block_shared(block) || (q && !!(q->flags & TCQ_F_INGRESS));
++}
++
+ static int tc_new_tfilter(struct sk_buff *skb, struct nlmsghdr *n,
+                         struct netlink_ext_ack *extack)
+ {
+@@ -2420,6 +2425,8 @@ replay:
+               flags |= TCA_ACT_FLAGS_NO_RTNL;
+       if (is_qdisc_ingress(parent))
+               flags |= TCA_ACT_FLAGS_AT_INGRESS;
++      if (is_ingress_or_clsact(block, q))
++              flags |= TCA_ACT_FLAGS_AT_INGRESS_OR_CLSACT;
+       err = tp->ops->change(net, skb, tp, cl, t->tcm_handle, tca, &fh,
+                             flags, extack);
+       if (err == 0) {
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2698f26f8f9401fc86da3f401c5975cf8379778c 100644 (file)
@@ -0,0 +1,2 @@
+net-sched-act_gate-snapshot-parameters-with-rcu-on-replace.patch
+net-sched-only-allow-act_ct-to-bind-to-clsact-ingress-qdiscs-and-shared-blocks.patch