Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

exec: Add KUnit test for bprm_stack_limits()

Since bprm_stack_limits() operates with very limited side-effects, add
it as the first exec.c KUnit test. Add to Kconfig and adjust MAINTAINERS
file to include it.

Tested on 64-bit UML:
$ tools/testing/kunit/kunit.py run exec

Link: https://lore.kernel.org/lkml/20240520021615.741800-1-keescook@chromium.org/
Signed-off-by: Kees Cook <kees@kernel.org>

+136
+2
MAINTAINERS
··· 8218 8218 T: git git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux.git for-next/execve 8219 8219 F: Documentation/userspace-api/ELF.rst 8220 8220 F: fs/*binfmt_*.c 8221 + F: fs/Kconfig.binfmt 8221 8222 F: fs/exec.c 8223 + F: fs/exec_test.c 8222 8224 F: include/linux/binfmts.h 8223 8225 F: include/linux/elf.h 8224 8226 F: include/uapi/linux/binfmts.h
+8
fs/Kconfig.binfmt
··· 176 176 certainly want to say Y here. Not necessary on systems that never 177 177 need debugging or only ever run flawless code. 178 178 179 + config EXEC_KUNIT_TEST 180 + bool "Build execve tests" if !KUNIT_ALL_TESTS 181 + depends on KUNIT=y 182 + default KUNIT_ALL_TESTS 183 + help 184 + This builds the exec KUnit tests, which tests boundary conditions 185 + of various aspects of the exec internals. 186 + 179 187 endmenu
+13
fs/exec.c
··· 486 486 return i; 487 487 } 488 488 489 + /* 490 + * Calculate bprm->argmin from: 491 + * - _STK_LIM 492 + * - ARG_MAX 493 + * - bprm->rlim_stack.rlim_cur 494 + * - bprm->argc 495 + * - bprm->envc 496 + * - bprm->p 497 + */ 489 498 static int bprm_stack_limits(struct linux_binprm *bprm) 490 499 { 491 500 unsigned long limit, ptr_size; ··· 2220 2211 2221 2212 fs_initcall(init_fs_exec_sysctls); 2222 2213 #endif /* CONFIG_SYSCTL */ 2214 + 2215 + #ifdef CONFIG_EXEC_KUNIT_TEST 2216 + #include "exec_test.c" 2217 + #endif
+113
fs/exec_test.c
··· 1 + // SPDX-License-Identifier: GPL-2.0-only 2 + #include <kunit/test.h> 3 + 4 + struct bprm_stack_limits_result { 5 + struct linux_binprm bprm; 6 + int expected_rc; 7 + unsigned long expected_argmin; 8 + }; 9 + 10 + static const struct bprm_stack_limits_result bprm_stack_limits_results[] = { 11 + /* Giant values produce -E2BIG */ 12 + { { .p = ULONG_MAX, .rlim_stack.rlim_cur = ULONG_MAX, 13 + .argc = INT_MAX, .envc = INT_MAX }, .expected_rc = -E2BIG }, 14 + /* 15 + * 0 rlim_stack will get raised to ARG_MAX. With 1 string pointer, 16 + * we should see p - ARG_MAX + sizeof(void *). 17 + */ 18 + { { .p = ULONG_MAX, .rlim_stack.rlim_cur = 0, 19 + .argc = 1, .envc = 0 }, .expected_argmin = ULONG_MAX - ARG_MAX + sizeof(void *)}, 20 + /* Validate that argc is always raised to a minimum of 1. */ 21 + { { .p = ULONG_MAX, .rlim_stack.rlim_cur = 0, 22 + .argc = 0, .envc = 0 }, .expected_argmin = ULONG_MAX - ARG_MAX + sizeof(void *)}, 23 + /* 24 + * 0 rlim_stack will get raised to ARG_MAX. With pointers filling ARG_MAX, 25 + * we should see -E2BIG. (Note argc is always raised to at least 1.) 26 + */ 27 + { { .p = ULONG_MAX, .rlim_stack.rlim_cur = 0, 28 + .argc = ARG_MAX / sizeof(void *), .envc = 0 }, .expected_rc = -E2BIG }, 29 + { { .p = ULONG_MAX, .rlim_stack.rlim_cur = 0, 30 + .argc = 0, .envc = ARG_MAX / sizeof(void *) - 1 }, .expected_rc = -E2BIG }, 31 + { { .p = ULONG_MAX, .rlim_stack.rlim_cur = 0, 32 + .argc = ARG_MAX / sizeof(void *) + 1, .envc = 0 }, .expected_rc = -E2BIG }, 33 + { { .p = ULONG_MAX, .rlim_stack.rlim_cur = 0, 34 + .argc = 0, .envc = ARG_MAX / sizeof(void *) }, .expected_rc = -E2BIG }, 35 + /* And with one less, we see space for exactly 1 pointer. */ 36 + { { .p = ULONG_MAX, .rlim_stack.rlim_cur = 0, 37 + .argc = (ARG_MAX / sizeof(void *)) - 1, .envc = 0 }, 38 + .expected_argmin = ULONG_MAX - sizeof(void *) }, 39 + { { .p = ULONG_MAX, .rlim_stack.rlim_cur = 0, 40 + .argc = 0, .envc = (ARG_MAX / sizeof(void *)) - 2, }, 41 + .expected_argmin = ULONG_MAX - sizeof(void *) }, 42 + /* If we raise rlim_stack / 4 to exactly ARG_MAX, nothing changes. */ 43 + { { .p = ULONG_MAX, .rlim_stack.rlim_cur = ARG_MAX * 4, 44 + .argc = ARG_MAX / sizeof(void *), .envc = 0 }, .expected_rc = -E2BIG }, 45 + { { .p = ULONG_MAX, .rlim_stack.rlim_cur = ARG_MAX * 4, 46 + .argc = 0, .envc = ARG_MAX / sizeof(void *) - 1 }, .expected_rc = -E2BIG }, 47 + { { .p = ULONG_MAX, .rlim_stack.rlim_cur = ARG_MAX * 4, 48 + .argc = ARG_MAX / sizeof(void *) + 1, .envc = 0 }, .expected_rc = -E2BIG }, 49 + { { .p = ULONG_MAX, .rlim_stack.rlim_cur = ARG_MAX * 4, 50 + .argc = 0, .envc = ARG_MAX / sizeof(void *) }, .expected_rc = -E2BIG }, 51 + { { .p = ULONG_MAX, .rlim_stack.rlim_cur = ARG_MAX * 4, 52 + .argc = (ARG_MAX / sizeof(void *)) - 1, .envc = 0 }, 53 + .expected_argmin = ULONG_MAX - sizeof(void *) }, 54 + { { .p = ULONG_MAX, .rlim_stack.rlim_cur = ARG_MAX * 4, 55 + .argc = 0, .envc = (ARG_MAX / sizeof(void *)) - 2, }, 56 + .expected_argmin = ULONG_MAX - sizeof(void *) }, 57 + /* But raising it another pointer * 4 will provide space for 1 more pointer. */ 58 + { { .p = ULONG_MAX, .rlim_stack.rlim_cur = (ARG_MAX + sizeof(void *)) * 4, 59 + .argc = ARG_MAX / sizeof(void *), .envc = 0 }, 60 + .expected_argmin = ULONG_MAX - sizeof(void *) }, 61 + { { .p = ULONG_MAX, .rlim_stack.rlim_cur = (ARG_MAX + sizeof(void *)) * 4, 62 + .argc = 0, .envc = ARG_MAX / sizeof(void *) - 1 }, 63 + .expected_argmin = ULONG_MAX - sizeof(void *) }, 64 + /* Raising rlim_stack / 4 to _STK_LIM / 4 * 3 will see more space. */ 65 + { { .p = ULONG_MAX, .rlim_stack.rlim_cur = 4 * (_STK_LIM / 4 * 3), 66 + .argc = 0, .envc = 0 }, 67 + .expected_argmin = ULONG_MAX - (_STK_LIM / 4 * 3) + sizeof(void *) }, 68 + { { .p = ULONG_MAX, .rlim_stack.rlim_cur = 4 * (_STK_LIM / 4 * 3), 69 + .argc = 0, .envc = 0 }, 70 + .expected_argmin = ULONG_MAX - (_STK_LIM / 4 * 3) + sizeof(void *) }, 71 + /* But raising it any further will see no increase. */ 72 + { { .p = ULONG_MAX, .rlim_stack.rlim_cur = 4 * (_STK_LIM / 4 * 3 + sizeof(void *)), 73 + .argc = 0, .envc = 0 }, 74 + .expected_argmin = ULONG_MAX - (_STK_LIM / 4 * 3) + sizeof(void *) }, 75 + { { .p = ULONG_MAX, .rlim_stack.rlim_cur = 4 * (_STK_LIM / 4 * + sizeof(void *)), 76 + .argc = 0, .envc = 0 }, 77 + .expected_argmin = ULONG_MAX - (_STK_LIM / 4 * 3) + sizeof(void *) }, 78 + { { .p = ULONG_MAX, .rlim_stack.rlim_cur = 4 * _STK_LIM, 79 + .argc = 0, .envc = 0 }, 80 + .expected_argmin = ULONG_MAX - (_STK_LIM / 4 * 3) + sizeof(void *) }, 81 + { { .p = ULONG_MAX, .rlim_stack.rlim_cur = 4 * _STK_LIM, 82 + .argc = 0, .envc = 0 }, 83 + .expected_argmin = ULONG_MAX - (_STK_LIM / 4 * 3) + sizeof(void *) }, 84 + }; 85 + 86 + static void exec_test_bprm_stack_limits(struct kunit *test) 87 + { 88 + /* Double-check the constants. */ 89 + KUNIT_EXPECT_EQ(test, _STK_LIM, SZ_8M); 90 + KUNIT_EXPECT_EQ(test, ARG_MAX, 32 * SZ_4K); 91 + 92 + for (int i = 0; i < ARRAY_SIZE(bprm_stack_limits_results); i++) { 93 + const struct bprm_stack_limits_result *result = &bprm_stack_limits_results[i]; 94 + struct linux_binprm bprm = result->bprm; 95 + int rc; 96 + 97 + rc = bprm_stack_limits(&bprm); 98 + KUNIT_EXPECT_EQ_MSG(test, rc, result->expected_rc, "on loop %d", i); 99 + KUNIT_EXPECT_EQ_MSG(test, bprm.argmin, result->expected_argmin, "on loop %d", i); 100 + } 101 + } 102 + 103 + static struct kunit_case exec_test_cases[] = { 104 + KUNIT_CASE(exec_test_bprm_stack_limits), 105 + {}, 106 + }; 107 + 108 + static struct kunit_suite exec_test_suite = { 109 + .name = "exec", 110 + .test_cases = exec_test_cases, 111 + }; 112 + 113 + kunit_test_suite(exec_test_suite);