Skip to contentSkip to conversation

kbuild: Use -fzero-init-padding-bits=all

PATCH In review Kees Cook 3 participants 9 messages Jan 27, 2025 – Feb 4, 2025

Subsystems (2)
lore
Share & tools
lore

Kees Cook

author
#0 lore raw

v2 cover kbuild: Use -fzero-init-padding-bits=all

Hi,

This fixes the union initialization regression introduced by GCC 15, and improves explicit padding initialization for structs.

I can take this via the hardening tree if that's easiest.

-Kees

v2: Add updated stackinit KUnit tests, improve change log, add Reviewed-By
v1: https://lore.kernel.org/lkml/[email protected]/

Kees Cook (3):
stackinit: Add old-style zero-init syntax to struct tests
stackinit: Add union initialization to selftests
kbuild: Use -fzero-init-padding-bits=all

 lib/stackinit_kunit.c      | 106 +++++++++++++++++++++++++++++++++++++
 scripts/Makefile.extrawarn |   3 ++
 2 files changed, 109 insertions(+)
Signature · 1 line
2.34.1
author
#1 lore raw

PATCH v2 1/3 stackinit: Add old-style zero-init syntax to struct tests

The deprecated way to do a full zero init of a structure is with "= { 0 }", but we weren't testing this style. Add it.

---
 lib/stackinit_kunit.c | 3 +++
 1 file changed, 3 insertions(+)
lib/stackinit_kunit.c +3 -0
index c40818ec9c18..7cc9af181e89 100644
@@ -101,6 +101,7 @@ static bool stackinit_range_contains(char *haystack_start, size_t haystack_size,
 
 #define INIT_STRUCT_none(var_type)	/**/
 #define INIT_STRUCT_zero(var_type)	= { }
+#define INIT_STRUCT_old_zero(var_type)	= { 0 }
 
 
 #define __static_partial		{ .two = 0, }
@@ -346,6 +347,7 @@ struct test_user {
 /* These should be fully initialized all the time! */
 DEFINE_SCALAR_TESTS(zero, ALWAYS_PASS);
 DEFINE_STRUCT_TESTS(zero, ALWAYS_PASS);
+DEFINE_STRUCT_TESTS(old_zero, ALWAYS_PASS);
 /* Struct initializers: padding may be left uninitialized. */
 DEFINE_STRUCT_INITIALIZER_TESTS(static, STRONG_PASS);
 DEFINE_STRUCT_INITIALIZER_TESTS(dynamic, STRONG_PASS);
@@ -440,6 +442,7 @@ static struct kunit_case stackinit_test_cases[] = {
 	/* These are explicitly initialized and should always pass. */
 	KUNIT_test_scalars(zero),
 	KUNIT_test_structs(zero),
+	KUNIT_test_structs(old_zero),
 	/* Padding here appears to be accidentally always initialized? */
 	KUNIT_test_structs(dynamic_partial),
 	KUNIT_test_structs(assigned_dynamic_partial),
Signature · 1 line
2.34.1
author
#2 lore raw

PATCH v2 2/3 stackinit: Add union initialization to selftests

In reply to Kees Cook

The stack initialization selftests were checking scalars, strings, and structs, but not unions. Add union tests (which are mostly identical setup to structs). This catches the recent union initialization behavioral changes seen in GCC 15. Before GCC 15, this new test passes:

ok 18 test_small_start_old_zero

With GCC 15, it fails:

not ok 18 test_small_start_old_zero

Specifically, a union with a larger member where a smaller member is initialized with the older "= { 0 }" syntax:

union test_small_start {
     char one:1;
     char two;
     short three;
     unsigned long four;
     struct big_struct {
             unsigned long array[8];
     } big;
};

This is a regression in compiler behavior that Linux has depended on. GCC does not seem likely to fix it, instead suggesting that affected projects start using -fzero-init-padding-bits=unions:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=118403

---
 lib/stackinit_kunit.c | 103 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 103 insertions(+)
lib/stackinit_kunit.c +103 -0
index 7cc9af181e89..fbe910c9c825 100644
@@ -47,10 +47,12 @@ static bool stackinit_range_contains(char *haystack_start, size_t haystack_size,
 #define DO_NOTHING_TYPE_SCALAR(var_type)	var_type
 #define DO_NOTHING_TYPE_STRING(var_type)	void
 #define DO_NOTHING_TYPE_STRUCT(var_type)	void
+#define DO_NOTHING_TYPE_UNION(var_type)		void
 
 #define DO_NOTHING_RETURN_SCALAR(ptr)		*(ptr)
 #define DO_NOTHING_RETURN_STRING(ptr)		/**/
 #define DO_NOTHING_RETURN_STRUCT(ptr)		/**/
+#define DO_NOTHING_RETURN_UNION(ptr)		/**/
 
 #define DO_NOTHING_CALL_SCALAR(var, name)			\
 		(var) = do_nothing_ ## name(&(var))
@@ -58,10 +60,13 @@ static bool stackinit_range_contains(char *haystack_start, size_t haystack_size,
 		do_nothing_ ## name(var)
 #define DO_NOTHING_CALL_STRUCT(var, name)			\
 		do_nothing_ ## name(&(var))
+#define DO_NOTHING_CALL_UNION(var, name)			\
+		do_nothing_ ## name(&(var))
 
 #define FETCH_ARG_SCALAR(var)		&var
 #define FETCH_ARG_STRING(var)		var
 #define FETCH_ARG_STRUCT(var)		&var
+#define FETCH_ARG_UNION(var)		&var
 
 /*
  * On m68k, if the leaf function test variable is longer than 8 bytes,
@@ -77,6 +82,7 @@ static bool stackinit_range_contains(char *haystack_start, size_t haystack_size,
 #define INIT_CLONE_SCALAR		/**/
 #define INIT_CLONE_STRING		[FILL_SIZE_STRING]
 #define INIT_CLONE_STRUCT		/**/
+#define INIT_CLONE_UNION		/**/
 
 #define ZERO_CLONE_SCALAR(zero)		memset(&(zero), 0x00, sizeof(zero))
 #define ZERO_CLONE_STRING(zero)		memset(&(zero), 0x00, sizeof(zero))
@@ -92,6 +98,7 @@ static bool stackinit_range_contains(char *haystack_start, size_t haystack_size,
 		zero.three = 0;				\
 		zero.four = 0;				\
 	} while (0)
+#define ZERO_CLONE_UNION(zero)		ZERO_CLONE_STRUCT(zero)
 
 #define INIT_SCALAR_none(var_type)	/**/
 #define INIT_SCALAR_zero(var_type)	= 0
@@ -147,6 +154,34 @@ static bool stackinit_range_contains(char *haystack_start, size_t haystack_size,
 #define INIT_STRUCT_assigned_copy(var_type)				\
 					; var = *(arg)
 
+/* Union initialization is the same as structs. */
+#define INIT_UNION_none(var_type)	INIT_STRUCT_none(var_type)
+#define INIT_UNION_zero(var_type)	INIT_STRUCT_zero(var_type)
+#define INIT_UNION_old_zero(var_type)	INIT_STRUCT_old_zero(var_type)
+
+#define INIT_UNION_static_partial(var_type)		\
+	INIT_STRUCT_static_partial(var_type)
+#define INIT_UNION_static_all(var_type)			\
+	INIT_STRUCT_static_all(var_type)
+#define INIT_UNION_dynamic_partial(var_type)		\
+	INIT_STRUCT_dynamic_partial(var_type)
+#define INIT_UNION_dynamic_all(var_type)		\
+	INIT_STRUCT_dynamic_all(var_type)
+#define INIT_UNION_runtime_partial(var_type)		\
+	INIT_STRUCT_runtime_partial(var_type)
+#define INIT_UNION_runtime_all(var_type)		\
+	INIT_STRUCT_runtime_all(var_type)
+#define INIT_UNION_assigned_static_partial(var_type)	\
+	INIT_STRUCT_assigned_static_partial(var_type)
+#define INIT_UNION_assigned_static_all(var_type)	\
+	INIT_STRUCT_assigned_static_all(var_type)
+#define INIT_UNION_assigned_dynamic_partial(var_type)	\
+	INIT_STRUCT_assigned_dynamic_partial(var_type)
+#define INIT_UNION_assigned_dynamic_all(var_type)	\
+	INIT_STRUCT_assigned_dynamic_all(var_type)
+#define INIT_UNION_assigned_copy(var_type)		\
+	INIT_STRUCT_assigned_copy(var_type)
+
 /*
  * @name: unique string name for the test
  * @var_type: type to be tested for zeroing initialization
@@ -295,6 +330,33 @@ struct test_user {
 	unsigned long four;
 };
 
+/* No padding: all members are the same size. */
+union test_same_sizes {
+	unsigned long one;
+	unsigned long two;
+	unsigned long three;
+	unsigned long four;
+};
+
+/* Mismatched sizes, with one and two being small */
+union test_small_start {
+	char one:1;
+	char two;
+	short three;
+	unsigned long four;
+	struct big_struct {
+		unsigned long array[8];
+	} big;
+};
+
+/* Mismatched sizes, with one and two being small */
+union test_small_end {
+	short one;
+	unsigned long two;
+	char three:1;
+	char four;
+};
+
 #define ALWAYS_PASS	WANT_SUCCESS
 #define ALWAYS_FAIL	XFAIL
 
@@ -333,6 +395,11 @@ struct test_user {
 			    struct test_ ## name, STRUCT, init, \
 			    xfail)
 
+#define DEFINE_UNION_TEST(name, init, xfail)			\
+		DEFINE_TEST(name ## _ ## init,			\
+			    union test_ ## name, STRUCT, init,	\
+			    xfail)
+
 #define DEFINE_STRUCT_TESTS(init, xfail)			\
 		DEFINE_STRUCT_TEST(small_hole, init, xfail);	\
 		DEFINE_STRUCT_TEST(big_hole, init, xfail);	\
@@ -344,10 +411,22 @@ struct test_user {
 				    xfail);			\
 		DEFINE_STRUCT_TESTS(base ## _ ## all, xfail)
 
+#define DEFINE_UNION_INITIALIZER_TESTS(base, xfail)		\
+		DEFINE_UNION_TESTS(base ## _ ## partial,	\
+				    xfail);			\
+		DEFINE_UNION_TESTS(base ## _ ## all, xfail)
+
+#define DEFINE_UNION_TESTS(init, xfail)				\
+		DEFINE_UNION_TEST(same_sizes, init, xfail);	\
+		DEFINE_UNION_TEST(small_start, init, xfail);	\
+		DEFINE_UNION_TEST(small_end, init, xfail);
+
 /* These should be fully initialized all the time! */
 DEFINE_SCALAR_TESTS(zero, ALWAYS_PASS);
 DEFINE_STRUCT_TESTS(zero, ALWAYS_PASS);
 DEFINE_STRUCT_TESTS(old_zero, ALWAYS_PASS);
+DEFINE_UNION_TESTS(zero, ALWAYS_PASS);
+DEFINE_UNION_TESTS(old_zero, ALWAYS_PASS);
 /* Struct initializers: padding may be left uninitialized. */
 DEFINE_STRUCT_INITIALIZER_TESTS(static, STRONG_PASS);
 DEFINE_STRUCT_INITIALIZER_TESTS(dynamic, STRONG_PASS);
@@ -355,6 +434,12 @@ DEFINE_STRUCT_INITIALIZER_TESTS(runtime, STRONG_PASS);
 DEFINE_STRUCT_INITIALIZER_TESTS(assigned_static, STRONG_PASS);
 DEFINE_STRUCT_INITIALIZER_TESTS(assigned_dynamic, STRONG_PASS);
 DEFINE_STRUCT_TESTS(assigned_copy, ALWAYS_FAIL);
+DEFINE_UNION_INITIALIZER_TESTS(static, STRONG_PASS);
+DEFINE_UNION_INITIALIZER_TESTS(dynamic, STRONG_PASS);
+DEFINE_UNION_INITIALIZER_TESTS(runtime, STRONG_PASS);
+DEFINE_UNION_INITIALIZER_TESTS(assigned_static, STRONG_PASS);
+DEFINE_UNION_INITIALIZER_TESTS(assigned_dynamic, STRONG_PASS);
+DEFINE_UNION_TESTS(assigned_copy, ALWAYS_FAIL);
 /* No initialization without compiler instrumentation. */
 DEFINE_SCALAR_TESTS(none, STRONG_PASS);
 DEFINE_STRUCT_TESTS(none, BYREF_PASS);
@@ -438,14 +523,23 @@ DEFINE_TEST_DRIVER(switch_2_none, uint64_t, SCALAR, ALWAYS_FAIL);
 		KUNIT_CASE(test_trailing_hole_ ## init),\
 		KUNIT_CASE(test_packed_ ## init)	\
 
+#define KUNIT_test_unions(init)				\
+		KUNIT_CASE(test_same_sizes_ ## init),	\
+		KUNIT_CASE(test_small_start_ ## init),	\
+		KUNIT_CASE(test_small_end_ ## init)	\
+
 static struct kunit_case stackinit_test_cases[] = {
 	/* These are explicitly initialized and should always pass. */
 	KUNIT_test_scalars(zero),
 	KUNIT_test_structs(zero),
 	KUNIT_test_structs(old_zero),
+	KUNIT_test_unions(zero),
+	KUNIT_test_unions(old_zero),
 	/* Padding here appears to be accidentally always initialized? */
 	KUNIT_test_structs(dynamic_partial),
 	KUNIT_test_structs(assigned_dynamic_partial),
+	KUNIT_test_unions(dynamic_partial),
+	KUNIT_test_unions(assigned_dynamic_partial),
 	/* Padding initialization depends on compiler behaviors. */
 	KUNIT_test_structs(static_partial),
 	KUNIT_test_structs(static_all),
@@ -455,8 +549,17 @@ static struct kunit_case stackinit_test_cases[] = {
 	KUNIT_test_structs(assigned_static_partial),
 	KUNIT_test_structs(assigned_static_all),
 	KUNIT_test_structs(assigned_dynamic_all),
+	KUNIT_test_unions(static_partial),
+	KUNIT_test_unions(static_all),
+	KUNIT_test_unions(dynamic_all),
+	KUNIT_test_unions(runtime_partial),
+	KUNIT_test_unions(runtime_all),
+	KUNIT_test_unions(assigned_static_partial),
+	KUNIT_test_unions(assigned_static_all),
+	KUNIT_test_unions(assigned_dynamic_all),
 	/* Everything fails this since it effectively performs a memcpy(). */
 	KUNIT_test_structs(assigned_copy),
+	KUNIT_test_unions(assigned_copy),
 	/* STRUCTLEAK_BYREF_ALL should cover everything from here down. */
 	KUNIT_test_scalars(none),
 	KUNIT_CASE(test_switch_1_none),
Signature · 1 line
2.34.1
author
#6 lore raw

PATCH v2 3/3 kbuild: Use -fzero-init-padding-bits=all

In reply to Kees Cook

GCC 15 introduces a regression in "= { 0 }" style initialization of unions that Linux has depended on for eliminating uninitialized variable contents. GCC does not seem likely to fix it[1], instead suggesting[2] that affected projects start using -fzero-init-padding-bits=unions.

To avoid future surprises beyond just the current situation with unions, enable -fzero-init-padding-bits=all when available (GCC 15+). This will correctly zero padding bits in unions and structs that might have been left uninitialized, and will make sure there is no immediate regression in union initializations. As seen in the stackinit KUnit selftest union cases, which were passing before, were failing under GCC 15:

not ok 18 test_small_start_old_zero
ok 29 test_small_start_dynamic_partial # SKIP XFAIL uninit bytes: 63 ok 32 test_small_start_assigned_dynamic_partial # SKIP XFAIL uninit bytes: 63 ok 67 test_small_start_static_partial # SKIP XFAIL uninit bytes: 63 ok 70 test_small_start_static_all # SKIP XFAIL uninit bytes: 56 ok 73 test_small_start_dynamic_all # SKIP XFAIL uninit bytes: 56 ok 82 test_small_start_assigned_static_partial # SKIP XFAIL uninit bytes: 63 ok 85 test_small_start_assigned_static_all # SKIP XFAIL uninit bytes: 56 ok 88 test_small_start_assigned_dynamic_all # SKIP XFAIL uninit bytes: 56

The above all now pass again with -fzero-init-padding-bits=all added.

This also fixes the following cases for struct initialization that had been XFAIL until now because there was no compiler support beyond the larger "-ftrivial-auto-var-init=zero" option:

ok 38 test_small_hole_static_all # SKIP XFAIL uninit bytes: 3 ok 39 test_big_hole_static_all # SKIP XFAIL uninit bytes: 124 ok 40 test_trailing_hole_static_all # SKIP XFAIL uninit bytes: 7 ok 42 test_small_hole_dynamic_all # SKIP XFAIL uninit bytes: 3 ok 43 test_big_hole_dynamic_all # SKIP XFAIL uninit bytes: 124 ok 44 test_trailing_hole_dynamic_all # SKIP XFAIL uninit bytes: 7 ok 58 test_small_hole_assigned_static_all # SKIP XFAIL uninit bytes: 3 ok 59 test_big_hole_assigned_static_all # SKIP XFAIL uninit bytes: 124 ok 60 test_trailing_hole_assigned_static_all # SKIP XFAIL uninit bytes: 7 ok 62 test_small_hole_assigned_dynamic_all # SKIP XFAIL uninit bytes: 3 ok 63 test_big_hole_assigned_dynamic_all # SKIP XFAIL uninit bytes: 124 ok 64 test_trailing_hole_assigned_dynamic_all # SKIP XFAIL uninit bytes: 7

All of the above now pass when built under GCC 15. Tests can be seen with:

    ./tools/testing/kunit/kunit.py run stackinit --arch=x86_64 \
        --make_option CC=gcc-15

Clang continues to fully initialize these kinds of variables[3] with additional flags.

---
Cc: Masahiro Yamada <[email protected]>
Cc: Nicolas Schier <[email protected]>
Cc: [email protected]

---
 scripts/Makefile.extrawarn | 3 +++
 1 file changed, 3 insertions(+)
scripts/Makefile.extrawarn +3 -0
index 1d13cecc7cc7..eb719f6d8d53 100644
@@ -77,6 +77,9 @@ KBUILD_CFLAGS += $(call cc-option,-Werror=designated-init)
 # Warn if there is an enum types mismatch
 KBUILD_CFLAGS += $(call cc-option,-Wenum-conversion)
 
+# Explicitly clear padding bits during variable initialization
+KBUILD_CFLAGS += $(call cc-option,-fzero-init-padding-bits=all)
+
 KBUILD_CFLAGS += -Wextra
 KBUILD_CFLAGS += -Wunused
 
Signature · 1 line
2.34.1
author
#7 lore raw
Quoted 2 lines · Kees Cook

Clang continues to fully initialize these kinds of variables[3] with additional flags.

Typo: "with" should be "without". I've fixed this locally.

Signature · 1 line
Kees Cook
#3 lore raw

Re: [PATCH v2 2/3] stackinit: Add union initialization to selftests

In reply to Kees Cook

Hi Kees,

Quoted 24 lines · Kees Cook

The stack initialization selftests were checking scalars, strings, and structs, but not unions. Add union tests (which are mostly identical setup to structs). This catches the recent union initialization behavioral changes seen in GCC 15. Before GCC 15, this new test passes:

ok 18 test_small_start_old_zero

With GCC 15, it fails:

not ok 18 test_small_start_old_zero

Specifically, a union with a larger member where a smaller member is initialized with the older "= { 0 }" syntax:

union test_small_start {
     char one:1;
     char two;
     short three;
     unsigned long four;
     struct big_struct {
             unsigned long array[8];
     } big;
};
Quoted 6 lines

This is a regression in compiler behavior that Linux has depended on. GCC does not seem likely to fix it, instead suggesting that affected projects start using -fzero-init-padding-bits=unions:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=118403

Thanks for your patch!

Quoted 17 lines
lib/stackinit_kunit.c +10 -0
@@ -295,6 +330,33 @@ struct test_user {
        unsigned long four;
 };
 
+/* No padding: all members are the same size. */
+union test_same_sizes {
+       unsigned long one;
+       unsigned long two;
+       unsigned long three;
+       unsigned long four;
+};
+
+/* Mismatched sizes, with one and two being small */
+union test_small_start {

lib/stackinit_kunit.c struct test_user { · line 352

Quoted 10 lines
lib/stackinit_kunit.c +10 -0
+       char one:1;
+       char two;
+       short three;
+       unsigned long four;
+       struct big_struct {
+               unsigned long array[8];
+       } big;
+};
+
+/* Mismatched sizes, with one and two being small */

three and four

lib/stackinit_kunit.c struct test_user { · line 362

Quoted 10 lines
lib/stackinit_kunit.c +7 -0
+union test_small_end {
+       short one;
+       unsigned long two;
+       char three:1;
+       char four;
+};
+
 #define ALWAYS_PASS    WANT_SUCCESS
 #define ALWAYS_FAIL    XFAIL
 

Gr{oetje,eeting}s,

Geert

--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- [email protected]

In personal conversations with technical people, I call myself a hacker. But when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds

Acked-by on v2 3/3
#8 lore raw
In reply to Kees Cook
Quoted 51 lines · Kees Cook

GCC 15 introduces a regression in "= { 0 }" style initialization of unions that Linux has depended on for eliminating uninitialized variable contents. GCC does not seem likely to fix it[1], instead suggesting[2] that affected projects start using -fzero-init-padding-bits=unions.

To avoid future surprises beyond just the current situation with unions, enable -fzero-init-padding-bits=all when available (GCC 15+). This will correctly zero padding bits in unions and structs that might have been left uninitialized, and will make sure there is no immediate regression in union initializations. As seen in the stackinit KUnit selftest union cases, which were passing before, were failing under GCC 15:

not ok 18 test_small_start_old_zero
ok 29 test_small_start_dynamic_partial # SKIP XFAIL uninit bytes: 63 ok 32 test_small_start_assigned_dynamic_partial # SKIP XFAIL uninit bytes: 63 ok 67 test_small_start_static_partial # SKIP XFAIL uninit bytes: 63 ok 70 test_small_start_static_all # SKIP XFAIL uninit bytes: 56 ok 73 test_small_start_dynamic_all # SKIP XFAIL uninit bytes: 56 ok 82 test_small_start_assigned_static_partial # SKIP XFAIL uninit bytes: 63 ok 85 test_small_start_assigned_static_all # SKIP XFAIL uninit bytes: 56 ok 88 test_small_start_assigned_dynamic_all # SKIP XFAIL uninit bytes: 56

The above all now pass again with -fzero-init-padding-bits=all added.

This also fixes the following cases for struct initialization that had been XFAIL until now because there was no compiler support beyond the larger "-ftrivial-auto-var-init=zero" option:

ok 38 test_small_hole_static_all # SKIP XFAIL uninit bytes: 3 ok 39 test_big_hole_static_all # SKIP XFAIL uninit bytes: 124 ok 40 test_trailing_hole_static_all # SKIP XFAIL uninit bytes: 7 ok 42 test_small_hole_dynamic_all # SKIP XFAIL uninit bytes: 3 ok 43 test_big_hole_dynamic_all # SKIP XFAIL uninit bytes: 124 ok 44 test_trailing_hole_dynamic_all # SKIP XFAIL uninit bytes: 7 ok 58 test_small_hole_assigned_static_all # SKIP XFAIL uninit bytes: 3 ok 59 test_big_hole_assigned_static_all # SKIP XFAIL uninit bytes: 124 ok 60 test_trailing_hole_assigned_static_all # SKIP XFAIL uninit bytes: 7 ok 62 test_small_hole_assigned_dynamic_all # SKIP XFAIL uninit bytes: 3 ok 63 test_big_hole_assigned_dynamic_all # SKIP XFAIL uninit bytes: 124 ok 64 test_trailing_hole_assigned_dynamic_all # SKIP XFAIL uninit bytes: 7

All of the above now pass when built under GCC 15. Tests can be seen with:

    ./tools/testing/kunit/kunit.py run stackinit --arch=x86_64 \
        --make_option CC=gcc-15

Clang continues to fully initialize these kinds of variables[3] with additional flags.

Quoted 7 lines

---

Quoted 24 lines
Cc Masahiro Yamada <[email protected]>
Cc Nicolas Schier <[email protected]>
---
 scripts/Makefile.extrawarn | 3 +++
 1 file changed, 3 insertions(+)
scripts/Makefile.extrawarn +3 -1
index 1d13cecc7cc7..eb719f6d8d53 100644
@@ -77,6 +77,9 @@ KBUILD_CFLAGS += $(call cc-option,-Werror=designated-init)
 # Warn if there is an enum types mismatch
 KBUILD_CFLAGS += $(call cc-option,-Wenum-conversion)
 
+# Explicitly clear padding bits during variable initialization
+KBUILD_CFLAGS += $(call cc-option,-fzero-init-padding-bits=all)
+
 KBUILD_CFLAGS += -Wextra
 KBUILD_CFLAGS += -Wunused
 
--

2.34.1

Signature · 2 lines
Best Regards
Masahiro Yamada
#4 lore raw

Re: [PATCH v2 2/3] stackinit: Add union initialization to selftests

In reply to Kees Cook

Hi Kees,

Quoted 24 lines · Kees Cook

The stack initialization selftests were checking scalars, strings, and structs, but not unions. Add union tests (which are mostly identical setup to structs). This catches the recent union initialization behavioral changes seen in GCC 15. Before GCC 15, this new test passes:

ok 18 test_small_start_old_zero

With GCC 15, it fails:

not ok 18 test_small_start_old_zero

Specifically, a union with a larger member where a smaller member is initialized with the older "= { 0 }" syntax:

union test_small_start {
     char one:1;
     char two;
     short three;
     unsigned long four;
     struct big_struct {
             unsigned long array[8];
     } big;
};
Quoted 6 lines

This is a regression in compiler behavior that Linux has depended on. GCC does not seem likely to fix it, instead suggesting that affected projects start using -fzero-init-padding-bits=unions:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=118403

I ran stackinit_kunit from v6.14-rc1 on m68k under ARAnyM. All small_start tests failed:

KTAP version 1
1..1
KTAP version 1
# Subtest: stackinit
# module: stackinit_kunit
1..108
ok 1 test_u8_zero
ok 2 test_u16_zero
ok 3 test_u32_zero
ok 4 test_u64_zero
ok 5 test_char_array_zero
ok 6 test_small_hole_zero
ok 7 test_big_hole_zero
ok 8 test_trailing_hole_zero
ok 9 test_packed_zero
ok 10 test_small_hole_old_zero
ok 11 test_big_hole_old_zero
ok 12 test_trailing_hole_old_zero
ok 13 test_packed_old_zero
ok 14 test_same_sizes_zero
# test_small_start_zero: ASSERTION FAILED at lib/stackinit_kunit.c:428 Expected stackinit_range_contains(fill_start, fill_size,
target_start, target_size) to be true, but is false

stackframe was not the same between calls!? (fill 32 wide, target offset by -12)
not ok 15 test_small_start_zero
ok 16 test_small_end_zero
ok 17 test_same_sizes_old_zero
# test_small_start_old_zero: ASSERTION FAILED at lib/stackinit_kunit.c:429 Expected stackinit_range_contains(fill_start, fill_size,
target_start, target_size) to be true, but is false

stackframe was not the same between calls!? (fill 32 wide, target offset by -12)
not ok 18 test_small_start_old_zero
ok 19 test_small_end_old_zero
ok 20 test_small_hole_dynamic_partial
ok 21 test_big_hole_dynamic_partial
ok 22 test_trailing_hole_dynamic_partial
ok 23 test_packed_dynamic_partial
ok 24 test_small_hole_assigned_dynamic_partial
ok 25 test_big_hole_assigned_dynamic_partial
ok 26 test_trailing_hole_assigned_dynamic_partial
ok 27 test_packed_assigned_dynamic_partial
ok 28 test_same_sizes_dynamic_partial
# test_small_start_dynamic_partial: ASSERTION FAILED at
lib/stackinit_kunit.c:438
Expected stackinit_range_contains(fill_start, fill_size,
target_start, target_size) to be true, but is false

stackframe was not the same between calls!? (fill 32 wide, target offset by -12)
not ok 29 test_small_start_dynamic_partial
ok 30 test_small_end_dynamic_partial
ok 31 test_same_sizes_assigned_dynamic_partial
# test_small_start_assigned_dynamic_partial: ASSERTION FAILED at
lib/stackinit_kunit.c:441
Expected stackinit_range_contains(fill_start, fill_size,
target_start, target_size) to be true, but is false

stackframe was not the same between calls!? (fill 32 wide, target offset by -12)
not ok 32 test_small_start_assigned_dynamic_partial
ok 33 test_small_end_assigned_dynamic_partial
ok 34 test_small_hole_static_partial
ok 35 test_big_hole_static_partial
ok 36 test_trailing_hole_static_partial
ok 37 test_packed_static_partial
ok 38 test_small_hole_static_all
ok 39 test_big_hole_static_all
ok 40 test_trailing_hole_static_all
ok 41 test_packed_static_all
ok 42 test_small_hole_dynamic_all
ok 43 test_big_hole_dynamic_all
ok 44 test_trailing_hole_dynamic_all
ok 45 test_packed_dynamic_all
ok 46 test_small_hole_runtime_partial
ok 47 test_big_hole_runtime_partial
ok 48 test_trailing_hole_runtime_partial
ok 49 test_packed_runtime_partial
ok 50 test_small_hole_runtime_all
ok 51 test_big_hole_runtime_all
ok 52 test_trailing_hole_runtime_all
ok 53 test_packed_runtime_all
ok 54 test_small_hole_assigned_static_partial
ok 55 test_big_hole_assigned_static_partial
ok 56 test_trailing_hole_assigned_static_partial
ok 57 test_packed_assigned_static_partial
ok 58 test_small_hole_assigned_static_all
ok 59 test_big_hole_assigned_static_all
ok 60 test_trailing_hole_assigned_static_all
ok 61 test_packed_assigned_static_all
ok 62 test_small_hole_assigned_dynamic_all
ok 63 test_big_hole_assigned_dynamic_all
ok 64 test_trailing_hole_assigned_dynamic_all
ok 65 test_packed_assigned_dynamic_all
ok 66 test_same_sizes_static_partial
# test_small_start_static_partial: ASSERTION FAILED at
lib/stackinit_kunit.c:437
Expected stackinit_range_contains(fill_start, fill_size,
target_start, target_size) to be true, but is false

stackframe was not the same between calls!? (fill 32 wide, target offset by -12)
not ok 67 test_small_start_static_partial
ok 68 test_small_end_static_partial
ok 69 test_same_sizes_static_all
# test_small_start_static_all: ASSERTION FAILED at lib/stackinit_kunit.c:437 Expected stackinit_range_contains(fill_start, fill_size,
target_start, target_size) to be true, but is false

stackframe was not the same between calls!? (fill 32 wide, target offset by -12)
not ok 70 test_small_start_static_all
ok 71 test_small_end_static_all
ok 72 test_same_sizes_dynamic_all
# test_small_start_dynamic_all: ASSERTION FAILED at
lib/stackinit_kunit.c:438
Expected stackinit_range_contains(fill_start, fill_size,
target_start, target_size) to be true, but is false

stackframe was not the same between calls!? (fill 32 wide, target offset by -12)
not ok 73 test_small_start_dynamic_all
ok 74 test_small_end_dynamic_all
ok 75 test_same_sizes_runtime_partial
# test_small_start_runtime_partial: ASSERTION FAILED at
lib/stackinit_kunit.c:439
Expected stackinit_range_contains(fill_start, fill_size,
target_start, target_size) to be true, but is false

stackframe was not the same between calls!? (fill 32 wide, target offset by -12)
not ok 76 test_small_start_runtime_partial
ok 77 test_small_end_runtime_partial
ok 78 test_same_sizes_runtime_all
# test_small_start_runtime_all: ASSERTION FAILED at
lib/stackinit_kunit.c:439
Expected stackinit_range_contains(fill_start, fill_size,
target_start, target_size) to be true, but is false

stackframe was not the same between calls!? (fill 32 wide, target offset by -12)
not ok 79 test_small_start_runtime_all
ok 80 test_small_end_runtime_all
ok 81 test_same_sizes_assigned_static_partial
# test_small_start_assigned_static_partial: ASSERTION FAILED at
lib/stackinit_kunit.c:440
Expected stackinit_range_contains(fill_start, fill_size,
target_start, target_size) to be true, but is false

stackframe was not the same between calls!? (fill 32 wide, target offset by -12)
not ok 82 test_small_start_assigned_static_partial
ok 83 test_small_end_assigned_static_partial
ok 84 test_same_sizes_assigned_static_all
# test_small_start_assigned_static_all: ASSERTION FAILED at
lib/stackinit_kunit.c:440
Expected stackinit_range_contains(fill_start, fill_size,
target_start, target_size) to be true, but is false

stackframe was not the same between calls!? (fill 32 wide, target offset by -12)
not ok 85 test_small_start_assigned_static_all
ok 86 test_small_end_assigned_static_all
ok 87 test_same_sizes_assigned_dynamic_all
# test_small_start_assigned_dynamic_all: ASSERTION FAILED at
lib/stackinit_kunit.c:441
Expected stackinit_range_contains(fill_start, fill_size,
target_start, target_size) to be true, but is false

stackframe was not the same between calls!? (fill 32 wide, target offset by -12)
not ok 88 test_small_start_assigned_dynamic_all
ok 89 test_small_end_assigned_dynamic_all
ok 90 test_small_hole_assigned_copy # SKIP XFAIL uninit bytes: 1 ok 91 test_big_hole_assigned_copy # SKIP XFAIL uninit bytes: 124 ok 92 test_trailing_hole_assigned_copy # SKIP XFAIL uninit bytes: 1 ok 93 test_packed_assigned_copy
ok 94 test_same_sizes_assigned_copy
# test_small_start_assigned_copy: ASSERTION FAILED at
lib/stackinit_kunit.c:442
Expected stackinit_range_contains(fill_start, fill_size,
target_start, target_size) to be true, but is false

stackframe was not the same between calls!? (fill 32 wide, target offset by -12)
not ok 95 test_small_start_assigned_copy
ok 96 test_small_end_assigned_copy
ok 97 test_u8_none
ok 98 test_u16_none
ok 99 test_u32_none
ok 100 test_u64_none
ok 101 test_char_array_none
ok 102 test_switch_1_none # SKIP XFAIL uninit bytes: 80
ok 103 test_switch_2_none # SKIP XFAIL uninit bytes: 80
ok 104 test_small_hole_none
ok 105 test_big_hole_none
ok 106 test_trailing_hole_none
ok 107 test_packed_none
ok 108 test_user
# stackinit: pass:90 fail:13 skip:5 total:108
# Totals: pass:90 fail:13 skip:5 total:108
not ok 1 stackinit

m68k-linux-gnu-gcc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0, GNU ld (GNU Binutils for Ubuntu) 2.42
Thanks!

Gr{oetje,eeting}s,

Geert

Signature · 5 lines
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- [email protected]

In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
                                -- Linus Torvalds
author
#5 lore raw

Re: [PATCH v2 2/3] stackinit: Add union initialization to selftests

Quoted 61 lines · Geert Uytterhoeven

Hi Kees,

Quoted 24 lines · Kees Cook

The stack initialization selftests were checking scalars, strings, and structs, but not unions. Add union tests (which are mostly identical setup to structs). This catches the recent union initialization behavioral changes seen in GCC 15. Before GCC 15, this new test passes:

ok 18 test_small_start_old_zero

With GCC 15, it fails:

not ok 18 test_small_start_old_zero

Specifically, a union with a larger member where a smaller member is initialized with the older "= { 0 }" syntax:

union test_small_start {
     char one:1;
     char two;
     short three;
     unsigned long four;
     struct big_struct {
             unsigned long array[8];
     } big;
};
Quoted 6 lines

This is a regression in compiler behavior that Linux has depended on. GCC does not seem likely to fix it, instead suggesting that affected projects start using -fzero-init-padding-bits=unions:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=118403

I ran stackinit_kunit from v6.14-rc1 on m68k under ARAnyM. All small_start tests failed:

KTAP version 1
1..1
KTAP version 1
# Subtest: stackinit
# module: stackinit_kunit
1..108
ok 1 test_u8_zero
ok 2 test_u16_zero
ok 3 test_u32_zero
ok 4 test_u64_zero
ok 5 test_char_array_zero
ok 6 test_small_hole_zero
ok 7 test_big_hole_zero
ok 8 test_trailing_hole_zero
ok 9 test_packed_zero
ok 10 test_small_hole_old_zero
ok 11 test_big_hole_old_zero
ok 12 test_trailing_hole_old_zero
ok 13 test_packed_old_zero
ok 14 test_same_sizes_zero
# test_small_start_zero: ASSERTION FAILED at lib/stackinit_kunit.c:428 Expected stackinit_range_contains(fill_start, fill_size,
target_start, target_size) to be true, but is false

Quoted 3 lines

stackframe was not the same between calls!? (fill 32 wide, target offset by -12) [...]
m68k-linux-gnu-gcc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0, GNU ld (GNU Binutils for Ubuntu) 2.42

Hm, I must have some kind of misunderstanding of m68k's stack layout (but only exposed for unions O_o). I will try to get this running in an emulator to investigate.

-Kees

Signature · 1 line
Kees Cook