summaryrefslogtreecommitdiff
path: root/lib/tests/stackinit_kunit.c
diff options
context:
space:
mode:
authorJustin Stitt <justinstitt@google.com>2026-10-02 12:12:53 -0700
committerKees Cook <kees@kernel.org>2026-10-05 02:19:01 -0700
commit6bf37b9c59dc8e7a765a5c7c4ff01ccf8106b122 (patch)
tree05beb918557ce77a600dc81810b3c52f5c3edafc /lib/tests/stackinit_kunit.c
parent633c60a2e87345bd70ced5a28561991fa5b0b1f3 (diff)
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 <justinstitt@google.com> Link: https://patch.msgid.link/20261002-kspp-125-v1-1-91286a454fda@google.com Signed-off-by: Kees Cook <kees@kernel.org>
Diffstat (limited to 'lib/tests/stackinit_kunit.c')
-rw-r--r--lib/tests/stackinit_kunit.c55
1 files changed, 49 insertions, 6 deletions
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. */