Use aligned nofault word loads for the strchr, strrchr, and strlen
kfunc families. Align once, hoist the full-word boundary, and use
word-at-a-time masks to skip irrelevant words or locate the first
matching or NUL byte.

Factor the common unaligned-byte, aligned-word, and byte-fallback
plumbing into an expansion macro. Keep algorithm-specific actions expanded
at each call site.

Allow the shared finder to match both ASCII case variants so the
case-insensitive substring kfuncs can use the same word-at-a-time scan.

If a word load faults, retry from the same address byte-at-a-time. Keep
the byte path under KMSAN to avoid reading uninitialized storage beyond
the terminator, and preserve the existing limits and error semantics.

Assisted-by: Codex:gpt-5.6-sol
Signed-off-by: Leon Hwang <[email protected]>
---
 kernel/bpf/helpers.c | 180 +++++++++++++++++++++++++++++++++----------
 1 file changed, 141 insertions(+), 39 deletions(-)

diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index 88b38db47de9..36075c683166 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -30,6 +30,8 @@
 #include <linux/irq_work.h>
 #include <linux/buildid.h>
 
+#include <asm/word-at-a-time.h>
+
 #include "../../lib/kstrtox.h"
 
 /* If kernel subsystem is allowing eBPF programs to call this function,
@@ -3727,6 +3729,95 @@ __bpf_kfunc void __bpf_trap(void)
  * __get_kernel_nofault instead of plain dereference to make them safe.
  */
 
+/* Abstract the common unaligned-byte, aligned-word, and byte-fallback scan. */
+#define bpf_str_for_each_word(s, limit, pos, word, byte, byte_label,   \
+                             byte_action, word_action, err_label)      \
+do {                                                                   \
+       __label__ byte_label;                                           \
+       size_t __word_end;                                              \
+                                                                       \
+       if (IS_ENABLED(CONFIG_KMSAN))                                   \
+               goto byte_label;                                        \
+                                                                       \
+       for (; (pos) < (limit) &&                                       \
+              !IS_ALIGNED((unsigned long)((s) + (pos)), sizeof(word)); \
+            (pos)++) {                                                 \
+               __get_kernel_nofault(&(byte), (s) + (pos),              \
+                                    unsigned char, err_label);         \
+               byte_action;                                            \
+       }                                                               \
+                                                                       \
+       __word_end = (pos) + round_down((limit) - (pos), sizeof(word)); \
+       for (; (pos) < __word_end; (pos) += sizeof(word)) {             \
+               __get_kernel_nofault(&(word), (s) + (pos),              \
+                                    unsigned long, byte_label);        \
+               word_action;                                            \
+       }                                                               \
+                                                                       \
+byte_label:                                                            \
+       for (; (pos) < (limit); (pos)++) {                              \
+               __get_kernel_nofault(&(byte), (s) + (pos),              \
+                                    unsigned char, err_label);         \
+               byte_action;                                            \
+       }                                                               \
+} while (0)
+
+static __always_inline int bpf_str_find(const char *s, size_t limit, unsigned 
char c,
+                                       bool ignore_case, bool nul_is_match)
+{
+       const struct word_at_a_time constants = WORD_AT_A_TIME_CONSTANTS;
+       const unsigned char match_c = ignore_case ? tolower(c) : c;
+       const unsigned char alt_c = ignore_case && islower(match_c) ?
+                                   toupper(match_c) : match_c;
+       const unsigned long repeated_c = REPEAT_BYTE(match_c);
+       const unsigned long repeated_alt = REPEAT_BYTE(alt_c);
+       unsigned long word, zero_data, char_data, alt_data;
+       unsigned long zero_at, char_at, alt_at;
+       const bool has_alt = alt_c != match_c;
+       unsigned char sc;
+       size_t pos = 0;
+
+       bpf_str_for_each_word(s, limit, pos, word, sc, byte_at_a_time, ({
+               if (sc == match_c || (has_alt && sc == alt_c))
+                       return pos;
+               if (sc == '\0')
+                       return nul_is_match ? pos : -ENOENT;
+       }), ({
+               zero_at = has_zero(word, &zero_data, &constants);
+               char_at = has_zero(word ^ repeated_c, &char_data, &constants);
+               alt_at = has_alt ? has_zero(word ^ repeated_alt, &alt_data, 
&constants) : 0;
+               if (!zero_at && !char_at && !alt_at)
+                       continue;
+
+               if (zero_at) {
+                       zero_data = prep_zero_mask(word, zero_data, &constants);
+                       zero_at = find_zero(create_zero_mask(zero_data));
+               } else {
+                       zero_at = sizeof(word);
+               }
+               if (char_at) {
+                       char_data = prep_zero_mask(word ^ repeated_c, 
char_data, &constants);
+                       char_at = find_zero(create_zero_mask(char_data));
+               } else {
+                       char_at = sizeof(word);
+               }
+               if (alt_at) {
+                       alt_data = prep_zero_mask(word ^ repeated_alt, 
alt_data, &constants);
+                       alt_at = find_zero(create_zero_mask(alt_data));
+                       char_at = min(char_at, alt_at);
+               }
+
+               if (char_at <= zero_at)
+                       return pos + char_at;
+               return nul_is_match ? pos + zero_at : -ENOENT;
+       }), err_out);
+
+       return pos == XATTR_SIZE_MAX ? -E2BIG : -ENOENT;
+
+err_out:
+       return -EFAULT;
+}
+
 static int __bpf_strncasecmp(const char *s1, const char *s2, bool ignore_case, 
size_t len)
 {
        char c1, c2;
@@ -3830,24 +3921,14 @@ __bpf_kfunc int bpf_strncasecmp(const char *s1__ign, 
const char *s2__ign, size_t
  */
 __bpf_kfunc int bpf_strnchr(const char *s__ign, size_t count, char c)
 {
-       char sc;
-       int i;
+       size_t limit;
 
        if (!copy_from_kernel_nofault_allowed(s__ign, 1))
                return -ERANGE;
 
+       limit = min_t(size_t, count, XATTR_SIZE_MAX);
        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;
+       return bpf_str_find(s__ign, limit, (unsigned char)c, false, false);
 }
 
 /**
@@ -3884,22 +3965,11 @@ __bpf_kfunc int bpf_strchr(const char *s__ign, char c)
  */
 __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;
+       return bpf_str_find(s__ign, XATTR_SIZE_MAX, (unsigned char)c, false, 
true);
 }
 
 /**
@@ -3916,22 +3986,44 @@ __bpf_kfunc int bpf_strchrnul(const char *s__ign, char 
c)
  */
 __bpf_kfunc int bpf_strrchr(const char *s__ign, int c)
 {
+       const struct word_at_a_time constants = WORD_AT_A_TIME_CONSTANTS;
+       const unsigned char uc = (unsigned char)(char)c;
+       const unsigned long repeated_c = REPEAT_BYTE(uc);
+       const bool char_matches = c == (char)c;
+       unsigned char byte, *bytes;
+       unsigned long word, data;
+       size_t pos = 0, i;
        char sc;
-       int i, last = -ENOENT;
+       int 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);
+
+       bpf_str_for_each_word(s__ign, XATTR_SIZE_MAX, pos, word, byte, 
byte_at_a_time, ({
+               sc = byte;
                if (sc == c)
-                       last = i;
+                       last = pos;
                if (sc == '\0')
                        return last;
-               s__ign++;
-       }
+       }), ({
+               if (!has_zero(word, &data, &constants) &&
+                   (!char_matches || !has_zero(word ^ repeated_c, &data, 
&constants)))
+                       continue;
+
+               bytes = (unsigned char *)&word;
+               for (i = 0; i < sizeof(word); i++) {
+                       sc = bytes[i];
+                       if (sc == c)
+                               last = pos + i;
+                       if (sc == '\0')
+                               return last;
+               }
+       }), err_out);
+
        return -E2BIG;
+
 err_out:
        return -EFAULT;
 }
@@ -3949,20 +4041,30 @@ __bpf_kfunc int bpf_strrchr(const char *s__ign, int c)
  */
 __bpf_kfunc int bpf_strnlen(const char *s__ign, size_t count)
 {
-       char c;
-       int i;
+       const struct word_at_a_time constants = WORD_AT_A_TIME_CONSTANTS;
+       unsigned long word, data;
+       size_t limit, pos = 0;
+       unsigned char c;
 
        if (!copy_from_kernel_nofault_allowed(s__ign, 1))
                return -ERANGE;
 
+       limit = min_t(size_t, count, XATTR_SIZE_MAX);
        guard(pagefault)();
-       for (i = 0; i < count && i < XATTR_SIZE_MAX; i++) {
-               __get_kernel_nofault(&c, s__ign, char, err_out);
+
+       bpf_str_for_each_word(s__ign, limit, pos, word, c, byte_at_a_time, ({
                if (c == '\0')
-                       return i;
-               s__ign++;
-       }
-       return i == XATTR_SIZE_MAX ? -E2BIG : i;
+                       return pos;
+       }), ({
+               if (!has_zero(word, &data, &constants))
+                       continue;
+
+               data = prep_zero_mask(word, data, &constants);
+               return pos + find_zero(create_zero_mask(data));
+       }), err_out);
+
+       return pos == XATTR_SIZE_MAX ? -E2BIG : pos;
+
 err_out:
        return -EFAULT;
 }
-- 
2.55.0


Reply via email to