Skip to content

Commit e71a29d

Browse files
committed
stackinit: Add union initialization to selftests
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 Link: https://lore.kernel.org/r/20250127191031.245214-2-kees@kernel.org Signed-off-by: Kees Cook <kees@kernel.org>
1 parent ad9f265 commit e71a29d

File tree

1 file changed

+103
-0
lines changed

1 file changed

+103
-0
lines changed

lib/stackinit_kunit.c

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,21 +47,26 @@ static bool stackinit_range_contains(char *haystack_start, size_t haystack_size,
4747
#define DO_NOTHING_TYPE_SCALAR(var_type) var_type
4848
#define DO_NOTHING_TYPE_STRING(var_type) void
4949
#define DO_NOTHING_TYPE_STRUCT(var_type) void
50+
#define DO_NOTHING_TYPE_UNION(var_type) void
5051

5152
#define DO_NOTHING_RETURN_SCALAR(ptr) *(ptr)
5253
#define DO_NOTHING_RETURN_STRING(ptr) /**/
5354
#define DO_NOTHING_RETURN_STRUCT(ptr) /**/
55+
#define DO_NOTHING_RETURN_UNION(ptr) /**/
5456

5557
#define DO_NOTHING_CALL_SCALAR(var, name) \
5658
(var) = do_nothing_ ## name(&(var))
5759
#define DO_NOTHING_CALL_STRING(var, name) \
5860
do_nothing_ ## name(var)
5961
#define DO_NOTHING_CALL_STRUCT(var, name) \
6062
do_nothing_ ## name(&(var))
63+
#define DO_NOTHING_CALL_UNION(var, name) \
64+
do_nothing_ ## name(&(var))
6165

6266
#define FETCH_ARG_SCALAR(var) &var
6367
#define FETCH_ARG_STRING(var) var
6468
#define FETCH_ARG_STRUCT(var) &var
69+
#define FETCH_ARG_UNION(var) &var
6570

6671
/*
6772
* 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,
7782
#define INIT_CLONE_SCALAR /**/
7883
#define INIT_CLONE_STRING [FILL_SIZE_STRING]
7984
#define INIT_CLONE_STRUCT /**/
85+
#define INIT_CLONE_UNION /**/
8086

8187
#define ZERO_CLONE_SCALAR(zero) memset(&(zero), 0x00, sizeof(zero))
8288
#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,
9298
zero.three = 0; \
9399
zero.four = 0; \
94100
} while (0)
101+
#define ZERO_CLONE_UNION(zero) ZERO_CLONE_STRUCT(zero)
95102

96103
#define INIT_SCALAR_none(var_type) /**/
97104
#define INIT_SCALAR_zero(var_type) = 0
@@ -147,6 +154,34 @@ static bool stackinit_range_contains(char *haystack_start, size_t haystack_size,
147154
#define INIT_STRUCT_assigned_copy(var_type) \
148155
; var = *(arg)
149156

157+
/* Union initialization is the same as structs. */
158+
#define INIT_UNION_none(var_type) INIT_STRUCT_none(var_type)
159+
#define INIT_UNION_zero(var_type) INIT_STRUCT_zero(var_type)
160+
#define INIT_UNION_old_zero(var_type) INIT_STRUCT_old_zero(var_type)
161+
162+
#define INIT_UNION_static_partial(var_type) \
163+
INIT_STRUCT_static_partial(var_type)
164+
#define INIT_UNION_static_all(var_type) \
165+
INIT_STRUCT_static_all(var_type)
166+
#define INIT_UNION_dynamic_partial(var_type) \
167+
INIT_STRUCT_dynamic_partial(var_type)
168+
#define INIT_UNION_dynamic_all(var_type) \
169+
INIT_STRUCT_dynamic_all(var_type)
170+
#define INIT_UNION_runtime_partial(var_type) \
171+
INIT_STRUCT_runtime_partial(var_type)
172+
#define INIT_UNION_runtime_all(var_type) \
173+
INIT_STRUCT_runtime_all(var_type)
174+
#define INIT_UNION_assigned_static_partial(var_type) \
175+
INIT_STRUCT_assigned_static_partial(var_type)
176+
#define INIT_UNION_assigned_static_all(var_type) \
177+
INIT_STRUCT_assigned_static_all(var_type)
178+
#define INIT_UNION_assigned_dynamic_partial(var_type) \
179+
INIT_STRUCT_assigned_dynamic_partial(var_type)
180+
#define INIT_UNION_assigned_dynamic_all(var_type) \
181+
INIT_STRUCT_assigned_dynamic_all(var_type)
182+
#define INIT_UNION_assigned_copy(var_type) \
183+
INIT_STRUCT_assigned_copy(var_type)
184+
150185
/*
151186
* @name: unique string name for the test
152187
* @var_type: type to be tested for zeroing initialization
@@ -295,6 +330,33 @@ struct test_user {
295330
unsigned long four;
296331
};
297332

333+
/* No padding: all members are the same size. */
334+
union test_same_sizes {
335+
unsigned long one;
336+
unsigned long two;
337+
unsigned long three;
338+
unsigned long four;
339+
};
340+
341+
/* Mismatched sizes, with one and two being small */
342+
union test_small_start {
343+
char one:1;
344+
char two;
345+
short three;
346+
unsigned long four;
347+
struct big_struct {
348+
unsigned long array[8];
349+
} big;
350+
};
351+
352+
/* Mismatched sizes, with one and two being small */
353+
union test_small_end {
354+
short one;
355+
unsigned long two;
356+
char three:1;
357+
char four;
358+
};
359+
298360
#define ALWAYS_PASS WANT_SUCCESS
299361
#define ALWAYS_FAIL XFAIL
300362

@@ -333,6 +395,11 @@ struct test_user {
333395
struct test_ ## name, STRUCT, init, \
334396
xfail)
335397

398+
#define DEFINE_UNION_TEST(name, init, xfail) \
399+
DEFINE_TEST(name ## _ ## init, \
400+
union test_ ## name, STRUCT, init, \
401+
xfail)
402+
336403
#define DEFINE_STRUCT_TESTS(init, xfail) \
337404
DEFINE_STRUCT_TEST(small_hole, init, xfail); \
338405
DEFINE_STRUCT_TEST(big_hole, init, xfail); \
@@ -344,17 +411,35 @@ struct test_user {
344411
xfail); \
345412
DEFINE_STRUCT_TESTS(base ## _ ## all, xfail)
346413

414+
#define DEFINE_UNION_INITIALIZER_TESTS(base, xfail) \
415+
DEFINE_UNION_TESTS(base ## _ ## partial, \
416+
xfail); \
417+
DEFINE_UNION_TESTS(base ## _ ## all, xfail)
418+
419+
#define DEFINE_UNION_TESTS(init, xfail) \
420+
DEFINE_UNION_TEST(same_sizes, init, xfail); \
421+
DEFINE_UNION_TEST(small_start, init, xfail); \
422+
DEFINE_UNION_TEST(small_end, init, xfail);
423+
347424
/* These should be fully initialized all the time! */
348425
DEFINE_SCALAR_TESTS(zero, ALWAYS_PASS);
349426
DEFINE_STRUCT_TESTS(zero, ALWAYS_PASS);
350427
DEFINE_STRUCT_TESTS(old_zero, ALWAYS_PASS);
428+
DEFINE_UNION_TESTS(zero, ALWAYS_PASS);
429+
DEFINE_UNION_TESTS(old_zero, ALWAYS_PASS);
351430
/* Struct initializers: padding may be left uninitialized. */
352431
DEFINE_STRUCT_INITIALIZER_TESTS(static, STRONG_PASS);
353432
DEFINE_STRUCT_INITIALIZER_TESTS(dynamic, STRONG_PASS);
354433
DEFINE_STRUCT_INITIALIZER_TESTS(runtime, STRONG_PASS);
355434
DEFINE_STRUCT_INITIALIZER_TESTS(assigned_static, STRONG_PASS);
356435
DEFINE_STRUCT_INITIALIZER_TESTS(assigned_dynamic, STRONG_PASS);
357436
DEFINE_STRUCT_TESTS(assigned_copy, ALWAYS_FAIL);
437+
DEFINE_UNION_INITIALIZER_TESTS(static, STRONG_PASS);
438+
DEFINE_UNION_INITIALIZER_TESTS(dynamic, STRONG_PASS);
439+
DEFINE_UNION_INITIALIZER_TESTS(runtime, STRONG_PASS);
440+
DEFINE_UNION_INITIALIZER_TESTS(assigned_static, STRONG_PASS);
441+
DEFINE_UNION_INITIALIZER_TESTS(assigned_dynamic, STRONG_PASS);
442+
DEFINE_UNION_TESTS(assigned_copy, ALWAYS_FAIL);
358443
/* No initialization without compiler instrumentation. */
359444
DEFINE_SCALAR_TESTS(none, STRONG_PASS);
360445
DEFINE_STRUCT_TESTS(none, BYREF_PASS);
@@ -438,14 +523,23 @@ DEFINE_TEST_DRIVER(switch_2_none, uint64_t, SCALAR, ALWAYS_FAIL);
438523
KUNIT_CASE(test_trailing_hole_ ## init),\
439524
KUNIT_CASE(test_packed_ ## init) \
440525

526+
#define KUNIT_test_unions(init) \
527+
KUNIT_CASE(test_same_sizes_ ## init), \
528+
KUNIT_CASE(test_small_start_ ## init), \
529+
KUNIT_CASE(test_small_end_ ## init) \
530+
441531
static struct kunit_case stackinit_test_cases[] = {
442532
/* These are explicitly initialized and should always pass. */
443533
KUNIT_test_scalars(zero),
444534
KUNIT_test_structs(zero),
445535
KUNIT_test_structs(old_zero),
536+
KUNIT_test_unions(zero),
537+
KUNIT_test_unions(old_zero),
446538
/* Padding here appears to be accidentally always initialized? */
447539
KUNIT_test_structs(dynamic_partial),
448540
KUNIT_test_structs(assigned_dynamic_partial),
541+
KUNIT_test_unions(dynamic_partial),
542+
KUNIT_test_unions(assigned_dynamic_partial),
449543
/* Padding initialization depends on compiler behaviors. */
450544
KUNIT_test_structs(static_partial),
451545
KUNIT_test_structs(static_all),
@@ -455,8 +549,17 @@ static struct kunit_case stackinit_test_cases[] = {
455549
KUNIT_test_structs(assigned_static_partial),
456550
KUNIT_test_structs(assigned_static_all),
457551
KUNIT_test_structs(assigned_dynamic_all),
552+
KUNIT_test_unions(static_partial),
553+
KUNIT_test_unions(static_all),
554+
KUNIT_test_unions(dynamic_all),
555+
KUNIT_test_unions(runtime_partial),
556+
KUNIT_test_unions(runtime_all),
557+
KUNIT_test_unions(assigned_static_partial),
558+
KUNIT_test_unions(assigned_static_all),
559+
KUNIT_test_unions(assigned_dynamic_all),
458560
/* Everything fails this since it effectively performs a memcpy(). */
459561
KUNIT_test_structs(assigned_copy),
562+
KUNIT_test_unions(assigned_copy),
460563
/* STRUCTLEAK_BYREF_ALL should cover everything from here down. */
461564
KUNIT_test_scalars(none),
462565
KUNIT_CASE(test_switch_1_none),

0 commit comments

Comments
 (0)