kselftest: arm64: mangle_pstate_invalid_compat_toggle and common utils
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>
This commit is contained in:
parent
313a4db7f3
commit
f96bf43403
|
@ -4,7 +4,7 @@
|
||||||
ARCH ?= $(shell uname -m 2>/dev/null || echo not)
|
ARCH ?= $(shell uname -m 2>/dev/null || echo not)
|
||||||
|
|
||||||
ifneq (,$(filter $(ARCH),aarch64 arm64))
|
ifneq (,$(filter $(ARCH),aarch64 arm64))
|
||||||
ARM64_SUBTARGETS ?= tags
|
ARM64_SUBTARGETS ?= tags signal
|
||||||
else
|
else
|
||||||
ARM64_SUBTARGETS :=
|
ARM64_SUBTARGETS :=
|
||||||
endif
|
endif
|
||||||
|
|
3
tools/testing/selftests/arm64/signal/.gitignore
vendored
Normal file
3
tools/testing/selftests/arm64/signal/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
mangle_*
|
||||||
|
fake_sigreturn_*
|
||||||
|
!*.[ch]
|
32
tools/testing/selftests/arm64/signal/Makefile
Normal file
32
tools/testing/selftests/arm64/signal/Makefile
Normal file
|
@ -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 $@
|
59
tools/testing/selftests/arm64/signal/README
Normal file
59
tools/testing/selftests/arm64/signal/README
Normal file
|
@ -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
|
29
tools/testing/selftests/arm64/signal/test_signals.c
Normal file
29
tools/testing/selftests/arm64/signal/test_signals.c
Normal file
|
@ -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;
|
||||||
|
}
|
93
tools/testing/selftests/arm64/signal/test_signals.h
Normal file
93
tools/testing/selftests/arm64/signal/test_signals.h
Normal file
|
@ -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
|
283
tools/testing/selftests/arm64/signal/test_signals_utils.c
Normal file
283
tools/testing/selftests/arm64/signal/test_signals_utils.c
Normal file
|
@ -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);
|
||||||
|
}
|
19
tools/testing/selftests/arm64/signal/test_signals_utils.h
Normal file
19
tools/testing/selftests/arm64/signal/test_signals_utils.h
Normal file
|
@ -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
|
|
@ -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,
|
||||||
|
};
|
150
tools/testing/selftests/arm64/signal/testcases/testcases.c
Normal file
150
tools/testing/selftests/arm64/signal/testcases/testcases.c
Normal file
|
@ -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;
|
||||||
|
}
|
100
tools/testing/selftests/arm64/signal/testcases/testcases.h
Normal file
100
tools/testing/selftests/arm64/signal/testcases/testcases.h
Normal file
|
@ -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
|
Loading…
Reference in a new issue