diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c index 58e357302850..3cbee140ede8 100644 --- a/kernel/bpf/helpers.c +++ b/kernel/bpf/helpers.c @@ -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;