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)
|
||||
|
||||
ifneq (,$(filter $(ARCH),aarch64 arm64))
|
||||
ARM64_SUBTARGETS ?= tags
|
||||
ARM64_SUBTARGETS ?= tags signal
|
||||
else
|
||||
ARM64_SUBTARGETS :=
|
||||
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