As Bartosz reported, str_has_prefix() has a bug: it can't differentiate between an empty prefix matching an empty string from a non-matching prefix. This is a fundamental design flaw in this API, and not something that can be fixed. As always, this is a consequence of choosing sizes instead of pointers. Sizes are often the wrong choice. In this specific case, pointers are more capable of differentiating these cases, as NULL is a special sentinel value that doesn't need to overload a valid value. Let's implement strprefix(), a function that returns a pointer right after the prefix if found, and a null pointer if not found. We'll slowly migrate existing code from str_has_prefix() to strprefix(). Reported-by: Bartosz Golaszewski Cc: Andy Shevchenko Signed-off-by: Alejandro Colomar --- include/linux/string.h | 19 ++++++++++++++++++- lib/tests/string_kunit.c | 9 +++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/include/linux/string.h b/include/linux/string.h index 6cb5cdd01158..0397e630bdc8 100644 --- a/include/linux/string.h +++ b/include/linux/string.h @@ -545,16 +545,33 @@ void memcpy_and_pad(void *dest, size_t dest_len, const void *src, size_t count, sizeof(*(obj)) - offsetof(typeof(*(obj)), member)); \ }) +/** + * strprefix - string prefix + * @s: The string to be tested + * @prefix: The string to see if @s starts with + * + * Returns: + * * @s+strlen(@prefix) if @s starts with @prefix + * * NULL if @s does not start with @prefix + */ +static __always_inline char *strprefix(const char *s, const char *prefix) +{ + size_t len = strlen(prefix); + 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 * + * DEPRECATED: Use strprefix() + * * A common way to test a prefix of a string is to do: * strncmp(str, prefix, sizeof(prefix) - 1) * * But this can lead to bugs due to typos, or if prefix is a pointer - * and not a constant. Instead use str_has_prefix(). + * and not a constant. Instead use strprefix(). * * Returns: * * strlen(@prefix) if @str starts with @prefix diff --git a/lib/tests/string_kunit.c b/lib/tests/string_kunit.c index 0819ace5b027..73d2614b1474 100644 --- a/lib/tests/string_kunit.c +++ b/lib/tests/string_kunit.c @@ -287,6 +287,14 @@ static void string_test_strspn(struct kunit *test) } } +static void string_test_strprefix(struct kunit *test) +{ + KUNIT_ASSERT_EQ(test, 0, strcmp(strprefix("foobar", "foo"), "bar")); + KUNIT_ASSERT_EQ(test, 0, strcmp(strprefix("foobar", ""), "foobar")); + KUNIT_ASSERT_EQ(test, 0, strcmp(strprefix("foobar", "foobar"), "")); + KUNIT_ASSERT_NULL(test, strprefix("foobar", "bar")); +} + static char strcmp_buffer1[STRCMP_LARGE_BUF_LEN]; static char strcmp_buffer2[STRCMP_LARGE_BUF_LEN]; @@ -891,6 +899,7 @@ static struct kunit_case string_test_cases[] = { KUNIT_CASE(string_test_strnchr), KUNIT_CASE(string_test_strrchr), KUNIT_CASE(string_test_strspn), + KUNIT_CASE(string_test_strprefix), KUNIT_CASE(string_test_strcmp), KUNIT_CASE(string_test_strcmp_long_strings), KUNIT_CASE(string_test_strncmp), -- 2.55.0