Commit 1f24c0d8 authored by Andrii Nakryiko's avatar Andrii Nakryiko
Browse files

Merge branch 'bpf-add-bpf_dynptr_memset-kfunc'

Ihor Solodrai says:

====================
bpf: add bpf_dynptr_memset() kfunc

Implement bpf_dynptr_memset() kfunc and add tests for it.

v3->v4:
  * do error checks after slice, nits
v2->v3:
  * nits and slow-path loop rewrite (Andrii)
  * simplify xdp chunks test (Mykyta)
v1->v2:
  * handle non-linear buffers with bpf_dynptr_write()
  * change function signature to include offset arg
  * add more test cases

v3: https://lore.kernel.org/bpf/20250630212113.573097-1-isolodrai@meta.com/
v2: https://lore.kernel.org/bpf/20250624205240.1311453-1-isolodrai@meta.com/
v1: https://lore.kernel.org/bpf/20250618223310.3684760-1-isolodrai@meta.com/
====================

Link: https://patch.msgid.link/20250702210309.3115903-1-isolodrai@meta.com


Signed-off-by: default avatarAndrii Nakryiko <andrii@kernel.org>
parents 38d95beb 7b296892
Loading
Loading
Loading
Loading
+47 −0
Original line number Diff line number Diff line
@@ -2907,6 +2907,52 @@ __bpf_kfunc int bpf_dynptr_copy(struct bpf_dynptr *dst_ptr, u32 dst_off,
	return 0;
}

/**
 * bpf_dynptr_memset() - Fill dynptr memory with a constant byte.
 * @p: Destination dynptr - where data will be filled
 * @offset: Offset into the dynptr to start filling from
 * @size: Number of bytes to fill
 * @val: Constant byte to fill the memory with
 *
 * Fills the @size bytes of the memory area pointed to by @p
 * at @offset with the constant byte @val.
 * Returns 0 on success; negative error, otherwise.
 */
 __bpf_kfunc int bpf_dynptr_memset(struct bpf_dynptr *p, u32 offset, u32 size, u8 val)
 {
	struct bpf_dynptr_kern *ptr = (struct bpf_dynptr_kern *)p;
	u32 chunk_sz, write_off;
	char buf[256];
	void* slice;
	int err;

	slice = bpf_dynptr_slice_rdwr(p, offset, NULL, size);
	if (likely(slice)) {
		memset(slice, val, size);
		return 0;
	}

	if (__bpf_dynptr_is_rdonly(ptr))
		return -EINVAL;

	err = bpf_dynptr_check_off_len(ptr, offset, size);
	if (err)
		return err;

	/* Non-linear data under the dynptr, write from a local buffer */
	chunk_sz = min_t(u32, sizeof(buf), size);
	memset(buf, val, chunk_sz);

	for (write_off = 0; write_off < size; write_off += chunk_sz) {
		chunk_sz = min_t(u32, sizeof(buf), size - write_off);
		err = __bpf_dynptr_write(ptr, offset + write_off, buf, chunk_sz, 0);
		if (err)
			return err;
	}

	return 0;
}

__bpf_kfunc void *bpf_cast_to_kern_ctx(void *obj)
{
	return obj;
@@ -3735,6 +3781,7 @@ BTF_ID_FLAGS(func, bpf_dynptr_is_rdonly)
BTF_ID_FLAGS(func, bpf_dynptr_size)
BTF_ID_FLAGS(func, bpf_dynptr_clone)
BTF_ID_FLAGS(func, bpf_dynptr_copy)
BTF_ID_FLAGS(func, bpf_dynptr_memset)
#ifdef CONFIG_NET
BTF_ID_FLAGS(func, bpf_modify_return_test_tp)
#endif
+8 −0
Original line number Diff line number Diff line
@@ -21,6 +21,14 @@ static struct {
	{"test_dynptr_data", SETUP_SYSCALL_SLEEP},
	{"test_dynptr_copy", SETUP_SYSCALL_SLEEP},
	{"test_dynptr_copy_xdp", 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},
	{"test_dynptr_memset_zero_adjusted", SETUP_SYSCALL_SLEEP},
	{"test_dynptr_memset_overflow", SETUP_SYSCALL_SLEEP},
	{"test_dynptr_memset_overflow_offset", SETUP_SYSCALL_SLEEP},
	{"test_dynptr_memset_readonly", SETUP_SKB_PROG},
	{"test_dynptr_memset_xdp_chunks", SETUP_XDP_PROG},
	{"test_ringbuf", SETUP_SYSCALL_SLEEP},
	{"test_skb_readonly", SETUP_SKB_PROG},
	{"test_dynptr_skb_data", SETUP_SKB_PROG},
+158 −0
Original line number Diff line number Diff line
@@ -681,6 +681,164 @@ int test_dynptr_copy_xdp(struct xdp_md *xdp)
	return XDP_DROP;
}

char memset_zero_data[] = "data to be zeroed";

SEC("?tp/syscalls/sys_enter_nanosleep")
int test_dynptr_memset_zero(void *ctx)
{
	__u32 data_sz = sizeof(memset_zero_data);
	char zeroes[32] = {'\0'};
	struct bpf_dynptr ptr;

	err = bpf_dynptr_from_mem(memset_zero_data, data_sz, 0, &ptr);
	err = err ?: bpf_dynptr_memset(&ptr, 0, data_sz, 0);
	err = err ?: bpf_memcmp(zeroes, memset_zero_data, data_sz);

	return 0;
}

#define DYNPTR_MEMSET_VAL 42

char memset_notzero_data[] = "data to be overwritten";

SEC("?tp/syscalls/sys_enter_nanosleep")
int test_dynptr_memset_notzero(void *ctx)
{
	u32 data_sz = sizeof(memset_notzero_data);
	struct bpf_dynptr ptr;
	char expected[32];

	__builtin_memset(expected, DYNPTR_MEMSET_VAL, data_sz);

	err = bpf_dynptr_from_mem(memset_notzero_data, data_sz, 0, &ptr);
	err = err ?: bpf_dynptr_memset(&ptr, 0, data_sz, DYNPTR_MEMSET_VAL);
	err = err ?: bpf_memcmp(expected, memset_notzero_data, data_sz);

	return 0;
}

char memset_zero_offset_data[] = "data to be zeroed partially";

SEC("?tp/syscalls/sys_enter_nanosleep")
int test_dynptr_memset_zero_offset(void *ctx)
{
	char expected[] = "data to \0\0\0\0eroed partially";
	__u32 data_sz = sizeof(memset_zero_offset_data);
	struct bpf_dynptr ptr;

	err = bpf_dynptr_from_mem(memset_zero_offset_data, data_sz, 0, &ptr);
	err = err ?: bpf_dynptr_memset(&ptr, 8, 4, 0);
	err = err ?: bpf_memcmp(expected, memset_zero_offset_data, data_sz);

	return 0;
}

char memset_zero_adjusted_data[] = "data to be zeroed partially";

SEC("?tp/syscalls/sys_enter_nanosleep")
int test_dynptr_memset_zero_adjusted(void *ctx)
{
	char expected[] = "data\0\0\0\0be zeroed partially";
	__u32 data_sz = sizeof(memset_zero_adjusted_data);
	struct bpf_dynptr ptr;

	err = bpf_dynptr_from_mem(memset_zero_adjusted_data, data_sz, 0, &ptr);
	err = err ?: bpf_dynptr_adjust(&ptr, 4, 8);
	err = err ?: bpf_dynptr_memset(&ptr, 0, bpf_dynptr_size(&ptr), 0);
	err = err ?: bpf_memcmp(expected, memset_zero_adjusted_data, data_sz);

	return 0;
}

char memset_overflow_data[] = "memset overflow data";

SEC("?tp/syscalls/sys_enter_nanosleep")
int test_dynptr_memset_overflow(void *ctx)
{
	__u32 data_sz = sizeof(memset_overflow_data);
	struct bpf_dynptr ptr;
	int ret;

	err = bpf_dynptr_from_mem(memset_overflow_data, data_sz, 0, &ptr);
	ret = bpf_dynptr_memset(&ptr, 0, data_sz + 1, 0);
	if (ret != -E2BIG)
		err = 1;

	return 0;
}

SEC("?tp/syscalls/sys_enter_nanosleep")
int test_dynptr_memset_overflow_offset(void *ctx)
{
	__u32 data_sz = sizeof(memset_overflow_data);
	struct bpf_dynptr ptr;
	int ret;

	err = bpf_dynptr_from_mem(memset_overflow_data, data_sz, 0, &ptr);
	ret = bpf_dynptr_memset(&ptr, 1, data_sz, 0);
	if (ret != -E2BIG)
		err = 1;

	return 0;
}

SEC("?cgroup_skb/egress")
int test_dynptr_memset_readonly(struct __sk_buff *skb)
{
	struct bpf_dynptr ptr;
	int ret;

	err = bpf_dynptr_from_skb(skb, 0, &ptr);

	/* cgroup skbs are read only, memset should fail */
	ret = bpf_dynptr_memset(&ptr, 0, bpf_dynptr_size(&ptr), 0);
	if (ret != -EINVAL)
		err = 1;

	return 0;
}

#define min_t(type, x, y) ({		\
	type __x = (x);			\
	type __y = (y);			\
	__x < __y ? __x : __y; })

SEC("xdp")
int test_dynptr_memset_xdp_chunks(struct xdp_md *xdp)
{
	u32 data_sz, chunk_sz, offset = 0;
	const int max_chunks = 200;
	struct bpf_dynptr ptr_xdp;
	char expected_buf[32];
	char buf[32];
	int i;

	__builtin_memset(expected_buf, DYNPTR_MEMSET_VAL, sizeof(expected_buf));

	/* ptr_xdp is backed by non-contiguous memory */
	bpf_dynptr_from_xdp(xdp, 0, &ptr_xdp);
	data_sz = bpf_dynptr_size(&ptr_xdp);

	err = bpf_dynptr_memset(&ptr_xdp, 0, data_sz, DYNPTR_MEMSET_VAL);
	if (err)
		goto out;

	bpf_for(i, 0, max_chunks) {
		offset = i * sizeof(buf);
		if (offset >= data_sz)
			goto out;
		chunk_sz = min_t(u32, sizeof(buf), data_sz - offset);
		err = bpf_dynptr_read(&buf, chunk_sz, &ptr_xdp, offset, 0);
		if (err)
			goto out;
		err = bpf_memcmp(buf, expected_buf, sizeof(buf));
		if (err)
			goto out;
	}
out:
	return XDP_DROP;
}

void *user_ptr;
/* Contains the copy of the data pointed by user_ptr.
 * Size 384 to make it not fit into a single kernel chunk when copying