On Wed, Jul 31, 2024 at 01:14:15PM +0100, Mark Brown wrote:
Add basic test coverage for specifying the shadow stack for a newly created thread via clone3(), including coverage of the newly extended argument structure. We check that a user specified shadow stack can be provided, and that invalid combinations of parameters are rejected.
In order to facilitate testing on systems without userspace shadow stack support we manually enable shadow stacks on startup, this is architecture specific due to the use of an arch_prctl() on x86. Due to interactions with potential userspace locking of features we actually detect support for shadow stacks on the running system by attempting to allocate a shadow stack page during initialisation using map_shadow_stack(), warning if this succeeds when the enable failed.
In order to allow testing of user configured shadow stacks on architectures with that feature we need to ensure that we do not return from the function where the clone3() syscall is called in the child process, doing so would trigger a shadow stack underflow. To do this we use inline assembly rather than the standard syscall wrapper to call clone3(). In order to avoid surprises we also use a syscall rather than the libc exit() function., this should be overly cautious.
Signed-off-by: Mark Brown broonie@kernel.org
tools/testing/selftests/clone3/clone3.c | 134 +++++++++++++++++++++- tools/testing/selftests/clone3/clone3_selftests.h | 38 ++++++ 2 files changed, 171 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/clone3/clone3.c b/tools/testing/selftests/clone3/clone3.c index 26221661e9ae..81c2e8648e8b 100644 --- a/tools/testing/selftests/clone3/clone3.c +++ b/tools/testing/selftests/clone3/clone3.c @@ -3,6 +3,7 @@ /* Based on Christian Brauner's clone3() example */ #define _GNU_SOURCE +#include <asm/mman.h> #include <errno.h> #include <inttypes.h> #include <linux/types.h> @@ -11,6 +12,7 @@ #include <stdint.h> #include <stdio.h> #include <stdlib.h> +#include <sys/mman.h> #include <sys/syscall.h> #include <sys/types.h> #include <sys/un.h> @@ -19,8 +21,12 @@ #include <sched.h> #include "../kselftest.h" +#include "../ksft_shstk.h" #include "clone3_selftests.h" +static bool shadow_stack_supported; +static size_t max_supported_args_size;
enum test_mode { CLONE3_ARGS_NO_TEST, CLONE3_ARGS_ALL_0, @@ -28,6 +34,10 @@ enum test_mode { CLONE3_ARGS_INVAL_EXIT_SIGNAL_NEG, CLONE3_ARGS_INVAL_EXIT_SIGNAL_CSIG, CLONE3_ARGS_INVAL_EXIT_SIGNAL_NSIG,
- CLONE3_ARGS_SHADOW_STACK,
- CLONE3_ARGS_SHADOW_STACK_NO_SIZE,
- CLONE3_ARGS_SHADOW_STACK_NO_POINTER,
- CLONE3_ARGS_SHADOW_STACK_NO_TOKEN,
}; typedef bool (*filter_function)(void); @@ -44,6 +54,44 @@ struct test { filter_function filter; };
+/*
- We check for shadow stack support by attempting to use
- map_shadow_stack() since features may have been locked by the
- dynamic linker resulting in spurious errors when we attempt to
- enable on startup. We warn if the enable failed.
- */
+static void test_shadow_stack_supported(void) +{
- long ret;
- ret = syscall(__NR_map_shadow_stack, 0, getpagesize(), 0);
- if (ret == -1) {
ksft_print_msg("map_shadow_stack() not supported\n");
- } else if ((void *)ret == MAP_FAILED) {
ksft_print_msg("Failed to map shadow stack\n");
- } else {
ksft_print_msg("Shadow stack supportd\n");
typo: supportd -> supported
shadow_stack_supported = true;
if (!shadow_stack_enabled)
ksft_print_msg("Mapped but did not enable shadow stack\n");
- }
+}
On my CET system, this reports:
... # clone3() syscall supported # Shadow stack supportd # Running test 'simple clone3()' ...
(happily doesn't print "Mapped but did not enable ...").
+static unsigned long long get_shadow_stack_page(unsigned long flags) +{
- unsigned long long page;
- page = syscall(__NR_map_shadow_stack, 0, getpagesize(), flags);
- if ((void *)page == MAP_FAILED) {
ksft_print_msg("map_shadow_stack() failed: %d\n", errno);
return 0;
- }
- return page;
+}
static int call_clone3(uint64_t flags, size_t size, enum test_mode test_mode) { struct __clone_args args = { @@ -89,6 +137,21 @@ static int call_clone3(uint64_t flags, size_t size, enum test_mode test_mode) case CLONE3_ARGS_INVAL_EXIT_SIGNAL_NSIG: args.exit_signal = 0x00000000000000f0ULL; break;
- case CLONE3_ARGS_SHADOW_STACK:
/* We need to specify a normal stack too to avoid corruption */
args.shadow_stack = get_shadow_stack_page(SHADOW_STACK_SET_TOKEN);
args.shadow_stack_size = getpagesize();
break;
# Running test 'Shadow stack on system with shadow stack' # [5496] Trying clone3() with flags 0 (size 0) # I am the parent (5496). My child's pid is 5505 # Child exited with signal 11 # [5496] clone3() with flags says: 11 expected 0 # [5496] Result (11) is different than expected (0) not ok 20 Shadow stack on system with shadow stack
The child segfaults immediately, it seems?
- case CLONE3_ARGS_SHADOW_STACK_NO_POINTER:
args.shadow_stack_size = getpagesize();
break;
# Running test 'Shadow stack with no pointer' # [5496] Trying clone3() with flags 0 (size 0) # Invalid argument - Failed to create new process # [5496] clone3() with flags says: -22 expected -22 ok 21 Shadow stack with no pointer
This seems like it misses the failure and reports ok
- case CLONE3_ARGS_SHADOW_STACK_NO_SIZE:
args.shadow_stack = get_shadow_stack_page(SHADOW_STACK_SET_TOKEN);
break;
# Running test 'Shadow stack with no size' # [5496] Trying clone3() with flags 0 (size 0) # Invalid argument - Failed to create new process # [5496] clone3() with flags says: -22 expected -22 ok 22 Shadow stack with no size
Same?
- case CLONE3_ARGS_SHADOW_STACK_NO_TOKEN:
args.shadow_stack = get_shadow_stack_page(0);
args.shadow_stack_size = getpagesize();
break;
This actually segfaults the parent:
# Running test 'Shadow stack with no token' # [5496] Trying clone3() with flags 0x100 (size 0) # I am the parent (5496). My child's pid is 5507 Segmentation fault
Let me know what would be most helpful to dig into more...