Skip to content

Commit b370f7e

Browse files
committed
lib/tests: Add randstruct KUnit test
Perform basic validation about layout randomization and initialization tracking when using CONFIG_RANDSTRUCT=y. Tested using: $ ./tools/testing/kunit/kunit.py run \ --kconfig_add CONFIG_RANDSTRUCT_FULL=y \ randstruct [17:22:30] ================= randstruct (2 subtests) ================== [17:22:30] [PASSED] randstruct_layout [17:22:30] [PASSED] randstruct_initializers [17:22:30] =================== [PASSED] randstruct ==================== [17:22:30] ============================================================ [17:22:30] Testing complete. Ran 2 tests: passed: 2 [17:22:30] Elapsed time: 5.091s total, 0.001s configuring, 4.974s building, 0.086s running Adding "--make_option LLVM=1" can be used to test Clang, which also passes. Acked-by: David Gow <[email protected]> Signed-off-by: Kees Cook <[email protected]>
1 parent e136a40 commit b370f7e

File tree

4 files changed

+293
-0
lines changed

4 files changed

+293
-0
lines changed

MAINTAINERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12892,6 +12892,7 @@ F: include/linux/overflow.h
1289212892
F: include/linux/randomize_kstack.h
1289312893
F: include/linux/ucopysize.h
1289412894
F: kernel/configs/hardening.config
12895+
F: lib/tests/randstruct_kunit.c
1289512896
F: lib/tests/usercopy_kunit.c
1289612897
F: mm/usercopy.c
1289712898
F: security/Kconfig.hardening

lib/Kconfig.debug

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2863,6 +2863,14 @@ config OVERFLOW_KUNIT_TEST
28632863

28642864
If unsure, say N.
28652865

2866+
config RANDSTRUCT_KUNIT_TEST
2867+
tristate "Test randstruct structure layout randomization at runtime" if !KUNIT_ALL_TESTS
2868+
depends on KUNIT
2869+
default KUNIT_ALL_TESTS
2870+
help
2871+
Builds unit tests for the checking CONFIG_RANDSTRUCT=y, which
2872+
randomizes structure layouts.
2873+
28662874
config STACKINIT_KUNIT_TEST
28672875
tristate "Test level of stack variable initialization" if !KUNIT_ALL_TESTS
28682876
depends on KUNIT

lib/tests/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ obj-$(CONFIG_MEMCPY_KUNIT_TEST) += memcpy_kunit.o
3535
CFLAGS_overflow_kunit.o = $(call cc-disable-warning, tautological-constant-out-of-range-compare)
3636
obj-$(CONFIG_OVERFLOW_KUNIT_TEST) += overflow_kunit.o
3737
obj-$(CONFIG_PRINTF_KUNIT_TEST) += printf_kunit.o
38+
obj-$(CONFIG_RANDSTRUCT_KUNIT_TEST) += randstruct_kunit.o
3839
obj-$(CONFIG_SCANF_KUNIT_TEST) += scanf_kunit.o
3940
obj-$(CONFIG_SIPHASH_KUNIT_TEST) += siphash_kunit.o
4041
obj-$(CONFIG_SLUB_KUNIT_TEST) += slub_kunit.o

lib/tests/randstruct_kunit.c

Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
/*
3+
* Test cases for struct randomization, i.e. CONFIG_RANDSTRUCT=y.
4+
*
5+
* For example, see:
6+
* "Running tests with kunit_tool" at Documentation/dev-tools/kunit/start.rst
7+
* ./tools/testing/kunit/kunit.py run randstruct [--raw_output] \
8+
* [--make_option LLVM=1] \
9+
* --kconfig_add CONFIG_RANDSTRUCT_FULL=y
10+
*
11+
*/
12+
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
13+
14+
#include <kunit/test.h>
15+
#include <linux/init.h>
16+
#include <linux/kernel.h>
17+
#include <linux/module.h>
18+
#include <linux/string.h>
19+
20+
#define DO_MANY_MEMBERS(macro, args...) \
21+
macro(a, args) \
22+
macro(b, args) \
23+
macro(c, args) \
24+
macro(d, args) \
25+
macro(e, args) \
26+
macro(f, args) \
27+
macro(g, args) \
28+
macro(h, args)
29+
30+
#define do_enum(x, ignored) MEMBER_NAME_ ## x,
31+
enum randstruct_member_names {
32+
DO_MANY_MEMBERS(do_enum)
33+
MEMBER_NAME_MAX,
34+
};
35+
/* Make sure the macros are working: want 8 test members. */
36+
_Static_assert(MEMBER_NAME_MAX == 8, "Number of test members changed?!");
37+
38+
/* This is an unsigned long member to match the function pointer size */
39+
#define unsigned_long_member(x, ignored) unsigned long x;
40+
struct randstruct_untouched {
41+
DO_MANY_MEMBERS(unsigned_long_member)
42+
};
43+
44+
/* Struct explicitly marked with __randomize_layout. */
45+
struct randstruct_shuffled {
46+
DO_MANY_MEMBERS(unsigned_long_member)
47+
} __randomize_layout;
48+
#undef unsigned_long_member
49+
50+
/* Struct implicitly randomized from being all func ptrs. */
51+
#define func_member(x, ignored) size_t (*x)(int);
52+
struct randstruct_funcs_untouched {
53+
DO_MANY_MEMBERS(func_member)
54+
} __no_randomize_layout;
55+
56+
struct randstruct_funcs_shuffled {
57+
DO_MANY_MEMBERS(func_member)
58+
};
59+
#undef func_member
60+
61+
#define func_body(x, ignored) \
62+
static noinline size_t func_##x(int arg) \
63+
{ \
64+
return offsetof(struct randstruct_funcs_untouched, x); \
65+
}
66+
DO_MANY_MEMBERS(func_body)
67+
68+
/* Various mixed types. */
69+
#define mixed_members \
70+
bool a; \
71+
short b; \
72+
unsigned int c __aligned(16); \
73+
size_t d; \
74+
char e; \
75+
u64 f; \
76+
union { \
77+
struct randstruct_shuffled shuffled; \
78+
uintptr_t g; \
79+
}; \
80+
union { \
81+
void *ptr; \
82+
char h; \
83+
};
84+
85+
struct randstruct_mixed_untouched {
86+
mixed_members
87+
};
88+
89+
struct randstruct_mixed_shuffled {
90+
mixed_members
91+
} __randomize_layout;
92+
#undef mixed_members
93+
94+
struct contains_randstruct_untouched {
95+
int before;
96+
struct randstruct_untouched untouched;
97+
int after;
98+
};
99+
100+
struct contains_randstruct_shuffled {
101+
int before;
102+
struct randstruct_shuffled shuffled;
103+
int after;
104+
};
105+
106+
static void randstruct_layout(struct kunit *test)
107+
{
108+
int mismatches;
109+
110+
#define check_mismatch(x, untouched, shuffled) \
111+
if (offsetof(untouched, x) != offsetof(shuffled, x)) \
112+
mismatches++; \
113+
kunit_info(test, #shuffled "::" #x " @ %zu (vs %zu)\n", \
114+
offsetof(shuffled, x), \
115+
offsetof(untouched, x)); \
116+
117+
#define check_pair(outcome, untouched, shuffled) \
118+
mismatches = 0; \
119+
DO_MANY_MEMBERS(check_mismatch, untouched, shuffled) \
120+
kunit_info(test, "Differing " #untouched " vs " #shuffled " member positions: %d\n", \
121+
mismatches); \
122+
KUNIT_##outcome##_MSG(test, mismatches, 0, \
123+
#untouched " vs " #shuffled " layouts: unlucky or broken?\n");
124+
125+
check_pair(EXPECT_EQ, struct randstruct_untouched, struct randstruct_untouched)
126+
check_pair(EXPECT_GT, struct randstruct_untouched, struct randstruct_shuffled)
127+
check_pair(EXPECT_GT, struct randstruct_untouched, struct randstruct_funcs_shuffled)
128+
check_pair(EXPECT_GT, struct randstruct_funcs_untouched, struct randstruct_funcs_shuffled)
129+
check_pair(EXPECT_GT, struct randstruct_mixed_untouched, struct randstruct_mixed_shuffled)
130+
#undef check_pair
131+
132+
#undef check_mismatch
133+
}
134+
135+
#define check_mismatch(x, ignore) \
136+
KUNIT_EXPECT_EQ_MSG(test, untouched->x, shuffled->x, \
137+
"Mismatched member value in %s initializer\n", \
138+
name);
139+
140+
static void test_check_init(struct kunit *test, const char *name,
141+
struct randstruct_untouched *untouched,
142+
struct randstruct_shuffled *shuffled)
143+
{
144+
DO_MANY_MEMBERS(check_mismatch)
145+
}
146+
147+
static void test_check_mixed_init(struct kunit *test, const char *name,
148+
struct randstruct_mixed_untouched *untouched,
149+
struct randstruct_mixed_shuffled *shuffled)
150+
{
151+
DO_MANY_MEMBERS(check_mismatch)
152+
}
153+
#undef check_mismatch
154+
155+
#define check_mismatch(x, ignore) \
156+
KUNIT_EXPECT_EQ_MSG(test, untouched->untouched.x, \
157+
shuffled->shuffled.x, \
158+
"Mismatched member value in %s initializer\n", \
159+
name);
160+
static void test_check_contained_init(struct kunit *test, const char *name,
161+
struct contains_randstruct_untouched *untouched,
162+
struct contains_randstruct_shuffled *shuffled)
163+
{
164+
DO_MANY_MEMBERS(check_mismatch)
165+
}
166+
#undef check_mismatch
167+
168+
#define check_mismatch(x, ignore) \
169+
KUNIT_EXPECT_PTR_EQ_MSG(test, untouched->x, shuffled->x, \
170+
"Mismatched member value in %s initializer\n", \
171+
name);
172+
173+
static void test_check_funcs_init(struct kunit *test, const char *name,
174+
struct randstruct_funcs_untouched *untouched,
175+
struct randstruct_funcs_shuffled *shuffled)
176+
{
177+
DO_MANY_MEMBERS(check_mismatch)
178+
}
179+
#undef check_mismatch
180+
181+
static void randstruct_initializers(struct kunit *test)
182+
{
183+
#define init_members \
184+
.a = 1, \
185+
.b = 3, \
186+
.c = 5, \
187+
.d = 7, \
188+
.e = 11, \
189+
.f = 13, \
190+
.g = 17, \
191+
.h = 19,
192+
struct randstruct_untouched untouched = {
193+
init_members
194+
};
195+
struct randstruct_shuffled shuffled = {
196+
init_members
197+
};
198+
struct randstruct_mixed_untouched mixed_untouched = {
199+
init_members
200+
};
201+
struct randstruct_mixed_shuffled mixed_shuffled = {
202+
init_members
203+
};
204+
struct contains_randstruct_untouched contains_untouched = {
205+
.untouched = {
206+
init_members
207+
},
208+
};
209+
struct contains_randstruct_shuffled contains_shuffled = {
210+
.shuffled = {
211+
init_members
212+
},
213+
};
214+
#define func_member(x, ignored) \
215+
.x = func_##x,
216+
struct randstruct_funcs_untouched funcs_untouched = {
217+
DO_MANY_MEMBERS(func_member)
218+
};
219+
struct randstruct_funcs_shuffled funcs_shuffled = {
220+
DO_MANY_MEMBERS(func_member)
221+
};
222+
223+
test_check_init(test, "named", &untouched, &shuffled);
224+
test_check_init(test, "unnamed", &untouched,
225+
&(struct randstruct_shuffled){
226+
init_members
227+
});
228+
229+
test_check_contained_init(test, "named", &contains_untouched, &contains_shuffled);
230+
test_check_contained_init(test, "unnamed", &contains_untouched,
231+
&(struct contains_randstruct_shuffled){
232+
.shuffled = (struct randstruct_shuffled){
233+
init_members
234+
},
235+
});
236+
237+
test_check_contained_init(test, "named", &contains_untouched, &contains_shuffled);
238+
test_check_contained_init(test, "unnamed copy", &contains_untouched,
239+
&(struct contains_randstruct_shuffled){
240+
/* full struct copy initializer */
241+
.shuffled = shuffled,
242+
});
243+
244+
test_check_mixed_init(test, "named", &mixed_untouched, &mixed_shuffled);
245+
test_check_mixed_init(test, "unnamed", &mixed_untouched,
246+
&(struct randstruct_mixed_shuffled){
247+
init_members
248+
});
249+
250+
test_check_funcs_init(test, "named", &funcs_untouched, &funcs_shuffled);
251+
test_check_funcs_init(test, "unnamed", &funcs_untouched,
252+
&(struct randstruct_funcs_shuffled){
253+
DO_MANY_MEMBERS(func_member)
254+
});
255+
256+
#undef func_member
257+
#undef init_members
258+
}
259+
260+
static int randstruct_test_init(struct kunit *test)
261+
{
262+
if (!IS_ENABLED(CONFIG_RANDSTRUCT))
263+
kunit_skip(test, "Not built with CONFIG_RANDSTRUCT=y");
264+
265+
return 0;
266+
}
267+
268+
static struct kunit_case randstruct_test_cases[] = {
269+
KUNIT_CASE(randstruct_layout),
270+
KUNIT_CASE(randstruct_initializers),
271+
{}
272+
};
273+
274+
static struct kunit_suite randstruct_test_suite = {
275+
.name = "randstruct",
276+
.init = randstruct_test_init,
277+
.test_cases = randstruct_test_cases,
278+
};
279+
280+
kunit_test_suites(&randstruct_test_suite);
281+
282+
MODULE_DESCRIPTION("Test cases for struct randomization");
283+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)