Merge branch 'bpf-fix-overlapping-copies-in-bpf_dynptr_copy'

Ayushman Rout says:

====================
bpf: Fix overlapping copies in bpf_dynptr_copy()

bpf_dynptr_copy() can corrupt data when both ranges are in the same xdp
packet and overlap: a slice of one range is used as the buffer for
__bpf_dynptr_read() or __bpf_dynptr_write(), and the chunk loop always
copies forward.

Patch 1 does not use a slice as the buffer for a copy within one packet
and copies from the end when the destination is above the source.
Patch 2 adds selftests. They pass with the fix.

Changes in v2:
- Dropped the bpf_xdp_copy_buf() change and fixed everything in
  bpf_dynptr_copy(), as suggested by Alexei
- Reworked the tests into bpf_dynptr_copy() tests where only one of the
  ranges crosses into frag0

v1: https://lore.kernel.org/bpf/20261024092659.76422-1-ayushmanrout27@gmail.com/
====================

Link: https://patch.msgid.link/20261026162810.12862-1-ayushmanrout27@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
Alexei Starovoitov 2026-11-01 01:10:23 +00:00
commit 5b96632457
No known key found for this signature in database
3 changed files with 105 additions and 4 deletions

View File

@ -3294,8 +3294,10 @@ __bpf_kfunc int bpf_dynptr_copy(const struct bpf_dynptr *dst_ptr, u64 dst_off,
{
const struct bpf_dynptr_kern *dst = (struct bpf_dynptr_kern *)dst_ptr;
const struct bpf_dynptr_kern *src = (struct bpf_dynptr_kern *)src_ptr;
bool same = src->data == dst->data;
void *src_slice, *dst_slice;
char buf[256];
bool rev;
u64 off;
src_slice = bpf_dynptr_slice(src_ptr, src_off, NULL, size);
@ -3306,25 +3308,31 @@ __bpf_kfunc int bpf_dynptr_copy(const struct bpf_dynptr *dst_ptr, u64 dst_off,
return 0;
}
if (src_slice)
/* The other range is copied piece by piece. A slice of the same packet
* may overlap with it, so it cannot be the buffer for that.
*/
if (src_slice && !same)
return __bpf_dynptr_write(dst, dst_off, src_slice, size, 0);
if (dst_slice)
if (dst_slice && !same)
return __bpf_dynptr_read(dst_slice, size, src, src_off, 0);
if (bpf_dynptr_check_off_len(dst, dst_off, size) ||
bpf_dynptr_check_off_len(src, src_off, size))
return -E2BIG;
/* like memmove(): from the end if the destination is above the source */
rev = same && dst->offset + dst_off > src->offset + src_off;
off = 0;
while (off < size) {
u64 chunk_sz = min_t(u64, sizeof(buf), size - off);
u64 pos = rev ? size - off - chunk_sz : off;
int err;
err = __bpf_dynptr_read(buf, chunk_sz, src, src_off + off, 0);
err = __bpf_dynptr_read(buf, chunk_sz, src, src_off + pos, 0);
if (err)
return err;
err = __bpf_dynptr_write(dst, dst_off + off, buf, chunk_sz, 0);
err = __bpf_dynptr_write(dst, dst_off + pos, buf, chunk_sz, 0);
if (err)
return err;

View File

@ -22,6 +22,9 @@ static struct {
{"test_dynptr_data", SETUP_SYSCALL_SLEEP},
{"test_dynptr_copy", SETUP_SYSCALL_SLEEP},
{"test_dynptr_copy_xdp", SETUP_XDP_PROG},
{"test_dynptr_copy_xdp_overlap", SETUP_XDP_PROG},
{"test_dynptr_copy_xdp_overlap_src_linear", SETUP_XDP_PROG},
{"test_dynptr_copy_xdp_overlap_dst_frag", SETUP_XDP_PROG},
{"test_dynptr_memset_zero", SETUP_SYSCALL_SLEEP},
{"test_dynptr_memset_notzero", SETUP_SYSCALL_SLEEP},
{"test_dynptr_memset_zero_offset", SETUP_SYSCALL_SLEEP},

View File

@ -759,6 +759,96 @@ int test_dynptr_copy_xdp(struct xdp_md *xdp)
return XDP_DROP;
}
static __always_inline int fill_xdp(struct bpf_dynptr *ptr, __u32 start, __u32 n)
{
__u8 b;
int i;
bpf_for(i, 0, n) {
b = (start + i) % 251;
if (bpf_dynptr_write(ptr, start + i, &b, 1, 0))
return 1;
}
return 0;
}
static __always_inline int check_xdp(struct bpf_dynptr *ptr, __u32 at, __u32 n, __u32 from)
{
__u8 b;
int i;
bpf_for(i, 0, n) {
if (bpf_dynptr_read(&b, 1, ptr, at + i, 0))
return 1;
if (b != (__u8)((from + i) % 251))
return 1;
}
return 0;
}
SEC("?xdp")
int test_dynptr_copy_xdp_overlap(struct xdp_md *xdp)
{
void *data_end = (void *)(long)xdp->data_end;
void *data = (void *)(long)xdp->data;
__u32 off = data_end - data - 1000;
struct bpf_dynptr ptr;
bpf_dynptr_from_xdp(xdp, 0, &ptr);
if (fill_xdp(&ptr, off, 1300)) {
err = 1;
return XDP_DROP;
}
err = bpf_dynptr_copy(&ptr, off + 100, &ptr, off, 1200);
if (err)
return XDP_DROP;
if (check_xdp(&ptr, off + 100, 1200, off))
err = 2;
return XDP_DROP;
}
SEC("?xdp")
int test_dynptr_copy_xdp_overlap_src_linear(struct xdp_md *xdp)
{
void *data_end = (void *)(long)xdp->data_end;
void *data = (void *)(long)xdp->data;
__u32 lin = data_end - data;
struct bpf_dynptr ptr;
bpf_dynptr_from_xdp(xdp, 0, &ptr);
if (fill_xdp(&ptr, lin - 64, 118)) {
err = 1;
return XDP_DROP;
}
err = bpf_dynptr_copy(&ptr, lin - 10, &ptr, lin - 64, 64);
if (err)
return XDP_DROP;
if (check_xdp(&ptr, lin - 10, 64, lin - 64))
err = 2;
return XDP_DROP;
}
SEC("?xdp")
int test_dynptr_copy_xdp_overlap_dst_frag(struct xdp_md *xdp)
{
void *data_end = (void *)(long)xdp->data_end;
void *data = (void *)(long)xdp->data;
__u32 lin = data_end - data;
struct bpf_dynptr ptr;
bpf_dynptr_from_xdp(xdp, 0, &ptr);
if (fill_xdp(&ptr, lin - 10, 84)) {
err = 1;
return XDP_DROP;
}
err = bpf_dynptr_copy(&ptr, lin + 10, &ptr, lin - 10, 64);
if (err)
return XDP_DROP;
if (check_xdp(&ptr, lin + 10, 64, lin - 10))
err = 2;
return XDP_DROP;
}
char memset_zero_data[] = "data to be zeroed";
SEC("?tp/syscalls/sys_enter_nanosleep")