This is a followup of commit 9c572a83037a ("net/sched: fix potential stack infoleak in em_text_dump()") as reported by Sashiko. The issue: em_text_dump() appends the text pattern with nla_append(), which is a bare skb_put_data() and does not pad the attribute to a 4-byte boundary. When pattern_len is not a multiple of four the skb tail is left unaligned. tcf_em_tree_dump() records the raw span as the ematch entry's nla_len and then writes the next ematch's nlattr header at that unaligned tail. Userspace walks the list with RTA_NEXT()/NLA_NEXT(), which rounds each attribute length up to four bytes, so it looks for the next entry at an aligned offset the kernel did not write. The ematch list is therefore misparsed as soon as a text ematch has a pattern length that is not a multiple of four. The Fix: append the pattern with nla_put_nohdr(), which reserves NLA_ALIGN(attrlen) bytes and zero-fills the padding, so the following ematch stays aligned. The emitted ematch entry then carries the padding in its nla_len, which is what NLA_NEXT() already assumes. em_text_change() only checks that len covers the struct and the pattern, so a dump replayed as a request is still accepted. A padded text ematch is the same size as the request that installed it: both sides carry the 8-byte match header and the 24-byte configuration followed by the pattern rounded up to four bytes, so a tree that installs can still be dumped. The kernel has no explicit bound on pattern_len; it comes from the request. TCA_OPTIONS carries a u16 nla_len, and for cls_cgroup it holds 56 bytes of nested headers, the ematch header and struct tcf_em_text ahead of the 4-byte-aligned pattern. So the largest pattern that can be installed is 65476 bytes (65532-byte options). At 65477 the aligned options length wraps to 0, the attribute is dropped by nlmsg_parse_deprecated() and cls_cgroup_change() returns -EINVAL. Since the dump is no larger than the request, the padding cannot push the dumped TCA_OPTIONS past U16_MAX. This also removes the last variable-length nla_append() caller (sch_gred's is a fixed-size, already aligned struct). Conditions to recreate the bug: - CAP_NET_ADMIN on the target netns (unshare -Urn is enough) - add a basic filter with two text ematches whose pattern_len is not a multiple of 4 (e.g. 1 and 2), then dump it with RTM_GETTFILTER; the first entry comes back with an unaligned nla_len and the next entry's nlattr header is written at that raw offset, while RTA_NEXT() reads an aligned offset three bytes later Reported-by: Sashiko (gemini) Closes: https://sashiko.dev/#/patchset/20260918133953.12494-1-bernard.ladenthin%40gmail.com Link: https://lore.kernel.org/netdev/e0f4776a-0457-4069-9f79-3c97de2a54d0@redhat.com/ Reviewed-by: Jamal Hadi Salim Signed-off-by: Victor Nogueira --- v1 -> v2: - No change to the code since v1. Sashiko (nipa) reported that the added padding could push the cls_cgroup TCA_OPTIONS nest past the u16 nla_len limit, and Paolo Abeni relayed the concern and asked that it be addressed here. On review the premise does not hold: a padded text ematch dumps to the same size as the request that installed it, so it cannot push TCA_OPTIONS above 65535. The changelog now explains where the resulting pattern_len limit (65476) comes from. A guard for this would have been dead code, so the patch is unchanged. - A filter at the maximal accepted size still cannot be dumped, but that is the pre-existing netlink dump skb limit near 32 KiB, not the u16 length, and it is unchanged by this patch. --- net/sched/em_text.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/sched/em_text.c b/net/sched/em_text.c index bc45edb8c03b..1e7b41e579e2 100644 --- a/net/sched/em_text.c +++ b/net/sched/em_text.c @@ -128,8 +128,8 @@ static int em_text_dump(struct sk_buff *skb, struct tcf_ematch *m) if (nla_put_nohdr(skb, sizeof(conf), &conf) < 0) goto nla_put_failure; - if (nla_append(skb, conf.pattern_len, - textsearch_get_pattern(tm->config)) < 0) + if (nla_put_nohdr(skb, conf.pattern_len, + textsearch_get_pattern(tm->config)) < 0) goto nla_put_failure; return 0; -- 2.34.1