]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
net/sched: act_gact, act_police: range check the fallback control action
authorHyunjung Ko <hj351016@gmail.com>
Thu, 6 Aug 2026 10:12:52 +0000 (19:12 +0900)
committerJakub Kicinski <kuba@kernel.org>
Mon, 10 Aug 2026 23:00:19 +0000 (16:00 -0700)
tcf_action_check_ctrlact() range checks the primary control action:

if (!opcode)
ret = action > TC_ACT_VALUE_MAX ? -EINVAL : 0;

TC_ACT_VALUE_MAX is TC_ACT_TRAP, so kernel-internal verdicts above it
cannot be set that way. But act_gact and act_police each carry a second,
independent control action supplied by user space that never reaches that
helper - TCA_GACT_PROB.paction and TCA_POLICE_RESULT. Both only reject
TC_ACT_GOTO_CHAIN, so any other value is stored verbatim and returned
verbatim from the action.

In particular user space can store TC_ACT_CONSUMED, which is
TC_ACT_VALUE_MAX + 1 and is deliberately not part of the UAPI value
range. That verdict tells every caller the action took ownership of the
skb, so nobody frees it: sch_handle_ingress(), sch_handle_egress() and
tcf_qevent_handle() all deliberately skip the free for it. The result is
one leaked sk_buff plus its data buffer per packet traversing the filter,
unbounded, for all traffic on the chain including kernel-generated
packets.

Both are trivially deterministic. act_gact clamps tcfg_pval to >= 1, so
with pval = 1 gact_determ() returns the fallback for every packet.
act_police has no mandatory rate, so rate = 0 leaves tcfp_mtu = ~0 and
tcf_police_mtu_check() always passes.

TC_ACT_CONSUMED was added by commit 720f22fed81b ("net: sched: refactor
reinsert action"), after both goto-chain guards were written:
commit 9469f375ab09 ("net/sched: act_gact: disallow 'goto chain' on
fallback control action") and
commit c08f5ed5d625 ("net/sched: act_police: disallow 'goto chain' on
fallback control action"). Neither guard was widened when the new
verdict appeared.

Factor the existing range test out of tcf_action_check_ctrlact() as
tcf_action_valid() and apply it to both fallbacks. The helper cannot call
tcf_action_check_ctrlact() directly because that also allocates a
goto_chain, which is exactly what these two sites must not do.

Reproduced on v7.2-rc6: kmemleak reports one leaked 232-byte
skbuff_head_cache object plus its 704-byte data buffer per packet. With
this patch both configurations are rejected with -EINVAL and kmemleak
reports none.

Fixes: 720f22fed81b ("net: sched: refactor reinsert action")
Cc: stable@vger.kernel.org # v5.3+
Signed-off-by: Hyunjung Ko <hj351016@gmail.com>
Acked-by: Jamal Hadi Salim <jhs@mojatatu.com>
Tested-by: Victor Nogueira <victor@mojatatu.com>
Link: https://patch.msgid.link/20260806101252.809593-1-hj351016@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
include/net/act_api.h
net/sched/act_gact.c
net/sched/act_police.c

index 20d9e55f8564d3138fe18b9e13a2cece96c6417f..fd03f6319e8807b64c87523e1d191bbf8140311c 100644 (file)
@@ -270,6 +270,25 @@ int tcf_action_check_ctrlact(int action, struct tcf_proto *tp,
 struct tcf_chain *tcf_action_set_ctrlact(struct tc_action *a, int action,
                                         struct tcf_chain *newchain);
 
+/* Range check for a control action supplied by user space.
+ *
+ * This is the same test tcf_action_check_ctrlact() applies to the primary
+ * control action, factored out for the *fallback* control actions
+ * (act_gact's TCA_GACT_PROB.paction and act_police's TCA_POLICE_RESULT),
+ * which must not reach tcf_action_check_ctrlact() because they have no
+ * goto_chain to allocate.  Without it, user space can store kernel-internal
+ * verdicts such as TC_ACT_CONSUMED, which is TC_ACT_VALUE_MAX + 1 and is
+ * deliberately not part of the UAPI value range.
+ */
+static inline bool tcf_action_valid(int action)
+{
+       int opcode = TC_ACT_EXT_OPCODE(action);
+
+       if (!opcode)
+               return action <= TC_ACT_VALUE_MAX;
+       return opcode <= TC_ACT_EXT_OPCODE_MAX || action == TC_ACT_UNSPEC;
+}
+
 #ifdef CONFIG_INET
 DECLARE_STATIC_KEY_FALSE(tcf_frag_xmit_count);
 #endif
index e949280eb800d9558cf101ced8f0d9742926c5f7..565860cccba6ddcaa81ceea03bdc7a01b09194d4 100644 (file)
@@ -89,6 +89,11 @@ static int tcf_gact_init(struct net *net, struct nlattr *nla,
                p_parm = nla_data(tb[TCA_GACT_PROB]);
                if (p_parm->ptype >= MAX_RAND)
                        return -EINVAL;
+               if (!tcf_action_valid(p_parm->paction)) {
+                       NL_SET_ERR_MSG(extack,
+                                      "invalid fallback control action");
+                       return -EINVAL;
+               }
                if (TC_ACT_EXT_CMP(p_parm->paction, TC_ACT_GOTO_CHAIN)) {
                        NL_SET_ERR_MSG(extack,
                                       "goto chain not allowed on fallback");
index b16468a98c55e32260e8d4cb1fe3d771fca65120..ce08f6840ef7cace9df81058a5c9fa88211503c7 100644 (file)
@@ -128,6 +128,12 @@ static int tcf_police_init(struct net *net, struct nlattr *nla,
 
        if (tb[TCA_POLICE_RESULT]) {
                tcfp_result = nla_get_u32(tb[TCA_POLICE_RESULT]);
+               if (!tcf_action_valid(tcfp_result)) {
+                       NL_SET_ERR_MSG(extack,
+                                      "invalid fallback control action");
+                       err = -EINVAL;
+                       goto failure;
+               }
                if (TC_ACT_EXT_CMP(tcfp_result, TC_ACT_GOTO_CHAIN)) {
                        NL_SET_ERR_MSG(extack,
                                       "goto chain not allowed on fallback");