Skip to content
This repository was archived by the owner on Nov 8, 2023. It is now read-only.

Commit 60371f4

Browse files
committed
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>
1 parent 3545def commit 60371f4

File tree

4 files changed

+136
-0
lines changed

4 files changed

+136
-0
lines changed

MAINTAINERS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8218,7 +8218,9 @@ S: Supported
82188218
T: git git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux.git for-next/execve
82198219
F: Documentation/userspace-api/ELF.rst
82208220
F: fs/*binfmt_*.c
8221+
F: fs/Kconfig.binfmt
82218222
F: fs/exec.c
8223+
F: fs/exec_test.c
82228224
F: include/linux/binfmts.h
82238225
F: include/linux/elf.h
82248226
F: include/uapi/linux/binfmts.h

fs/Kconfig.binfmt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,4 +176,12 @@ config COREDUMP
176176
certainly want to say Y here. Not necessary on systems that never
177177
need debugging or only ever run flawless code.
178178

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+
179187
endmenu

fs/exec.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,15 @@ static int count_strings_kernel(const char *const *argv)
486486
return i;
487487
}
488488

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+
*/
489498
static int bprm_stack_limits(struct linux_binprm *bprm)
490499
{
491500
unsigned long limit, ptr_size;
@@ -2211,3 +2220,7 @@ static int __init init_fs_exec_sysctls(void)
22112220

22122221
fs_initcall(init_fs_exec_sysctls);
22132222
#endif /* CONFIG_SYSCTL */
2223+
2224+
#ifdef CONFIG_EXEC_KUNIT_TEST
2225+
#include "exec_test.c"
2226+
#endif

fs/exec_test.c

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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);

0 commit comments

Comments
 (0)