Skip to contentSkip to conversation

string: Check for "nonstring" attribute on strscpy() arguments

PATCH Applied Kees Cook 3 participants 5 messages Aug 5, 2024 – Aug 23, 2024

Subsystems (2)
lore
Share & tools
lore

Kees Cook

author maintainer
#0 lore raw

PATCH 1/1 string: Check for "nonstring" attribute on strscpy() arguments

GCC already checks for arguments that are marked with the "nonstring"[1] attribute when used on standard C String API functions (e.g. strcpy). Gain this compile-time checking also for the kernel's primary string copying function, strscpy().

Note that Clang has neither "nonstring" nor __builtin_has_attribute().

---
Cc: Andy Shevchenko <[email protected]>
Cc: Justin Stitt <[email protected]>
Cc: Luc Van Oostenryck <[email protected]>
Cc: Nick Desaulniers <[email protected]>
Cc: Miguel Ojeda <[email protected]>
Cc: Marco Elver <[email protected]>
Cc: Nathan Chancellor <[email protected]>
Cc: Hao Luo <[email protected]>
Cc: Przemek Kitszel <[email protected]>
Cc: [email protected]
Cc: [email protected]

---
 include/linux/compiler.h       |  3 +++
 include/linux/compiler_types.h |  7 +++++++
 include/linux/string.h         | 12 ++++++++----
 3 files changed, 18 insertions(+), 4 deletions(-)
include/linux/compiler.h +3 -0
index 2df665fa2964..ec55bcce4146 100644
@@ -242,6 +242,9 @@ static inline void *offset_to_ptr(const int *off)
 /* &a[0] degrades to a pointer: a different type from an array */
 #define __must_be_array(a)	BUILD_BUG_ON_ZERO(__same_type((a), &(a)[0]))
 
+/* Require C Strings (i.e. NUL-terminated) lack the "nonstring" attribute. */
+#define __must_be_cstr(p)	BUILD_BUG_ON_ZERO(__annotated(p, nonstring))
+
 /*
  * This returns a constant expression while determining if an argument is
  * a constant expression, most importantly without evaluating the argument.
include/linux/compiler_types.h +7 -0
index f14c275950b5..1a957ea2f4fe 100644
@@ -421,6 +421,13 @@ struct ftrace_likely_data {
 #define __member_size(p)	__builtin_object_size(p, 1)
 #endif
 
+/* Determine if an attribute has been applied to a variable. */
+#if __has_builtin(__builtin_has_attribute)
+#define __annotated(var, attr)	__builtin_has_attribute(var, attr)
+#else
+#define __annotated(var, attr)	(false)
+#endif
+
 /*
  * Some versions of gcc do not mark 'asm goto' volatile:
  *
include/linux/string.h +8 -4
index 9edace076ddb..95b3fc308f4f 100644
@@ -76,12 +76,16 @@ ssize_t sized_strscpy(char *, const char *, size_t);
  * known size.
  */
 #define __strscpy0(dst, src, ...)	\
-	sized_strscpy(dst, src, sizeof(dst) + __must_be_array(dst))
-#define __strscpy1(dst, src, size)	sized_strscpy(dst, src, size)
+	sized_strscpy(dst, src, sizeof(dst) + __must_be_array(dst) +	\
+				__must_be_cstr(dst) + __must_be_cstr(src))
+#define __strscpy1(dst, src, size)	\
+	sized_strscpy(dst, src, size + __must_be_cstr(dst) + __must_be_cstr(src))
 
 #define __strscpy_pad0(dst, src, ...)	\
-	sized_strscpy_pad(dst, src, sizeof(dst) + __must_be_array(dst))
-#define __strscpy_pad1(dst, src, size)	sized_strscpy_pad(dst, src, size)
+	sized_strscpy_pad(dst, src, sizeof(dst) + __must_be_array(dst) +	\
+				    __must_be_cstr(dst) + __must_be_cstr(src))
+#define __strscpy_pad1(dst, src, size)	\
+	sized_strscpy_pad(dst, src, size + __must_be_cstr(dst) + __must_be_cstr(src))
 
 /**
  * strscpy - Copy a C-string into a sized buffer
Signature · 1 line
2.34.1
#1 lore raw
Quoted 24 lines · Kees Cook

GCC already checks for arguments that are marked with the "nonstring"[1] attribute when used on standard C String API functions (e.g. strcpy). Gain this compile-time checking also for the kernel's primary string copying function, strscpy().

Note that Clang has neither "nonstring" nor __builtin_has_attribute().

include/linux/string.h +2 -2
index 9edace076ddb..95b3fc308f4f 100644
@@ -76,12 +76,16 @@ ssize_t sized_strscpy(char *, const char *, size_t);
   * known size.
   */
  #define __strscpy0(dst, src, ...)	\
-	sized_strscpy(dst, src, sizeof(dst) + __must_be_array(dst))
-#define __strscpy1(dst, src, size)	sized_strscpy(dst, src, size)
+	sized_strscpy(dst, src, sizeof(dst) + __must_be_array(dst) +	\
+				__must_be_cstr(dst) + __must_be_cstr(src))

include/linux/string.h ssize_t sized_strscpy(char *, const char *, size_t); · line 88

Quoted 10 lines
include/linux/string.h +6 -2
+#define __strscpy1(dst, src, size)	\
+	sized_strscpy(dst, src, size + __must_be_cstr(dst) + __must_be_cstr(src))
 
  #define __strscpy_pad0(dst, src, ...)	\
-	sized_strscpy_pad(dst, src, sizeof(dst) + __must_be_array(dst))
-#define __strscpy_pad1(dst, src, size)	sized_strscpy_pad(dst, src, size)
+	sized_strscpy_pad(dst, src, sizeof(dst) + __must_be_array(dst) +	\
+				    __must_be_cstr(dst) + __must_be_cstr(src))
+#define __strscpy_pad1(dst, src, size)	\
+	sized_strscpy_pad(dst, src, size + __must_be_cstr(dst) + __must_be_cstr(src))

any way to avoid the usual caveat of repeating macro argument?

a variant of BUILD_BUG that is checking argument and otherwise pasting it would nail it, but I didn't pondered how to implement such

author maintainer
#2 lore raw
Quoted 37 lines · Przemek Kitszel
Quoted 23 lines · Kees Cook

GCC already checks for arguments that are marked with the "nonstring"[1] attribute when used on standard C String API functions (e.g. strcpy). Gain this compile-time checking also for the kernel's primary string copying function, strscpy().

Note that Clang has neither "nonstring" nor __builtin_has_attribute().

include/linux/string.h +1 -2
index 9edace076ddb..95b3fc308f4f 100644
@@ -76,12 +76,16 @@ ssize_t sized_strscpy(char *, const char *, size_t);
   * known size.
   */
  #define __strscpy0(dst, src, ...)	\
-	sized_strscpy(dst, src, sizeof(dst) + __must_be_array(dst))
-#define __strscpy1(dst, src, size)	sized_strscpy(dst, src, size)
+	sized_strscpy(dst, src, sizeof(dst) + __must_be_array(dst) +	\

include/linux/string.h ssize_t sized_strscpy(char *, const char *, size_t); · line 87

Quoted 10 lines
include/linux/string.h +7 -2
+				__must_be_cstr(dst) + __must_be_cstr(src))
+#define __strscpy1(dst, src, size)	\
+	sized_strscpy(dst, src, size + __must_be_cstr(dst) + __must_be_cstr(src))
  #define __strscpy_pad0(dst, src, ...)	\
-	sized_strscpy_pad(dst, src, sizeof(dst) + __must_be_array(dst))
-#define __strscpy_pad1(dst, src, size)	sized_strscpy_pad(dst, src, size)
+	sized_strscpy_pad(dst, src, sizeof(dst) + __must_be_array(dst) +	\
+				    __must_be_cstr(dst) + __must_be_cstr(src))
+#define __strscpy_pad1(dst, src, size)	\
+	sized_strscpy_pad(dst, src, size + __must_be_cstr(dst) + __must_be_cstr(src))

any way to avoid the usual caveat of repeating macro argument?

Quoted 2 lines

a variant of BUILD_BUG that is checking argument and otherwise pasting it would nail it, but I didn't pondered how to implement such

The use of __must_be_cstr() shouldn't cause side-effects, so from what I can tell this is all okay. And since BUILD_BUG_ON_ZERO() resolves to a constant expression, it shouldn't change the processing of the "size" argument in the strscpy internals...

Signature · 1 line
Kees Cook
Reviewed-by Tested-by on v1 1/1
#3 lore raw
In reply to Kees Cook

include/linux/compiler_types.h struct ftrace_likely_data { · line 429

Quoted 7 lines · Kees Cook
include/linux/compiler_types.h +6 -0
+/* Determine if an attribute has been applied to a variable. */
+#if __has_builtin(__builtin_has_attribute)
+#define __annotated(var, attr) __builtin_has_attribute(var, attr)
+#else
+#define __annotated(var, attr) (false)
+#endif

__annotated is obviously best-effort given this definition, and we do similar things elsewhere, and it has a double-underscore. However, I wonder if this being a "query" (vs. something like an attribute) may imply that it has a greater risk of someone thinking it will always reply with the right answer... (if e.g. they copy-paste another use). Perhaps there is a more explicit name to let users recall that.

Anyway, it looks sensible to me: more compile-time checking seldomly hurts (apart from complexity in these definitions :). So:

I also introduced a mistake on purpose and I got the expected build error, so:

Cheers,
Miguel

author maintainer Applied
#4 lore raw
In reply to Kees Cook
Quoted 8 lines · Kees Cook

GCC already checks for arguments that are marked with the "nonstring"[1] attribute when used on standard C String API functions (e.g. strcpy). Gain this compile-time checking also for the kernel's primary string copying function, strscpy().

Note that Clang has neither "nonstring" nor __builtin_has_attribute().

[...]

Applied to for-next/hardening, thanks!

[1/1] string: Check for "nonstring" attribute on strscpy() arguments

      https://git.kernel.org/kees/c/559048d156ff

Take care,

Signature · 1 line
Kees Cook