blob: 989f75a491b757b9da55fc4a22c28bcc4475eb03 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2023 ARM Limited.
*
* Tests for GCS mode locking. These tests rely on both having GCS
* unconfigured on entry and on the kselftest harness running each
* test in a fork()ed process which will have it's own mode.
*/
#include <limits.h>
#include <sys/auxv.h>
#include <sys/prctl.h>
#include <asm/hwcap.h>
#include "kselftest_harness.h"
#include "gcs-util.h"
#define my_syscall2(num, arg1, arg2) \
({ \
register long _num __asm__ ("x8") = (num); \
register long _arg1 __asm__ ("x0") = (long)(arg1); \
register long _arg2 __asm__ ("x1") = (long)(arg2); \
register long _arg3 __asm__ ("x2") = 0; \
register long _arg4 __asm__ ("x3") = 0; \
register long _arg5 __asm__ ("x4") = 0; \
\
__asm__ volatile ( \
"svc #0\n" \
: "=r"(_arg1) \
: "r"(_arg1), "r"(_arg2), \
"r"(_arg3), "r"(_arg4), \
"r"(_arg5), "r"(_num) \
: "memory", "cc" \
); \
_arg1; \
})
/* No mode bits are rejected for locking */
TEST(lock_all_modes)
{
int ret;
ret = prctl(PR_LOCK_SHADOW_STACK_STATUS, ULONG_MAX, 0, 0, 0);
ASSERT_EQ(ret, 0);
}
FIXTURE(valid_modes)
{
};
FIXTURE_VARIANT(valid_modes)
{
unsigned long mode;
};
FIXTURE_VARIANT_ADD(valid_modes, enable)
{
.mode = PR_SHADOW_STACK_ENABLE,
};
FIXTURE_VARIANT_ADD(valid_modes, enable_write)
{
.mode = PR_SHADOW_STACK_ENABLE | PR_SHADOW_STACK_WRITE,
};
FIXTURE_VARIANT_ADD(valid_modes, enable_push)
{
.mode = PR_SHADOW_STACK_ENABLE | PR_SHADOW_STACK_PUSH,
};
FIXTURE_VARIANT_ADD(valid_modes, enable_write_push)
{
.mode = PR_SHADOW_STACK_ENABLE | PR_SHADOW_STACK_WRITE |
PR_SHADOW_STACK_PUSH,
};
FIXTURE_SETUP(valid_modes)
{
}
FIXTURE_TEARDOWN(valid_modes)
{
}
/* We can set the mode at all */
TEST_F(valid_modes, set)
{
int ret;
ret = my_syscall2(__NR_prctl, PR_SET_SHADOW_STACK_STATUS,
variant->mode);
ASSERT_EQ(ret, 0);
_exit(0);
}
/* Enabling, locking then disabling is rejected */
TEST_F(valid_modes, enable_lock_disable)
{
unsigned long mode;
int ret;
ret = my_syscall2(__NR_prctl, PR_SET_SHADOW_STACK_STATUS,
variant->mode);
ASSERT_EQ(ret, 0);
ret = prctl(PR_GET_SHADOW_STACK_STATUS, &mode, 0, 0, 0);
ASSERT_EQ(ret, 0);
ASSERT_EQ(mode, variant->mode);
ret = prctl(PR_LOCK_SHADOW_STACK_STATUS, variant->mode, 0, 0, 0);
ASSERT_EQ(ret, 0);
ret = my_syscall2(__NR_prctl, PR_SET_SHADOW_STACK_STATUS, 0);
ASSERT_EQ(ret, -EBUSY);
_exit(0);
}
/* Locking then enabling is rejected */
TEST_F(valid_modes, lock_enable)
{
unsigned long mode;
int ret;
ret = prctl(PR_LOCK_SHADOW_STACK_STATUS, variant->mode, 0, 0, 0);
ASSERT_EQ(ret, 0);
ret = my_syscall2(__NR_prctl, PR_SET_SHADOW_STACK_STATUS,
variant->mode);
ASSERT_EQ(ret, -EBUSY);
ret = prctl(PR_GET_SHADOW_STACK_STATUS, &mode, 0, 0, 0);
ASSERT_EQ(ret, 0);
ASSERT_EQ(mode, 0);
_exit(0);
}
/* Locking then changing other modes is fine */
TEST_F(valid_modes, lock_enable_disable_others)
{
unsigned long mode;
int ret;
ret = my_syscall2(__NR_prctl, PR_SET_SHADOW_STACK_STATUS,
variant->mode);
ASSERT_EQ(ret, 0);
ret = prctl(PR_GET_SHADOW_STACK_STATUS, &mode, 0, 0, 0);
ASSERT_EQ(ret, 0);
ASSERT_EQ(mode, variant->mode);
ret = prctl(PR_LOCK_SHADOW_STACK_STATUS, variant->mode, 0, 0, 0);
ASSERT_EQ(ret, 0);
ret = my_syscall2(__NR_prctl, PR_SET_SHADOW_STACK_STATUS,
PR_SHADOW_STACK_ALL_MODES);
ASSERT_EQ(ret, 0);
ret = prctl(PR_GET_SHADOW_STACK_STATUS, &mode, 0, 0, 0);
ASSERT_EQ(ret, 0);
ASSERT_EQ(mode, PR_SHADOW_STACK_ALL_MODES);
ret = my_syscall2(__NR_prctl, PR_SET_SHADOW_STACK_STATUS,
variant->mode);
ASSERT_EQ(ret, 0);
ret = prctl(PR_GET_SHADOW_STACK_STATUS, &mode, 0, 0, 0);
ASSERT_EQ(ret, 0);
ASSERT_EQ(mode, variant->mode);
_exit(0);
}
int main(int argc, char **argv)
{
unsigned long mode;
int ret;
if (!(getauxval(AT_HWCAP) & HWCAP_GCS))
ksft_exit_skip("SKIP GCS not supported\n");
ret = prctl(PR_GET_SHADOW_STACK_STATUS, &mode, 0, 0, 0);
if (ret) {
ksft_print_msg("Failed to read GCS state: %d\n", ret);
return EXIT_FAILURE;
}
if (mode & PR_SHADOW_STACK_ENABLE) {
ksft_print_msg("GCS was enabled, test unsupported\n");
return KSFT_SKIP;
}
return test_harness_run(argc, argv);
}