root/tools/testing/selftests/arm64/gcs/gcs-locking.c
// 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);
}