From: David Laight <david.laight.linux@gmail.com>
To: Kees Cook <kees@kernel.org>
Cc: linux-hardening@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH next 2/3] fortify: Optimise strnlen()
Date: Tue, 31 Mar 2026 23:09:14 +0100 [thread overview]
Message-ID: <20260331230914.43698e74@pumpkin> (raw)
In-Reply-To: <202603301650.E7C1536632@keescook>
On Mon, 30 Mar 2026 16:54:27 -0700
Kees Cook <kees@kernel.org> wrote:
> On Mon, Mar 30, 2026 at 02:20:02PM +0100, david.laight.linux@gmail.com wrote:
> > 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.
>
> This will need very very careful checking. It took a lot of tweaking to
> get __compiletime_strlen() to behave correctly across all supported
> versions of GCC and Clang. It has some very ugly glitches possible. I'm
> not saying your replacement is broken: I mean to say I'm going to need
> to spend a bunch of time proving to myself that this is a safe
> replacement.
There are two differences:
1) I removed the check for p[0] being constant, just p[size - 1].
2) The return value is 'not constant' (rather than SIZE_MAX) for
non-constant strings.
This includes any case where __builtin_strlen() returns a non-constant.
I did wonder what happens if you have:
static char foo[] = "foo";
then later:
foo[1] = 0;
The compiler could determine that both foo[0] and foo[3] are never
changed, so (both versions of) __compiletime_strlen() would return
the non-constant result of __builtin_strlen().
The current compilers don't report foo[3] as being constant unless
the string itself in constant, but they might get 'better'.
> Outside of that, though, I don't think dropping the various SIZE_MAX
> tests is correct. Again, I'll need to spend some time double-checking.
They needed changing to match the function returning 'not constant'
rather than SIZE_MAX.
Actually, as I noted in the other email (after writing, but not sending,
this one) __compiletime_strlen() is fundamentally broken with the current
compilers.
Any uses should be replaced by __builtin_strlen().
David
>
> -Kees
>
> >
> > 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-31 22:09 UTC|newest]
Thread overview: 20+ 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 ` [PATCH next 2/3] fortify: Optimise strnlen() david.laight.linux
2026-03-30 23:54 ` Kees Cook
2026-03-31 22:09 ` David Laight [this message]
2026-03-31 23:51 ` Kees Cook
2026-04-01 13:48 ` David Laight
2026-04-03 8:50 ` David Laight
2026-04-16 14:22 ` 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=20260331230914.43698e74@pumpkin \
--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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.