net/sched: act_gate: budget the per-entry list in get_fill_size

tcf_gate_get_fill_size returns only the TCA_GATE_PARMS size, but
tcf_gate_dump also emits three 64-bit timestamps, the clock id, flags,
priority and the variable-length TCA_GATE_ENTRY_LIST nest. The per-entry
nest is unbounded: parse_gate_list places no cap on the number of
sched-entries, so a gate with many entries can push the real dump well
past the skb that tca_get_fill allocates from this size.

RTM_NEWACTION then fails the add-notify with -EINVAL while the action is
already committed to the IDR, and a subsequent RTM_GETACTION on the
installed gate also returns -EINVAL because its dump no longer fits.

Fix this by accounting for the missing fields in tcf_gate_get_fill_size
along with all elements in the entries list.

Note that sizing the reply from the action lets an oversized gate
install cleanly for the first time: with the input unbounded by
parse_gate_list, the sized skb can now grow well above
NLMSG_GOODSIZE per netlink request (a transient GFP_KERNEL allocation
reachable only with namespace-local CAP_NET_ADMIN). Overload from a
malicious netns admin is hardening material, not net, per the
discussion at
https://lore.kernel.org/netdev/20260914191108.55a1a4f1@kernel.org/;
a follow-up patch for net-next will cap the sched-entry count.

Fixes: 4e76e75d6a ("net sched actions: calculate add/delete event message size")
Reported-by: Sashiko <sashiko-bot@kernel.org>
Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260824153903.4143642-1-victor@mojatatu.com
Tested-by: hybris <hybris@mojatatu.ai>
Co-developed-by: Jamal Hadi Salim <jhs@mojatatu.com>
Signed-off-by: Jamal Hadi Salim <jhs@mojatatu.com>
Signed-off-by: Victor Nogueira <victor@mojatatu.com>
Link: https://patch.msgid.link/QDISC-3BLH.v1.20260914203033@mojatatu.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Victor Nogueira 2026-09-20 14:07:01 -03:00 committed by Jakub Kicinski
parent 9c572a8303
commit cfa165cbfb

View File

@ -681,7 +681,35 @@ static void tcf_gate_stats_update(struct tc_action *a, u64 bytes, u64 packets,
static size_t tcf_gate_get_fill_size(const struct tc_action *act)
{
return nla_total_size(sizeof(struct tc_gate));
struct tcf_gate *gact = to_gate(act);
const struct tcf_gate_params *p;
struct tcfg_gate_entry *entry;
size_t size = nla_total_size(sizeof(struct tc_gate)) /* TCA_GATE_PARMS */
+ 3 * nla_total_size_64bit(sizeof(u64)) /* TCA_GATE_BASE_TIME
* TCA_GATE_CYCLE_TIME
* TCA_GATE_CYCLE_TIME_EXT
*/
+ nla_total_size(sizeof(s32)) /* TCA_GATE_CLOCKID */
+ nla_total_size(sizeof(u32)) /* TCA_GATE_FLAGS */
+ nla_total_size(sizeof(s32)) /* TCA_GATE_PRIORITY */
+ nla_total_size(0); /* TCA_GATE_ENTRY_LIST */
/* TCA_GATE_TM is budgeted by tcf_action_shared_attrs_size() */
rcu_read_lock();
p = rcu_dereference(gact->param);
if (p) {
list_for_each_entry_rcu(entry, &p->entries, list)
/* TCA_GATE_ONE_ENTRY nest and its attributes */
size += nla_total_size(0)
+ nla_total_size(sizeof(u32)) /* TCA_GATE_ENTRY_INDEX */
+ nla_total_size(0) /* TCA_GATE_ENTRY_GATE */
+ nla_total_size(sizeof(u32)) /* TCA_GATE_ENTRY_INTERVAL */
+ nla_total_size(sizeof(s32)) /* TCA_GATE_ENTRY_MAX_OCTETS */
+ nla_total_size(sizeof(s32)); /* TCA_GATE_ENTRY_IPV */
}
rcu_read_unlock();
return size;
}
static void tcf_gate_entry_destructor(void *priv)