Re: [PATCH bpf-next v6 4/4] selftests/bpf: Add tests for string kfuncs

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On 6/20/25 13:52, Viktor Malik wrote:
> Add both positive and negative tests cases using string kfuncs added in
> the previous patches.
> 
> Positive tests check that the functions work as expected.
> 
> Negative tests pass various incorrect strings to the kfuncs and check
> for the expected error codes:
>   -E2BIG  when passing too long strings
>   -EFAULT when trying to read inaccessible kernel memory
>   -ERANGE when passing userspace pointers on arches with non-overlapping
>           address spaces
> 
> A majority of the tests use the RUN_TESTS helper which executes BPF
> programs with BPF_PROG_TEST_RUN and check for the expected return value.
> An exception to this are tests for long strings as we need to memset the
> long string from userspace (at least I haven't found an ergonomic way to
> memset it from a BPF program), which cannot be done using the RUN_TESTS
> infrastructure.
> 
> Suggested-by: Eduard Zingerman <eddyz87@xxxxxxxxx>
> Signed-off-by: Viktor Malik <vmalik@xxxxxxxxxx>
> ---
>  .../selftests/bpf/prog_tests/string_kfuncs.c  | 63 +++++++++++++++
>  .../bpf/progs/string_kfuncs_failure1.c        | 77 +++++++++++++++++++
>  .../bpf/progs/string_kfuncs_failure2.c        | 21 +++++
>  .../bpf/progs/string_kfuncs_success.c         | 35 +++++++++
>  4 files changed, 196 insertions(+)
>  create mode 100644 tools/testing/selftests/bpf/prog_tests/string_kfuncs.c
>  create mode 100644 tools/testing/selftests/bpf/progs/string_kfuncs_failure1.c
>  create mode 100644 tools/testing/selftests/bpf/progs/string_kfuncs_failure2.c
>  create mode 100644 tools/testing/selftests/bpf/progs/string_kfuncs_success.c
> 
> diff --git a/tools/testing/selftests/bpf/prog_tests/string_kfuncs.c b/tools/testing/selftests/bpf/prog_tests/string_kfuncs.c
> new file mode 100644
> index 000000000000..39322f1649ea
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/string_kfuncs.c
> @@ -0,0 +1,63 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright (C) 2025 Red Hat, Inc.*/
> +#include <test_progs.h>
> +#include "string_kfuncs_success.skel.h"
> +#include "string_kfuncs_failure1.skel.h"
> +#include "string_kfuncs_failure2.skel.h"
> +#include <sys/mman.h>
> +
> +static const char * const string_kfuncs[] = {
> +	"strcmp",
> +	"strchr",
> +	"strchrnul",
> +	"strnchr",
> +	"strrchr",
> +	"strlen",
> +	"strnlen",
> +	"strspn",
> +	"strcspn",
> +	"strstr",
> +	"strnstr",
> +};
> +
> +void run_too_long_tests(void)
> +{
> +	struct string_kfuncs_failure2 *skel;
> +	struct bpf_program *prog;
> +	char test_name[256];
> +	int err, i;
> +
> +	skel = string_kfuncs_failure2__open_and_load();
> +	if (!ASSERT_OK_PTR(skel, "string_kfuncs_failure2__open_and_load"))
> +		return;
> +
> +	memset(skel->bss->long_str, 'a', sizeof(skel->bss->long_str));
> +
> +	for (i = 0; i < ARRAY_SIZE(string_kfuncs); i++) {
> +		sprintf(test_name, "test_%s_too_long", string_kfuncs[i]);
> +		if (!test__start_subtest(test_name))
> +			continue;
> +
> +		prog = bpf_object__find_program_by_name(skel->obj, test_name);
> +		if (!ASSERT_OK_PTR(prog, "bpf_object__find_program_by_name"))
> +			goto cleanup;
> +
> +		LIBBPF_OPTS(bpf_test_run_opts, topts);
> +		err = bpf_prog_test_run_opts(bpf_program__fd(prog), &topts);
> +		if (!ASSERT_OK(err, "bpf_prog_test_run"))
> +			goto cleanup;
> +
> +		ASSERT_EQ(topts.retval, -E2BIG, "reading too long string fails with -E2BIG");
> +	}
> +
> +cleanup:
> +	string_kfuncs_failure2__destroy(skel);
> +}
> +
> +void test_string_kfuncs(void)
> +{
> +	RUN_TESTS(string_kfuncs_success);
> +	RUN_TESTS(string_kfuncs_failure1);
> +
> +	run_too_long_tests();
> +}
> diff --git a/tools/testing/selftests/bpf/progs/string_kfuncs_failure1.c b/tools/testing/selftests/bpf/progs/string_kfuncs_failure1.c
> new file mode 100644
> index 000000000000..7f03bdafd98f
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/progs/string_kfuncs_failure1.c
> @@ -0,0 +1,77 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright (C) 2025 Red Hat, Inc.*/
> +#include "vmlinux.h"
> +#include <bpf/bpf_helpers.h>
> +#include <linux/limits.h>
> +#include "bpf_misc.h"
> +#include "errno.h"
> +
> +char *user_ptr = (char *)1;
> +char *invalid_kern_ptr = (char *)-1;
> +
> +/* When passing userspace pointers, the error code differs based on arch:
> + *   -ERANGE on arches with non-overlapping address spaces
> + *   -EFAULT on other arches
> + */
> +#if defined(__TARGET_ARCH_arm) || defined(__TARGET_ARCH_loongarch) || \
> +    defined(__TARGET_ARCH_powerpc) || defined(__TARGET_ARCH_x86)
> +#define USER_PTR_ERR -ERANGE
> +#else
> +#define USER_PTR_ERR -EFAULT
> +#endif
> +
> +/* Passing NULL to string kfuncs (treated as a userspace ptr) */
> +SEC("syscall") __retval(USER_PTR_ERR) int test_strcmp_null1(void *ctx) { return bpf_strcmp(NULL, "hello"); }
> +SEC("syscall")  __retval(USER_PTR_ERR)int test_strcmp_null2(void *ctx) { return bpf_strcmp("hello", NULL); }
> +SEC("syscall")  __retval(USER_PTR_ERR)int test_strchr_null(void *ctx) { return bpf_strchr(NULL, 'a'); }
> +SEC("syscall")  __retval(USER_PTR_ERR)int test_strchrnul_null(void *ctx) { return bpf_strchrnul(NULL, 'a'); }
> +SEC("syscall")  __retval(USER_PTR_ERR)int test_strnchr_null(void *ctx) { return bpf_strnchr(NULL, 1, 'a'); }
> +SEC("syscall")  __retval(USER_PTR_ERR)int test_strrchr_null(void *ctx) { return bpf_strrchr(NULL, 'a'); }
> +SEC("syscall")  __retval(USER_PTR_ERR)int test_strlen_null(void *ctx) { return bpf_strlen(NULL); }
> +SEC("syscall")  __retval(USER_PTR_ERR)int test_strnlen_null(void *ctx) { return bpf_strnlen(NULL, 1); }
> +SEC("syscall")  __retval(USER_PTR_ERR)int test_strspn_null1(void *ctx) { return bpf_strspn(NULL, "hello"); }
> +SEC("syscall")  __retval(USER_PTR_ERR)int test_strspn_null2(void *ctx) { return bpf_strspn("hello", NULL); }
> +SEC("syscall")  __retval(USER_PTR_ERR)int test_strcspn_null1(void *ctx) { return bpf_strcspn(NULL, "hello"); }
> +SEC("syscall")  __retval(USER_PTR_ERR)int test_strcspn_null2(void *ctx) { return bpf_strcspn("hello", NULL); }
> +SEC("syscall")  __retval(USER_PTR_ERR)int test_strstr_null1(void *ctx) { return bpf_strstr(NULL, "hello"); }
> +SEC("syscall")  __retval(USER_PTR_ERR)int test_strstr_null2(void *ctx) { return bpf_strstr("hello", NULL); }
> +SEC("syscall")  __retval(USER_PTR_ERR)int test_strnstr_null1(void *ctx) { return bpf_strnstr(NULL, "hello", 1); }
> +SEC("syscall")  __retval(USER_PTR_ERR)int test_strnstr_null2(void *ctx) { return bpf_strnstr("hello", NULL, 1); }
> +
> +/* Passing userspace ptr to string kfuncs */
> +SEC("syscall") __retval(USER_PTR_ERR) int test_strcmp_user_ptr1(void *ctx) { return bpf_strcmp(user_ptr, "hello"); }
> +SEC("syscall") __retval(USER_PTR_ERR) int test_strcmp_user_ptr2(void *ctx) { return bpf_strcmp("hello", user_ptr); }
> +SEC("syscall") __retval(USER_PTR_ERR) int test_strchr_user_ptr(void *ctx) { return bpf_strchr(user_ptr, 'a'); }
> +SEC("syscall") __retval(USER_PTR_ERR) int test_strchrnul_user_ptr(void *ctx) { return bpf_strchrnul(user_ptr, 'a'); }
> +SEC("syscall") __retval(USER_PTR_ERR) int test_strnchr_user_ptr(void *ctx) { return bpf_strnchr(user_ptr, 1, 'a'); }
> +SEC("syscall") __retval(USER_PTR_ERR) int test_strrchr_user_ptr(void *ctx) { return bpf_strrchr(user_ptr, 'a'); }
> +SEC("syscall") __retval(USER_PTR_ERR) int test_strlen_user_ptr(void *ctx) { return bpf_strlen(user_ptr); }
> +SEC("syscall") __retval(USER_PTR_ERR) int test_strnlen_user_ptr(void *ctx) { return bpf_strnlen(user_ptr, 1); }
> +SEC("syscall") __retval(USER_PTR_ERR) int test_strspn_user_ptr1(void *ctx) { return bpf_strspn(user_ptr, "hello"); }
> +SEC("syscall") __retval(USER_PTR_ERR) int test_strspn_user_ptr2(void *ctx) { return bpf_strspn("hello", user_ptr); }
> +SEC("syscall") __retval(USER_PTR_ERR) int test_strcspn_user_ptr1(void *ctx) { return bpf_strcspn(user_ptr, "hello"); }
> +SEC("syscall") __retval(USER_PTR_ERR) int test_strcspn_user_ptr2(void *ctx) { return bpf_strcspn("hello", user_ptr); }
> +SEC("syscall") __retval(USER_PTR_ERR) int test_strstr_user_ptr1(void *ctx) { return bpf_strstr(user_ptr, "hello"); }
> +SEC("syscall") __retval(USER_PTR_ERR) int test_strstr_user_ptr2(void *ctx) { return bpf_strstr("hello", user_ptr); }
> +SEC("syscall") __retval(USER_PTR_ERR) int test_strnstr_user_ptr1(void *ctx) { return bpf_strnstr(user_ptr, "hello", 1); }
> +SEC("syscall") __retval(USER_PTR_ERR) int test_strnstr_user_ptr2(void *ctx) { return bpf_strnstr("hello", user_ptr, 1); }

For some reason, these tests are failing on s390x. I'll investigate.

Sorry for yet another faulty revision.

Viktor

> +
> +/* Passing invalid kernel ptr to string kfuncs should always return -EFAULT */
> +SEC("syscall") __retval(-EFAULT) int test_strcmp_pagefault1(void *ctx) { return bpf_strcmp(invalid_kern_ptr, "hello"); }
> +SEC("syscall") __retval(-EFAULT) int test_strcmp_pagefault2(void *ctx) { return bpf_strcmp("hello", invalid_kern_ptr); }
> +SEC("syscall") __retval(-EFAULT) int test_strchr_pagefault(void *ctx) { return bpf_strchr(invalid_kern_ptr, 'a'); }
> +SEC("syscall") __retval(-EFAULT) int test_strchrnul_pagefault(void *ctx) { return bpf_strchrnul(invalid_kern_ptr, 'a'); }
> +SEC("syscall") __retval(-EFAULT) int test_strnchr_pagefault(void *ctx) { return bpf_strnchr(invalid_kern_ptr, 1, 'a'); }
> +SEC("syscall") __retval(-EFAULT) int test_strrchr_pagefault(void *ctx) { return bpf_strrchr(invalid_kern_ptr, 'a'); }
> +SEC("syscall") __retval(-EFAULT) int test_strlen_pagefault(void *ctx) { return bpf_strlen(invalid_kern_ptr); }
> +SEC("syscall") __retval(-EFAULT) int test_strnlen_pagefault(void *ctx) { return bpf_strnlen(invalid_kern_ptr, 1); }
> +SEC("syscall") __retval(-EFAULT) int test_strspn_pagefault1(void *ctx) { return bpf_strspn(invalid_kern_ptr, "hello"); }
> +SEC("syscall") __retval(-EFAULT) int test_strspn_pagefault2(void *ctx) { return bpf_strspn("hello", invalid_kern_ptr); }
> +SEC("syscall") __retval(-EFAULT) int test_strcspn_pagefault1(void *ctx) { return bpf_strcspn(invalid_kern_ptr, "hello"); }
> +SEC("syscall") __retval(-EFAULT) int test_strcspn_pagefault2(void *ctx) { return bpf_strcspn("hello", invalid_kern_ptr); }
> +SEC("syscall") __retval(-EFAULT) int test_strstr_pagefault1(void *ctx) { return bpf_strstr(invalid_kern_ptr, "hello"); }
> +SEC("syscall") __retval(-EFAULT) int test_strstr_pagefault2(void *ctx) { return bpf_strstr("hello", invalid_kern_ptr); }
> +SEC("syscall") __retval(-EFAULT) int test_strnstr_pagefault1(void *ctx) { return bpf_strnstr(invalid_kern_ptr, "hello", 1); }
> +SEC("syscall") __retval(-EFAULT) int test_strnstr_pagefault2(void *ctx) { return bpf_strnstr("hello", invalid_kern_ptr, 1); }
> +
> +char _license[] SEC("license") = "GPL";
> diff --git a/tools/testing/selftests/bpf/progs/string_kfuncs_failure2.c b/tools/testing/selftests/bpf/progs/string_kfuncs_failure2.c
> new file mode 100644
> index 000000000000..685d221d8aa0
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/progs/string_kfuncs_failure2.c
> @@ -0,0 +1,21 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright (C) 2025 Red Hat, Inc.*/
> +#include "vmlinux.h"
> +#include <bpf/bpf_helpers.h>
> +#include <linux/limits.h>
> +
> +char long_str[XATTR_SIZE_MAX + 1];
> +
> +SEC("syscall") int test_strcmp_too_long(void *ctx) { return bpf_strcmp(long_str, long_str); }
> +SEC("syscall") int test_strchr_too_long(void *ctx) { return bpf_strchr(long_str, 'b'); }
> +SEC("syscall") int test_strchrnul_too_long(void *ctx) { return bpf_strchrnul(long_str, 'b'); }
> +SEC("syscall") int test_strnchr_too_long(void *ctx) { return bpf_strnchr(long_str, sizeof(long_str), 'b'); }
> +SEC("syscall") int test_strrchr_too_long(void *ctx) { return bpf_strrchr(long_str, 'b'); }
> +SEC("syscall") int test_strlen_too_long(void *ctx) { return bpf_strlen(long_str); }
> +SEC("syscall") int test_strnlen_too_long(void *ctx) { return bpf_strnlen(long_str, sizeof(long_str)); }
> +SEC("syscall") int test_strspn_too_long(void *ctx) { return bpf_strspn(long_str, "a"); }
> +SEC("syscall") int test_strcspn_too_long(void *ctx) { return bpf_strcspn(long_str, "b"); }
> +SEC("syscall") int test_strstr_too_long(void *ctx) { return bpf_strstr(long_str, "hello"); }
> +SEC("syscall") int test_strnstr_too_long(void *ctx) { return bpf_strnstr(long_str, "hello", sizeof(long_str)); }
> +
> +char _license[] SEC("license") = "GPL";
> diff --git a/tools/testing/selftests/bpf/progs/string_kfuncs_success.c b/tools/testing/selftests/bpf/progs/string_kfuncs_success.c
> new file mode 100644
> index 000000000000..d0e94921e811
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/progs/string_kfuncs_success.c
> @@ -0,0 +1,35 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/* Copyright (C) 2025 Red Hat, Inc.*/
> +#include "vmlinux.h"
> +#include <bpf/bpf_helpers.h>
> +#include "bpf_misc.h"
> +
> +char str[] = "hello world";
> +
> +#define __test(retval) SEC("syscall") __success __retval(retval)
> +
> +/* Functional tests */
> +__test(0) int test_strcmp_eq(void *ctx) { return bpf_strcmp(str, "hello world"); }
> +__test(1) int test_strcmp_neq(void *ctx) { return bpf_strcmp(str, "hello"); }
> +__test(1) int test_strchr_found(void *ctx) { return bpf_strchr(str, 'e'); }
> +__test(11) int test_strchr_null(void *ctx) { return bpf_strchr(str, '\0'); }
> +__test(-1) int test_strchr_notfound(void *ctx) { return bpf_strchr(str, 'x'); }
> +__test(1) int test_strchrnul_found(void *ctx) { return bpf_strchrnul(str, 'e'); }
> +__test(11) int test_strchrnul_notfound(void *ctx) { return bpf_strchrnul(str, 'x'); }
> +__test(1) int test_strnchr_found(void *ctx) { return bpf_strnchr(str, 5, 'e'); }
> +__test(11) int test_strnchr_null(void *ctx) { return bpf_strnchr(str, 12, '\0'); }
> +__test(-1) int test_strnchr_notfound(void *ctx) { return bpf_strnchr(str, 5, 'w'); }
> +__test(9) int test_strrchr_found(void *ctx) { return bpf_strrchr(str, 'l'); }
> +__test(-1) int test_strrchr_notfound(void *ctx) { return bpf_strrchr(str, 'x'); }
> +__test(11) int test_strlen(void *ctx) { return bpf_strlen(str); }
> +__test(11) int test_strnlen(void *ctx) { return bpf_strnlen(str, 12); }
> +__test(5) int test_strspn(void *ctx) { return bpf_strspn(str, "ehlo"); }
> +__test(2) int test_strcspn(void *ctx) { return bpf_strcspn(str, "lo"); }
> +__test(6) int test_strstr_found(void *ctx) { return bpf_strstr(str, "world"); }
> +__test(-1) int test_strstr_notfound(void *ctx) { return bpf_strstr(str, "hi"); }
> +__test(0) int test_strstr_empty(void *ctx) { return bpf_strstr(str, ""); }
> +__test(0) int test_strnstr_found(void *ctx) { return bpf_strnstr(str, "hello", 6); }
> +__test(-1) int test_strnstr_notfound(void *ctx) { return bpf_strnstr(str, "hi", 10); }
> +__test(0) int test_strnstr_empty(void *ctx) { return bpf_strnstr(str, "", 1); }
> +
> +char _license[] SEC("license") = "GPL";





[Index of Archives]     [Linux Samsung SoC]     [Linux Rockchip SoC]     [Linux Actions SoC]     [Linux for Synopsys ARC Processors]     [Linux NFS]     [Linux NILFS]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]


  Powered by Linux