This small patchset is about avoid user-memory-access vulnerability for LIVE_FRAMES at specific xdp_md context.
--- KaFai Wan (2): bpf, test_run: Fix user-memory-access vulnerability for LIVE_FRAMES selftests/bpf: Add test for xdp_md context with LIVE_FRAMES in BPF_PROG_TEST_RUN
net/bpf/test_run.c | 23 +++++++++---------- .../bpf/prog_tests/xdp_context_test_run.c | 19 +++++++++++++++ .../bpf/prog_tests/xdp_do_redirect.c | 6 ++--- .../bpf/progs/test_xdp_context_test_run.c | 6 +++++ 4 files changed, 39 insertions(+), 15 deletions(-)
When testing XDP programs with LIVE_FRAMES mode, if the metalen is set to >= (XDP_PACKET_HEADROOM - sizeof(struct xdp_frame)), there won't be enough space for the xdp_frame conversion in xdp_update_frame_from_buff(). Additionally, the xdp_frame structure may be filled with user-provided data, which can lead to a memory access vulnerability when converting to skb.
This fix reverts to the original version and ensures data_hard_start correctly points to the xdp_frame structure, eliminating the security risk.
Reported-by: Yinhao Hu dddddd@hust.edu.cn Reported-by: Kaiyan Mei M202472210@hust.edu.cn Reviewed-by: Dongliang Mu dzm91@hust.edu.cn Fixes: 294635a8165a ("bpf, test_run: fix &xdp_frame misplacement for LIVE_FRAMES") Signed-off-by: KaFai Wan kafai.wan@linux.dev --- net/bpf/test_run.c | 23 +++++++++---------- .../bpf/prog_tests/xdp_do_redirect.c | 6 ++--- 2 files changed, 14 insertions(+), 15 deletions(-)
diff --git a/net/bpf/test_run.c b/net/bpf/test_run.c index 655efac6f133..00234eba7c76 100644 --- a/net/bpf/test_run.c +++ b/net/bpf/test_run.c @@ -90,11 +90,9 @@ static bool bpf_test_timer_continue(struct bpf_test_timer *t, int iterations, struct xdp_page_head { struct xdp_buff orig_ctx; struct xdp_buff ctx; - union { - /* ::data_hard_start starts here */ - DECLARE_FLEX_ARRAY(struct xdp_frame, frame); - DECLARE_FLEX_ARRAY(u8, data); - }; + /* ::data_hard_start starts here */ + struct xdp_frame frame; + DECLARE_FLEX_ARRAY(u8, data); };
struct xdp_test_data { @@ -131,10 +129,11 @@ static void xdp_test_run_init_page(netmem_ref netmem, void *arg) frm_len = orig_ctx->data_end - orig_ctx->data_meta; meta_len = orig_ctx->data - orig_ctx->data_meta; headroom -= meta_len; + headroom += sizeof(head->frame);
new_ctx = &head->ctx; - frm = head->frame; - data = head->data; + frm = &head->frame; + data = frm; memcpy(data + headroom, orig_ctx->data_meta, frm_len);
xdp_init_buff(new_ctx, TEST_XDP_FRAME_SIZE, &xdp->rxq); @@ -215,8 +214,8 @@ static bool frame_was_changed(const struct xdp_page_head *head) * i.e. has the highest chances to be overwritten. If those two are * untouched, it's most likely safe to skip the context reset. */ - return head->frame->data != head->orig_ctx.data || - head->frame->flags != head->orig_ctx.flags; + return head->frame.data != head->orig_ctx.data || + head->frame.flags != head->orig_ctx.flags; }
static bool ctx_was_changed(struct xdp_page_head *head) @@ -234,8 +233,8 @@ static void reset_ctx(struct xdp_page_head *head) head->ctx.data = head->orig_ctx.data; head->ctx.data_meta = head->orig_ctx.data_meta; head->ctx.data_end = head->orig_ctx.data_end; - xdp_update_frame_from_buff(&head->ctx, head->frame); - head->frame->mem_type = head->orig_ctx.rxq->mem.type; + xdp_update_frame_from_buff(&head->ctx, &head->frame); + head->frame.mem_type = head->orig_ctx.rxq->mem.type; }
static int xdp_recv_frames(struct xdp_frame **frames, int nframes, @@ -301,7 +300,7 @@ static int xdp_test_run_batch(struct xdp_test_data *xdp, struct bpf_prog *prog, head = phys_to_virt(page_to_phys(page)); reset_ctx(head); ctx = &head->ctx; - frm = head->frame; + frm = &head->frame; xdp->frame_cnt++;
act = bpf_prog_run_xdp(prog, ctx); diff --git a/tools/testing/selftests/bpf/prog_tests/xdp_do_redirect.c b/tools/testing/selftests/bpf/prog_tests/xdp_do_redirect.c index dd34b0cc4b4e..f7615c265e6e 100644 --- a/tools/testing/selftests/bpf/prog_tests/xdp_do_redirect.c +++ b/tools/testing/selftests/bpf/prog_tests/xdp_do_redirect.c @@ -59,12 +59,12 @@ static int attach_tc_prog(struct bpf_tc_hook *hook, int fd)
/* The maximum permissible size is: PAGE_SIZE - sizeof(struct xdp_page_head) - * SKB_DATA_ALIGN(sizeof(struct skb_shared_info)) - XDP_PACKET_HEADROOM = - * 3408 bytes for 64-byte cacheline and 3216 for 256-byte one. + * 3368 bytes for 64-byte cacheline and 3216 for 256-byte one. */ #if defined(__s390x__) -#define MAX_PKT_SIZE 3216 +#define MAX_PKT_SIZE 3176 #else -#define MAX_PKT_SIZE 3408 +#define MAX_PKT_SIZE 3368 #endif
#define PAGE_SIZE_4K 4096
KaFai Wan kafai.wan@linux.dev writes:
This fix reverts to the original version and ensures data_hard_start correctly points to the xdp_frame structure, eliminating the security risk.
This is wrong. We should just be checking the meta_len on input to account for the size of xdp_frame. I'll send a patch.
-Toke
On Mon, 2026-01-05 at 11:46 +0100, Toke Høiland-Jørgensen wrote:
KaFai Wan kafai.wan@linux.dev writes:
This fix reverts to the original version and ensures data_hard_start correctly points to the xdp_frame structure, eliminating the security risk.
This is wrong. We should just be checking the meta_len on input to account for the size of xdp_frame. I'll send a patch.
Current version the actual limit of the max input meta_len for live frames is XDP_PACKET_HEADROOM - sizeof(struct xdp_frame), not XDP_PACKET_HEADROOM.
The original version not set xdp_buff->data_hard_start with xdp_frame, I set it with the correct position by adding the headroom, so there is no need for user to reduce the max input meta_len.
This patch is failed with the xdp_do_redirect test, I'll fix and send v2 if you're ok with that.
-Toke
KaFai Wan kafai.wan@linux.dev writes:
On Mon, 2026-01-05 at 11:46 +0100, Toke Høiland-Jørgensen wrote:
KaFai Wan kafai.wan@linux.dev writes:
This fix reverts to the original version and ensures data_hard_start correctly points to the xdp_frame structure, eliminating the security risk.
This is wrong. We should just be checking the meta_len on input to account for the size of xdp_frame. I'll send a patch.
Current version the actual limit of the max input meta_len for live frames is XDP_PACKET_HEADROOM - sizeof(struct xdp_frame), not XDP_PACKET_HEADROOM.
By "current version", you mean the patch I sent[0], right?
If so, that was deliberate: the stack limits the maximum data_meta size to XDP_PACKET_HEADROOM - sizeof(struct xdp_frame), so there's no reason not to do the same for bpf_prog_run(). And some chance that diverging here will end up surfacing other bugs down the line.
-Toke
[0] https://lore.kernel.org/r/20260105114747.1358750-1-toke@redhat.com
On Mon, 2026-01-05 at 17:43 +0100, Toke Høiland-Jørgensen wrote:
KaFai Wan kafai.wan@linux.dev writes:
On Mon, 2026-01-05 at 11:46 +0100, Toke Høiland-Jørgensen wrote:
KaFai Wan kafai.wan@linux.dev writes:
This fix reverts to the original version and ensures data_hard_start correctly points to the xdp_frame structure, eliminating the security risk.
This is wrong. We should just be checking the meta_len on input to account for the size of xdp_frame. I'll send a patch.
Current version the actual limit of the max input meta_len for live frames is XDP_PACKET_HEADROOM - sizeof(struct xdp_frame), not XDP_PACKET_HEADROOM.
By "current version", you mean the patch I sent[0], right?
If so, that was deliberate: the stack limits the maximum data_meta size to XDP_PACKET_HEADROOM - sizeof(struct xdp_frame), so there's no reason not to do the same for bpf_prog_run(). And some chance that diverging here will end up surfacing other bugs down the line.
Oh, I see. Thank you for your explanation.
-Toke
[0] https://lore.kernel.org/r/20260105114747.1358750-1-toke@redhat.com
Add a test case uses xdp_md as context parameter for BPF_PROG_TEST_RUN with LIVE_FRAMES flag. The test ensures that potential user-memory-access vulnerabilities are properly prevented.
Signed-off-by: KaFai Wan kafai.wan@linux.dev --- .../bpf/prog_tests/xdp_context_test_run.c | 19 +++++++++++++++++++ .../bpf/progs/test_xdp_context_test_run.c | 6 ++++++ 2 files changed, 25 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c b/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c index ee94c281888a..0276daaae45c 100644 --- a/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c +++ b/tools/testing/selftests/bpf/prog_tests/xdp_context_test_run.c @@ -45,6 +45,7 @@ void test_xdp_context_error(int prog_fd, struct bpf_test_run_opts opts, void test_xdp_context_test_run(void) { struct test_xdp_context_test_run *skel = NULL; + char data_xdp[sizeof(pkt_v4) + XDP_PACKET_HEADROOM]; char data[sizeof(pkt_v4) + sizeof(__u32)]; char bad_ctx[sizeof(struct xdp_md) + 1]; struct xdp_md ctx_in, ctx_out; @@ -55,6 +56,12 @@ void test_xdp_context_test_run(void) .ctx_size_out = sizeof(ctx_out), .repeat = 1, ); + DECLARE_LIBBPF_OPTS(bpf_test_run_opts, opts_xdp, + .data_in = &data_xdp, + .data_size_in = sizeof(data_xdp), + .flags = BPF_F_TEST_XDP_LIVE_FRAMES, + .repeat = 1, + ); int err, prog_fd;
skel = test_xdp_context_test_run__open_and_load(); @@ -70,6 +77,18 @@ void test_xdp_context_test_run(void) ASSERT_EQ(errno, E2BIG, "extradata-errno"); ASSERT_ERR(err, "bpf_prog_test_run(extradata)");
+ memset(&ctx_in, 0, sizeof(ctx_in)); + ctx_in.data_meta = 0; + ctx_in.data = 0xf4; + ctx_in.data_end = sizeof(data_xdp); + opts_xdp.ctx_in = &ctx_in; + opts_xdp.ctx_size_in = sizeof(ctx_in); + *(__u32 *)(data_xdp + 0) = 0x28d6a0b5; + *(__u32 *)(data_xdp + 4) = 0xf273eea3; + *(struct ipv4_packet *)(data_xdp + ctx_in.data) = pkt_v4; + err = bpf_prog_test_run_opts(bpf_program__fd(skel->progs.xdp_pass), &opts_xdp); + ASSERT_OK(err, "bpf_prog_test_run(valid meta)"); + *(__u32 *)data = XDP_PASS; *(struct ipv4_packet *)(data + sizeof(__u32)) = pkt_v4; opts.ctx_in = &ctx_in; diff --git a/tools/testing/selftests/bpf/progs/test_xdp_context_test_run.c b/tools/testing/selftests/bpf/progs/test_xdp_context_test_run.c index d7b88cd05afd..2166928d4680 100644 --- a/tools/testing/selftests/bpf/progs/test_xdp_context_test_run.c +++ b/tools/testing/selftests/bpf/progs/test_xdp_context_test_run.c @@ -17,4 +17,10 @@ int xdp_context(struct xdp_md *xdp) return ret; }
+SEC("xdp") +int xdp_pass(struct xdp_md *xdp) +{ + return XDP_PASS; +} + char _license[] SEC("license") = "GPL";
syzbot ci has tested the following series
[v1] bpf, test_run: Fix user-memory-access vulnerability for LIVE_FRAMES https://lore.kernel.org/all/20260104162350.347403-1-kafai.wan@linux.dev * [PATCH bpf-next 1/2] bpf, test_run: Fix user-memory-access vulnerability for LIVE_FRAMES * [PATCH bpf-next 2/2] selftests/bpf: Add test for xdp_md context with LIVE_FRAMES in BPF_PROG_TEST_RUN
and found the following issue: BUG: missing reserved tailroom
Full report is available here: https://ci.syzbot.org/series/688d9198-9fed-495e-9113-3d4187428ee3
***
BUG: missing reserved tailroom
tree: bpf-next URL: https://kernel.googlesource.com/pub/scm/linux/kernel/git/bpf/bpf-next.git base: a069190b590e108223cd841a1c2d0bfb92230ecc arch: amd64 compiler: Debian clang version 21.1.8 (++20251202083448+f68f64eb8130-1~exp1~20251202083504.46), Debian LLD 21.1.8 config: https://ci.syzbot.org/builds/3375fbfc-d3f8-45a8-8db2-62ac76ebb7b4/config C repro: https://ci.syzbot.org/findings/37614d32-0f44-4a1c-9822-586c95cfeb11/c_repro syz repro: https://ci.syzbot.org/findings/37614d32-0f44-4a1c-9822-586c95cfeb11/syz_repr...
------------[ cut here ]------------ XDP_WARN: xdp_update_frame_from_buff(line:414): Driver BUG: missing reserved tailroom WARNING: net/core/xdp.c:618 at xdp_warn+0x1d/0x40 net/core/xdp.c:618, CPU#1: syz.0.17/5990 Modules linked in: CPU: 1 UID: 0 PID: 5990 Comm: syz.0.17 Not tainted syzkaller #0 PREEMPT(full) Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-debian-1.16.2-1 04/01/2014 RIP: 0010:xdp_warn+0x25/0x40 net/core/xdp.c:618 Code: 90 90 90 90 90 f3 0f 1e fa 41 57 41 56 53 89 d3 49 89 f6 49 89 ff e8 da 83 63 f8 48 8d 3d e3 de 70 06 4c 89 f6 89 da 4c 89 f9 <67> 48 0f b9 3a 5b 41 5e 41 5f c3 cc cc cc cc cc 66 66 2e 0f 1f 84 RSP: 0018:ffffc90004427580 EFLAGS: 00010293 RAX: ffffffff895fc996 RBX: 000000000000019e RCX: ffffffff8c94c7e0 RDX: 000000000000019e RSI: ffffffff8da38c06 RDI: ffffffff8fd0a880 RBP: 0000000000000f68 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000000 R12: ffff8881b4ae3ec0 R13: ffff8881b4ae3198 R14: ffffffff8da38c06 R15: ffffffff8c94c7e0 FS: 000055555699b500(0000) GS:ffff8882a9a1f000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 0000001b30463fff CR3: 0000000175022000 CR4: 00000000000006f0 Call Trace: <TASK> xdp_update_frame_from_buff include/net/xdp.h:414 [inline] xdp_test_run_init_page+0x482/0x590 net/bpf/test_run.c:143 page_pool_set_pp_info net/core/page_pool.c:716 [inline] __page_pool_alloc_netmems_slow+0x33c/0x710 net/core/page_pool.c:631 page_pool_alloc_netmems net/core/page_pool.c:667 [inline] page_pool_alloc_pages+0xe6/0x1f0 net/core/page_pool.c:675 page_pool_dev_alloc_pages include/net/page_pool/helpers.h:96 [inline] xdp_test_run_batch net/bpf/test_run.c:294 [inline] bpf_test_run_xdp_live+0x714/0x1cf0 net/bpf/test_run.c:378 bpf_prog_test_run_xdp+0x7d2/0x1120 net/bpf/test_run.c:1387 bpf_prog_test_run+0x2c7/0x340 kernel/bpf/syscall.c:4698 __sys_bpf+0x643/0x950 kernel/bpf/syscall.c:6220 __do_sys_bpf kernel/bpf/syscall.c:6315 [inline] __se_sys_bpf kernel/bpf/syscall.c:6313 [inline] __x64_sys_bpf+0x7c/0x90 kernel/bpf/syscall.c:6313 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0xf0/0xf80 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7f389319acb9 Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48 RSP: 002b:00007ffc43c08dd8 EFLAGS: 00000246 ORIG_RAX: 0000000000000141 RAX: ffffffffffffffda RBX: 00007f3893405fa0 RCX: 00007f389319acb9 RDX: 0000000000000048 RSI: 0000200000000600 RDI: 000000000000000a RBP: 00007f3893208bf7 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000 R13: 00007f3893405fac R14: 00007f3893405fa0 R15: 00007f3893405fa0 </TASK> ---------------- Code disassembly (best guess): 0: 90 nop 1: 90 nop 2: 90 nop 3: 90 nop 4: 90 nop 5: f3 0f 1e fa endbr64 9: 41 57 push %r15 b: 41 56 push %r14 d: 53 push %rbx e: 89 d3 mov %edx,%ebx 10: 49 89 f6 mov %rsi,%r14 13: 49 89 ff mov %rdi,%r15 16: e8 da 83 63 f8 call 0xf86383f5 1b: 48 8d 3d e3 de 70 06 lea 0x670dee3(%rip),%rdi # 0x670df05 22: 4c 89 f6 mov %r14,%rsi 25: 89 da mov %ebx,%edx 27: 4c 89 f9 mov %r15,%rcx * 2a: 67 48 0f b9 3a ud1 (%edx),%rdi <-- trapping instruction 2f: 5b pop %rbx 30: 41 5e pop %r14 32: 41 5f pop %r15 34: c3 ret 35: cc int3 36: cc int3 37: cc int3 38: cc int3 39: cc int3 3a: 66 data16 3b: 66 data16 3c: 2e cs 3d: 0f .byte 0xf 3e: 1f (bad) 3f: 84 .byte 0x84
***
If these findings have caused you to resend the series or submit a separate fix, please add the following tag to your commit message: Tested-by: syzbot@syzkaller.appspotmail.com
--- This report is generated by a bot. It may contain errors. syzbot ci engineers can be reached at syzkaller@googlegroups.com.
linux-kselftest-mirror@lists.linaro.org