Merge branch 'bpf-add-kfuncs-for-read-only-string-operations'

Viktor Malik says:

====================
bpf: Add kfuncs for read-only string operations

String operations are commonly used in programming and BPF programs are
no exception. Since it is cumbersome to reimplement them over and over,
this series introduce kfuncs which provide the most common operations.
For now, we only limit ourselves to functions which do not copy memory
since these usually introduce undefined behaviour in case the
source/destination buffers overlap which would have to be prevented by
the verifier.

The kernel already contains implementations for all of these, however,
it is not possible to use them from BPF context. The main reason is that
the verifier is not able to check that it is safe to access the entire
string and that the string is null-terminated and the function won't
loop forever. Therefore, the operations are open-coded using
__get_kernel_nofault instead of plain dereference and bounded to at most
XATTR_SIZE_MAX characters to make them safe. That allows to skip all the
verfier checks for the passed-in strings as safety is ensured
dynamically.

All of the proposed functions return integers, even those that normally
(in the kernel or libc) return pointers into the strings. The reason is
that since the strings are generally treated as unsafe, the pointers
couldn't be dereferenced anyways. So, instead, we return an index to the
string and let user decide what to do with it. The integer APIs also
allow to return various error codes when unexpected situations happen
while processing the strings.

The series include both positive and negative tests using the kfuncs.

Changelog
---------

Changes in v8:
- Return -ENOENT (instead of -1) when "item not found" for relevant
  functions (Alexei).
- Small adjustments of the string algorithms (Andrii).
- Adapt comments to kernel style (Alexei).

Changes in v7:
- Disable negative tests passing NULL and 0x1 to kfuncs on s390 as they
  aren't relevant (see comment in string_kfuncs_failure1.c for details).

Changes in v6:
- Improve the third patch which allows to use macros in __retval in
  selftests. The previous solution broke several tests.

Changes in v5:
- Make all kfuncs return integers (Andrii).
- Return -ERANGE when passing non-kernel pointers on arches with
  non-overlapping address spaces (Alexei).
- Implement "unbounded" variants using the bounded ones (Andrii).
- Add more negative test cases.

Changes in v4 (all suggested by Andrii):
- Open-code all the kfuncs, not just the unbounded variants.
- Introduce `pagefault` lock guard to simplify the implementation
- Return appropriate error codes (-E2BIG and -EFAULT) on failures
- Const-ify all arguments and return values
- Add negative test-cases

Changes in v3:
- Open-code unbounded variants with __get_kernel_nofault instead of
  dereference (suggested by Alexei).
- Use the __sz suffix for size parameters in bounded variants (suggested
  by Eduard and Alexei).
- Make tests more compact (suggested by Eduard).
- Add benchmark.
====================

Link: https://patch.msgid.link/cover.1750917800.git.vmalik@redhat.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
Alexei Starovoitov 2025-06-26 09:44:46 -07:00
commit 5046acc113
9 changed files with 615 additions and 23 deletions

View File

@ -296,6 +296,8 @@ static inline bool pagefault_disabled(void)
*/
#define faulthandler_disabled() (pagefault_disabled() || in_atomic())
DEFINE_LOCK_GUARD_0(pagefault, pagefault_disable(), pagefault_enable())
#ifndef CONFIG_ARCH_HAS_SUBPAGE_FAULTS
/**

View File

@ -24,6 +24,7 @@
#include <linux/bpf_mem_alloc.h>
#include <linux/kasan.h>
#include <linux/bpf_verifier.h>
#include <linux/uaccess.h>
#include "../../lib/kstrtox.h"
@ -3278,6 +3279,376 @@ __bpf_kfunc void __bpf_trap(void)
{
}
/*
* Kfuncs for string operations.
*
* Since strings are not necessarily %NUL-terminated, we cannot directly call
* in-kernel implementations. Instead, we open-code the implementations using
* __get_kernel_nofault instead of plain dereference to make them safe.
*/
/**
* bpf_strcmp - Compare two strings
* @s1__ign: One string
* @s2__ign: Another string
*
* Return:
* * %0 - Strings are equal
* * %-1 - @s1__ign is smaller
* * %1 - @s2__ign is smaller
* * %-EFAULT - Cannot read one of the strings
* * %-E2BIG - One of strings is too large
* * %-ERANGE - One of strings is outside of kernel address space
*/
__bpf_kfunc int bpf_strcmp(const char *s1__ign, const char *s2__ign)
{
char c1, c2;
int i;
if (!copy_from_kernel_nofault_allowed(s1__ign, 1) ||
!copy_from_kernel_nofault_allowed(s2__ign, 1)) {
return -ERANGE;
}
guard(pagefault)();
for (i = 0; i < XATTR_SIZE_MAX; i++) {
__get_kernel_nofault(&c1, s1__ign, char, err_out);
__get_kernel_nofault(&c2, s2__ign, char, err_out);
if (c1 != c2)
return c1 < c2 ? -1 : 1;
if (c1 == '\0')
return 0;
s1__ign++;
s2__ign++;
}
return -E2BIG;
err_out:
return -EFAULT;
}
/**
* bpf_strnchr - Find a character in a length limited string
* @s__ign: The string to be searched
* @count: The number of characters to be searched
* @c: The character to search for
*
* Note that the %NUL-terminator is considered part of the string, and can
* be searched for.
*
* Return:
* * >=0 - Index of the first occurrence of @c within @s__ign
* * %-ENOENT - @c not found in the first @count characters of @s__ign
* * %-EFAULT - Cannot read @s__ign
* * %-E2BIG - @s__ign is too large
* * %-ERANGE - @s__ign is outside of kernel address space
*/
__bpf_kfunc int bpf_strnchr(const char *s__ign, size_t count, char c)
{
char sc;
int i;
if (!copy_from_kernel_nofault_allowed(s__ign, 1))
return -ERANGE;
guard(pagefault)();
for (i = 0; i < count && i < XATTR_SIZE_MAX; i++) {
__get_kernel_nofault(&sc, s__ign, char, err_out);
if (sc == c)
return i;
if (sc == '\0')
return -ENOENT;
s__ign++;
}
return i == XATTR_SIZE_MAX ? -E2BIG : -ENOENT;
err_out:
return -EFAULT;
}
/**
* bpf_strchr - Find the first occurrence of a character in a string
* @s__ign: The string to be searched
* @c: The character to search for
*
* Note that the %NUL-terminator is considered part of the string, and can
* be searched for.
*
* Return:
* * >=0 - The index of the first occurrence of @c within @s__ign
* * %-ENOENT - @c not found in @s__ign
* * %-EFAULT - Cannot read @s__ign
* * %-E2BIG - @s__ign is too large
* * %-ERANGE - @s__ign is outside of kernel address space
*/
__bpf_kfunc int bpf_strchr(const char *s__ign, char c)
{
return bpf_strnchr(s__ign, XATTR_SIZE_MAX, c);
}
/**
* bpf_strchrnul - Find and return a character in a string, or end of string
* @s__ign: The string to be searched
* @c: The character to search for
*
* Return:
* * >=0 - Index of the first occurrence of @c within @s__ign or index of
* the null byte at the end of @s__ign when @c is not found
* * %-EFAULT - Cannot read @s__ign
* * %-E2BIG - @s__ign is too large
* * %-ERANGE - @s__ign is outside of kernel address space
*/
__bpf_kfunc int bpf_strchrnul(const char *s__ign, char c)
{
char sc;
int i;
if (!copy_from_kernel_nofault_allowed(s__ign, 1))
return -ERANGE;
guard(pagefault)();
for (i = 0; i < XATTR_SIZE_MAX; i++) {
__get_kernel_nofault(&sc, s__ign, char, err_out);
if (sc == '\0' || sc == c)
return i;
s__ign++;
}
return -E2BIG;
err_out:
return -EFAULT;
}
/**
* bpf_strrchr - Find the last occurrence of a character in a string
* @s__ign: The string to be searched
* @c: The character to search for
*
* Return:
* * >=0 - Index of the last occurrence of @c within @s__ign
* * %-ENOENT - @c not found in @s__ign
* * %-EFAULT - Cannot read @s__ign
* * %-E2BIG - @s__ign is too large
* * %-ERANGE - @s__ign is outside of kernel address space
*/
__bpf_kfunc int bpf_strrchr(const char *s__ign, int c)
{
char sc;
int i, last = -ENOENT;
if (!copy_from_kernel_nofault_allowed(s__ign, 1))
return -ERANGE;
guard(pagefault)();
for (i = 0; i < XATTR_SIZE_MAX; i++) {
__get_kernel_nofault(&sc, s__ign, char, err_out);
if (sc == c)
last = i;
if (sc == '\0')
return last;
s__ign++;
}
return -E2BIG;
err_out:
return -EFAULT;
}
/**
* bpf_strlen - Calculate the length of a length-limited string
* @s__ign: The string
* @count: The maximum number of characters to count
*
* Return:
* * >=0 - The length of @s__ign
* * %-EFAULT - Cannot read @s__ign
* * %-E2BIG - @s__ign is too large
* * %-ERANGE - @s__ign is outside of kernel address space
*/
__bpf_kfunc int bpf_strnlen(const char *s__ign, size_t count)
{
char c;
int i;
if (!copy_from_kernel_nofault_allowed(s__ign, 1))
return -ERANGE;
guard(pagefault)();
for (i = 0; i < count && i < XATTR_SIZE_MAX; i++) {
__get_kernel_nofault(&c, s__ign, char, err_out);
if (c == '\0')
return i;
s__ign++;
}
return i == XATTR_SIZE_MAX ? -E2BIG : i;
err_out:
return -EFAULT;
}
/**
* bpf_strlen - Calculate the length of a string
* @s__ign: The string
*
* Return:
* * >=0 - The length of @s__ign
* * %-EFAULT - Cannot read @s__ign
* * %-E2BIG - @s__ign is too large
* * %-ERANGE - @s__ign is outside of kernel address space
*/
__bpf_kfunc int bpf_strlen(const char *s__ign)
{
return bpf_strnlen(s__ign, XATTR_SIZE_MAX);
}
/**
* bpf_strspn - Calculate the length of the initial substring of @s__ign which
* only contains letters in @accept__ign
* @s__ign: The string to be searched
* @accept__ign: The string to search for
*
* Return:
* * >=0 - The length of the initial substring of @s__ign which only
* contains letters from @accept__ign
* * %-EFAULT - Cannot read one of the strings
* * %-E2BIG - One of the strings is too large
* * %-ERANGE - One of the strings is outside of kernel address space
*/
__bpf_kfunc int bpf_strspn(const char *s__ign, const char *accept__ign)
{
char cs, ca;
int i, j;
if (!copy_from_kernel_nofault_allowed(s__ign, 1) ||
!copy_from_kernel_nofault_allowed(accept__ign, 1)) {
return -ERANGE;
}
guard(pagefault)();
for (i = 0; i < XATTR_SIZE_MAX; i++) {
__get_kernel_nofault(&cs, s__ign, char, err_out);
if (cs == '\0')
return i;
for (j = 0; j < XATTR_SIZE_MAX; j++) {
__get_kernel_nofault(&ca, accept__ign + j, char, err_out);
if (cs == ca || ca == '\0')
break;
}
if (j == XATTR_SIZE_MAX)
return -E2BIG;
if (ca == '\0')
return i;
s__ign++;
}
return -E2BIG;
err_out:
return -EFAULT;
}
/**
* strcspn - Calculate the length of the initial substring of @s__ign which
* does not contain letters in @reject__ign
* @s__ign: The string to be searched
* @reject__ign: The string to search for
*
* Return:
* * >=0 - The length of the initial substring of @s__ign which does not
* contain letters from @reject__ign
* * %-EFAULT - Cannot read one of the strings
* * %-E2BIG - One of the strings is too large
* * %-ERANGE - One of the strings is outside of kernel address space
*/
__bpf_kfunc int bpf_strcspn(const char *s__ign, const char *reject__ign)
{
char cs, cr;
int i, j;
if (!copy_from_kernel_nofault_allowed(s__ign, 1) ||
!copy_from_kernel_nofault_allowed(reject__ign, 1)) {
return -ERANGE;
}
guard(pagefault)();
for (i = 0; i < XATTR_SIZE_MAX; i++) {
__get_kernel_nofault(&cs, s__ign, char, err_out);
if (cs == '\0')
return i;
for (j = 0; j < XATTR_SIZE_MAX; j++) {
__get_kernel_nofault(&cr, reject__ign + j, char, err_out);
if (cs == cr || cr == '\0')
break;
}
if (j == XATTR_SIZE_MAX)
return -E2BIG;
if (cr != '\0')
return i;
s__ign++;
}
return -E2BIG;
err_out:
return -EFAULT;
}
/**
* bpf_strnstr - Find the first substring in a length-limited string
* @s1__ign: The string to be searched
* @s2__ign: The string to search for
* @len: the maximum number of characters to search
*
* Return:
* * >=0 - Index of the first character of the first occurrence of @s2__ign
* within the first @len characters of @s1__ign
* * %-ENOENT - @s2__ign not found in the first @len characters of @s1__ign
* * %-EFAULT - Cannot read one of the strings
* * %-E2BIG - One of the strings is too large
* * %-ERANGE - One of the strings is outside of kernel address space
*/
__bpf_kfunc int bpf_strnstr(const char *s1__ign, const char *s2__ign, size_t len)
{
char c1, c2;
int i, j;
if (!copy_from_kernel_nofault_allowed(s1__ign, 1) ||
!copy_from_kernel_nofault_allowed(s2__ign, 1)) {
return -ERANGE;
}
guard(pagefault)();
for (i = 0; i < XATTR_SIZE_MAX; i++) {
for (j = 0; i + j < len && j < XATTR_SIZE_MAX; j++) {
__get_kernel_nofault(&c2, s2__ign + j, char, err_out);
if (c2 == '\0')
return i;
__get_kernel_nofault(&c1, s1__ign + j, char, err_out);
if (c1 == '\0')
return -ENOENT;
if (c1 != c2)
break;
}
if (j == XATTR_SIZE_MAX)
return -E2BIG;
if (i + j == len)
return -ENOENT;
s1__ign++;
}
return -E2BIG;
err_out:
return -EFAULT;
}
/**
* bpf_strstr - Find the first substring in a string
* @s1__ign: The string to be searched
* @s2__ign: The string to search for
*
* Return:
* * >=0 - Index of the first character of the first occurrence of @s2__ign
* within @s1__ign
* * %-ENOENT - @s2__ign is not a substring of @s1__ign
* * %-EFAULT - Cannot read one of the strings
* * %-E2BIG - One of the strings is too large
* * %-ERANGE - One of the strings is outside of kernel address space
*/
__bpf_kfunc int bpf_strstr(const char *s1__ign, const char *s2__ign)
{
return bpf_strnstr(s1__ign, s2__ign, XATTR_SIZE_MAX);
}
__bpf_kfunc_end_defs();
BTF_KFUNCS_START(generic_btf_ids)
@ -3397,6 +3768,17 @@ BTF_ID_FLAGS(func, bpf_iter_dmabuf_next, KF_ITER_NEXT | KF_RET_NULL | KF_SLEEPAB
BTF_ID_FLAGS(func, bpf_iter_dmabuf_destroy, KF_ITER_DESTROY | KF_SLEEPABLE)
#endif
BTF_ID_FLAGS(func, __bpf_trap)
BTF_ID_FLAGS(func, bpf_strcmp);
BTF_ID_FLAGS(func, bpf_strchr);
BTF_ID_FLAGS(func, bpf_strchrnul);
BTF_ID_FLAGS(func, bpf_strnchr);
BTF_ID_FLAGS(func, bpf_strrchr);
BTF_ID_FLAGS(func, bpf_strlen);
BTF_ID_FLAGS(func, bpf_strnlen);
BTF_ID_FLAGS(func, bpf_strspn);
BTF_ID_FLAGS(func, bpf_strcspn);
BTF_ID_FLAGS(func, bpf_strstr);
BTF_ID_FLAGS(func, bpf_strnstr);
BTF_KFUNCS_END(common_btf_ids)
static const struct btf_kfunc_id_set common_kfunc_set = {

View File

@ -0,0 +1,65 @@
// 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 test_cases[] = {
"strcmp",
"strchr",
"strchrnul",
"strnchr",
"strrchr",
"strlen",
"strnlen",
"strspn_str",
"strspn_accept",
"strcspn_str",
"strcspn_reject",
"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(test_cases); i++) {
sprintf(test_name, "test_%s_too_long", test_cases[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();
}

View File

@ -83,9 +83,11 @@
* expect return value to match passed parameter:
* - a decimal number
* - a hexadecimal number, when starts from 0x
* - literal INT_MIN
* - literal POINTER_VALUE (see definition below)
* - literal TEST_DATA_LEN (see definition below)
* - a macro which expands to one of the above
* - literal _INT_MIN (expands to INT_MIN)
* In addition, two special macros are defined below:
* - POINTER_VALUE
* - TEST_DATA_LEN
* __retval_unpriv Same, but load program in unprivileged mode.
*
* __description Text to be used instead of a program name for display
@ -125,8 +127,8 @@
#define __success_unpriv __attribute__((btf_decl_tag("comment:test_expect_success_unpriv")))
#define __log_level(lvl) __attribute__((btf_decl_tag("comment:test_log_level="#lvl)))
#define __flag(flag) __attribute__((btf_decl_tag("comment:test_prog_flags="#flag)))
#define __retval(val) __attribute__((btf_decl_tag("comment:test_retval="#val)))
#define __retval_unpriv(val) __attribute__((btf_decl_tag("comment:test_retval_unpriv="#val)))
#define __retval(val) __attribute__((btf_decl_tag("comment:test_retval="XSTR(val))))
#define __retval_unpriv(val) __attribute__((btf_decl_tag("comment:test_retval_unpriv="XSTR(val))))
#define __auxiliary __attribute__((btf_decl_tag("comment:test_auxiliary")))
#define __auxiliary_unpriv __attribute__((btf_decl_tag("comment:test_auxiliary_unpriv")))
#define __btf_path(path) __attribute__((btf_decl_tag("comment:test_btf_path=" path)))
@ -155,7 +157,7 @@
#define __imm_insn(name, expr) [name]"i"(*(long *)&(expr))
/* Magic constants used with __retval() */
#define POINTER_VALUE 0xcafe4all
#define POINTER_VALUE 0xbadcafe
#define TEST_DATA_LEN 64
#ifndef __used

View File

@ -0,0 +1,87 @@
// 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
/*
* On s390, __get_kernel_nofault (used in string kfuncs) returns 0 for NULL and
* user_ptr (instead of causing an exception) so the below two groups of tests
* are not applicable.
*/
#ifndef __TARGET_ARCH_s390
/* 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); }
#endif /* __TARGET_ARCH_s390 */
/* 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";

View File

@ -0,0 +1,23 @@
// 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_str_too_long(void *ctx) { return bpf_strspn(long_str, "a"); }
SEC("syscall") int test_strspn_accept_too_long(void *ctx) { return bpf_strspn("b", long_str); }
SEC("syscall") int test_strcspn_str_too_long(void *ctx) { return bpf_strcspn(long_str, "b"); }
SEC("syscall") int test_strcspn_reject_too_long(void *ctx) { return bpf_strcspn("b", long_str); }
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";

View File

@ -0,0 +1,37 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (C) 2025 Red Hat, Inc.*/
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include "bpf_misc.h"
#include "errno.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(-ENOENT) 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(-ENOENT) 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(11) int test_strrchr_null(void *ctx) { return bpf_strrchr(str, '\0'); }
__test(-ENOENT) 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(-ENOENT) 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(-ENOENT) 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";

View File

@ -77,7 +77,7 @@ l0_%=: exit; \
SEC("tc")
__description("MOD32 overflow, check 1")
__success __retval(INT_MIN)
__success __retval(_INT_MIN)
__naked void mod32_overflow_check_1(void)
{
asm volatile (" \
@ -92,7 +92,7 @@ __naked void mod32_overflow_check_1(void)
SEC("tc")
__description("MOD32 overflow, check 2")
__success __retval(INT_MIN)
__success __retval(_INT_MIN)
__naked void mod32_overflow_check_2(void)
{
asm volatile (" \

View File

@ -40,7 +40,7 @@
#define TEST_TAG_LOAD_MODE_PFX "comment:load_mode="
/* Warning: duplicated in bpf_misc.h */
#define POINTER_VALUE 0xcafe4all
#define POINTER_VALUE 0xbadcafe
#define TEST_DATA_LEN 64
#ifdef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS
@ -318,20 +318,14 @@ static int parse_caps(const char *str, __u64 *val, const char *name)
static int parse_retval(const char *str, int *val, const char *name)
{
struct {
char *name;
int val;
} named_values[] = {
{ "INT_MIN" , INT_MIN },
{ "POINTER_VALUE", POINTER_VALUE },
{ "TEST_DATA_LEN", TEST_DATA_LEN },
};
int i;
for (i = 0; i < ARRAY_SIZE(named_values); ++i) {
if (strcmp(str, named_values[i].name) != 0)
continue;
*val = named_values[i].val;
/*
* INT_MIN is defined as (-INT_MAX -1), i.e. it doesn't expand to a
* single int and cannot be parsed with strtol, so we handle it
* separately here. In addition, it expands to different expressions in
* different compilers so we use a prefixed _INT_MIN instead.
*/
if (strcmp(str, "_INT_MIN") == 0) {
*val = INT_MIN;
return 0;
}