On 4/5/24 1:44 PM, Dev Jain wrote: > This patch introduces two signal tests, and generic test wrappers similar to > selftests/arm64/signal directory, along with the mangling testcases found > therein. arm_cpsr, dumped by the kernel to user space in the ucontext structure > to the signal handler, is mangled with. The kernel must spot this illegal > attempt and the testcases are expected to terminate via SEGV. > > Signed-off-by: Dev Jain <dev.jain@xxxxxxx> > --- > .../selftests/arm/signal/test_signals.c | 27 ++ > .../selftests/arm/signal/test_signals.h | 74 +++++ > .../selftests/arm/signal/test_signals_utils.c | 257 ++++++++++++++++++ > .../selftests/arm/signal/test_signals_utils.h | 128 +++++++++ > .../signal/testcases/mangle_cpsr_aif_bits.c | 33 +++ > .../mangle_cpsr_invalid_compat_toggle.c | 29 ++ Too many files/tests in one patch. Break this patch logically into multiple tests for easy to review and follow. > 6 files changed, 548 insertions(+) > create mode 100644 tools/testing/selftests/arm/signal/test_signals.c > create mode 100644 tools/testing/selftests/arm/signal/test_signals.h > create mode 100644 tools/testing/selftests/arm/signal/test_signals_utils.c > create mode 100644 tools/testing/selftests/arm/signal/test_signals_utils.h > create mode 100644 tools/testing/selftests/arm/signal/testcases/mangle_cpsr_aif_bits.c > create mode 100644 tools/testing/selftests/arm/signal/testcases/mangle_cpsr_invalid_compat_toggle.c > > diff --git a/tools/testing/selftests/arm/signal/test_signals.c b/tools/testing/selftests/arm/signal/test_signals.c > new file mode 100644 > index 000000000000..1ecf1e9f041c > --- /dev/null > +++ b/tools/testing/selftests/arm/signal/test_signals.c > @@ -0,0 +1,27 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright (C) 2024 ARM Limited > + * > + * Generic test wrapper for arm 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 = &tde; > + > +int main(int argc, char *argv[]) > +{ > + ksft_print_msg("%s :: %s\n", current->name, current->descr); > + if (test_setup(current) && test_init(current)) { > + test_run(current); > + test_cleanup(current); > + } > + test_result(current); > + > + return current->result; > +} This test isn't TAP compliant. Please make this and all tests TAP compilant. The 1/4 patch has example of TAP usage. > diff --git a/tools/testing/selftests/arm/signal/test_signals.h b/tools/testing/selftests/arm/signal/test_signals.h > new file mode 100644 > index 000000000000..bbd147127d66 > --- /dev/null > +++ b/tools/testing/selftests/arm/signal/test_signals.h > @@ -0,0 +1,74 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > +/* Copyright (C) 2024 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 from the tree. > + */ > +#include <asm/ptrace.h> > +#include <asm/hwcap.h> > + > +/* > + * 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; > + > + 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; > + unsigned int result; > + /* optional sa_flags for the installed handler */ > + int sa_flags; > + ucontext_t saved_uc; > + /* used by get_current_ctx() */ > + size_t live_sz; > + ucontext_t *live_uc; > + volatile sig_atomic_t live_uc_valid; > + /* optional test private data */ > + void *priv; > + > + /* a custom setup: called alternatively to default_setup */ > + int (*setup)(struct tdescr *td); > + /* a custom init: called by default test init after test_setup */ > + bool (*init)(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 starting test */ > + 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/arm/signal/test_signals_utils.c b/tools/testing/selftests/arm/signal/test_signals_utils.c > new file mode 100644 > index 000000000000..96aeb11de151 > --- /dev/null > +++ b/tools/testing/selftests/arm/signal/test_signals_utils.c > @@ -0,0 +1,257 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* Copyright (C) 2024 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 <asm/unistd.h> > + > +#include <kselftest.h> > + > +#include "test_signals.h" > +#include "test_signals_utils.h" > + > + > +extern struct tdescr *current; > + > +static int sig_copyctx = SIGTRAP; > + > +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->result == KSFT_SKIP) { > + fprintf(stderr, "==>> completed. SKIP.\n"); > + } else if (td->pass) { > + fprintf(stderr, "==>> completed. PASS(1)\n"); > + td->result = KSFT_PASS; > + } else { > + fprintf(stdout, "==>> completed. FAIL(0)\n"); > + td->result = KSFT_FAIL; > + } > + > + if (force_exit) > + exit(td->result); > +} > + > +/* > + * 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) > +{ > + > + /* Mangling PC to avoid loops on original SIGILL */ > + ((ucontext_t *)uc)->uc_mcontext.arm_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%lX si_addr@:%p si_code:%d token@:%p offset:%d\n", > + ((ucontext_t *)uc)->uc_mcontext.arm_sp, > + si->si_addr, si->si_code, td->token, td->token - si->si_addr); > + > + /* > + * 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 == sig_copyctx && current->live_uc) { > + fprintf(stderr, "Handled SIG_COPYCTX\n"); > + } else { > + if (signum == SIGALRM && current->timeout) { > + fprintf(stderr, "-- Timeout !\n"); > + } else { > + fprintf(stderr, > + "-- RX UNEXPECTED SIGNAL: %d code %d address %p\n", > + signum, si->si_code, si->si_addr); > + } > + 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); > +} > + > +int test_init(struct tdescr *td) > +{ > + if (td->sig_trig == sig_copyctx) { > + fprintf(stdout, > + "Signal %d is RESERVED, cannot be used as a trigger. Aborting\n", > + sig_copyctx); > + return 0; > + } > + /* just in case */ > + unblock_signal(sig_copyctx); > + > + td->minsigstksz = getauxval(AT_MINSIGSTKSZ); > + if (!td->minsigstksz) > + td->minsigstksz = MINSIGSTKSZ; > + fprintf(stderr, "Detected MINSTKSIGSZ:%d\n", td->minsigstksz); > + > + /* Perform test specific additional initialization */ > + if (td->init && !td->init(td)) { > + fprintf(stderr, "FAILED Testcase initialization.\n"); > + return 0; > + } > + td->initialized = 1; > + fprintf(stderr, "Testcase initialized.\n"); > + > + 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); > + > + /* Default result is FAIL if test setup fails */ > + td->result = KSFT_FAIL; > + if (td->setup) > + return td->setup(td); > + else > + return default_setup(td); > +} > + > +int test_run(struct tdescr *td) > +{ > + if (td->trigger) > + return td->trigger(td); > + else if (td->sig_trig) > + return default_trigger(td); > + else > + return td->run(td, NULL, NULL); > +} > + > +void test_result(struct tdescr *td) > +{ > + if (td->initialized && td->result != KSFT_SKIP && 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/arm/signal/test_signals_utils.h b/tools/testing/selftests/arm/signal/test_signals_utils.h > new file mode 100644 > index 000000000000..386dcc6c268d > --- /dev/null > +++ b/tools/testing/selftests/arm/signal/test_signals_utils.h > @@ -0,0 +1,128 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > +/* Copyright (C) 2024 ARM Limited */ > + > +#ifndef __TEST_SIGNALS_UTILS_H__ > +#define __TEST_SIGNALS_UTILS_H__ > + > +#include <assert.h> > +#include <stdio.h> > +#include <string.h> > + > +#include <linux/compiler.h> > +#include "test_signals.h" > + > +int test_init(struct tdescr *td); > +int test_setup(struct tdescr *td); > +void test_cleanup(struct tdescr *td); > +int test_run(struct tdescr *td); > +void test_result(struct tdescr *td); > + > +/* > + * Obtaining a valid and full-blown ucontext_t from userspace is tricky: > + * libc getcontext does() not save all the regs and messes with some of > + * them (pstate value in particular is not reliable). > + * > + * Here we use a service signal to grab the ucontext_t from inside a > + * dedicated signal handler, since there, it is populated by Kernel > + * itself in setup_sigframe(). The grabbed context is then stored and > + * made available in td->live_uc. > + * > + * As service-signal is used a SIGTRAP induced by a 'brk' instruction, > + * because here we have to avoid syscalls to trigger the signal since > + * they would cause any SVE sigframe content (if any) to be removed. > + * > + * Anyway this function really serves a dual purpose: > + * > + * 1. grab a valid sigcontext into td->live_uc for result analysis: in > + * such case it returns 1. > + * > + * 2. detect if, somehow, a previously grabbed live_uc context has been > + * used actively with a sigreturn: in such a case the execution would have > + * magically resumed in the middle of this function itself (seen_already==1): > + * in such a case return 0, since in fact we have not just simply grabbed > + * the context. > + * > + * This latter case is useful to detect when a fake_sigreturn test-case has > + * unexpectedly survived without hitting a SEGV. > + * > + * Note that the case of runtime dynamically sized sigframes (like in SVE > + * context) is still NOT addressed: sigframe size is supposed to be fixed > + * at sizeof(ucontext_t). > + */ > +static __always_inline bool get_current_context(struct tdescr *td, > + ucontext_t *dest_uc, > + size_t dest_sz) > +{ > + static volatile bool seen_already; > + int i; > + char *uc = (char *)dest_uc; > + > + assert(td && dest_uc); > + /* it's a genuine invocation..reinit */ > + seen_already = 0; > + td->live_uc_valid = 0; > + td->live_sz = dest_sz; > + > + /* > + * This is a memset() but we don't want the compiler to > + * optimise it into either instructions or a library call > + * which might be incompatible with streaming mode. > + */ > + for (i = 0; i < td->live_sz; i++) { > + uc[i] = 0; > + OPTIMIZER_HIDE_VAR(uc[0]); > + } > + > + td->live_uc = dest_uc; > + /* > + * Grab ucontext_t triggering a SIGTRAP. > + * > + * Note that: > + * - live_uc_valid is declared volatile sig_atomic_t in > + * struct tdescr since it will be changed inside the > + * sig_copyctx handler > + * - the additional 'memory' clobber is there to avoid possible > + * compiler's assumption on live_uc_valid and the content > + * pointed by dest_uc, which are all changed inside the signal > + * handler > + * - BRK causes a debug exception which is handled by the Kernel > + * and finally causes the SIGTRAP signal to be delivered to this > + * test thread. Since such delivery happens on the ret_to_user() > + * /do_notify_resume() debug exception return-path, we are sure > + * that the registered SIGTRAP handler has been run to completion > + * before the execution path is restored here: as a consequence > + * we can be sure that the volatile sig_atomic_t live_uc_valid > + * carries a meaningful result. Being in a single thread context > + * we'll also be sure that any access to memory modified by the > + * handler (namely ucontext_t) will be visible once returned. > + * - note that since we are using a breakpoint instruction here > + * to cause a SIGTRAP, the ucontext_t grabbed from the signal > + * handler would naturally contain a PC pointing exactly to this > + * BRK line, which means that, on return from the signal handler, > + * or if we place the ucontext_t on the stack to fake a sigreturn, > + * we'll end up in an infinite loop of BRK-SIGTRAP-handler. > + * For this reason we take care to artificially move forward the > + * PC to the next instruction while inside the signal handler. > + */ > + asm volatile ("brk #666" > + : "+m" (*dest_uc) > + : > + : "memory"); > + > + /* > + * If we get here with seen_already==1 it implies the td->live_uc > + * context has been used to get back here....this probably means > + * a test has failed to cause a SEGV...anyway live_uc does not > + * point to a just acquired copy of ucontext_t...so return 0 > + */ > + if (seen_already) { > + fprintf(stdout, > + "Unexpected successful sigreturn detected: live_uc is stale !\n"); > + return 0; > + } > + seen_already = 1; > + > + return td->live_uc_valid; > +} > + > +#endif > diff --git a/tools/testing/selftests/arm/signal/testcases/mangle_cpsr_aif_bits.c b/tools/testing/selftests/arm/signal/testcases/mangle_cpsr_aif_bits.c > new file mode 100644 > index 000000000000..f422cd11ccf2 > --- /dev/null > +++ b/tools/testing/selftests/arm/signal/testcases/mangle_cpsr_aif_bits.c > @@ -0,0 +1,33 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright (C) 2024 ARM Limited > + * > + * Try to mangle the ucontext from inside a signal handler, mangling the > + * AIF bits in an illegal manner: this attempt must be spotted by Kernel > + * and the test case is expected to be terminated via SEGV. > + * > + */ > + > +#include "test_signals_utils.h" > + > +static int mangle_invalid_cpsr_run(struct tdescr *td, siginfo_t *si, > + ucontext_t *uc) > +{ > + > + /* > + * This config should trigger a SIGSEGV by Kernel when it checks > + * the sigframe consistency in valid_user_regs() routine. > + */ > + uc->uc_mcontext.arm_cpsr |= PSR_A_BIT | PSR_I_BIT | PSR_F_BIT; > + > + return 1; > +} > + > +struct tdescr tde = { > + .sanity_disabled = true, > + .name = "MANGLE_CPSR_INVALID_AIF_BITS", > + .descr = "Mangling uc_mcontext with INVALID AIF_BITS", > + .sig_trig = SIGUSR1, > + .sig_ok = SIGSEGV, > + .run = mangle_invalid_cpsr_run, > +}; > diff --git a/tools/testing/selftests/arm/signal/testcases/mangle_cpsr_invalid_compat_toggle.c b/tools/testing/selftests/arm/signal/testcases/mangle_cpsr_invalid_compat_toggle.c > new file mode 100644 > index 000000000000..cb7eb8aec7f2 > --- /dev/null > +++ b/tools/testing/selftests/arm/signal/testcases/mangle_cpsr_invalid_compat_toggle.c > @@ -0,0 +1,29 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright (C) 2024 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" > + > +static int mangle_invalid_cpsr_run(struct tdescr *td, siginfo_t *si, > + ucontext_t *uc) > +{ > + > + /* This config should trigger a SIGSEGV by Kernel */ > + uc->uc_mcontext.arm_cpsr ^= MODE32_BIT; > + > + return 1; > +} > + > +struct tdescr tde = { > + .sanity_disabled = true, > + .name = "MANGLE_CPSR_INVALID_STATE_TOGGLE", > + .descr = "Mangling uc_mcontext with INVALID STATE_TOGGLE", > + .sig_trig = SIGUSR1, > + .sig_ok = SIGSEGV, > + .run = mangle_invalid_cpsr_run, > +}; -- BR, Muhammad Usama Anjum