Hi! str_has_prefix() is based on sizes and lengths, which means it must overload the valid length 0 to mean not found. This is a typical consequence of choosing to design an API around sizes and lengths, and vanishes when moving to pointer-based similar APIs. According to its author (Steven R.), str_has_prefix() was never meant to accept an empty prefix, but that's not documented, and there's no obvious reason why that would be acceptable for a generic string function (as opposed to a function very specific to its original use). Anyway, as suggested by Steven, I've added a WARN_ON_ONCE() to catch such uses, and also added documentation stating this limitation. Users of strings need a function whose behavior extends naturally to the empty string, and we can do better, by returning a pointer. And indeed, users have ended up using str_has_prefix() with empty prefix, expecting it to work (as is natural of a generic string API). This patch set adds strprefix(), deprecates str_has_prefix(), and uses strprefix() in one place to fix a bug reported by Bartosz. We can later work on replacing the other uses of str_has_prefix(), which might hide similar bugs. This patch set also adds infrastructure to make string functions return a pointer that is as qualified as the input pointer. Changes in v5 ~~~~~~~~~~~~~ - [1/7] - Fix .rst formatting. [sashiko] - [2/7] - Move the function to "lib/string.h" to be able to use WARN_ON_ONCE(). [Andy, Bart] - [3/7] -- no changes -- - [4/7] - Simplify const_cast(), which makes it more robust. 'T:' didn't work, because in case const T is the same type as T, there's ambiguity in which branch should be taken, and the compiler rejects that. We could go back to 'default:' (as in v3), but that is only needed for casting 'const char **' to 'char **'; since we don't need that at the moment, let's keep it simple, and we'll be able to add it in the future if we ever want that. - [5/7] -- no changes -- - [6/7] -- no changes -- - [7/7] - New patch; use strprefix() to implement strstarts(). I didn't apply some changes requrested by reviewers: - I didn't move strprefix() to "lib/string.h" implementing it as a loop with manual byte operations. The only reason for that seems to be performance, but I expect by being inline, the compiler should be able to completely drop the strlen(3) call in many cases, making the inline function faster. See range-diff below. v1: v2: v3: v4: Testing ~~~~~~~ (I've indented the output, to better see the commands.) $ git log --oneline --graph v7.4-rc1^..strprefix5 * 382a44f7c686 (HEAD -> strprefix, tag: strprefix5) string: Implement strstarts() with strprefix() * 0ca0b7aabcfc string: Make strprefix() const-qualifier-generic * 1294b3d64440 string: Add [C]QChar_of() and QVoid_of() * b615b5afb05e stddef: Add const_cast() * 49252e78c9b6 device property: Fix fwnode_name_eq() for empty node names * df3a35c469c6 string: Warn if the prefix is the empty string * f25bf43d7f18 string: Add strprefix(), and deprecate str_has_prefix() * 6628e7d4c6b2 (tag: v7.4-rc1, master) Linux 7.4-rc1 $ cat .kunitconfig CONFIG_KUNIT=y CONFIG_STRING_KUNIT_TEST=y $ mkdir -p ~/tmp/strprefix5 $ export TMPDIR=$HOME/tmp/strprefix5/ $ ./tools/testing/kunit/kunit.py run "string*" [13:27:47] Configuring KUnit Kernel ... [13:27:47] Building KUnit Kernel ... Populating config with: $ make ARCH=um O=.kunit olddefconfig Building with: $ make all compile_commands.json scripts_gdb ARCH=um O=.kunit --jobs=24 [13:28:02] Starting KUnit Kernel (1/1)... [13:28:02] ============================================================ Running tests with: $ .kunit/linux 'kunit.filter_glob=string*' kunit.enable=1 mem=1G console=tty kunit_shutdown=halt [13:28:02] ============= string-stream-test (12 subtests) ============= [13:28:02] [PASSED] string_stream_managed_init_test [13:28:02] [PASSED] string_stream_unmanaged_init_test [13:28:02] [PASSED] string_stream_managed_free_test [13:28:02] [PASSED] string_stream_resource_free_test [13:28:02] [PASSED] string_stream_line_add_test [13:28:02] [PASSED] string_stream_variable_length_line_test [13:28:02] [PASSED] string_stream_append_test [13:28:02] [PASSED] string_stream_append_auto_newline_test [13:28:02] [PASSED] string_stream_append_empty_string_test [13:28:02] [PASSED] string_stream_no_auto_newline_test [13:28:02] [PASSED] string_stream_auto_newline_test [13:28:02] [PASSED] string_stream_performance_test [13:28:02] =============== [PASSED] string-stream-test ================ [13:28:02] =================== string (29 subtests) =================== [13:28:02] [PASSED] string_test_memset16 [13:28:02] [PASSED] string_test_memset32 [13:28:02] [PASSED] string_test_memset64 [13:28:02] [PASSED] string_test_strlen [13:28:02] [PASSED] string_test_strnlen [13:28:02] [PASSED] string_test_strchr [13:28:02] [PASSED] string_test_strnchr [13:28:02] [PASSED] string_test_strrchr [13:28:02] [PASSED] string_test_strspn [13:28:02] [PASSED] string_test_strprefix [13:28:02] [PASSED] string_test_strcmp [13:28:02] [PASSED] string_test_strcmp_long_strings [13:28:02] [PASSED] string_test_strncmp [13:28:02] [PASSED] string_test_strncmp_long_strings [13:28:02] [PASSED] string_test_strcasecmp [13:28:02] [PASSED] string_test_strcasecmp_long_strings [13:28:02] [PASSED] string_test_strncasecmp [13:28:02] [PASSED] string_test_strncasecmp_long_strings [13:28:02] [PASSED] string_test_strscpy [13:28:02] [PASSED] string_test_strcat [13:28:02] [PASSED] string_test_strncat [13:28:02] [PASSED] string_test_strlcat [13:28:02] [PASSED] string_test_strtomem [13:28:02] [PASSED] string_test_memtostr [13:28:02] [PASSED] string_test_strends [13:28:02] [SKIPPED] string_bench_strlen (not enabled) [13:28:02] [SKIPPED] string_bench_strnlen (not enabled) [13:28:02] [SKIPPED] string_bench_strchr (not enabled) [13:28:02] [SKIPPED] string_bench_strrchr (not enabled) [13:28:02] ===================== [PASSED] string ====================== [13:28:02] =============== string_helpers (4 subtests) ================ [13:28:02] [PASSED] test_get_size [13:28:02] [PASSED] test_upper_lower [13:28:02] [PASSED] test_unescape [13:28:02] [PASSED] test_escape [13:28:02] ================= [PASSED] string_helpers ================== [13:28:02] ============================================================ [13:28:02] Testing complete. Ran 45 tests: passed: 41, skipped: 4 [13:28:02] Elapsed time: 15.583s total, 0.000s configuring, 15.366s building, 0.172s running $ make allmodconfig HOSTCC scripts/basic/fixdep HOSTCC scripts/kconfig/conf.o HOSTCC scripts/kconfig/confdata.o HOSTCC scripts/kconfig/expr.o LEX scripts/kconfig/lexer.lex.c YACC scripts/kconfig/parser.tab.[ch] HOSTCC scripts/kconfig/lexer.lex.o HOSTCC scripts/kconfig/menu.o HOSTCC scripts/kconfig/parser.tab.o HOSTCC scripts/kconfig/preprocess.o HOSTCC scripts/kconfig/symbol.o HOSTCC scripts/kconfig/util.o HOSTLD scripts/kconfig/conf # # configuration written to .config # $ time make -j24 | ts -s 00:00:01 GEN arch/x86/include/generated/asm/orc_hash.h 00:00:01 UPD arch/x86/include/generated/uapi/asm/bpf_perf_event.h 00:00:01 UPD arch/x86/include/generated/uapi/asm/errno.h ... 00:21:49 LD [M] net/qrtr/qrtr-tun.ko 00:21:49 LD [M] net/qrtr/qrtr-mhi.ko 00:21:49 LD [M] virt/lib/irqbypass.ko real 21m48.882s user 439m26.908s sys 25m20.132s Have a lovely day! Alex Alejandro Colomar (7): string: Add strprefix(), and deprecate str_has_prefix() string: Warn if the prefix is the empty string device property: Fix fwnode_name_eq() for empty node names stddef: Add const_cast() string: Add [C]QChar_of() and QVoid_of() string: Make strprefix() const-qualifier-generic string: Implement strstarts() with strprefix() drivers/base/property.c | 5 +--- include/linux/stddef.h | 11 +++++++++ include/linux/string.h | 52 ++++++++++++++++++++++++++++++++-------- lib/string.c | 24 +++++++++++++++++++ lib/tests/string_kunit.c | 9 +++++++ 5 files changed, 87 insertions(+), 14 deletions(-) Range-diff against v4: 1: a74fabd985a8 ! 1: f25bf43d7f18 string: Add strprefix(), and deprecate str_has_prefix() @@ include/linux/string.h: void memcpy_and_pad(void *dest, size_t dest_len, const v + * and not a constant. Instead use strprefix(). + * + * Returns: -+ * * @s+strlen(@prefix) if @s starts with @prefix ++ * * @s + strlen(@prefix) if @s starts with @prefix + * * NULL if @s does not start with @prefix + */ +static inline char *strprefix(const char *s, const char *prefix) 2: cd09427148a0 ! 2: df3a35c469c6 string: Warn if the prefix is the empty string @@ Commit message Warn if it happens. Also, document that if such corner case is needed, strprefix() is preferable. + We need to move str_has_prefix() to "lib/string.h" to be able to use + WARN_ON_ONCE(). + Reported-by: Bartosz Golaszewski Link: https://lore.kernel.org/linux-hardening/20261104-fwnode-for-each-prop-v1-1-6e28fe1c72c4@oss.qualcomm.com/ Suggested-by: Steven Rostedt + Cc: Andy Shevchenko Signed-off-by: Alejandro Colomar ## include/linux/string.h ## +@@ include/linux/string.h: extern char * strstr(const char *, const char *); + #ifndef __HAVE_ARCH_STRNSTR + extern char * strnstr(const char *, const char *, size_t); + #endif ++extern size_t str_has_prefix(const char *str, const char *prefix); + #ifndef __HAVE_ARCH_STRLEN + extern __kernel_size_t strlen(const char *); + #endif @@ include/linux/string.h: static inline char *strprefix(const char *s, const char *prefix) - * This is similar to strprefix(), but returns an offset instead of - * a pointer. - * + return strncmp(s, prefix, len) == 0 ? (char *) s + len : NULL; + } + +-/** +- * str_has_prefix - Test if a string has a given prefix +- * @str: The string to test +- * @prefix: The string to see if @str starts with +- * +- * This is similar to strprefix(), but returns an offset instead of +- * a pointer. +- * +- * Returns: +- * * strlen(@prefix) if @str starts with @prefix +- * * 0 if @str does not start with @prefix +- */ +-static __always_inline size_t str_has_prefix(const char *str, const char *prefix) +-{ +- size_t len = strlen(prefix); +- return strncmp(str, prefix, len) == 0 ? len : 0; +-} +- + /** + * strstarts - does @str start with @prefix? + * @str: string to examine + + ## lib/string.c ## +@@ lib/string.c: char *strnstr(const char *s1, const char *s2, size_t len) + EXPORT_SYMBOL(strnstr); + #endif + ++/** ++ * str_has_prefix - Test if a string has a given prefix ++ * @str: The string to test ++ * @prefix: The string to see if @str starts with ++ * ++ * This is similar to strprefix(), but returns an offset instead of ++ * a pointer. ++ * + * Unlike for strprefix(), if the prefix is an empty string, the + * behavior is undefined. If you need to support arbitrary prefixes, + * use strprefix(). + * - * Returns: - * * strlen(@prefix) if @str starts with @prefix - * * 0 if @str does not start with @prefix -@@ include/linux/string.h: static inline char *strprefix(const char *s, const char *prefix) - static __always_inline size_t str_has_prefix(const char *str, const char *prefix) - { - size_t len = strlen(prefix); ++ * Returns: ++ * * strlen(@prefix) if @str starts with @prefix ++ * * 0 if @str does not start with @prefix ++ */ ++size_t str_has_prefix(const char *str, const char *prefix) ++{ ++ size_t len = strlen(prefix); + WARN_ON_ONCE(len == 0); - return strncmp(str, prefix, len) == 0 ? len : 0; - } - ++ return strncmp(str, prefix, len) == 0 ? len : 0; ++} ++EXPORT_SYMBOL(str_has_prefix); ++ + #ifndef __HAVE_ARCH_MEMCHR + /** + * memchr - Find a character in an area of memory. 3: 0058b60f662c = 3: 49252e78c9b6 device property: Fix fwnode_name_eq() for empty node names 4: ce623fe0432b ! 4: b615b5afb05e stddef: Add const_cast() @@ Commit message It's similar to C++'s const_cast(), but it is more restrictive: it can only cast away 'const', and not arbitrary qualifiers. - David made it a bit more robust. - - Link: https://lore.kernel.org/linux-hardening/20261111102535.434c2eee@pumpkin/ - Co-developed-by: David Laight + Cc: David Laight Signed-off-by: Alejandro Colomar ## include/linux/stddef.h ## @@ include/linux/stddef.h: enum { + * @T: Type for the cast. + * @p: Pointer to be casted. + * -+ * If @p is of type const @T, then it is casted to @T. -+ * If @p is of type @T, then it is returned without any casts. ++ * Cast @p to type @T. The macro verifies that @p is of type const @T. + * This avoids mistakes that change anything other than 'const' in a cast. + */ -+#define const_cast(T, p) _Generic(p, const T: (T) (p), T: (p)) ++#define const_cast(T, p) _Generic(p, const T: (T) (p)) + #endif 5: 206d2045abcf = 5: 1294b3d64440 string: Add [C]QChar_of() and QVoid_of() 6: 69d1792e5edf ! 6: 0ca0b7aabcfc string: Make strprefix() const-qualifier-generic @@ Commit message ## include/linux/string.h ## @@ include/linux/string.h: void memcpy_and_pad(void *dest, size_t dest_len, const void *src, size_t count, - * * @s+strlen(@prefix) if @s starts with @prefix + * * @s + strlen(@prefix) if @s starts with @prefix * * NULL if @s does not start with @prefix */ -static inline char *strprefix(const char *s, const char *prefix) @@ include/linux/string.h: void memcpy_and_pad(void *dest, size_t dest_len, const v +#define strprefix(s, pfx) const_cast(CQChar_of(s) *, strprefix(s, pfx)) /** - * str_has_prefix - Test if a string has a given prefix + * strstarts - does @str start with @prefix? -: ------------ > 7: 382a44f7c686 string: Implement strstarts() with strprefix() -- 2.55.0