From: Leon Hwang <leon.hwang@linux.dev>
To: bpf@vger.kernel.org
Cc: Alexei Starovoitov <ast@kernel.org>,
Daniel Borkmann <daniel@iogearbox.net>,
Andrii Nakryiko <andrii@kernel.org>,
Eduard Zingerman <eddyz87@gmail.com>,
Kumar Kartikeya Dwivedi <memxor@gmail.com>,
Martin KaFai Lau <martin.lau@linux.dev>,
Song Liu <song@kernel.org>,
Yonghong Song <yonghong.song@linux.dev>,
Jiri Olsa <jolsa@kernel.org>,
Emil Tsalapatis <emil@etsalapatis.com>,
Ihor Solodrai <ihor.solodrai@linux.dev>,
Shuah Khan <shuah@kernel.org>, Leon Hwang <leon.hwang@linux.dev>,
Rong Tao <rongtao@cestc.cn>,
Yuzuki Ishiyama <ishiyama@hpc.is.uec.ac.jp>,
Viktor Malik <vmalik@redhat.com>,
linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org
Subject: [RFC PATCH bpf-next 4/6] bpf: Optimize string substring kfuncs
Date: Tue, 28 Jul 2026 22:57:25 +0800 [thread overview]
Message-ID: <20260728145727.45153-5-leon.hwang@linux.dev> (raw)
In-Reply-To: <20260728145727.45153-1-leon.hwang@linux.dev>
Use the word-at-a-time character finder to skip haystack regions which
cannot begin a match. For case-insensitive searches, check both ASCII
case variants of the first needle byte.
At each candidate, use a direct fault-safe word matcher. When both
strings have the same relative alignment, skip equal words and inspect
locally loaded bytes only for mismatches and case folding.
Keep needle-first access, fall back at the same byte after a word-load
fault, and retain the extra needle-byte check at the search limit so
fault and boundary behavior remain unchanged.
Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Leon Hwang <leon.hwang@linux.dev>
---
kernel/bpf/helpers.c | 142 +++++++++++++++++++++++++++++++++----------
1 file changed, 111 insertions(+), 31 deletions(-)
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index af29acc90245..93d5fa409cd7 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -4281,11 +4281,90 @@ __bpf_kfunc int bpf_strcspn(const char *s__ign, const char *reject__ign)
return __bpf_strspn(s__ign, reject__ign, true);
}
-static int __bpf_strnstr(const char *s1, const char *s2, size_t len,
- bool ignore_case)
+#define bpf_str_match_byte(byte1, byte2, ignore_case) \
+do { \
+ char __c1 = (byte1); \
+ char __c2 = (byte2); \
+ \
+ if (ignore_case) { \
+ __c1 = tolower(__c1); \
+ __c2 = tolower(__c2); \
+ } \
+ if (__c1 == '\0') \
+ return -ENOENT; \
+ if (__c1 != __c2) \
+ return 0; \
+} while (0)
+
+static __always_inline int
+bpf_str_match_at(const char *s1, const char *s2, size_t limit, bool ignore_case)
{
- char c1, c2;
- int i, j;
+ const struct word_at_a_time constants = WORD_AT_A_TIME_CONSTANTS;
+ unsigned char byte1, byte2, *bytes1, *bytes2;
+ unsigned long word1, word2, data;
+ size_t pos = 0, word_end, i;
+
+ if (IS_ENABLED(CONFIG_KMSAN) ||
+ !IS_ALIGNED((unsigned long)s1 ^ (unsigned long)s2, sizeof(word1)))
+ goto byte_at_a_time;
+
+ while (pos < limit && !IS_ALIGNED((unsigned long)(s1 + pos), sizeof(word1))) {
+ __get_kernel_nofault(&byte2, s2 + pos, unsigned char, err_out);
+ if (byte2 == '\0')
+ return 1;
+ __get_kernel_nofault(&byte1, s1 + pos, unsigned char, err_out);
+ bpf_str_match_byte(byte1, byte2, ignore_case);
+ pos++;
+ }
+
+ word_end = pos + round_down(limit - pos, sizeof(word1));
+ while (pos < word_end) {
+ __get_kernel_nofault(&word2, s2 + pos, unsigned long, byte_at_a_time);
+ if (has_zero(word2, &data, &constants))
+ goto byte_at_a_time;
+ __get_kernel_nofault(&word1, s1 + pos, unsigned long, byte_at_a_time);
+ if (word1 == word2) {
+ pos += sizeof(word1);
+ continue;
+ }
+
+ bytes1 = (unsigned char *)&word1;
+ bytes2 = (unsigned char *)&word2;
+ for (i = 0; i < sizeof(word1); i++)
+ bpf_str_match_byte(bytes1[i], bytes2[i], ignore_case);
+ pos += sizeof(word1);
+ }
+
+byte_at_a_time:
+ for (; pos < limit; pos++) {
+ __get_kernel_nofault(&byte2, s2 + pos, unsigned char, err_out);
+ if (byte2 == '\0')
+ return 1;
+ __get_kernel_nofault(&byte1, s1 + pos, unsigned char, err_out);
+ bpf_str_match_byte(byte1, byte2, ignore_case);
+ }
+
+ if (limit == XATTR_SIZE_MAX)
+ return -E2BIG;
+
+ /*
+ * Read one extra byte from s2 to cover the case when it is a suffix
+ * of the first limit bytes of s1.
+ */
+ __get_kernel_nofault(&byte2, s2 + limit, unsigned char, err_out);
+ return byte2 == '\0' ? 1 : -ENOENT;
+
+err_out:
+ return -EFAULT;
+}
+
+#undef bpf_str_match_byte
+
+static int __bpf_strnstr(const char *s1, const char *s2, size_t len, bool ignore_case)
+{
+ unsigned char first, candidate;
+ size_t pos = 0, limit;
+ int ret, offset;
if (!copy_from_kernel_nofault_allowed(s1, 1) ||
!copy_from_kernel_nofault_allowed(s2, 1)) {
@@ -4293,37 +4372,38 @@ static int __bpf_strnstr(const char *s1, const char *s2, size_t len,
}
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 + j, char, err_out);
- if (c2 == '\0')
- return i;
- /*
- * We allow reading an extra byte from s2 (note the
- * `i + j <= len` above) to cover the case when s2 is
- * a suffix of the first len chars of s1.
- */
- if (i + j == len)
- break;
- __get_kernel_nofault(&c1, s1 + j, char, err_out);
-
- if (ignore_case) {
- c1 = tolower(c1);
- c2 = tolower(c2);
- }
+ __get_kernel_nofault(&first, s2, unsigned char, err_out);
+ if (first == '\0')
+ return 0;
- if (c1 == '\0')
- return -ENOENT;
- if (c1 != c2)
- break;
+ while (pos < XATTR_SIZE_MAX && pos < len) {
+ limit = min_t(size_t, len - pos, XATTR_SIZE_MAX - pos);
+ offset = bpf_str_find(s1 + pos, limit, first, ignore_case, true);
+ if (offset < 0) {
+ if (offset == -ENOENT && limit == XATTR_SIZE_MAX - pos)
+ return -E2BIG;
+ return offset;
}
- if (j == XATTR_SIZE_MAX)
- return -E2BIG;
- if (i + j == len)
+ pos += offset;
+
+ /*
+ * bpf_str_find() also returns the position of a terminating
+ * NUL. Distinguish it from a first-character match.
+ */
+ __get_kernel_nofault(&candidate, s1 + pos, unsigned char, err_out);
+ if (candidate == '\0')
return -ENOENT;
- s1++;
+
+ limit = min_t(size_t, len - pos, XATTR_SIZE_MAX);
+ ret = bpf_str_match_at(s1 + pos, s2, limit, ignore_case);
+ if (ret > 0)
+ return pos;
+ if (ret < 0)
+ return ret;
+ pos++;
}
- return -E2BIG;
+ return pos == XATTR_SIZE_MAX ? -E2BIG : -ENOENT;
+
err_out:
return -EFAULT;
}
--
2.55.0
next prev parent reply other threads:[~2026-07-28 14:58 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-28 14:57 [RFC PATCH bpf-next 0/6] bpf: Optimize string kfuncs Leon Hwang
2026-07-28 14:57 ` [RFC PATCH bpf-next 1/6] bpf: Optimize string scan kfuncs Leon Hwang
2026-07-28 14:57 ` [RFC PATCH bpf-next 2/6] bpf: Optimize string comparison kfuncs Leon Hwang
2026-07-28 14:57 ` [RFC PATCH bpf-next 3/6] bpf: Optimize string span kfuncs Leon Hwang
2026-07-28 14:57 ` Leon Hwang [this message]
2026-07-28 14:57 ` [RFC PATCH bpf-next 5/6] selftests/bpf: Exercise word-at-a-time string kfuncs Leon Hwang
2026-07-28 14:57 ` [RFC PATCH bpf-next 6/6] selftests/bpf: Benchmark " Leon Hwang
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260728145727.45153-5-leon.hwang@linux.dev \
--to=leon.hwang@linux.dev \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=eddyz87@gmail.com \
--cc=emil@etsalapatis.com \
--cc=ihor.solodrai@linux.dev \
--cc=ishiyama@hpc.is.uec.ac.jp \
--cc=jolsa@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=martin.lau@linux.dev \
--cc=memxor@gmail.com \
--cc=rongtao@cestc.cn \
--cc=shuah@kernel.org \
--cc=song@kernel.org \
--cc=vmalik@redhat.com \
--cc=yonghong.song@linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox