Contributors: 1
Author Tokens Token Proportion Commits Commit Proportion
Mark Brown 2740 100.00% 2 100.00%
Total 2740 2

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (C) 2023 ARM Limited.
 */

#define _GNU_SOURCE

#include <pthread.h>
#include <stdbool.h>

#include <sys/auxv.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/ptrace.h>
#include <sys/uio.h>

#include <asm/hwcap.h>
#include <asm/mman.h>

#include <linux/compiler.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;                                                                \
})

static noinline void gcs_recurse(int depth)
{
	if (depth)
		gcs_recurse(depth - 1);

	/* Prevent tail call optimization so we actually recurse */
	asm volatile("dsb sy" : : : "memory");
}

/* Smoke test that a function call and return works*/
TEST(can_call_function)
{
	gcs_recurse(0);
}

static void *gcs_test_thread(void *arg)
{
	int ret;
	unsigned long mode;

	/*
	 * Some libcs don't seem to fill unused arguments with 0 but
	 * the kernel validates this so we supply all 5 arguments.
	 */
	ret = prctl(PR_GET_SHADOW_STACK_STATUS, &mode, 0, 0, 0);
	if (ret != 0) {
		ksft_print_msg("PR_GET_SHADOW_STACK_STATUS failed: %d\n", ret);
		return NULL;
	}

	if (!(mode & PR_SHADOW_STACK_ENABLE)) {
		ksft_print_msg("GCS not enabled in thread, mode is %lu\n",
			       mode);
		return NULL;
	}

	/* Just in case... */
	gcs_recurse(0);

	/* Use a non-NULL value to indicate a pass */
	return &gcs_test_thread;
}

/* Verify that if we start a new thread it has GCS enabled */
TEST(gcs_enabled_thread)
{
	pthread_t thread;
	void *thread_ret;
	int ret;

	ret = pthread_create(&thread, NULL, gcs_test_thread, NULL);
	ASSERT_TRUE(ret == 0);
	if (ret != 0)
		return;

	ret = pthread_join(thread, &thread_ret);
	ASSERT_TRUE(ret == 0);
	if (ret != 0)
		return;

	ASSERT_TRUE(thread_ret != NULL);
}

/* Read the GCS until we find the terminator */
TEST(gcs_find_terminator)
{
	unsigned long *gcs, *cur;

	gcs = get_gcspr();
	cur = gcs;
	while (*cur)
		cur++;

	ksft_print_msg("GCS in use from %p-%p\n", gcs, cur);

	/*
	 * We should have at least whatever called into this test so
	 * the two pointer should differ.
	 */
	ASSERT_TRUE(gcs != cur);
}

/*
 * We can access a GCS via ptrace
 *
 * This could usefully have a fixture but note that each test is
 * fork()ed into a new child whcih causes issues.  Might be better to
 * lift at least some of this out into a separate, non-harness, test
 * program.
 */
TEST(ptrace_read_write)
{
	pid_t child, pid;
	int ret, status;
	siginfo_t si;
	uint64_t val, rval, gcspr;
	struct user_gcs child_gcs;
	struct iovec iov, local_iov, remote_iov;

	child = fork();
	if (child == -1) {
		ksft_print_msg("fork() failed: %d (%s)\n",
			       errno, strerror(errno));
		ASSERT_NE(child, -1);
	}

	if (child == 0) {
		/*
		 * In child, make sure there's something on the stack and
		 * ask to be traced.
		 */
		gcs_recurse(0);
		if (ptrace(PTRACE_TRACEME, -1, NULL, NULL))
			ksft_exit_fail_msg("PTRACE_TRACEME %s",
					   strerror(errno));

		if (raise(SIGSTOP))
			ksft_exit_fail_msg("raise(SIGSTOP) %s",
					   strerror(errno));

		return;
	}

	ksft_print_msg("Child: %d\n", child);

	/* Attach to the child */
	while (1) {
		int sig;

		pid = wait(&status);
		if (pid == -1) {
			ksft_print_msg("wait() failed: %s",
				       strerror(errno));
			goto error;
		}

		/*
		 * This should never happen but it's hard to flag in
		 * the framework.
		 */
		if (pid != child)
			continue;

		if (WIFEXITED(status) || WIFSIGNALED(status))
			ksft_exit_fail_msg("Child died unexpectedly\n");

		if (!WIFSTOPPED(status))
			goto error;

		sig = WSTOPSIG(status);

		if (ptrace(PTRACE_GETSIGINFO, pid, NULL, &si)) {
			if (errno == ESRCH) {
				ASSERT_NE(errno, ESRCH);
				return;
			}

			if (errno == EINVAL) {
				sig = 0; /* bust group-stop */
				goto cont;
			}

			ksft_print_msg("PTRACE_GETSIGINFO: %s\n",
				       strerror(errno));
			goto error;
		}

		if (sig == SIGSTOP && si.si_code == SI_TKILL &&
		    si.si_pid == pid)
			break;

	cont:
		if (ptrace(PTRACE_CONT, pid, NULL, sig)) {
			if (errno == ESRCH) {
				ASSERT_NE(errno, ESRCH);
				return;
			}

			ksft_print_msg("PTRACE_CONT: %s\n", strerror(errno));
			goto error;
		}
	}

	/* Where is the child GCS? */
	iov.iov_base = &child_gcs;
	iov.iov_len = sizeof(child_gcs);
	ret = ptrace(PTRACE_GETREGSET, child, NT_ARM_GCS, &iov);
	if (ret != 0) {
		ksft_print_msg("Failed to read child GCS state: %s (%d)\n",
			       strerror(errno), errno);
		goto error;
	}

	/* We should have inherited GCS over fork(), confirm */
	if (!(child_gcs.features_enabled & PR_SHADOW_STACK_ENABLE)) {
		ASSERT_TRUE(child_gcs.features_enabled &
			    PR_SHADOW_STACK_ENABLE);
		goto error;
	}

	gcspr = child_gcs.gcspr_el0;
	ksft_print_msg("Child GCSPR 0x%lx, flags %llx, locked %llx\n",
		       gcspr, child_gcs.features_enabled,
		       child_gcs.features_locked);

	/* Ideally we'd cross check with the child memory map */

	errno = 0;
	val = ptrace(PTRACE_PEEKDATA, child, (void *)gcspr, NULL);
	ret = errno;
	if (ret != 0)
		ksft_print_msg("PTRACE_PEEKDATA failed: %s (%d)\n",
			       strerror(ret), ret);
	EXPECT_EQ(ret, 0);

	/* The child should be in a function, the GCSPR shouldn't be 0 */
	EXPECT_NE(val, 0);

	/* Same thing via process_vm_readv() */
	local_iov.iov_base = &rval;
	local_iov.iov_len = sizeof(rval);
	remote_iov.iov_base = (void *)gcspr;
	remote_iov.iov_len = sizeof(rval);
	ret = process_vm_readv(child, &local_iov, 1, &remote_iov, 1, 0);
	if (ret == -1)
		ksft_print_msg("process_vm_readv() failed: %s (%d)\n",
			       strerror(errno), errno);
	EXPECT_EQ(ret, sizeof(rval));
	EXPECT_EQ(val, rval);

	/* Write data via a peek */
	ret = ptrace(PTRACE_POKEDATA, child, (void *)gcspr, NULL);
	if (ret == -1)
		ksft_print_msg("PTRACE_POKEDATA failed: %s (%d)\n",
			       strerror(errno), errno);
	EXPECT_EQ(ret, 0);
	EXPECT_EQ(0, ptrace(PTRACE_PEEKDATA, child, (void *)gcspr, NULL));

	/* Restore what we had before */
	ret = ptrace(PTRACE_POKEDATA, child, (void *)gcspr, val);
	if (ret == -1)
		ksft_print_msg("PTRACE_POKEDATA failed: %s (%d)\n",
			       strerror(errno), errno);
	EXPECT_EQ(ret, 0);
	EXPECT_EQ(val, ptrace(PTRACE_PEEKDATA, child, (void *)gcspr, NULL));

	/* That's all, folks */
	kill(child, SIGKILL);
	return;

error:
	kill(child, SIGKILL);
	ASSERT_FALSE(true);
}

FIXTURE(map_gcs)
{
	unsigned long *stack;
};

FIXTURE_VARIANT(map_gcs)
{
	size_t stack_size;
	unsigned long flags;
};

FIXTURE_VARIANT_ADD(map_gcs, s2k_cap_marker)
{
	.stack_size = 2 * 1024,
	.flags = SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN,
};

FIXTURE_VARIANT_ADD(map_gcs, s2k_cap)
{
	.stack_size = 2 * 1024,
	.flags = SHADOW_STACK_SET_TOKEN,
};

FIXTURE_VARIANT_ADD(map_gcs, s2k_marker)
{
	.stack_size = 2 * 1024,
	.flags = SHADOW_STACK_SET_MARKER,
};

FIXTURE_VARIANT_ADD(map_gcs, s2k)
{
	.stack_size = 2 * 1024,
	.flags = 0,
};

FIXTURE_VARIANT_ADD(map_gcs, s4k_cap_marker)
{
	.stack_size = 4 * 1024,
	.flags = SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN,
};

FIXTURE_VARIANT_ADD(map_gcs, s4k_cap)
{
	.stack_size = 4 * 1024,
	.flags = SHADOW_STACK_SET_TOKEN,
};

FIXTURE_VARIANT_ADD(map_gcs, s3k_marker)
{
	.stack_size = 4 * 1024,
	.flags = SHADOW_STACK_SET_MARKER,
};

FIXTURE_VARIANT_ADD(map_gcs, s4k)
{
	.stack_size = 4 * 1024,
	.flags = 0,
};

FIXTURE_VARIANT_ADD(map_gcs, s16k_cap_marker)
{
	.stack_size = 16 * 1024,
	.flags = SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN,
};

FIXTURE_VARIANT_ADD(map_gcs, s16k_cap)
{
	.stack_size = 16 * 1024,
	.flags = SHADOW_STACK_SET_TOKEN,
};

FIXTURE_VARIANT_ADD(map_gcs, s16k_marker)
{
	.stack_size = 16 * 1024,
	.flags = SHADOW_STACK_SET_MARKER,
};

FIXTURE_VARIANT_ADD(map_gcs, s16k)
{
	.stack_size = 16 * 1024,
	.flags = 0,
};

FIXTURE_VARIANT_ADD(map_gcs, s64k_cap_marker)
{
	.stack_size = 64 * 1024,
	.flags = SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN,
};

FIXTURE_VARIANT_ADD(map_gcs, s64k_cap)
{
	.stack_size = 64 * 1024,
	.flags = SHADOW_STACK_SET_TOKEN,
};

FIXTURE_VARIANT_ADD(map_gcs, s64k_marker)
{
	.stack_size = 64 * 1024,
	.flags = SHADOW_STACK_SET_MARKER,
};

FIXTURE_VARIANT_ADD(map_gcs, s64k)
{
	.stack_size = 64 * 1024,
	.flags = 0,
};

FIXTURE_VARIANT_ADD(map_gcs, s128k_cap_marker)
{
	.stack_size = 128 * 1024,
	.flags = SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN,
};

FIXTURE_VARIANT_ADD(map_gcs, s128k_cap)
{
	.stack_size = 128 * 1024,
	.flags = SHADOW_STACK_SET_TOKEN,
};

FIXTURE_VARIANT_ADD(map_gcs, s128k_marker)
{
	.stack_size = 128 * 1024,
	.flags = SHADOW_STACK_SET_MARKER,
};

FIXTURE_VARIANT_ADD(map_gcs, s128k)
{
	.stack_size = 128 * 1024,
	.flags = 0,
};

FIXTURE_SETUP(map_gcs)
{
	self->stack = (void *)syscall(__NR_map_shadow_stack, 0,
				      variant->stack_size, 
				      variant->flags);
	ASSERT_FALSE(self->stack == MAP_FAILED);
	ksft_print_msg("Allocated stack from %p-%p\n", self->stack,
		       self->stack + variant->stack_size);
}

FIXTURE_TEARDOWN(map_gcs)
{
	int ret;

	if (self->stack != MAP_FAILED) {
		ret = munmap(self->stack, variant->stack_size);
		ASSERT_EQ(ret, 0);
	}
}

/* The stack has a cap token */
TEST_F(map_gcs, stack_capped)
{
	unsigned long *stack = self->stack;
	size_t cap_index;

	cap_index = (variant->stack_size / sizeof(unsigned long));

	switch (variant->flags & (SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN)) {
	case SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN:
		cap_index -= 2;
		break;
	case SHADOW_STACK_SET_TOKEN:
		cap_index -= 1;
		break;
	case SHADOW_STACK_SET_MARKER:
	case 0:
		/* No cap, no test */
		return;
	}

	ASSERT_EQ(stack[cap_index], GCS_CAP(&stack[cap_index]));
}

/* The top of the stack is 0 */
TEST_F(map_gcs, stack_terminated)
{
	unsigned long *stack = self->stack;
	size_t term_index;

	if (!(variant->flags & SHADOW_STACK_SET_MARKER))
		return;

	term_index = (variant->stack_size / sizeof(unsigned long)) - 1;

	ASSERT_EQ(stack[term_index], 0);
}

/* Writes should fault */
TEST_F_SIGNAL(map_gcs, not_writeable, SIGSEGV)
{
	self->stack[0] = 0;
}

/* Put it all together, we can safely switch to and from the stack */
TEST_F(map_gcs, stack_switch)
{
	size_t cap_index;
	cap_index = (variant->stack_size / sizeof(unsigned long));
	unsigned long *orig_gcspr_el0, *pivot_gcspr_el0;

	/* Skip over the stack terminator and point at the cap */
	switch (variant->flags & (SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN)) {
	case SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN:
		cap_index -= 2;
		break;
	case SHADOW_STACK_SET_TOKEN:
		cap_index -= 1;
		break;
	case SHADOW_STACK_SET_MARKER:
	case 0:
		/* No cap, no test */
		return;
	}
	pivot_gcspr_el0 = &self->stack[cap_index];

	/* Pivot to the new GCS */
	ksft_print_msg("Pivoting to %p from %p, target has value 0x%lx\n",
		       pivot_gcspr_el0, get_gcspr(),
		       *pivot_gcspr_el0);
	gcsss1(pivot_gcspr_el0);
	orig_gcspr_el0 = gcsss2();
	ksft_print_msg("Pivoted to %p from %p, target has value 0x%lx\n",
		       get_gcspr(), orig_gcspr_el0,
		       *pivot_gcspr_el0);

	ksft_print_msg("Pivoted, GCSPR_EL0 now %p\n", get_gcspr());

	/* New GCS must be in the new buffer */
	ASSERT_TRUE((unsigned long)get_gcspr() > (unsigned long)self->stack);
	ASSERT_TRUE((unsigned long)get_gcspr() <=
		    (unsigned long)self->stack + variant->stack_size);

	/* We should be able to use all but 2 slots of the new stack */
	ksft_print_msg("Recursing %zu levels\n", cap_index - 1);
	gcs_recurse(cap_index - 1);

	/* Pivot back to the original GCS */
	gcsss1(orig_gcspr_el0);
	pivot_gcspr_el0 = gcsss2();

	gcs_recurse(0);
	ksft_print_msg("Pivoted back to GCSPR_EL0 0x%p\n", get_gcspr());
}

/* We fault if we try to go beyond the end of the stack */
TEST_F_SIGNAL(map_gcs, stack_overflow, SIGSEGV)
{
	size_t cap_index;
	cap_index = (variant->stack_size / sizeof(unsigned long));
	unsigned long *orig_gcspr_el0, *pivot_gcspr_el0;

	/* Skip over the stack terminator and point at the cap */
	switch (variant->flags & (SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN)) {
	case SHADOW_STACK_SET_MARKER | SHADOW_STACK_SET_TOKEN:
		cap_index -= 2;
		break;
	case SHADOW_STACK_SET_TOKEN:
		cap_index -= 1;
		break;
	case SHADOW_STACK_SET_MARKER:
	case 0:
		/* No cap, no test but we need to SEGV to avoid a false fail */
		orig_gcspr_el0 = get_gcspr();
		*orig_gcspr_el0 = 0;
		return;
	}
	pivot_gcspr_el0 = &self->stack[cap_index];

	/* Pivot to the new GCS */
	ksft_print_msg("Pivoting to %p from %p, target has value 0x%lx\n",
		       pivot_gcspr_el0, get_gcspr(),
		       *pivot_gcspr_el0);
	gcsss1(pivot_gcspr_el0);
	orig_gcspr_el0 = gcsss2();
	ksft_print_msg("Pivoted to %p from %p, target has value 0x%lx\n",
		       pivot_gcspr_el0, orig_gcspr_el0,
		       *pivot_gcspr_el0);

	ksft_print_msg("Pivoted, GCSPR_EL0 now %p\n", get_gcspr());

	/* New GCS must be in the new buffer */
	ASSERT_TRUE((unsigned long)get_gcspr() > (unsigned long)self->stack);
	ASSERT_TRUE((unsigned long)get_gcspr() <=
		    (unsigned long)self->stack + variant->stack_size);

	/* Now try to recurse, we should fault doing this. */
	ksft_print_msg("Recursing %zu levels...\n", cap_index + 1);
	gcs_recurse(cap_index + 1);
	ksft_print_msg("...done\n");

	/* Clean up properly to try to guard against spurious passes. */
	gcsss1(orig_gcspr_el0);
	pivot_gcspr_el0 = gcsss2();
	ksft_print_msg("Pivoted back to GCSPR_EL0 0x%p\n", get_gcspr());
}

FIXTURE(map_invalid_gcs)
{
};

FIXTURE_VARIANT(map_invalid_gcs)
{
	size_t stack_size;
};

FIXTURE_SETUP(map_invalid_gcs)
{
}

FIXTURE_TEARDOWN(map_invalid_gcs)
{
}

/* GCS must be larger than 16 bytes */
FIXTURE_VARIANT_ADD(map_invalid_gcs, too_small)
{
	.stack_size = 8,
};

/* GCS size must be 16 byte aligned */
FIXTURE_VARIANT_ADD(map_invalid_gcs, unligned_1)  { .stack_size = 1024 + 1  };
FIXTURE_VARIANT_ADD(map_invalid_gcs, unligned_2)  { .stack_size = 1024 + 2  };
FIXTURE_VARIANT_ADD(map_invalid_gcs, unligned_3)  { .stack_size = 1024 + 3  };
FIXTURE_VARIANT_ADD(map_invalid_gcs, unligned_4)  { .stack_size = 1024 + 4  };
FIXTURE_VARIANT_ADD(map_invalid_gcs, unligned_5)  { .stack_size = 1024 + 5  };
FIXTURE_VARIANT_ADD(map_invalid_gcs, unligned_6)  { .stack_size = 1024 + 6  };
FIXTURE_VARIANT_ADD(map_invalid_gcs, unligned_7)  { .stack_size = 1024 + 7  };

TEST_F(map_invalid_gcs, do_map)
{
	void *stack;

	stack = (void *)syscall(__NR_map_shadow_stack, 0,
				variant->stack_size, 0);
	ASSERT_TRUE(stack == MAP_FAILED);
	if (stack != MAP_FAILED)
		munmap(stack, variant->stack_size);
}

FIXTURE(invalid_mprotect)
{
	unsigned long *stack;
	size_t stack_size;
};

FIXTURE_VARIANT(invalid_mprotect)
{
	unsigned long flags;
};

FIXTURE_SETUP(invalid_mprotect)
{
	self->stack_size = sysconf(_SC_PAGE_SIZE);
	self->stack = (void *)syscall(__NR_map_shadow_stack, 0,
				      self->stack_size, 0);
	ASSERT_FALSE(self->stack == MAP_FAILED);
	ksft_print_msg("Allocated stack from %p-%p\n", self->stack,
		       self->stack + self->stack_size);
}

FIXTURE_TEARDOWN(invalid_mprotect)
{
	int ret;

	if (self->stack != MAP_FAILED) {
		ret = munmap(self->stack, self->stack_size);
		ASSERT_EQ(ret, 0);
	}
}

FIXTURE_VARIANT_ADD(invalid_mprotect, exec)
{
	.flags = PROT_EXEC,
};

TEST_F(invalid_mprotect, do_map)
{
	int ret;

	ret = mprotect(self->stack, self->stack_size, variant->flags);
	ASSERT_EQ(ret, -1);
}

TEST_F(invalid_mprotect, do_map_read)
{
	int ret;

	ret = mprotect(self->stack, self->stack_size,
		       variant->flags | PROT_READ);
	ASSERT_EQ(ret, -1);
}

int main(int argc, char **argv)
{
	unsigned long gcs_mode;
	int ret;

	if (!(getauxval(AT_HWCAP) & HWCAP_GCS))
		ksft_exit_skip("SKIP GCS not supported\n");

	/* 
	 * Force shadow stacks on, our tests *should* be fine with or
	 * without libc support and with or without this having ended
	 * up tagged for GCS and enabled by the dynamic linker.  We
	 * can't use the libc prctl() function since we can't return
	 * from enabling the stack.
	 */
	ret = my_syscall2(__NR_prctl, PR_GET_SHADOW_STACK_STATUS, &gcs_mode);
	if (ret) {
		ksft_print_msg("Failed to read GCS state: %d\n", ret);
		return EXIT_FAILURE;
	}
	
	if (!(gcs_mode & PR_SHADOW_STACK_ENABLE)) {
		gcs_mode = PR_SHADOW_STACK_ENABLE;
		ret = my_syscall2(__NR_prctl, PR_SET_SHADOW_STACK_STATUS,
				  gcs_mode);
		if (ret) {
			ksft_print_msg("Failed to configure GCS: %d\n", ret);
			return EXIT_FAILURE;
		}
	}

	/* Avoid returning in case libc doesn't understand GCS */
	exit(test_harness_run(argc, argv));
}