From 6bf37b9c59dc8e7a765a5c7c4ff01ccf8106b122 Mon Sep 17 00:00:00 2001 From: Justin Stitt Date: Fri, 2 Oct 2026 12:12:53 -0700 Subject: kunit/stackinit: Cover declarations bypassed by goto and switch Under -ftrivial-auto-var-init= the initialization is emitted where a variable is declared. A switch dispatch that reaches a case label below the declaration, or a goto that jumps past it, never reaches that point, so the variable was left uninitialized. Commit 9cf016e6b49b ("lib: test_stackinit.c: XFAIL switch variable init tests") marked test_switch_1_none and test_switch_2_none as expected failures, noting the test should be retained "so that we can evaluate compiler fixes". Clang 24 emits the initialization on the jumps that bypass the declaration, so make the expectation depend on the compiler version instead of failing unconditionally. Add test_goto_none as well. The existing cases only cover a declaration sitting between a switch and its first case label, while the compiler change covers plain goto too. GCC continues to initialize only at the declaration, and reports as much with -Wtrivial-auto-var-init, so it keeps the expected-failure path. Link: https://github.com/llvm/llvm-project/pull/181937 Link: https://github.com/KSPP/linux/issues/125 Link: https://llvm.org/pr44916 Assisted-by: LLM Signed-off-by: Justin Stitt Link: https://patch.msgid.link/20261002-kspp-125-v1-1-91286a454fda@google.com Signed-off-by: Kees Cook --- lib/tests/stackinit_kunit.c | 55 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 6 deletions(-) (limited to 'lib/tests/stackinit_kunit.c') diff --git a/lib/tests/stackinit_kunit.c b/lib/tests/stackinit_kunit.c index ff2784769772..4788f6c10c04 100644 --- a/lib/tests/stackinit_kunit.c +++ b/lib/tests/stackinit_kunit.c @@ -382,6 +382,17 @@ union test_small_end { # define STRONG_PASS WANT_SUCCESS #endif +/* + * Clang 24 and later emit initialization on the jumps that bypass the + * declaration. GCC still initializes only at the declaration. Track + * test expectation based on CONFIG_INIT_STACK_NONE above. + */ +#if defined(CONFIG_CC_IS_CLANG) && CONFIG_CLANG_VERSION >= 240000 +# define BYPASS_PASS STRONG_PASS +#else +# define BYPASS_PASS XFAIL +#endif + #define DEFINE_SCALAR_TEST(name, init, xfail) \ DEFINE_TEST(name ## _ ## init, name, SCALAR, \ init, xfail) @@ -493,6 +504,36 @@ static int noinline __leaf_switch_none(int path, bool fill) return 0; } +static noinline int __leaf_goto_none(bool fill) +{ + goto bypass; + /* + * This declaration is jumped over by the goto above, so it is + * never reached. Compilers that initialize only at the point of + * declaration leave this variable untouched. + */ + uint64_t var[10]; + +bypass: + target_start = &var; + target_size = sizeof(var); + if (fill) { + fill_start = &var; + fill_size = sizeof(var); + + memset(fill_start, (forced_mask | 0x55) & FILL_BYTE, fill_size); + } + memcpy(check_buf, target_start, target_size); + + return 0; +} + +static noinline int leaf_goto_none(unsigned long sp, bool fill, + uint64_t *arg) +{ + return __leaf_goto_none(fill); +} + static noinline int leaf_switch_1_none(unsigned long sp, bool fill, uint64_t *arg) { @@ -506,13 +547,14 @@ static noinline int leaf_switch_2_none(unsigned long sp, bool fill, } /* - * These are expected to fail for most configurations because neither - * GCC nor Clang have a way to perform initialization of variables in - * non-code areas (i.e. in a switch statement before the first "case"). - * https://llvm.org/pr44916 + * A declaration whose definition point is jumped over, either by a switch + * dispatch reaching a "case" label below it or by a goto, used to be left + * uninitialized: the initialization is emitted where the variable is + * declared, and that point is never reached. */ -DEFINE_TEST_DRIVER(switch_1_none, uint64_t, SCALAR, ALWAYS_FAIL); -DEFINE_TEST_DRIVER(switch_2_none, uint64_t, SCALAR, ALWAYS_FAIL); +DEFINE_TEST_DRIVER(switch_1_none, uint64_t, SCALAR, BYPASS_PASS); +DEFINE_TEST_DRIVER(switch_2_none, uint64_t, SCALAR, BYPASS_PASS); +DEFINE_TEST_DRIVER(goto_none, uint64_t, SCALAR, BYPASS_PASS); #define KUNIT_test_scalars(init) \ KUNIT_CASE(test_u8_ ## init), \ @@ -568,6 +610,7 @@ static struct kunit_case stackinit_test_cases[] = { KUNIT_test_scalars(none), KUNIT_CASE(test_switch_1_none), KUNIT_CASE(test_switch_2_none), + KUNIT_CASE(test_goto_none), /* STRUCTLEAK_BYREF should cover from here down. */ KUNIT_test_structs(none), /* STRUCTLEAK will only cover this. */ -- cgit v1.2.3 From 0f82b48209e85a1f049df20455703a2767f65cfd Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Mon, 5 Oct 2026 02:15:36 -0700 Subject: kunit/stackinit: Clean up XFAIL under instrumentation Commit 8530ea3c9b97 ("Kbuild: remove structleak gcc plugin") dropped the stackinit GCC plugins, so there is no longer a gradient of test coverage. Collapse the now identical USER_PASS, BYREF_PASS, STRONG_PASS into a single INSTRUMENTED_PASS definition. Signed-off-by: Kees Cook --- lib/tests/stackinit_kunit.c | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) (limited to 'lib/tests/stackinit_kunit.c') diff --git a/lib/tests/stackinit_kunit.c b/lib/tests/stackinit_kunit.c index 4788f6c10c04..d589536afa20 100644 --- a/lib/tests/stackinit_kunit.c +++ b/lib/tests/stackinit_kunit.c @@ -369,17 +369,13 @@ union test_small_end { char four; }; -#define ALWAYS_PASS WANT_SUCCESS -#define ALWAYS_FAIL XFAIL +#define ALWAYS_PASS WANT_SUCCESS +#define ALWAYS_FAIL XFAIL #ifdef CONFIG_INIT_STACK_NONE -# define USER_PASS XFAIL -# define BYREF_PASS XFAIL -# define STRONG_PASS XFAIL +# define INSTRUMENTED_PASS XFAIL #else -# define USER_PASS WANT_SUCCESS -# define BYREF_PASS WANT_SUCCESS -# define STRONG_PASS WANT_SUCCESS +# define INSTRUMENTED_PASS WANT_SUCCESS #endif /* @@ -388,7 +384,7 @@ union test_small_end { * test expectation based on CONFIG_INIT_STACK_NONE above. */ #if defined(CONFIG_CC_IS_CLANG) && CONFIG_CLANG_VERSION >= 240000 -# define BYPASS_PASS STRONG_PASS +# define BYPASS_PASS INSTRUMENTED_PASS #else # define BYPASS_PASS XFAIL #endif @@ -443,23 +439,23 @@ 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); -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_INITIALIZER_TESTS(static, INSTRUMENTED_PASS); +DEFINE_STRUCT_INITIALIZER_TESTS(dynamic, INSTRUMENTED_PASS); +DEFINE_STRUCT_INITIALIZER_TESTS(runtime, INSTRUMENTED_PASS); +DEFINE_STRUCT_INITIALIZER_TESTS(assigned_static, INSTRUMENTED_PASS); +DEFINE_STRUCT_INITIALIZER_TESTS(assigned_dynamic, INSTRUMENTED_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_INITIALIZER_TESTS(static, INSTRUMENTED_PASS); +DEFINE_UNION_INITIALIZER_TESTS(dynamic, INSTRUMENTED_PASS); +DEFINE_UNION_INITIALIZER_TESTS(runtime, INSTRUMENTED_PASS); +DEFINE_UNION_INITIALIZER_TESTS(assigned_static, INSTRUMENTED_PASS); +DEFINE_UNION_INITIALIZER_TESTS(assigned_dynamic, INSTRUMENTED_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); +DEFINE_SCALAR_TESTS(none, INSTRUMENTED_PASS); +DEFINE_STRUCT_TESTS(none, INSTRUMENTED_PASS); /* Initialization of members with __user attribute. */ -DEFINE_TEST(user, struct test_user, STRUCT, none, USER_PASS); +DEFINE_TEST(user, struct test_user, STRUCT, none, INSTRUMENTED_PASS); /* * Check two uses through a variable declaration outside either path, -- cgit v1.2.3