public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
From: Kees Cook <kees@kernel.org>
To: david.laight.linux@gmail.com
Cc: linux-hardening@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH next 2/3] fortify: Optimise strnlen()
Date: Mon, 30 Mar 2026 16:54:27 -0700	[thread overview]
Message-ID: <202603301650.E7C1536632@keescook> (raw)
In-Reply-To: <20260330132003.3379-3-david.laight.linux@gmail.com>

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.

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.

-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
> 

-- 
Kees Cook

  reply	other threads:[~2026-03-30 23:54 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 ` [PATCH next 2/3] fortify: Optimise strnlen() david.laight.linux
2026-03-30 23:54   ` Kees Cook [this message]
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=202603301650.E7C1536632@keescook \
    --to=kees@kernel.org \
    --cc=david.laight.linux@gmail.com \
    --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