Netdev List
 help / color / mirror / Atom feed
* [PATCH 5.10.y] net/sched: fix pedit partial COW leading to page cache corruption
       [not found] <20260630-cve-2026-46331-v1-1-c1986f356f26@atmark-techno.com>
@ 2026-06-30  8:27 ` Dominique Martinet
  2026-07-02  0:38   ` Sasha Levin
  0 siblings, 1 reply; 3+ messages in thread
From: Dominique Martinet @ 2026-06-30  8:27 UTC (permalink / raw)
  To: dominique.martinet, stable
  Cc: Jamal Hadi Salim, Cong Wang, Jiri Pirko, David S. Miller,
	Jakub Kicinski, Mat Martineau, Paolo Abeni, netdev, linux-kernel,
	Rajat Gupta, Yiming Qian, Keenan Dong, Han Guidong, Zhang Cen,
	Davide Caratti, Toke Høiland-Jørgensen, Victor Nogueira

From: Rajat Gupta <rajat.gupta@oss.qualcomm.com>

[ Upstream commit 899ee91156e57784090c5565e4f31bd7dbffbc5a ]

tcf_pedit_act() computes the COW range for skb_ensure_writable()
once before the key loop using tcfp_off_max_hint, but the hint does
not account for the runtime header offset added by typed keys. This
can leave part of the write region un-COW'd.

Fix by moving skb_ensure_writable() inside the per-key loop where
the actual write offset is known, and add overflow checking on the
offset arithmetic. For negative offsets (e.g. Ethernet header edits
at ingress), use skb_cow() to COW the headroom instead. Guard
offset_valid() against INT_MIN, where negation is undefined.

Fixes: 8b796475fd78 ("net/sched: act_pedit: really ensure the skb is writable")
Reported-by: Yiming Qian <yimingqian591@gmail.com>
Reported-by: Keenan Dong <keenanat2000@gmail.com>
Reported-by: Han Guidong <2045gemini@gmail.com>
Reported-by: Zhang Cen <rollkingzzc@gmail.com>
Reviewed-by: Han Guidong <2045gemini@gmail.com>
Tested-by: Han Guidong <2045gemini@gmail.com>
Reviewed-by: Davide Caratti <dcaratti@redhat.com>
Tested-by: Davide Caratti <dcaratti@redhat.com>
Reviewed-by: Toke Høiland-Jørgensen <toke@redhat.com>
Tested-by: Toke Høiland-Jørgensen <toke@redhat.com>
Reviewed-by: Victor Nogueira <victor@mojatatu.com>
Tested-by: Victor Nogueira <victor@mojatatu.com>
Acked-by: Jamal Hadi Salim <jhs@mojatatu.com>
Signed-off-by: Rajat Gupta <rajat.gupta@oss.qualcomm.com>
Link: https://patch.msgid.link/20260531123221.48732-1-jhs@mojatatu.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
[Dominique: plenty of context conflict but the code itself could still
mostly be used]
Signed-off-by: Dominique Martinet <dominique.martinet@atmark-techno.com>
---
(Sorry for double-send to stable@vger, I hadn't intended to send the
Cc-free version...)

CVE-2026-46331 got attention and I've had to backport this to 5.10, so
here we are, this should fix it.

Unfortunately there are more conflicts with 5.15 so I didn't do this
one (but if anyone cares older 5.4/4.19 cherry-picks cleanly from this,
they're no longer stable kernels so I won't bother sending)

I've tested in egress with a trivial rule redirecting port inspired from
the man page (and checking on target with tcpdump because return path is
broken), so I hopefully didn't botch this too badly:

tc qdisc replace dev eth0 root handle 1: htb
tc filter add dev eth0 parent 1: u32 match ip dport 23 0xffff action pedit pedit munge ip dport set 22
---
 include/net/tc_act/tc_pedit.h |  1 -
 net/sched/act_pedit.c         | 84 ++++++++++++++++++++++---------------------
 2 files changed, 43 insertions(+), 42 deletions(-)

diff --git a/include/net/tc_act/tc_pedit.h b/include/net/tc_act/tc_pedit.h
index 3e02709a1df656931942be4851a115dd6bef8b4c..748cf87a4d7ea5c92b4fd48dd3302b8ad64944fe 100644
--- a/include/net/tc_act/tc_pedit.h
+++ b/include/net/tc_act/tc_pedit.h
@@ -14,7 +14,6 @@ struct tcf_pedit {
 	struct tc_action	common;
 	unsigned char		tcfp_nkeys;
 	unsigned char		tcfp_flags;
-	u32			tcfp_off_max_hint;
 	struct tc_pedit_key	*tcfp_keys;
 	struct tcf_pedit_key_ex	*tcfp_keys_ex;
 };
diff --git a/net/sched/act_pedit.c b/net/sched/act_pedit.c
index a44101b2f441919b7d4b0be177aa735a3c547350..cebeed6214558b1e1dd717b411c9ffa108a2293d 100644
--- a/net/sched/act_pedit.c
+++ b/net/sched/act_pedit.c
@@ -14,6 +14,8 @@
 #include <linux/module.h>
 #include <linux/init.h>
 #include <linux/slab.h>
+#include <linux/overflow.h>
+#include <asm/unaligned.h>
 #include <net/netlink.h>
 #include <net/pkt_sched.h>
 #include <linux/tc_act/tc_pedit.h>
@@ -229,21 +231,11 @@ static int tcf_pedit_init(struct net *net, struct nlattr *nla,
 		p->tcfp_nkeys = parm->nkeys;
 	}
 	memcpy(p->tcfp_keys, parm->keys, ksize);
-	p->tcfp_off_max_hint = 0;
 	for (i = 0; i < p->tcfp_nkeys; ++i) {
-		u32 cur = p->tcfp_keys[i].off;
-
 		/* sanitize the shift value for any later use */
 		p->tcfp_keys[i].shift = min_t(size_t, BITS_PER_TYPE(int) - 1,
 					      p->tcfp_keys[i].shift);
 
-		/* The AT option can read a single byte, we can bound the actual
-		 * value with uchar max.
-		 */
-		cur += (0xff & p->tcfp_keys[i].offmask) >> p->tcfp_keys[i].shift;
-
-		/* Each key touches 4 bytes starting from the computed offset */
-		p->tcfp_off_max_hint = max(p->tcfp_off_max_hint, cur + 4);
 	}
 
 	p->tcfp_flags = parm->flags;
@@ -277,15 +269,12 @@ static void tcf_pedit_cleanup(struct tc_action *a)
 	kfree(p->tcfp_keys_ex);
 }
 
-static bool offset_valid(struct sk_buff *skb, int offset)
+static bool offset_valid(struct sk_buff *skb, int offset, int len)
 {
-	if (offset > 0 && offset > skb->len)
+	if (offset < -(int)skb_headroom(skb))
 		return false;
 
-	if  (offset < 0 && -offset > skb_headroom(skb))
-		return false;
-
-	return true;
+	return offset <= (int)skb->len - len;
 }
 
 static int pedit_skb_hdr_offset(struct sk_buff *skb,
@@ -325,18 +314,10 @@ static int tcf_pedit_act(struct sk_buff *skb, const struct tc_action *a,
 			 struct tcf_result *res)
 {
 	struct tcf_pedit *p = to_pedit(a);
-	u32 max_offset;
 	int i;
 
 	spin_lock(&p->tcf_lock);
 
-	max_offset = (skb_transport_header_was_set(skb) ?
-		      skb_transport_offset(skb) :
-		      skb_network_offset(skb)) +
-		     p->tcfp_off_max_hint;
-	if (skb_ensure_writable(skb, min(skb->len, max_offset)))
-		goto unlock;
-
 	tcf_lastuse_update(&p->tcf_tm);
 
 	if (p->tcfp_nkeys > 0) {
@@ -347,10 +328,11 @@ static int tcf_pedit_act(struct sk_buff *skb, const struct tc_action *a,
 		enum pedit_cmd cmd = TCA_PEDIT_KEY_EX_CMD_SET;
 
 		for (i = p->tcfp_nkeys; i > 0; i--, tkey++) {
-			u32 *ptr, hdata;
+			int write_offset, write_len;
 			int offset = tkey->off;
 			int hoffset;
-			u32 val;
+			u32 cur_val, val;
+			u32 *ptr;
 			int rc;
 
 			if (tkey_ex) {
@@ -369,13 +351,15 @@ static int tcf_pedit_act(struct sk_buff *skb, const struct tc_action *a,
 
 			if (tkey->offmask) {
 				u8 *d, _d;
+				int at_offset;
 
-				if (!offset_valid(skb, hoffset + tkey->at)) {
-					pr_info("tc action pedit 'at' offset %d out of bounds\n",
-						hoffset + tkey->at);
+				if (check_add_overflow(hoffset, (int)tkey->at, &at_offset) ||
+				    !offset_valid(skb, at_offset, sizeof(_d))) {
+					pr_info_ratelimited("tc action pedit 'at' offset %d out of bounds\n",
+							    hoffset + tkey->at);
 					goto bad;
 				}
-				d = skb_header_pointer(skb, hoffset + tkey->at,
+				d = skb_header_pointer(skb, at_offset,
 						       sizeof(_d), &_d);
 				if (!d)
 					goto bad;
@@ -387,23 +371,44 @@ static int tcf_pedit_act(struct sk_buff *skb, const struct tc_action *a,
 				goto bad;
 			}
 
-			if (!offset_valid(skb, hoffset + offset)) {
-				pr_info("tc action pedit offset %d out of bounds\n",
-					hoffset + offset);
+			if (check_add_overflow(hoffset, offset, &write_offset)) {
+				pr_info_ratelimited("tc action pedit offset overflow\n");
 				goto bad;
 			}
 
-			ptr = skb_header_pointer(skb, hoffset + offset,
-						 sizeof(hdata), &hdata);
-			if (!ptr)
+			if (!offset_valid(skb, write_offset, sizeof(*ptr))) {
+				pr_info_ratelimited("tc action pedit offset %d out of bounds\n",
+						    write_offset);
 				goto bad;
+			}
+
+			if (write_offset < 0) {
+				if (skb_cow(skb, -write_offset))
+					goto bad;
+				if (write_offset + (int)sizeof(*ptr) > 0) {
+					if (skb_ensure_writable(skb,
+								min_t(int, skb->len,
+								      write_offset + (int)sizeof(*ptr))))
+						goto bad;
+				}
+			} else {
+				if (check_add_overflow(write_offset, (int)sizeof(*ptr),
+						       &write_len))
+					goto bad;
+				if (skb_ensure_writable(skb, min_t(int, skb->len,
+								   write_len)))
+					goto bad;
+			}
+
+			ptr = (u32 *)(skb->data + write_offset);
+			cur_val = get_unaligned(ptr);
 			/* just do it, baby */
 			switch (cmd) {
 			case TCA_PEDIT_KEY_EX_CMD_SET:
 				val = tkey->val;
 				break;
 			case TCA_PEDIT_KEY_EX_CMD_ADD:
-				val = (*ptr + tkey->val) & ~tkey->mask;
+				val = (cur_val + tkey->val) & ~tkey->mask;
 				break;
 			default:
 				pr_info("tc action pedit bad command (%d)\n",
@@ -411,9 +416,7 @@ static int tcf_pedit_act(struct sk_buff *skb, const struct tc_action *a,
 				goto bad;
 			}
 
-			*ptr = ((*ptr & tkey->mask) ^ val);
-			if (ptr == &hdata)
-				skb_store_bits(skb, hoffset + offset, ptr, 4);
+			put_unaligned((cur_val & tkey->mask) ^ val, ptr);
 		}
 
 		goto done;
@@ -425,7 +428,6 @@ static int tcf_pedit_act(struct sk_buff *skb, const struct tc_action *a,
 	p->tcf_qstats.overlimits++;
 done:
 	bstats_update(&p->tcf_bstats, skb);
-unlock:
 	spin_unlock(&p->tcf_lock);
 	return p->tcf_action;
 }

---
base-commit: d9666dca97c01de1c7395ac53041634eb75120e2
change-id: 20260630-cve-2026-46331-4f851ccf8244

Best regards,
-- 
Dominique Martinet <dominique.martinet@atmark-techno.com>




^ permalink raw reply related	[flat|nested] 3+ messages in thread

* Re: [PATCH 5.10.y] net/sched: fix pedit partial COW leading to page cache corruption
  2026-06-30  8:27 ` [PATCH 5.10.y] net/sched: fix pedit partial COW leading to page cache corruption Dominique Martinet
@ 2026-07-02  0:38   ` Sasha Levin
  2026-07-02  1:00     ` Dominique Martinet
  0 siblings, 1 reply; 3+ messages in thread
From: Sasha Levin @ 2026-07-02  0:38 UTC (permalink / raw)
  To: dominique.martinet, stable
  Cc: Sasha Levin, Jamal Hadi Salim, Cong Wang, Jiri Pirko,
	David S. Miller, Jakub Kicinski, Mat Martineau, Paolo Abeni,
	netdev, linux-kernel, Rajat Gupta, Yiming Qian, Keenan Dong,
	Han Guidong, Zhang Cen, Davide Caratti,
	Toke Høiland-Jørgensen, Victor Nogueira

> [Dominique: plenty of context conflict but the code itself could still
> mostly be used]

Thanks! However, the same upstream fix (899ee91156e5) is already queued for
5.10.y via Wentao Guan's backport, which brings in the act_pedit
RCU/percpu-stats prerequisites first and then applies the fix nearly
verbatim.

-- 
Thanks,
Sasha

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH 5.10.y] net/sched: fix pedit partial COW leading to page cache corruption
  2026-07-02  0:38   ` Sasha Levin
@ 2026-07-02  1:00     ` Dominique Martinet
  0 siblings, 0 replies; 3+ messages in thread
From: Dominique Martinet @ 2026-07-02  1:00 UTC (permalink / raw)
  To: Sasha Levin
  Cc: stable, Jamal Hadi Salim, Cong Wang, Jiri Pirko, David S. Miller,
	Jakub Kicinski, Mat Martineau, Paolo Abeni, netdev, linux-kernel,
	Rajat Gupta, Yiming Qian, Keenan Dong, Han Guidong, Zhang Cen,
	Davide Caratti, Toke Høiland-Jørgensen, Victor Nogueira

Sasha Levin wrote on Wed, Jul 01, 2026 at 08:38:34PM -0400:
> > [Dominique: plenty of context conflict but the code itself could still
> > mostly be used]
> 
> Thanks! However, the same upstream fix (899ee91156e5) is already queued for
> 5.10.y via Wentao Guan's backport, which brings in the act_pedit
> RCU/percpu-stats prerequisites first and then applies the fix nearly
> verbatim.

Oh, thanks!
Sorry for the noise, I'll remember to check next time.

-- 
Dominique

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-07-02  1:00 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
     [not found] <20260630-cve-2026-46331-v1-1-c1986f356f26@atmark-techno.com>
2026-06-30  8:27 ` [PATCH 5.10.y] net/sched: fix pedit partial COW leading to page cache corruption Dominique Martinet
2026-07-02  0:38   ` Sasha Levin
2026-07-02  1:00     ` Dominique Martinet

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox