From: david.laight.linux@gmail.com
To: Kees Cook <kees@kernel.org>,
linux-hardening@vger.kernel.org, linux-kernel@vger.kernel.org
Cc: David Laight <david.laight.linux@gmail.com>
Subject: [PATCH next 2/3] fortify: Optimise strnlen()
Date: Mon, 30 Mar 2026 14:20:02 +0100 [thread overview]
Message-ID: <20260330132003.3379-3-david.laight.linux@gmail.com> (raw)
In-Reply-To: <20260330132003.3379-1-david.laight.linux@gmail.com>
From: David Laight <david.laight.linux@gmail.com>
If the string is constant there is no need to call __real_strlen()
even when maxlen is a variable - just return the smaller value.
If the size of the string variable is unknown fortify_panic() can't be
called, change the condition so that the compiler can optimise it away.
Change __compiletime_strlen(p) to return a 'non-constant' value
for non-constant strings (the same as __builtin_strlen()).
Simplify since it is only necessary to check that the size is constant
and that the last character is '\0'.
Explain why it is different from __builtin_strlen().
Update the kunit tests to match.
Signed-off-by: David Laight <david.laight.linux@gmail.com>
---
include/linux/fortify-string.h | 44 +++++++++++++++++-----------------
lib/tests/fortify_kunit.c | 8 +++----
2 files changed, 26 insertions(+), 26 deletions(-)
diff --git a/include/linux/fortify-string.h b/include/linux/fortify-string.h
index 214d237214d5..758afd7c5f8a 100644
--- a/include/linux/fortify-string.h
+++ b/include/linux/fortify-string.h
@@ -58,19 +58,22 @@ void __read_overflow2_field(size_t avail, size_t wanted) __compiletime_warning("
void __write_overflow(void) __compiletime_error("detected write beyond size of object (1st parameter)");
void __write_overflow_field(size_t avail, size_t wanted) __compiletime_warning("detected write beyond size of field (1st parameter); maybe use struct_group()?");
-#define __compiletime_strlen(p) \
-({ \
- char *__p = (char *)(p); \
- size_t __ret = SIZE_MAX; \
- const size_t __p_size = __member_size(p); \
- if (__p_size != SIZE_MAX && \
- __builtin_constant_p(*__p)) { \
- size_t __p_len = __p_size - 1; \
- if (__builtin_constant_p(__p[__p_len]) && \
- __p[__p_len] == '\0') \
- __ret = __builtin_strlen(__p); \
- } \
- __ret; \
+/*
+ * __builtin_strlen() generates a compile-time error for 'const char foo[4] = "abcd";'.
+ * But that is a valid source for both strnlen() and strscpy() with a constant
+ * length less than or equal to 4.
+ * __compiletime_strlen() returns a non-constant for such items.
+ * Beware of strings with embedded '\0', __builtin_strlen() can be much smaller
+ * than __member_size();
+ * The return value must only be used when it is a constant.
+ */
+extern size_t __fortify_undefined;
+#define __compiletime_strlen(p) \
+({ \
+ char *__p = (char *)(p); \
+ const size_t __p_size = __member_size(p); \
+ __p_size == SIZE_MAX || !statically_true(__p[__p_size - 1] == '\0') ? \
+ __fortify_undefined : __builtin_strlen(__p); \
})
#if defined(__SANITIZE_ADDRESS__)
@@ -215,16 +218,13 @@ __FORTIFY_INLINE __kernel_size_t strnlen(const char * const POS p, __kernel_size
const size_t p_len = __compiletime_strlen(p);
size_t ret;
- /* We can take compile-time actions when maxlen is const. */
- if (__builtin_constant_p(maxlen) && p_len != SIZE_MAX) {
- /* If p is const, we can use its compile-time-known len. */
- if (maxlen >= p_size)
- return p_len;
- }
+ /* If p is const, we can use its compile-time-known len. */
+ if (__builtin_constant_p(p_len))
+ return p_len < maxlen ? p_len : maxlen;
/* Do not check characters beyond the end of p. */
- ret = __real_strnlen(p, maxlen < p_size ? maxlen : p_size);
- if (p_size <= ret && maxlen != ret)
+ ret = __real_strnlen(p, p_size < maxlen ? p_size : maxlen);
+ if (ret == p_size && p_size < maxlen)
fortify_panic(FORTIFY_FUNC_strnlen, FORTIFY_READ, p_size, ret + 1, ret);
return ret;
}
@@ -289,7 +289,7 @@ __FORTIFY_INLINE ssize_t sized_strscpy(char * const POS p, const char * const PO
if (statically_true(p_size < SIZE_MAX)) {
len = __compiletime_strlen(q);
- if (len < SIZE_MAX && statically_true(len < size)) {
+ if (statically_true(len < size)) {
__underlying_memcpy(p, q, len + 1);
return len;
}
diff --git a/lib/tests/fortify_kunit.c b/lib/tests/fortify_kunit.c
index fc9c76f026d6..9b3b7201c02d 100644
--- a/lib/tests/fortify_kunit.c
+++ b/lib/tests/fortify_kunit.c
@@ -102,11 +102,11 @@ static void fortify_test_known_sizes(struct kunit *test)
KUNIT_EXPECT_EQ(test, __compiletime_strlen(unchanging_12), 12);
KUNIT_EXPECT_FALSE(test, __is_constexpr(__builtin_strlen(array_unknown)));
- KUNIT_EXPECT_EQ(test, __compiletime_strlen(array_unknown), SIZE_MAX);
+ KUNIT_EXPECT_FALSE(test, __is_constexpr(__compiletime_strlen(array_unknown)));
/* Externally defined and dynamically sized string pointer: */
KUNIT_EXPECT_FALSE(test, __is_constexpr(__builtin_strlen(test->name)));
- KUNIT_EXPECT_EQ(test, __compiletime_strlen(test->name), SIZE_MAX);
+ KUNIT_EXPECT_FALSE(test, __is_constexpr(__compiletime_strlen(test->name)));
}
/* This is volatile so the optimizer can't perform DCE below. */
@@ -128,12 +128,12 @@ static noinline size_t want_minus_one(int pick)
str = "1";
break;
}
- return __compiletime_strlen(str);
+ return __builtin_constant_p(__compiletime_strlen(str));
}
static void fortify_test_control_flow_split(struct kunit *test)
{
- KUNIT_EXPECT_EQ(test, want_minus_one(pick), SIZE_MAX);
+ KUNIT_EXPECT_FALSE(test, want_minus_one(pick));
}
#define KUNIT_EXPECT_BOS(test, p, expected, name) \
--
2.39.5
next prev parent reply other threads:[~2026-03-30 13:20 UTC|newest]
Thread overview: 19+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-03-30 13:20 [PATCH next 0/3] fortify: Minor changes to strlen() and strnlen() david.laight.linux
2026-03-30 13:20 ` [PATCH next 1/3] fortify: replace __compiletime_lessthan() with statically_true() david.laight.linux
2026-03-30 23:50 ` Kees Cook
2026-03-30 13:20 ` david.laight.linux [this message]
2026-03-30 23:54 ` [PATCH next 2/3] fortify: Optimise strnlen() Kees Cook
2026-03-31 22:09 ` David Laight
2026-03-31 23:51 ` Kees Cook
2026-04-01 13:48 ` David Laight
2026-04-03 8:50 ` David Laight
2026-03-31 6:36 ` Kees Cook
2026-03-31 10:14 ` David Laight
2026-03-31 14:55 ` David Laight
2026-03-31 15:56 ` Kees Cook
2026-04-01 0:15 ` kernel test robot
2026-04-03 8:23 ` David Laight
2026-03-30 13:20 ` [PATCH next 3/3] fortify: Simplify strlen() logic david.laight.linux
2026-03-31 6:07 ` Kees Cook
2026-03-31 8:58 ` David Laight
2026-03-31 6:18 ` Kees Cook
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=20260330132003.3379-3-david.laight.linux@gmail.com \
--to=david.laight.linux@gmail.com \
--cc=kees@kernel.org \
--cc=linux-hardening@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
/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