All of lore.kernel.org
 help / color / mirror / Atom feed
From: Rasmus Villemoes <ravi@prevas.dk>
To: Ngo Luong Thanh Tra <ngotra27101996@gmail.com>
Cc: u-boot@lists.denx.de,
	 Ngo Luong Thanh Tra <S4210155@student.rmit.edu.au>,
	 Alexander Sverdlin <alexander.sverdlin@siemens.com>,
	 Casey Connolly <casey.connolly@linaro.org>,
	 Simon Glass <sjg@chromium.org>,  Tom Rini <trini@konsulko.com>
Subject: Re: [PATCH v2] common: cli_hush: fix console_buffer overflow on boot retry
Date: Thu, 09 Apr 2026 10:53:12 +0200	[thread overview]
Message-ID: <87zf3co6pz.fsf@prevas.dk> (raw)
In-Reply-To: <20260331032525.62304-1-S4210155@student.rmit.edu.au> (Ngo Luong Thanh Tra's message of "Tue, 31 Mar 2026 10:25:11 +0700")

On Tue, Mar 31 2026, Ngo Luong Thanh Tra <ngotra27101996@gmail.com> wrote:

> Add const_strcpy() macro to <linux/build_bug.h> that enforces at
> compile time that the destination is a char array (not a pointer),
> the source is a string literal, and the source fits in the
> destination including the NUL terminator. It uses __builtin_strcpy()
> so the compiler can optimize the copy.
>
> Fix the console_buffer extern declaration in <console.h> to include
> the array size so that sizeof(console_buffer) is valid at call sites.
>
> Replace the unbounded strcpy() in cli_hush.c with const_strcpy() to
> catch at compile time any configuration where CONFIG_SYS_CBSIZE is
> smaller than the boot retry command string.
>
> Fixes: 657e19f8f2dd ("cli_hush: support running bootcmd on boot retry")
> Signed-off-by: Ngo Luong Thanh Tra <S4210155@student.rmit.edu.au>
> To: u-boot@lists.denx.de
> ---
>
[snip]
> diff --git a/include/linux/build_bug.h b/include/linux/build_bug.h
> index 20c2dc7f4bd..d775bf1bf91 100644
> --- a/include/linux/build_bug.h
> +++ b/include/linux/build_bug.h
> @@ -76,4 +76,27 @@
>  #define static_assert(expr, ...) __static_assert(expr, ##__VA_ARGS__, #expr)
>  #define __static_assert(expr, msg, ...) _Static_assert(expr, msg)
>  
> +/**
> + * const_strcpy - Copy a string literal to a char array with compile-time checks
> + * @d: destination char array (must be a char array, not a pointer)
> + * @s: source string literal
> + *
> + * Enforces at compile time that:
> + * (a) @d is a char array, not a pointer
> + * (b) @s is a string literal (adjacent string concatenation trick)
> + * (c) @s fits in @d including the NUL terminator
> + *
> + * Uses __builtin_strcpy() so the compiler can optimize the copy into
> + * immediate stores rather than emitting a function call.
> + *
> + * Note: @s is used twice in the macro expansion but this is intentional
> + * and safe: the ("" s "") trick enforces at compile time that @s is a
> + * string literal, and string literals have no side effects.
> + */
> +#define const_strcpy(d, s) ({						\
> +	BUILD_BUG_ON(__builtin_types_compatible_p(typeof(d), char *));	\
> +	BUILD_BUG_ON(sizeof(d) < sizeof("" s ""));			\
> +	__builtin_strcpy(d, s);						\
> +})
> +

+1 for the explanations you've added, that was very much my intention
that you should write something like that. (Note that d is also expanded
multiple times, but only _evalutated_ exactly once, since it appearing
inside typeof() or sizeof() does not cause it to be evaluated).

That said, I really think you should use the static_assert() version I
proposed. (Yes, there was a missing closing parenthesis that needed
fixing, other than that it seems to work).

That is evaluated much earlier by the compiler, and gives a more
to-the-point error message, instead of all the gunk that the
__attribute__((__error__)) implementation gives. For example, I just
tried adding

extern char *test_dst_p;
void test_const_strcpy(void)
{
	const_strcpy(test_dst_p, "foo");
}

to lib/string.c. With the BUILD_BUG_ON version, that gives

===
In file included from lib/string.c:21:
lib/string.c: In function ‘test_const_strcpy’:
include/linux/compiler.h:346:45: error: call to ‘__compiletime_assert_0’ declared with attribute error: BUILD_BUG_ON failed: __builtin_types_compatible_p(typeof(test_dst_p), char *)
  346 |         _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
      |                                             ^
include/linux/compiler.h:327:25: note: in definition of macro ‘__compiletime_assert’
  327 |                         prefix ## suffix();                             \
      |                         ^~~~~~
include/linux/compiler.h:346:9: note: in expansion of macro ‘_compiletime_assert’
  346 |         _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
      |         ^~~~~~~~~~~~~~~~~~~
include/linux/build_bug.h:38:37: note: in expansion of macro ‘compiletime_assert’
   38 | #define BUILD_BUG_ON_MSG(cond, msg) compiletime_assert(!(cond), msg)
      |                                     ^~~~~~~~~~~~~~~~~~
include/linux/build_bug.h:49:9: note: in expansion of macro ‘BUILD_BUG_ON_MSG’
   49 |         BUILD_BUG_ON_MSG(condition, "BUILD_BUG_ON failed: " #condition)
      |         ^~~~~~~~~~~~~~~~
include/linux/string.h:163:3: note: in expansion of macro ‘BUILD_BUG_ON’
  163 |   BUILD_BUG_ON(__builtin_types_compatible_p(typeof(d), char *)); \
      |   ^~~~~~~~~~~~
lib/string.c:31:9: note: in expansion of macro ‘const_strcpy’
   31 |         const_strcpy(test_dst_p, "foo");
      |         ^~~~~~~~~~~~
===

whereas with static_assert(), one gets

===
In file included from include/linux/string.h:6,
                 from lib/string.c:23:
lib/string.c: In function ‘test_const_strcpy’:
include/linux/build_bug.h:77:41: error: static assertion failed: "destination must be char array"
   77 | #define __static_assert(expr, msg, ...) _Static_assert(expr, msg)
      |                                         ^~~~~~~~~~~~~~
include/linux/build_bug.h:76:34: note: in expansion of macro ‘__static_assert’
   76 | #define static_assert(expr, ...) __static_assert(expr, ##__VA_ARGS__, #expr)
      |                                  ^~~~~~~~~~~~~~~
include/linux/string.h:156:3: note: in expansion of macro ‘static_assert’
  156 |   static_assert(__same_type(d, char[]), "destination must be char array"); \
      |   ^~~~~~~~~~~~~
lib/string.c:31:9: note: in expansion of macro ‘const_strcpy’
   31 |         const_strcpy(test_dst_p, "foo");
      |         ^~~~~~~~~~~~
===

That's much fewer lines of gunk, and moreover, the very first "error:"
line explains the problem, contrary to the rather opaque

  error: call to ‘__compiletime_assert_0’ declared with attribute error: BUILD_BUG_ON failed: __builtin_types_compatible_p(typeof(test_dst_p), char *)

And as others have pointed out, we really want to positively assert that
the destination has type char[], not merely that it is not char* - and
the documentation for __builtin_types_compatible_p explicitly says that
int[5] is compatible with int[], so even though we obviously want the
destination to have a known size, the type comparison to char[] is ok.

I also don't think this belongs in build_bug.h. It's a string operation,
so it's more natural in linux/string.h, where you must then add an
include of linux/build_bug.h to make that header self-contained. Or
maybe there's some other more appropriate header, but build_bug.h is not
it.

Another thing: You _can_ elide the type check for s as you've done as
the ""s"" trick _mostly_ does enforce it to be a string literal, but

(a) the error message one gets when there's a syntax error, e.g. when using
some const char* variable, is not as nice as what one can get from a
static_assert() ; something like

lib/string.c:34:38: error: expected ‘)’ before ‘test_src_p’
   34 |         const_strcpy(test_dst_array, test_src_p);

vs

include/linux/build_bug.h:77:41: error: static assertion failed: "source must be a string literal"
   77 | #define __static_assert(expr, msg, ...) _Static_assert(expr, msg)


(b) there are evil ways to defeat the ""s"" trick:

    extern char test_dst_array[12];

    const_strcpy(test_dst_array, ""[0] + "foo bar baz frobble");

Since the s expression here both starts and ends with a string literal,
the concatenation trick works. However, ""[0] is simply another way of
spelling the integer 0 (because it's the nul terminator in the empty
string), and adding that to a string literal produces a value of type
"const char *", pointing to that string. So the sizeof("" s "") will
evaluate to 4 or 8, hence the size comparison will succeed, even though
the pointed-to string is actually longer than the destination array. So
this should not compile, but does, without the explicit type check.

Rasmus

      parent reply	other threads:[~2026-04-09  8:53 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-03-31  3:25 [PATCH v2] common: cli_hush: fix console_buffer overflow on boot retry Ngo Luong Thanh Tra
2026-03-31  6:29 ` Sverdlin, Alexander
2026-04-03 13:21 ` Simon Glass
2026-04-09  8:53 ` Rasmus Villemoes [this message]

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=87zf3co6pz.fsf@prevas.dk \
    --to=ravi@prevas.dk \
    --cc=S4210155@student.rmit.edu.au \
    --cc=alexander.sverdlin@siemens.com \
    --cc=casey.connolly@linaro.org \
    --cc=ngotra27101996@gmail.com \
    --cc=sjg@chromium.org \
    --cc=trini@konsulko.com \
    --cc=u-boot@lists.denx.de \
    /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.