Skip to content

Commit 346c960

Browse files
Rajat Guptashreeya-patel98
authored andcommitted
net/sched: fix pedit partial COW leading to page cache corruption
jira VULN-188480 cve CVE-2026-46331 commit-author Rajat Gupta <rajat.gupta@oss.qualcomm.com> commit 899ee91 upstream-diff | This tree predates the act_pedit RCU refactor (struct tcf_pedit_parms and the RCU 'parms' pointer), so the upstream commit does not apply cleanly. The fix is adapted to the flat struct tcf_pedit layout: parms->tcfp_* accesses become p->tcfp_*, and only the tcfp_off_max_hint field is removed from the struct. The COW-correctness change is otherwise identical to upstream -- the pre-loop skb_ensure_writable() keyed on tcfp_off_max_hint is removed, and per-key COW (skb_ensure_writable(), or skb_cow() for negative offsets) is performed inside the loop with check_add_overflow() guards and the len-aware offset_valid(). The datapath read/write switches from skb_header_pointer()/ skb_store_bits() to a direct skb->data pointer with get_unaligned()/put_unaligned(), matching upstream. Additionally, <linux/unaligned.h> does not exist in this tree (it was created by upstream commit 5f60d5f "move asm/unaligned.h to linux/unaligned.h" in v6.12, which postdates 5.14), so the include is changed to <asm/unaligned.h>, which provides the same helpers here. 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: 8b79647 ("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> (cherry picked from commit 899ee91) Signed-off-by: Shreeya Patel <spatel@ciq.com> Signed-off-by: Shreeya Patel <spatel@ciq.com>
1 parent 17e4221 commit 346c960

2 files changed

Lines changed: 49 additions & 49 deletions

File tree

include/net/tc_act/tc_pedit.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ struct tcf_pedit {
1414
struct tc_action common;
1515
unsigned char tcfp_nkeys;
1616
unsigned char tcfp_flags;
17-
u32 tcfp_off_max_hint;
1817
struct tc_pedit_key *tcfp_keys;
1918
struct tcf_pedit_key_ex *tcfp_keys_ex;
2019
};

net/sched/act_pedit.c

Lines changed: 49 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
#include <linux/module.h>
1515
#include <linux/init.h>
1616
#include <linux/slab.h>
17+
#include <linux/overflow.h>
18+
#include <asm/unaligned.h>
1719
#include <net/netlink.h>
1820
#include <net/pkt_sched.h>
1921
#include <linux/tc_act/tc_pedit.h>
@@ -228,21 +230,10 @@ static int tcf_pedit_init(struct net *net, struct nlattr *nla,
228230
p->tcfp_nkeys = parm->nkeys;
229231
}
230232
memcpy(p->tcfp_keys, parm->keys, ksize);
231-
p->tcfp_off_max_hint = 0;
232233
for (i = 0; i < p->tcfp_nkeys; ++i) {
233-
u32 cur = p->tcfp_keys[i].off;
234-
235234
/* sanitize the shift value for any later use */
236235
p->tcfp_keys[i].shift = min_t(size_t, BITS_PER_TYPE(int) - 1,
237236
p->tcfp_keys[i].shift);
238-
239-
/* The AT option can read a single byte, we can bound the actual
240-
* value with uchar max.
241-
*/
242-
cur += (0xff & p->tcfp_keys[i].offmask) >> p->tcfp_keys[i].shift;
243-
244-
/* Each key touches 4 bytes starting from the computed offset */
245-
p->tcfp_off_max_hint = max(p->tcfp_off_max_hint, cur + 4);
246237
}
247238

248239
p->tcfp_flags = parm->flags;
@@ -276,15 +267,12 @@ static void tcf_pedit_cleanup(struct tc_action *a)
276267
kfree(p->tcfp_keys_ex);
277268
}
278269

279-
static bool offset_valid(struct sk_buff *skb, int offset)
270+
static bool offset_valid(struct sk_buff *skb, int offset, int len)
280271
{
281-
if (offset > 0 && offset > skb->len)
272+
if (offset < -(int)skb_headroom(skb))
282273
return false;
283274

284-
if (offset < 0 && -offset > skb_headroom(skb))
285-
return false;
286-
287-
return true;
275+
return offset <= (int)skb->len - len;
288276
}
289277

290278
static int pedit_skb_hdr_offset(struct sk_buff *skb,
@@ -324,18 +312,10 @@ static int tcf_pedit_act(struct sk_buff *skb, const struct tc_action *a,
324312
struct tcf_result *res)
325313
{
326314
struct tcf_pedit *p = to_pedit(a);
327-
u32 max_offset;
328315
int i;
329316

330317
spin_lock(&p->tcf_lock);
331318

332-
max_offset = (skb_transport_header_was_set(skb) ?
333-
skb_transport_offset(skb) :
334-
skb_network_offset(skb)) +
335-
p->tcfp_off_max_hint;
336-
if (skb_ensure_writable(skb, min(skb->len, max_offset)))
337-
goto unlock;
338-
339319
tcf_lastuse_update(&p->tcf_tm);
340320

341321
if (p->tcfp_nkeys > 0) {
@@ -346,10 +326,11 @@ static int tcf_pedit_act(struct sk_buff *skb, const struct tc_action *a,
346326
enum pedit_cmd cmd = TCA_PEDIT_KEY_EX_CMD_SET;
347327

348328
for (i = p->tcfp_nkeys; i > 0; i--, tkey++) {
349-
u32 *ptr, hdata;
329+
int write_offset, write_len;
350330
int offset = tkey->off;
351-
int hoffset;
352-
u32 val;
331+
int hoffset = 0;
332+
u32 cur_val, val;
333+
u32 *ptr;
353334
int rc;
354335

355336
if (tkey_ex) {
@@ -361,58 +342,79 @@ static int tcf_pedit_act(struct sk_buff *skb, const struct tc_action *a,
361342

362343
rc = pedit_skb_hdr_offset(skb, htype, &hoffset);
363344
if (rc) {
364-
pr_info("tc action pedit bad header type specified (0x%x)\n",
365-
htype);
345+
pr_info_ratelimited("tc action pedit unable to extract header offset for header type (0x%x)\n",
346+
htype);
366347
goto bad;
367348
}
368349

369350
if (tkey->offmask) {
370351
u8 *d, _d;
352+
int at_offset;
371353

372-
if (!offset_valid(skb, hoffset + tkey->at)) {
373-
pr_info("tc action pedit 'at' offset %d out of bounds\n",
374-
hoffset + tkey->at);
354+
if (check_add_overflow(hoffset, (int)tkey->at, &at_offset) ||
355+
!offset_valid(skb, at_offset, sizeof(_d))) {
356+
pr_info_ratelimited("tc action pedit 'at' offset %d out of bounds\n",
357+
hoffset + tkey->at);
375358
goto bad;
376359
}
377-
d = skb_header_pointer(skb, hoffset + tkey->at,
360+
d = skb_header_pointer(skb, at_offset,
378361
sizeof(_d), &_d);
379362
if (!d)
380363
goto bad;
381364
offset += (*d & tkey->offmask) >> tkey->shift;
382365
}
383366

384367
if (offset % 4) {
385-
pr_info("tc action pedit offset must be on 32 bit boundaries\n");
368+
pr_info_ratelimited("tc action pedit offset must be on 32 bit boundaries\n");
386369
goto bad;
387370
}
388371

389-
if (!offset_valid(skb, hoffset + offset)) {
390-
pr_info("tc action pedit offset %d out of bounds\n",
391-
hoffset + offset);
372+
if (check_add_overflow(hoffset, offset, &write_offset)) {
373+
pr_info_ratelimited("tc action pedit offset overflow\n");
392374
goto bad;
393375
}
394376

395-
ptr = skb_header_pointer(skb, hoffset + offset,
396-
sizeof(hdata), &hdata);
397-
if (!ptr)
377+
if (!offset_valid(skb, write_offset, sizeof(*ptr))) {
378+
pr_info_ratelimited("tc action pedit offset %d out of bounds\n",
379+
write_offset);
398380
goto bad;
381+
}
382+
383+
if (write_offset < 0) {
384+
if (skb_cow(skb, -write_offset))
385+
goto bad;
386+
if (write_offset + (int)sizeof(*ptr) > 0) {
387+
if (skb_ensure_writable(skb,
388+
min_t(int, skb->len,
389+
write_offset + (int)sizeof(*ptr))))
390+
goto bad;
391+
}
392+
} else {
393+
if (check_add_overflow(write_offset, (int)sizeof(*ptr),
394+
&write_len))
395+
goto bad;
396+
if (skb_ensure_writable(skb, min_t(int, skb->len,
397+
write_len)))
398+
goto bad;
399+
}
400+
401+
ptr = (u32 *)(skb->data + write_offset);
402+
cur_val = get_unaligned(ptr);
399403
/* just do it, baby */
400404
switch (cmd) {
401405
case TCA_PEDIT_KEY_EX_CMD_SET:
402406
val = tkey->val;
403407
break;
404408
case TCA_PEDIT_KEY_EX_CMD_ADD:
405-
val = (*ptr + tkey->val) & ~tkey->mask;
409+
val = (cur_val + tkey->val) & ~tkey->mask;
406410
break;
407411
default:
408-
pr_info("tc action pedit bad command (%d)\n",
409-
cmd);
412+
pr_info_ratelimited("tc action pedit bad command (%d)\n",
413+
cmd);
410414
goto bad;
411415
}
412416

413-
*ptr = ((*ptr & tkey->mask) ^ val);
414-
if (ptr == &hdata)
415-
skb_store_bits(skb, hoffset + offset, ptr, 4);
417+
put_unaligned((cur_val & tkey->mask) ^ val, ptr);
416418
}
417419

418420
goto done;
@@ -424,7 +426,6 @@ static int tcf_pedit_act(struct sk_buff *skb, const struct tc_action *a,
424426
p->tcf_qstats.overlimits++;
425427
done:
426428
bstats_update(&p->tcf_bstats, skb);
427-
unlock:
428429
spin_unlock(&p->tcf_lock);
429430
return p->tcf_action;
430431
}

0 commit comments

Comments
 (0)