kselftest: arm64: mangle_pstate_invalid_compat_toggle and common utils
authorCristian Marussi <cristian.marussi@arm.com>
Fri, 25 Oct 2019 17:57:07 +0000 (18:57 +0100)
committerCatalin Marinas <catalin.marinas@arm.com>
Fri, 8 Nov 2019 11:10:33 +0000 (11:10 +0000)
Add some arm64/signal specific boilerplate and utility code to help
further testcases' development.

Introduce also one simple testcase mangle_pstate_invalid_compat_toggle
and some related helpers: it is a simple mangle testcase which messes
with the ucontext_t from within the signal handler, trying to toggle
PSTATE state bits to switch the system between 32bit/64bit execution
state. Expects SIGSEGV on test PASS.

Reviewed-by: Dave Martin <Dave.Martin@arm.com>
Signed-off-by: Cristian Marussi <cristian.marussi@arm.com>
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>
tools/testing/selftests/arm64/Makefile
tools/testing/selftests/arm64/signal/.gitignore [new file with mode: 0644]
tools/testing/selftests/arm64/signal/Makefile [new file with mode: 0644]
tools/testing/selftests/arm64/signal/README [new file with mode: 0644]
tools/testing/selftests/arm64/signal/test_signals.c [new file with mode: 0644]
tools/testing/selftests/arm64/signal/test_signals.h [new file with mode: 0644]
tools/testing/selftests/arm64/signal/test_signals_utils.c [new file with mode: 0644]
tools/testing/selftests/arm64/signal/test_signals_utils.h [new file with mode: 0644]
tools/testing/selftests/arm64/signal/testcases/mangle_pstate_invalid_compat_toggle.c [new file with mode: 0644]
tools/testing/selftests/arm64/signal/testcases/testcases.c [new file with mode: 0644]
tools/testing/selftests/arm64/signal/testcases/testcases.h [new file with mode: 0644]

index cd27ca6..93b567d 100644 (file)
@@ -4,7 +4,7 @@
 ARCH ?= $(shell uname -m 2>/dev/null || echo not)
 
 ifneq (,$(filter $(ARCH),aarch64 arm64))
-ARM64_SUBTARGETS ?= tags
+ARM64_SUBTARGETS ?= tags signal
 else
 ARM64_SUBTARGETS :=
 endif
diff --git a/tools/testing/selftests/arm64/signal/.gitignore b/tools/testing/selftests/arm64/signal/.gitignore
new file mode 100644 (file)
index 0000000..3c5b4e8
--- /dev/null
@@ -0,0 +1,3 @@
+mangle_*
+fake_sigreturn_*
+!*.[ch]
diff --git a/tools/testing/selftests/arm64/signal/Makefile b/tools/testing/selftests/arm64/signal/Makefile
new file mode 100644 (file)
index 0000000..f78f519
--- /dev/null
@@ -0,0 +1,32 @@
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (C) 2019 ARM Limited
+
+# Additional include paths needed by kselftest.h and local headers
+CFLAGS += -D_GNU_SOURCE -std=gnu99 -I.
+
+SRCS := $(filter-out testcases/testcases.c,$(wildcard testcases/*.c))
+PROGS := $(patsubst %.c,%,$(SRCS))
+
+# Generated binaries to be installed by top KSFT script
+TEST_GEN_PROGS := $(notdir $(PROGS))
+
+# Get Kernel headers installed and use them.
+KSFT_KHDR_INSTALL := 1
+
+# Including KSFT lib.mk here will also mangle the TEST_GEN_PROGS list
+# to account for any OUTPUT target-dirs optionally provided by
+# the toplevel makefile
+include ../../lib.mk
+
+$(TEST_GEN_PROGS): $(PROGS)
+       cp $(PROGS) $(OUTPUT)/
+
+clean:
+       $(CLEAN)
+       rm -f $(PROGS)
+
+# Common test-unit targets to build common-layout test-cases executables
+# Needs secondary expansion to properly include the testcase c-file in pre-reqs
+.SECONDEXPANSION:
+$(PROGS): test_signals.c test_signals_utils.c testcases/testcases.c $$@.c test_signals.h test_signals_utils.h testcases/testcases.h
+       $(CC) $(CFLAGS) $^ -o $@
diff --git a/tools/testing/selftests/arm64/signal/README b/tools/testing/selftests/arm64/signal/README
new file mode 100644 (file)
index 0000000..967a531
--- /dev/null
@@ -0,0 +1,59 @@
+KSelfTest arm64/signal/
+=======================
+
+Signals Tests
++++++++++++++
+
+- Tests are built around a common main compilation unit: such shared main
+  enforces a standard sequence of operations needed to perform a single
+  signal-test (setup/trigger/run/result/cleanup)
+
+- The above mentioned ops are configurable on a test-by-test basis: each test
+  is described (and configured) using the descriptor signals.h::struct tdescr
+
+- Each signal testcase is compiled into its own executable: a separate
+  executable is used for each test since many tests complete successfully
+  by receiving some kind of fatal signal from the Kernel, so it's safer
+  to run each test unit in its own standalone process, so as to start each
+  test from a clean slate.
+
+- New tests can be simply defined in testcases/ dir providing a proper struct
+  tdescr overriding all the defaults we wish to change (as of now providing a
+  custom run method is mandatory though)
+
+- Signals' test-cases hereafter defined belong currently to two
+  principal families:
+
+  - 'mangle_' tests: a real signal (SIGUSR1) is raised and used as a trigger
+    and then the test case code modifies the signal frame from inside the
+    signal handler itself.
+
+  - 'fake_sigreturn_' tests: a brand new custom artificial sigframe structure
+    is placed on the stack and a sigreturn syscall is called to simulate a
+    real signal return. This kind of tests does not use a trigger usually and
+    they are just fired using some simple included assembly trampoline code.
+
+ - Most of these tests are successfully passing if the process gets killed by
+   some fatal signal: usually SIGSEGV or SIGBUS. Since while writing this
+   kind of tests it is extremely easy in fact to end-up injecting other
+   unrelated SEGV bugs in the testcases, it becomes extremely tricky to
+   be really sure that the tests are really addressing what they are meant
+   to address and they are not instead falling apart due to unplanned bugs
+   in the test code.
+   In order to alleviate the misery of the life of such test-developer, a few
+   helpers are provided:
+
+   - a couple of ASSERT_BAD/GOOD_CONTEXT() macros to easily parse a ucontext_t
+     and verify if it is indeed GOOD or BAD (depending on what we were
+     expecting), using the same logic/perspective as in the arm64 Kernel signals
+     routines.
+
+   - a sanity mechanism to be used in 'fake_sigreturn_'-alike tests: enabled by
+     default it takes care to verify that the test-execution had at least
+     successfully progressed up to the stage of triggering the fake sigreturn
+     call.
+
+  In both cases test results are expected in terms of:
+   - some fatal signal sent by the Kernel to the test process
+  or
+  - analyzing some final regs state
diff --git a/tools/testing/selftests/arm64/signal/test_signals.c b/tools/testing/selftests/arm64/signal/test_signals.c
new file mode 100644 (file)
index 0000000..cb97034
--- /dev/null
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 ARM Limited
+ *
+ * Generic test wrapper for arm64 signal tests.
+ *
+ * Each test provides its own tde struct tdescr descriptor to link with
+ * this wrapper. Framework provides common helpers.
+ */
+#include <kselftest.h>
+
+#include "test_signals.h"
+#include "test_signals_utils.h"
+
+struct tdescr *current;
+
+int main(int argc, char *argv[])
+{
+       current = &tde;
+
+       ksft_print_msg("%s :: %s\n", current->name, current->descr);
+       if (test_setup(current)) {
+               test_run(current);
+               test_result(current);
+               test_cleanup(current);
+       }
+
+       return current->pass ? KSFT_PASS : KSFT_FAIL;
+}
diff --git a/tools/testing/selftests/arm64/signal/test_signals.h b/tools/testing/selftests/arm64/signal/test_signals.h
new file mode 100644 (file)
index 0000000..d730e90
--- /dev/null
@@ -0,0 +1,93 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2019 ARM Limited */
+
+#ifndef __TEST_SIGNALS_H__
+#define __TEST_SIGNALS_H__
+
+#include <signal.h>
+#include <stdbool.h>
+#include <ucontext.h>
+
+/*
+ * Using ARCH specific and sanitized Kernel headers installed by KSFT
+ * framework since we asked for it by setting flag KSFT_KHDR_INSTALL
+ * in our Makefile.
+ */
+#include <asm/ptrace.h>
+#include <asm/hwcap.h>
+
+#define __stringify_1(x...)    #x
+#define __stringify(x...)      __stringify_1(x)
+
+#define get_regval(regname, out)                       \
+{                                                      \
+       asm volatile("mrs %0, " __stringify(regname)    \
+       : "=r" (out)                                    \
+       :                                               \
+       : "memory");                                    \
+}
+
+/*
+ * Feature flags used in tdescr.feats_required to specify
+ * any feature by the test
+ */
+enum {
+       FSSBS_BIT,
+       FMAX_END
+};
+
+#define FEAT_SSBS              (1UL << FSSBS_BIT)
+
+/*
+ * A descriptor used to describe and configure a test case.
+ * Fields with a non-trivial meaning are described inline in the following.
+ */
+struct tdescr {
+       /* KEEP THIS FIELD FIRST for easier lookup from assembly */
+       void                    *token;
+       /* when disabled token based sanity checking is skipped in handler */
+       bool                    sanity_disabled;
+       /* just a name for the test-case; manadatory field */
+       char                    *name;
+       char                    *descr;
+       unsigned long           feats_required;
+       /* bitmask of effectively supported feats: populated at run-time */
+       unsigned long           feats_supported;
+       bool                    initialized;
+       unsigned int            minsigstksz;
+       /* signum used as a test trigger. Zero if no trigger-signal is used */
+       int                     sig_trig;
+       /*
+        * signum considered as a successful test completion.
+        * Zero when no signal is expected on success
+        */
+       int                     sig_ok;
+       /* signum expected on unsupported CPU features. */
+       int                     sig_unsupp;
+       /* a timeout in second for test completion */
+       unsigned int            timeout;
+       bool                    triggered;
+       bool                    pass;
+       /* optional sa_flags for the installed handler */
+       int                     sa_flags;
+       ucontext_t              saved_uc;
+       /* optional test private data */
+       void                    *priv;
+
+       /* a custom setup function to be called before test starts */
+       int (*setup)(struct tdescr *td);
+       /* a custom cleanup function called before test exits */
+       void (*cleanup)(struct tdescr *td);
+       /* an optional function to be used as a trigger for test starting */
+       int (*trigger)(struct tdescr *td);
+       /*
+        * the actual test-core: invoked differently depending on the
+        * presence of the trigger function above; this is mandatory
+        */
+       int (*run)(struct tdescr *td, siginfo_t *si, ucontext_t *uc);
+       /* an optional function for custom results' processing */
+       void (*check_result)(struct tdescr *td);
+};
+
+extern struct tdescr tde;
+#endif
diff --git a/tools/testing/selftests/arm64/signal/test_signals_utils.c b/tools/testing/selftests/arm64/signal/test_signals_utils.c
new file mode 100644 (file)
index 0000000..fbce417
--- /dev/null
@@ -0,0 +1,283 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2019 ARM Limited */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <string.h>
+#include <unistd.h>
+#include <assert.h>
+#include <sys/auxv.h>
+#include <linux/auxvec.h>
+#include <ucontext.h>
+
+#include "test_signals.h"
+#include "test_signals_utils.h"
+#include "testcases/testcases.h"
+
+extern struct tdescr *current;
+
+static char const *const feats_names[FMAX_END] = {
+       " SSBS ",
+};
+
+#define MAX_FEATS_SZ   128
+static char feats_string[MAX_FEATS_SZ];
+
+static inline char *feats_to_string(unsigned long feats)
+{
+       size_t flen = MAX_FEATS_SZ - 1;
+
+       for (int i = 0; i < FMAX_END; i++) {
+               if (feats & (1UL << i)) {
+                       size_t tlen = strlen(feats_names[i]);
+
+                       assert(flen > tlen);
+                       flen -= tlen;
+                       strncat(feats_string, feats_names[i], flen);
+               }
+       }
+
+       return feats_string;
+}
+
+static void unblock_signal(int signum)
+{
+       sigset_t sset;
+
+       sigemptyset(&sset);
+       sigaddset(&sset, signum);
+       sigprocmask(SIG_UNBLOCK, &sset, NULL);
+}
+
+static void default_result(struct tdescr *td, bool force_exit)
+{
+       if (td->pass)
+               fprintf(stderr, "==>> completed. PASS(1)\n");
+       else
+               fprintf(stdout, "==>> completed. FAIL(0)\n");
+       if (force_exit)
+               exit(td->pass ? EXIT_SUCCESS : EXIT_FAILURE);
+}
+
+/*
+ * The following handle_signal_* helpers are used by main default_handler
+ * and are meant to return true when signal is handled successfully:
+ * when false is returned instead, it means that the signal was somehow
+ * unexpected in that context and it was NOT handled; default_handler will
+ * take care of such unexpected situations.
+ */
+
+static bool handle_signal_unsupported(struct tdescr *td,
+                                     siginfo_t *si, void *uc)
+{
+       if (feats_ok(td))
+               return false;
+
+       /* Mangling PC to avoid loops on original SIGILL */
+       ((ucontext_t *)uc)->uc_mcontext.pc += 4;
+
+       if (!td->initialized) {
+               fprintf(stderr,
+                       "Got SIG_UNSUPP @test_init. Ignore.\n");
+       } else {
+               fprintf(stderr,
+                       "-- RX SIG_UNSUPP on unsupported feat...OK\n");
+               td->pass = 1;
+               default_result(current, 1);
+       }
+
+       return true;
+}
+
+static bool handle_signal_trigger(struct tdescr *td,
+                                 siginfo_t *si, void *uc)
+{
+       td->triggered = 1;
+       /* ->run was asserted NON-NULL in test_setup() already */
+       td->run(td, si, uc);
+
+       return true;
+}
+
+static bool handle_signal_ok(struct tdescr *td,
+                            siginfo_t *si, void *uc)
+{
+       /*
+        * it's a bug in the test code when this assert fail:
+        * if sig_trig was defined, it must have been used before getting here.
+        */
+       assert(!td->sig_trig || td->triggered);
+       fprintf(stderr,
+               "SIG_OK -- SP:0x%llX  si_addr@:%p  si_code:%d  token@:%p  offset:%ld\n",
+               ((ucontext_t *)uc)->uc_mcontext.sp,
+               si->si_addr, si->si_code, td->token, td->token - si->si_addr);
+       /*
+        * fake_sigreturn tests, which have sanity_enabled=1, set, at the very
+        * last time, the token field to the SP address used to place the fake
+        * sigframe: so token==0 means we never made it to the end,
+        * segfaulting well-before, and the test is possibly broken.
+        */
+       if (!td->sanity_disabled && !td->token) {
+               fprintf(stdout,
+                       "current->token ZEROED...test is probably broken!\n");
+               abort();
+       }
+       /*
+        * Trying to narrow down the SEGV to the ones generated by Kernel itself
+        * via arm64_notify_segfault(). This is a best-effort check anyway, and
+        * the si_code check may need to change if this aspect of the kernel
+        * ABI changes.
+        */
+       if (td->sig_ok == SIGSEGV && si->si_code != SEGV_ACCERR) {
+               fprintf(stdout,
+                       "si_code != SEGV_ACCERR...test is probably broken!\n");
+               abort();
+       }
+       td->pass = 1;
+       /*
+        * Some tests can lead to SEGV loops: in such a case we want to
+        * terminate immediately exiting straight away; some others are not
+        * supposed to outlive the signal handler code, due to the content of
+        * the fake sigframe which caused the signal itself.
+        */
+       default_result(current, 1);
+
+       return true;
+}
+
+static void default_handler(int signum, siginfo_t *si, void *uc)
+{
+       if (current->sig_unsupp && signum == current->sig_unsupp &&
+           handle_signal_unsupported(current, si, uc)) {
+               fprintf(stderr, "Handled SIG_UNSUPP\n");
+       } else if (current->sig_trig && signum == current->sig_trig &&
+                  handle_signal_trigger(current, si, uc)) {
+               fprintf(stderr, "Handled SIG_TRIG\n");
+       } else if (current->sig_ok && signum == current->sig_ok &&
+                  handle_signal_ok(current, si, uc)) {
+               fprintf(stderr, "Handled SIG_OK\n");
+       } else {
+               if (signum == SIGALRM && current->timeout) {
+                       fprintf(stderr, "-- Timeout !\n");
+               } else {
+                       fprintf(stderr,
+                               "-- RX UNEXPECTED SIGNAL: %d\n", signum);
+               }
+               default_result(current, 1);
+       }
+}
+
+static int default_setup(struct tdescr *td)
+{
+       struct sigaction sa;
+
+       sa.sa_sigaction = default_handler;
+       sa.sa_flags = SA_SIGINFO | SA_RESTART;
+       sa.sa_flags |= td->sa_flags;
+       sigemptyset(&sa.sa_mask);
+       /* uncatchable signals naturally skipped ... */
+       for (int sig = 1; sig < 32; sig++)
+               sigaction(sig, &sa, NULL);
+       /*
+        * RT Signals default disposition is Term but they cannot be
+        * generated by the Kernel in response to our tests; so just catch
+        * them all and report them as UNEXPECTED signals.
+        */
+       for (int sig = SIGRTMIN; sig <= SIGRTMAX; sig++)
+               sigaction(sig, &sa, NULL);
+
+       /* just in case...unblock explicitly all we need */
+       if (td->sig_trig)
+               unblock_signal(td->sig_trig);
+       if (td->sig_ok)
+               unblock_signal(td->sig_ok);
+       if (td->sig_unsupp)
+               unblock_signal(td->sig_unsupp);
+
+       if (td->timeout) {
+               unblock_signal(SIGALRM);
+               alarm(td->timeout);
+       }
+       fprintf(stderr, "Registered handlers for all signals.\n");
+
+       return 1;
+}
+
+static inline int default_trigger(struct tdescr *td)
+{
+       return !raise(td->sig_trig);
+}
+
+static int test_init(struct tdescr *td)
+{
+       td->minsigstksz = getauxval(AT_MINSIGSTKSZ);
+       if (!td->minsigstksz)
+               td->minsigstksz = MINSIGSTKSZ;
+       fprintf(stderr, "Detected MINSTKSIGSZ:%d\n", td->minsigstksz);
+
+       if (td->feats_required) {
+               td->feats_supported = 0;
+               /*
+                * Checking for CPU required features using both the
+                * auxval and the arm64 MRS Emulation to read sysregs.
+                */
+               if (getauxval(AT_HWCAP) & HWCAP_SSBS)
+                       td->feats_supported |= FEAT_SSBS;
+               if (feats_ok(td))
+                       fprintf(stderr,
+                               "Required Features: [%s] supported\n",
+                               feats_to_string(td->feats_required &
+                                               td->feats_supported));
+               else
+                       fprintf(stderr,
+                               "Required Features: [%s] NOT supported\n",
+                               feats_to_string(td->feats_required &
+                                               ~td->feats_supported));
+       }
+
+       td->initialized = 1;
+       return 1;
+}
+
+int test_setup(struct tdescr *td)
+{
+       /* assert core invariants symptom of a rotten testcase */
+       assert(current);
+       assert(td);
+       assert(td->name);
+       assert(td->run);
+
+       if (!test_init(td))
+               return 0;
+
+       if (td->setup)
+               return td->setup(td);
+       else
+               return default_setup(td);
+}
+
+int test_run(struct tdescr *td)
+{
+       if (td->sig_trig) {
+               if (td->trigger)
+                       return td->trigger(td);
+               else
+                       return default_trigger(td);
+       } else {
+               return td->run(td, NULL, NULL);
+       }
+}
+
+void test_result(struct tdescr *td)
+{
+       if (td->check_result)
+               td->check_result(td);
+       default_result(td, 0);
+}
+
+void test_cleanup(struct tdescr *td)
+{
+       if (td->cleanup)
+               td->cleanup(td);
+}
diff --git a/tools/testing/selftests/arm64/signal/test_signals_utils.h b/tools/testing/selftests/arm64/signal/test_signals_utils.h
new file mode 100644 (file)
index 0000000..47a7592
--- /dev/null
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2019 ARM Limited */
+
+#ifndef __TEST_SIGNALS_UTILS_H__
+#define __TEST_SIGNALS_UTILS_H__
+
+#include "test_signals.h"
+
+int test_setup(struct tdescr *td);
+void test_cleanup(struct tdescr *td);
+int test_run(struct tdescr *td);
+void test_result(struct tdescr *td);
+
+static inline bool feats_ok(struct tdescr *td)
+{
+       return (td->feats_required & td->feats_supported) == td->feats_required;
+}
+
+#endif
diff --git a/tools/testing/selftests/arm64/signal/testcases/mangle_pstate_invalid_compat_toggle.c b/tools/testing/selftests/arm64/signal/testcases/mangle_pstate_invalid_compat_toggle.c
new file mode 100644 (file)
index 0000000..2cb118b
--- /dev/null
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 ARM Limited
+ *
+ * Try to mangle the ucontext from inside a signal handler, toggling
+ * the execution state bit: this attempt must be spotted by Kernel and
+ * the test case is expected to be terminated via SEGV.
+ */
+
+#include "test_signals_utils.h"
+#include "testcases.h"
+
+static int mangle_invalid_pstate_run(struct tdescr *td, siginfo_t *si,
+                                    ucontext_t *uc)
+{
+       ASSERT_GOOD_CONTEXT(uc);
+
+       /* This config should trigger a SIGSEGV by Kernel */
+       uc->uc_mcontext.pstate ^= PSR_MODE32_BIT;
+
+       return 1;
+}
+
+struct tdescr tde = {
+               .sanity_disabled = true,
+               .name = "MANGLE_PSTATE_INVALID_STATE_TOGGLE",
+               .descr = "Mangling uc_mcontext with INVALID STATE_TOGGLE",
+               .sig_trig = SIGUSR1,
+               .sig_ok = SIGSEGV,
+               .run = mangle_invalid_pstate_run,
+};
diff --git a/tools/testing/selftests/arm64/signal/testcases/testcases.c b/tools/testing/selftests/arm64/signal/testcases/testcases.c
new file mode 100644 (file)
index 0000000..1914a01
--- /dev/null
@@ -0,0 +1,150 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2019 ARM Limited */
+#include "testcases.h"
+
+struct _aarch64_ctx *get_header(struct _aarch64_ctx *head, uint32_t magic,
+                               size_t resv_sz, size_t *offset)
+{
+       size_t offs = 0;
+       struct _aarch64_ctx *found = NULL;
+
+       if (!head || resv_sz < HDR_SZ)
+               return found;
+
+       while (offs <= resv_sz - HDR_SZ &&
+              head->magic != magic && head->magic) {
+               offs += head->size;
+               head = GET_RESV_NEXT_HEAD(head);
+       }
+       if (head->magic == magic) {
+               found = head;
+               if (offset)
+                       *offset = offs;
+       }
+
+       return found;
+}
+
+bool validate_extra_context(struct extra_context *extra, char **err)
+{
+       struct _aarch64_ctx *term;
+
+       if (!extra || !err)
+               return false;
+
+       fprintf(stderr, "Validating EXTRA...\n");
+       term = GET_RESV_NEXT_HEAD(extra);
+       if (!term || term->magic || term->size) {
+               *err = "Missing terminator after EXTRA context";
+               return false;
+       }
+       if (extra->datap & 0x0fUL)
+               *err = "Extra DATAP misaligned";
+       else if (extra->size & 0x0fUL)
+               *err = "Extra SIZE misaligned";
+       else if (extra->datap != (uint64_t)term + sizeof(*term))
+               *err = "Extra DATAP misplaced (not contiguos)";
+       if (*err)
+               return false;
+
+       return true;
+}
+
+bool validate_reserved(ucontext_t *uc, size_t resv_sz, char **err)
+{
+       bool terminated = false;
+       size_t offs = 0;
+       int flags = 0;
+       struct extra_context *extra = NULL;
+       struct _aarch64_ctx *head =
+               (struct _aarch64_ctx *)uc->uc_mcontext.__reserved;
+
+       if (!err)
+               return false;
+       /* Walk till the end terminator verifying __reserved contents */
+       while (head && !terminated && offs < resv_sz) {
+               if ((uint64_t)head & 0x0fUL) {
+                       *err = "Misaligned HEAD";
+                       return false;
+               }
+
+               switch (head->magic) {
+               case 0:
+                       if (head->size)
+                               *err = "Bad size for terminator";
+                       else
+                               terminated = true;
+                       break;
+               case FPSIMD_MAGIC:
+                       if (flags & FPSIMD_CTX)
+                               *err = "Multiple FPSIMD_MAGIC";
+                       else if (head->size !=
+                                sizeof(struct fpsimd_context))
+                               *err = "Bad size for fpsimd_context";
+                       flags |= FPSIMD_CTX;
+                       break;
+               case ESR_MAGIC:
+                       if (head->size != sizeof(struct esr_context))
+                               *err = "Bad size for esr_context";
+                       break;
+               case SVE_MAGIC:
+                       if (flags & SVE_CTX)
+                               *err = "Multiple SVE_MAGIC";
+                       else if (head->size !=
+                                sizeof(struct sve_context))
+                               *err = "Bad size for sve_context";
+                       flags |= SVE_CTX;
+                       break;
+               case EXTRA_MAGIC:
+                       if (flags & EXTRA_CTX)
+                               *err = "Multiple EXTRA_MAGIC";
+                       else if (head->size !=
+                                sizeof(struct extra_context))
+                               *err = "Bad size for extra_context";
+                       flags |= EXTRA_CTX;
+                       extra = (struct extra_context *)head;
+                       break;
+               case KSFT_BAD_MAGIC:
+                       /*
+                        * This is a BAD magic header defined
+                        * artificially by a testcase and surely
+                        * unknown to the Kernel parse_user_sigframe().
+                        * It MUST cause a Kernel induced SEGV
+                        */
+                       *err = "BAD MAGIC !";
+                       break;
+               default:
+                       /*
+                        * A still unknown Magic: potentially freshly added
+                        * to the Kernel code and still unknown to the
+                        * tests.
+                        */
+                       fprintf(stdout,
+                               "SKIP Unknown MAGIC: 0x%X - Is KSFT arm64/signal up to date ?\n",
+                               head->magic);
+                       break;
+               }
+
+               if (*err)
+                       return false;
+
+               offs += head->size;
+               if (resv_sz < offs + sizeof(*head)) {
+                       *err = "HEAD Overrun";
+                       return false;
+               }
+
+               if (flags & EXTRA_CTX)
+                       if (!validate_extra_context(extra, err))
+                               return false;
+
+               head = GET_RESV_NEXT_HEAD(head);
+       }
+
+       if (terminated && !(flags & FPSIMD_CTX)) {
+               *err = "Missing FPSIMD";
+               return false;
+       }
+
+       return true;
+}
diff --git a/tools/testing/selftests/arm64/signal/testcases/testcases.h b/tools/testing/selftests/arm64/signal/testcases/testcases.h
new file mode 100644 (file)
index 0000000..04987f7
--- /dev/null
@@ -0,0 +1,100 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2019 ARM Limited */
+#ifndef __TESTCASES_H__
+#define __TESTCASES_H__
+
+#include <stddef.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <ucontext.h>
+#include <signal.h>
+
+/* Architecture specific sigframe definitions */
+#include <asm/sigcontext.h>
+
+#define FPSIMD_CTX     (1 << 0)
+#define SVE_CTX                (1 << 1)
+#define EXTRA_CTX      (1 << 2)
+
+#define KSFT_BAD_MAGIC 0xdeadbeef
+
+#define HDR_SZ \
+       sizeof(struct _aarch64_ctx)
+
+#define GET_SF_RESV_HEAD(sf) \
+       (struct _aarch64_ctx *)(&(sf).uc.uc_mcontext.__reserved)
+
+#define GET_SF_RESV_SIZE(sf) \
+       sizeof((sf).uc.uc_mcontext.__reserved)
+
+#define GET_UCP_RESV_SIZE(ucp) \
+       sizeof((ucp)->uc_mcontext.__reserved)
+
+#define ASSERT_BAD_CONTEXT(uc) do {                                    \
+       char *err = NULL;                                               \
+       if (!validate_reserved((uc), GET_UCP_RESV_SIZE((uc)), &err)) {  \
+               if (err)                                                \
+                       fprintf(stderr,                                 \
+                               "Using badly built context - ERR: %s\n",\
+                               err);                                   \
+       } else {                                                        \
+               abort();                                                \
+       }                                                               \
+} while (0)
+
+#define ASSERT_GOOD_CONTEXT(uc) do {                                    \
+       char *err = NULL;                                                \
+       if (!validate_reserved((uc), GET_UCP_RESV_SIZE((uc)), &err)) {   \
+               if (err)                                                 \
+                       fprintf(stderr,                                  \
+                               "Detected BAD context - ERR: %s\n", err);\
+               abort();                                                 \
+       } else {                                                         \
+               fprintf(stderr, "uc context validated.\n");              \
+       }                                                                \
+} while (0)
+
+/*
+ * A simple record-walker for __reserved area: it walks through assuming
+ * only to find a proper struct __aarch64_ctx header descriptor.
+ *
+ * Instead it makes no assumptions on the content and ordering of the
+ * records, any needed bounds checking must be enforced by the caller
+ * if wanted: this way can be used by caller on any maliciously built bad
+ * contexts.
+ *
+ * head->size accounts both for payload and header _aarch64_ctx size !
+ */
+#define GET_RESV_NEXT_HEAD(h) \
+       (struct _aarch64_ctx *)((char *)(h) + (h)->size)
+
+struct fake_sigframe {
+       siginfo_t       info;
+       ucontext_t      uc;
+};
+
+
+bool validate_reserved(ucontext_t *uc, size_t resv_sz, char **err);
+
+bool validate_extra_context(struct extra_context *extra, char **err);
+
+struct _aarch64_ctx *get_header(struct _aarch64_ctx *head, uint32_t magic,
+                               size_t resv_sz, size_t *offset);
+
+static inline struct _aarch64_ctx *get_terminator(struct _aarch64_ctx *head,
+                                                 size_t resv_sz,
+                                                 size_t *offset)
+{
+       return get_header(head, 0, resv_sz, offset);
+}
+
+static inline void write_terminator_record(struct _aarch64_ctx *tail)
+{
+       if (tail) {
+               tail->magic = 0;
+               tail->size = 0;
+       }
+}
+#endif