aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorKees Cook <keescook@chromium.org>2019-01-23 14:24:32 -0500
committerKees Cook <keescook@chromium.org>2019-03-04 12:29:52 -0500
commit50ceaa95ea09703722b30b4afa617c972071cd7f (patch)
treeb339af520900708b49d393580dff6c70330bb232 /lib
parent81a56f6dcd20325607d6008f4bb560c96f4c821a (diff)
lib: Introduce test_stackinit module
Adds test for stack initialization coverage. We have several build options that control the level of stack variable initialization. This test lets us visualize which options cover which cases, and provide tests for some of the pathological padding conditions the compiler will sometimes fail to initialize. All options pass the explicit initialization cases and the partial initializers (even with padding): test_stackinit: u8_zero ok test_stackinit: u16_zero ok test_stackinit: u32_zero ok test_stackinit: u64_zero ok test_stackinit: char_array_zero ok test_stackinit: small_hole_zero ok test_stackinit: big_hole_zero ok test_stackinit: trailing_hole_zero ok test_stackinit: packed_zero ok test_stackinit: small_hole_dynamic_partial ok test_stackinit: big_hole_dynamic_partial ok test_stackinit: trailing_hole_dynamic_partial ok test_stackinit: packed_dynamic_partial ok test_stackinit: small_hole_static_partial ok test_stackinit: big_hole_static_partial ok test_stackinit: trailing_hole_static_partial ok test_stackinit: packed_static_partial ok test_stackinit: packed_static_all ok test_stackinit: packed_dynamic_all ok test_stackinit: packed_runtime_all ok The results of the other tests (which contain no explicit initialization), change based on the build's configured compiler instrumentation. No options: test_stackinit: small_hole_static_all FAIL (uninit bytes: 3) test_stackinit: big_hole_static_all FAIL (uninit bytes: 61) test_stackinit: trailing_hole_static_all FAIL (uninit bytes: 7) test_stackinit: small_hole_dynamic_all FAIL (uninit bytes: 3) test_stackinit: big_hole_dynamic_all FAIL (uninit bytes: 61) test_stackinit: trailing_hole_dynamic_all FAIL (uninit bytes: 7) test_stackinit: small_hole_runtime_partial FAIL (uninit bytes: 23) test_stackinit: big_hole_runtime_partial FAIL (uninit bytes: 127) test_stackinit: trailing_hole_runtime_partial FAIL (uninit bytes: 24) test_stackinit: packed_runtime_partial FAIL (uninit bytes: 24) test_stackinit: small_hole_runtime_all FAIL (uninit bytes: 3) test_stackinit: big_hole_runtime_all FAIL (uninit bytes: 61) test_stackinit: trailing_hole_runtime_all FAIL (uninit bytes: 7) test_stackinit: u8_none FAIL (uninit bytes: 1) test_stackinit: u16_none FAIL (uninit bytes: 2) test_stackinit: u32_none FAIL (uninit bytes: 4) test_stackinit: u64_none FAIL (uninit bytes: 8) test_stackinit: char_array_none FAIL (uninit bytes: 16) test_stackinit: switch_1_none FAIL (uninit bytes: 8) test_stackinit: switch_2_none FAIL (uninit bytes: 8) test_stackinit: small_hole_none FAIL (uninit bytes: 24) test_stackinit: big_hole_none FAIL (uninit bytes: 128) test_stackinit: trailing_hole_none FAIL (uninit bytes: 32) test_stackinit: packed_none FAIL (uninit bytes: 32) test_stackinit: user FAIL (uninit bytes: 32) test_stackinit: failures: 25 CONFIG_GCC_PLUGIN_STRUCTLEAK_USER=y This only tries to initialize structs with __user markings, so only the difference from above is now the "user" test passes: test_stackinit: small_hole_static_all FAIL (uninit bytes: 3) test_stackinit: big_hole_static_all FAIL (uninit bytes: 61) test_stackinit: trailing_hole_static_all FAIL (uninit bytes: 7) test_stackinit: small_hole_dynamic_all FAIL (uninit bytes: 3) test_stackinit: big_hole_dynamic_all FAIL (uninit bytes: 61) test_stackinit: trailing_hole_dynamic_all FAIL (uninit bytes: 7) test_stackinit: small_hole_runtime_partial FAIL (uninit bytes: 23) test_stackinit: big_hole_runtime_partial FAIL (uninit bytes: 127) test_stackinit: trailing_hole_runtime_partial FAIL (uninit bytes: 24) test_stackinit: packed_runtime_partial FAIL (uninit bytes: 24) test_stackinit: small_hole_runtime_all FAIL (uninit bytes: 3) test_stackinit: big_hole_runtime_all FAIL (uninit bytes: 61) test_stackinit: trailing_hole_runtime_all FAIL (uninit bytes: 7) test_stackinit: u8_none FAIL (uninit bytes: 1) test_stackinit: u16_none FAIL (uninit bytes: 2) test_stackinit: u32_none FAIL (uninit bytes: 4) test_stackinit: u64_none FAIL (uninit bytes: 8) test_stackinit: char_array_none FAIL (uninit bytes: 16) test_stackinit: switch_1_none FAIL (uninit bytes: 8) test_stackinit: switch_2_none FAIL (uninit bytes: 8) test_stackinit: small_hole_none FAIL (uninit bytes: 24) test_stackinit: big_hole_none FAIL (uninit bytes: 128) test_stackinit: trailing_hole_none FAIL (uninit bytes: 32) test_stackinit: packed_none FAIL (uninit bytes: 32) test_stackinit: user ok test_stackinit: failures: 24 CONFIG_GCC_PLUGIN_STRUCTLEAK_BYREF=y This initializes all structures passed by reference (scalars and strings remain uninitialized): test_stackinit: small_hole_static_all ok test_stackinit: big_hole_static_all ok test_stackinit: trailing_hole_static_all ok test_stackinit: small_hole_dynamic_all ok test_stackinit: big_hole_dynamic_all ok test_stackinit: trailing_hole_dynamic_all ok test_stackinit: small_hole_runtime_partial ok test_stackinit: big_hole_runtime_partial ok test_stackinit: trailing_hole_runtime_partial ok test_stackinit: packed_runtime_partial ok test_stackinit: small_hole_runtime_all ok test_stackinit: big_hole_runtime_all ok test_stackinit: trailing_hole_runtime_all ok test_stackinit: u8_none FAIL (uninit bytes: 1) test_stackinit: u16_none FAIL (uninit bytes: 2) test_stackinit: u32_none FAIL (uninit bytes: 4) test_stackinit: u64_none FAIL (uninit bytes: 8) test_stackinit: char_array_none FAIL (uninit bytes: 16) test_stackinit: switch_1_none FAIL (uninit bytes: 8) test_stackinit: switch_2_none FAIL (uninit bytes: 8) test_stackinit: small_hole_none ok test_stackinit: big_hole_none ok test_stackinit: trailing_hole_none ok test_stackinit: packed_none ok test_stackinit: user ok test_stackinit: failures: 7 CONFIG_GCC_PLUGIN_STRUCTLEAK_BYREF_ALL=y This initializes all variables, so it matches above with the scalars and arrays included: test_stackinit: small_hole_static_all ok test_stackinit: big_hole_static_all ok test_stackinit: trailing_hole_static_all ok test_stackinit: small_hole_dynamic_all ok test_stackinit: big_hole_dynamic_all ok test_stackinit: trailing_hole_dynamic_all ok test_stackinit: small_hole_runtime_partial ok test_stackinit: big_hole_runtime_partial ok test_stackinit: trailing_hole_runtime_partial ok test_stackinit: packed_runtime_partial ok test_stackinit: small_hole_runtime_all ok test_stackinit: big_hole_runtime_all ok test_stackinit: trailing_hole_runtime_all ok test_stackinit: u8_none ok test_stackinit: u16_none ok test_stackinit: u32_none ok test_stackinit: u64_none ok test_stackinit: char_array_none ok test_stackinit: switch_1_none ok test_stackinit: switch_2_none ok test_stackinit: small_hole_none ok test_stackinit: big_hole_none ok test_stackinit: trailing_hole_none ok test_stackinit: packed_none ok test_stackinit: user ok test_stackinit: all tests passed! Signed-off-by: Kees Cook <keescook@chromium.org> Reviewed-by: Ard Biesheuvel <ard.biesheuvel@linaro.org>
Diffstat (limited to 'lib')
-rw-r--r--lib/Kconfig.debug10
-rw-r--r--lib/Makefile1
-rw-r--r--lib/test_stackinit.c378
3 files changed, 389 insertions, 0 deletions
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index d4df5b24d75e..6f543a4bc704 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -2001,6 +2001,16 @@ config TEST_OBJAGG
2001 2001
2002 If unsure, say N. 2002 If unsure, say N.
2003 2003
2004config TEST_STACKINIT
2005 tristate "Test level of stack variable initialization"
2006 help
2007 Test if the kernel is zero-initializing stack variables and
2008 padding. Coverage is controlled by compiler flags,
2009 CONFIG_GCC_PLUGIN_STRUCTLEAK, CONFIG_GCC_PLUGIN_STRUCTLEAK_BYREF,
2010 or CONFIG_GCC_PLUGIN_STRUCTLEAK_BYREF_ALL.
2011
2012 If unsure, say N.
2013
2004endif # RUNTIME_TESTING_MENU 2014endif # RUNTIME_TESTING_MENU
2005 2015
2006config MEMTEST 2016config MEMTEST
diff --git a/lib/Makefile b/lib/Makefile
index e1b59da71418..c81a66d4d00d 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -76,6 +76,7 @@ obj-$(CONFIG_TEST_KMOD) += test_kmod.o
76obj-$(CONFIG_TEST_DEBUG_VIRTUAL) += test_debug_virtual.o 76obj-$(CONFIG_TEST_DEBUG_VIRTUAL) += test_debug_virtual.o
77obj-$(CONFIG_TEST_MEMCAT_P) += test_memcat_p.o 77obj-$(CONFIG_TEST_MEMCAT_P) += test_memcat_p.o
78obj-$(CONFIG_TEST_OBJAGG) += test_objagg.o 78obj-$(CONFIG_TEST_OBJAGG) += test_objagg.o
79obj-$(CONFIG_TEST_STACKINIT) += test_stackinit.o
79 80
80ifeq ($(CONFIG_DEBUG_KOBJECT),y) 81ifeq ($(CONFIG_DEBUG_KOBJECT),y)
81CFLAGS_kobject.o += -DDEBUG 82CFLAGS_kobject.o += -DDEBUG
diff --git a/lib/test_stackinit.c b/lib/test_stackinit.c
new file mode 100644
index 000000000000..13115b6f2b88
--- /dev/null
+++ b/lib/test_stackinit.c
@@ -0,0 +1,378 @@
1// SPDX-Licenses: GPLv2
2/*
3 * Test cases for compiler-based stack variable zeroing via future
4 * compiler flags or CONFIG_GCC_PLUGIN_STRUCTLEAK*.
5 */
6#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
7
8#include <linux/init.h>
9#include <linux/kernel.h>
10#include <linux/module.h>
11#include <linux/string.h>
12
13/* Exfiltration buffer. */
14#define MAX_VAR_SIZE 128
15static char check_buf[MAX_VAR_SIZE];
16
17/* Character array to trigger stack protector in all functions. */
18#define VAR_BUFFER 32
19
20/* Volatile mask to convince compiler to copy memory with 0xff. */
21static volatile u8 forced_mask = 0xff;
22
23/* Location and size tracking to validate fill and test are colocated. */
24static void *fill_start, *target_start;
25static size_t fill_size, target_size;
26
27static bool range_contains(char *haystack_start, size_t haystack_size,
28 char *needle_start, size_t needle_size)
29{
30 if (needle_start >= haystack_start &&
31 needle_start + needle_size <= haystack_start + haystack_size)
32 return true;
33 return false;
34}
35
36#define DO_NOTHING_TYPE_SCALAR(var_type) var_type
37#define DO_NOTHING_TYPE_STRING(var_type) void
38#define DO_NOTHING_TYPE_STRUCT(var_type) void
39
40#define DO_NOTHING_RETURN_SCALAR(ptr) *(ptr)
41#define DO_NOTHING_RETURN_STRING(ptr) /**/
42#define DO_NOTHING_RETURN_STRUCT(ptr) /**/
43
44#define DO_NOTHING_CALL_SCALAR(var, name) \
45 (var) = do_nothing_ ## name(&(var))
46#define DO_NOTHING_CALL_STRING(var, name) \
47 do_nothing_ ## name(var)
48#define DO_NOTHING_CALL_STRUCT(var, name) \
49 do_nothing_ ## name(&(var))
50
51#define FETCH_ARG_SCALAR(var) &var
52#define FETCH_ARG_STRING(var) var
53#define FETCH_ARG_STRUCT(var) &var
54
55#define FILL_SIZE_STRING 16
56
57#define INIT_CLONE_SCALAR /**/
58#define INIT_CLONE_STRING [FILL_SIZE_STRING]
59#define INIT_CLONE_STRUCT /**/
60
61#define INIT_SCALAR_none /**/
62#define INIT_SCALAR_zero = 0
63
64#define INIT_STRING_none [FILL_SIZE_STRING] /**/
65#define INIT_STRING_zero [FILL_SIZE_STRING] = { }
66
67#define INIT_STRUCT_none /**/
68#define INIT_STRUCT_zero = { }
69#define INIT_STRUCT_static_partial = { .two = 0, }
70#define INIT_STRUCT_static_all = { .one = arg->one, \
71 .two = arg->two, \
72 .three = arg->three, \
73 .four = arg->four, \
74 }
75#define INIT_STRUCT_dynamic_partial = { .two = arg->two, }
76#define INIT_STRUCT_dynamic_all = { .one = arg->one, \
77 .two = arg->two, \
78 .three = arg->three, \
79 .four = arg->four, \
80 }
81#define INIT_STRUCT_runtime_partial ; \
82 var.two = 0
83#define INIT_STRUCT_runtime_all ; \
84 var.one = 0; \
85 var.two = 0; \
86 var.three = 0; \
87 memset(&var.four, 0, \
88 sizeof(var.four))
89
90/*
91 * @name: unique string name for the test
92 * @var_type: type to be tested for zeroing initialization
93 * @which: is this a SCALAR, STRING, or STRUCT type?
94 * @init_level: what kind of initialization is performed
95 */
96#define DEFINE_TEST_DRIVER(name, var_type, which) \
97/* Returns 0 on success, 1 on failure. */ \
98static noinline __init int test_ ## name (void) \
99{ \
100 var_type zero INIT_CLONE_ ## which; \
101 int ignored; \
102 u8 sum = 0, i; \
103 \
104 /* Notice when a new test is larger than expected. */ \
105 BUILD_BUG_ON(sizeof(zero) > MAX_VAR_SIZE); \
106 \
107 /* Fill clone type with zero for per-field init. */ \
108 memset(&zero, 0x00, sizeof(zero)); \
109 /* Fill stack with 0xFF. */ \
110 ignored = leaf_ ##name((unsigned long)&ignored, 1, \
111 FETCH_ARG_ ## which(zero)); \
112 /* Clear entire check buffer for later bit tests. */ \
113 memset(check_buf, 0x00, sizeof(check_buf)); \
114 /* Extract stack-defined variable contents. */ \
115 ignored = leaf_ ##name((unsigned long)&ignored, 0, \
116 FETCH_ARG_ ## which(zero)); \
117 \
118 /* Validate that compiler lined up fill and target. */ \
119 if (!range_contains(fill_start, fill_size, \
120 target_start, target_size)) { \
121 pr_err(#name ": stack fill missed target!?\n"); \
122 pr_err(#name ": fill %zu wide\n", fill_size); \
123 pr_err(#name ": target offset by %d\n", \
124 (int)((ssize_t)(uintptr_t)fill_start - \
125 (ssize_t)(uintptr_t)target_start)); \
126 return 1; \
127 } \
128 \
129 /* Look for any set bits in the check region. */ \
130 for (i = 0; i < sizeof(check_buf); i++) \
131 sum += (check_buf[i] != 0); \
132 \
133 if (sum == 0) \
134 pr_info(#name " ok\n"); \
135 else \
136 pr_warn(#name " FAIL (uninit bytes: %d)\n", \
137 sum); \
138 \
139 return (sum != 0); \
140}
141#define DEFINE_TEST(name, var_type, which, init_level) \
142/* no-op to force compiler into ignoring "uninitialized" vars */\
143static noinline __init DO_NOTHING_TYPE_ ## which(var_type) \
144do_nothing_ ## name(var_type *ptr) \
145{ \
146 /* Will always be true, but compiler doesn't know. */ \
147 if ((unsigned long)ptr > 0x2) \
148 return DO_NOTHING_RETURN_ ## which(ptr); \
149 else \
150 return DO_NOTHING_RETURN_ ## which(ptr + 1); \
151} \
152static noinline __init int leaf_ ## name(unsigned long sp, \
153 bool fill, \
154 var_type *arg) \
155{ \
156 char buf[VAR_BUFFER]; \
157 var_type var INIT_ ## which ## _ ## init_level; \
158 \
159 target_start = &var; \
160 target_size = sizeof(var); \
161 /* \
162 * Keep this buffer around to make sure we've got a \
163 * stack frame of SOME kind... \
164 */ \
165 memset(buf, (char)(sp && 0xff), sizeof(buf)); \
166 /* Fill variable with 0xFF. */ \
167 if (fill) { \
168 fill_start = &var; \
169 fill_size = sizeof(var); \
170 memset(fill_start, \
171 (char)((sp && 0xff) | forced_mask), \
172 fill_size); \
173 } \
174 \
175 /* Silence "never initialized" warnings. */ \
176 DO_NOTHING_CALL_ ## which(var, name); \
177 \
178 /* Exfiltrate "var". */ \
179 memcpy(check_buf, target_start, target_size); \
180 \
181 return (int)buf[0] | (int)buf[sizeof(buf) - 1]; \
182} \
183DEFINE_TEST_DRIVER(name, var_type, which)
184
185/* Structure with no padding. */
186struct test_packed {
187 unsigned long one;
188 unsigned long two;
189 unsigned long three;
190 unsigned long four;
191};
192
193/* Simple structure with padding likely to be covered by compiler. */
194struct test_small_hole {
195 size_t one;
196 char two;
197 /* 3 byte padding hole here. */
198 int three;
199 unsigned long four;
200};
201
202/* Try to trigger unhandled padding in a structure. */
203struct test_aligned {
204 u32 internal1;
205 u64 internal2;
206} __aligned(64);
207
208struct test_big_hole {
209 u8 one;
210 u8 two;
211 u8 three;
212 /* 61 byte padding hole here. */
213 struct test_aligned four;
214} __aligned(64);
215
216struct test_trailing_hole {
217 char *one;
218 char *two;
219 char *three;
220 char four;
221 /* "sizeof(unsigned long) - 1" byte padding hole here. */
222};
223
224/* Test if STRUCTLEAK is clearing structs with __user fields. */
225struct test_user {
226 u8 one;
227 unsigned long two;
228 char __user *three;
229 unsigned long four;
230};
231
232#define DEFINE_SCALAR_TEST(name, init) \
233 DEFINE_TEST(name ## _ ## init, name, SCALAR, init)
234
235#define DEFINE_SCALAR_TESTS(init) \
236 DEFINE_SCALAR_TEST(u8, init); \
237 DEFINE_SCALAR_TEST(u16, init); \
238 DEFINE_SCALAR_TEST(u32, init); \
239 DEFINE_SCALAR_TEST(u64, init); \
240 DEFINE_TEST(char_array_ ## init, unsigned char, STRING, init)
241
242#define DEFINE_STRUCT_TEST(name, init) \
243 DEFINE_TEST(name ## _ ## init, \
244 struct test_ ## name, STRUCT, init)
245
246#define DEFINE_STRUCT_TESTS(init) \
247 DEFINE_STRUCT_TEST(small_hole, init); \
248 DEFINE_STRUCT_TEST(big_hole, init); \
249 DEFINE_STRUCT_TEST(trailing_hole, init); \
250 DEFINE_STRUCT_TEST(packed, init)
251
252/* These should be fully initialized all the time! */
253DEFINE_SCALAR_TESTS(zero);
254DEFINE_STRUCT_TESTS(zero);
255/* Static initialization: padding may be left uninitialized. */
256DEFINE_STRUCT_TESTS(static_partial);
257DEFINE_STRUCT_TESTS(static_all);
258/* Dynamic initialization: padding may be left uninitialized. */
259DEFINE_STRUCT_TESTS(dynamic_partial);
260DEFINE_STRUCT_TESTS(dynamic_all);
261/* Runtime initialization: padding may be left uninitialized. */
262DEFINE_STRUCT_TESTS(runtime_partial);
263DEFINE_STRUCT_TESTS(runtime_all);
264/* No initialization without compiler instrumentation. */
265DEFINE_SCALAR_TESTS(none);
266DEFINE_STRUCT_TESTS(none);
267DEFINE_TEST(user, struct test_user, STRUCT, none);
268
269/*
270 * Check two uses through a variable declaration outside either path,
271 * which was noticed as a special case in porting earlier stack init
272 * compiler logic.
273 */
274static int noinline __leaf_switch_none(int path, bool fill)
275{
276 switch (path) {
277 uint64_t var;
278
279 case 1:
280 target_start = &var;
281 target_size = sizeof(var);
282 if (fill) {
283 fill_start = &var;
284 fill_size = sizeof(var);
285
286 memset(fill_start, forced_mask | 0x55, fill_size);
287 }
288 memcpy(check_buf, target_start, target_size);
289 break;
290 case 2:
291 target_start = &var;
292 target_size = sizeof(var);
293 if (fill) {
294 fill_start = &var;
295 fill_size = sizeof(var);
296
297 memset(fill_start, forced_mask | 0xaa, fill_size);
298 }
299 memcpy(check_buf, target_start, target_size);
300 break;
301 default:
302 var = 5;
303 return var & forced_mask;
304 }
305 return 0;
306}
307
308static noinline __init int leaf_switch_1_none(unsigned long sp, bool fill,
309 uint64_t *arg)
310{
311 return __leaf_switch_none(1, fill);
312}
313
314static noinline __init int leaf_switch_2_none(unsigned long sp, bool fill,
315 uint64_t *arg)
316{
317 return __leaf_switch_none(2, fill);
318}
319
320DEFINE_TEST_DRIVER(switch_1_none, uint64_t, SCALAR);
321DEFINE_TEST_DRIVER(switch_2_none, uint64_t, SCALAR);
322
323static int __init test_stackinit_init(void)
324{
325 unsigned int failures = 0;
326
327#define test_scalars(init) do { \
328 failures += test_u8_ ## init (); \
329 failures += test_u16_ ## init (); \
330 failures += test_u32_ ## init (); \
331 failures += test_u64_ ## init (); \
332 failures += test_char_array_ ## init (); \
333 } while (0)
334
335#define test_structs(init) do { \
336 failures += test_small_hole_ ## init (); \
337 failures += test_big_hole_ ## init (); \
338 failures += test_trailing_hole_ ## init (); \
339 failures += test_packed_ ## init (); \
340 } while (0)
341
342 /* These are explicitly initialized and should always pass. */
343 test_scalars(zero);
344 test_structs(zero);
345 /* Padding here appears to be accidentally always initialized? */
346 test_structs(dynamic_partial);
347 /* Padding initialization depends on compiler behaviors. */
348 test_structs(static_partial);
349 test_structs(static_all);
350 test_structs(dynamic_all);
351 test_structs(runtime_partial);
352 test_structs(runtime_all);
353
354 /* STRUCTLEAK_BYREF_ALL should cover everything from here down. */
355 test_scalars(none);
356 failures += test_switch_1_none();
357 failures += test_switch_2_none();
358
359 /* STRUCTLEAK_BYREF should cover from here down. */
360 test_structs(none);
361
362 /* STRUCTLEAK will only cover this. */
363 failures += test_user();
364
365 if (failures == 0)
366 pr_info("all tests passed!\n");
367 else
368 pr_err("failures: %u\n", failures);
369
370 return failures ? -EINVAL : 0;
371}
372module_init(test_stackinit_init);
373
374static void __exit test_stackinit_exit(void)
375{ }
376module_exit(test_stackinit_exit);
377
378MODULE_LICENSE("GPL");