This series introduces the selftests/arm directory, which tests 32 and 64-bit kernel compatibility with 32-bit ELFs running on the Aarch platform. The need for this bucket of tests is that 32 bit applications built on legacy ARM architecture must not break on the new Aarch64 platforms and the 64-bit kernel. The kernel must emulate the data structures, system calls and the registers according to Aarch32, when running a 32-bit process; this directory fills that testing requirement.
One may find similarity between this directory and selftests/arm64; it is advisable to refer to that since a lot has been pulled from there itself.
The mm directory includes a test for checking 4GB limit of the virtual address space of a process.
The signal directory contains two tests, following a common theme: mangle with arm_cpsr, dumped by the kernel to user space while invoking the signal handler; kernel must spot this illegal attempt and terminate the program by SEGV.
The elf directory includes a test for checking the 32-bit status of the ELF.
The abi directory includes two ptrace tests, in the first, a 32-bit parent debugs a 32-bit child, and in the second, a 64-bit parent debugs a 32-bit child. The second test will be skipped when running on a 32-bit kernel.
Credits to Mark Brown for suggesting this work.
Testing: The series has been tested on the Aarch64 kernel. For the Aarch32 kernel, I used qemu-system-arm with machine 'vexpress-a15', along with a buildroot rootfs; the individual statically built tests pass on that, but building the entire test suite on that remains untested, due to my lack of experience with qemu and rootfses. Since I have done some changes in selftests/arm64, I have tested that those tests do not break.
v2->v3: - mm, elf: Split into multiple testcases - Eliminate copying in signal/ using ifdeffery and pulling from selftests/arm64 - Delete config file, since it does not make sense for testing a 32-bit kernel - Split ptrace in selftests/arm64, and pull some stuff from there - Add abi tests containing ptrace and ptrace_64 - Fix build warnings in selftests/arm64 (can be applied independent of this series)
v1->v2: - Formatting changes - Add .gitignore files and config file
v1: - https://lore.kernel.org/all/20240405084410.256788-1-dev.jain@arm.com/
Dev Jain (9): selftests/arm: Add mm test selftests/arm: Add elf test selftests: arm, arm64: Use ifdeffery to pull signal infrastructure selftests/arm: Add signal tests selftests/arm64: Fix build warnings for ptrace selftests/arm64: Split ptrace, use ifdeffery selftests/arm: Add ptrace test selftests/arm: Add ptrace_64 test selftests: Add build infrastructure along with README
tools/testing/selftests/Makefile | 1 + tools/testing/selftests/arm/Makefile | 56 ++++++++ tools/testing/selftests/arm/README | 32 +++++ tools/testing/selftests/arm/abi/.gitignore | 4 + tools/testing/selftests/arm/abi/Makefile | 26 ++++ tools/testing/selftests/arm/abi/ptrace.c | 82 +++++++++++ tools/testing/selftests/arm/abi/ptrace.h | 57 ++++++++ tools/testing/selftests/arm/abi/ptrace_64.c | 91 ++++++++++++ .../selftests/arm/abi/trivial_32bit_program.c | 14 ++ tools/testing/selftests/arm/elf/.gitignore | 2 + tools/testing/selftests/arm/elf/Makefile | 6 + tools/testing/selftests/arm/elf/parse_elf.c | 77 ++++++++++ tools/testing/selftests/arm/mm/.gitignore | 2 + tools/testing/selftests/arm/mm/Makefile | 6 + tools/testing/selftests/arm/mm/compat_va.c | 89 ++++++++++++ tools/testing/selftests/arm/signal/.gitignore | 3 + tools/testing/selftests/arm/signal/Makefile | 30 ++++ .../selftests/arm/signal/test_signals.c | 2 + .../selftests/arm/signal/test_signals.h | 2 + .../selftests/arm/signal/test_signals_utils.c | 2 + .../selftests/arm/signal/test_signals_utils.h | 2 + .../testcases/mangle_cpsr_invalid_aif_bits.c | 33 +++++ .../mangle_cpsr_invalid_compat_toggle.c | 29 ++++ tools/testing/selftests/arm64/abi/ptrace.c | 121 ++-------------- tools/testing/selftests/arm64/abi/ptrace.h | 135 ++++++++++++++++++ .../selftests/arm64/signal/test_signals.h | 12 ++ .../arm64/signal/test_signals_utils.c | 51 +++++-- .../arm64/signal/test_signals_utils.h | 3 + 28 files changed, 850 insertions(+), 120 deletions(-) create mode 100644 tools/testing/selftests/arm/Makefile create mode 100644 tools/testing/selftests/arm/README create mode 100644 tools/testing/selftests/arm/abi/.gitignore create mode 100644 tools/testing/selftests/arm/abi/Makefile create mode 100644 tools/testing/selftests/arm/abi/ptrace.c create mode 100644 tools/testing/selftests/arm/abi/ptrace.h create mode 100644 tools/testing/selftests/arm/abi/ptrace_64.c create mode 100644 tools/testing/selftests/arm/abi/trivial_32bit_program.c create mode 100644 tools/testing/selftests/arm/elf/.gitignore create mode 100644 tools/testing/selftests/arm/elf/Makefile create mode 100644 tools/testing/selftests/arm/elf/parse_elf.c create mode 100644 tools/testing/selftests/arm/mm/.gitignore create mode 100644 tools/testing/selftests/arm/mm/Makefile create mode 100644 tools/testing/selftests/arm/mm/compat_va.c create mode 100644 tools/testing/selftests/arm/signal/.gitignore create mode 100644 tools/testing/selftests/arm/signal/Makefile 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_invalid_aif_bits.c create mode 100644 tools/testing/selftests/arm/signal/testcases/mangle_cpsr_invalid_compat_toggle.c create mode 100644 tools/testing/selftests/arm64/abi/ptrace.h
This patch tests the 4GB VA restriction for 32-bit processes; it is required to test the compat layer, whether the kernel knows that it is running a 32-bit process or not. Chunks are allocated until the VA gets exhausted; mmap must fail beyond 4GB. This is asserted against the VA mappings found in /proc/self/maps.
Signed-off-by: Dev Jain dev.jain@arm.com --- v2->v3: - Split into multiple testcases
tools/testing/selftests/arm/mm/compat_va.c | 89 ++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 tools/testing/selftests/arm/mm/compat_va.c
diff --git a/tools/testing/selftests/arm/mm/compat_va.c b/tools/testing/selftests/arm/mm/compat_va.c new file mode 100644 index 000000000000..20aa419eff29 --- /dev/null +++ b/tools/testing/selftests/arm/mm/compat_va.c @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2024 ARM Limited + * + * Author : Dev Jain dev.jain@arm.com + * + * Tests 4GB VA restriction for 32 bit process + */ + +#define _GNU_SOURCE +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/mman.h> + +#include <linux/sizes.h> +#include <kselftest.h> + +#define MAP_CHUNK_SIZE SZ_1M +#define NR_CHUNKS_4G ((SZ_1G / MAP_CHUNK_SIZE) * 4) /* prevent overflow */ + +static int validate_address_hint(void) +{ + char *ptr; + + ptr = mmap((void *) (1UL << 29), MAP_CHUNK_SIZE, PROT_READ | + PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + + if (ptr == MAP_FAILED) + return 0; + + return -1; +} + +int main(int argc, char *argv[]) +{ + char *ptr[NR_CHUNKS_4G + 3]; + char line[1000]; + int chunks; + FILE *file; + int ret; + int i; + + ksft_print_header(); + ksft_set_plan(2); + + /* try allocation beyond 4 GB */ + for (i = 0; i < NR_CHUNKS_4G + 3; ++i) { + ptr[i] = mmap(NULL, MAP_CHUNK_SIZE, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + + if (ptr[i] == MAP_FAILED) { + if (validate_address_hint()) + ksft_exit_fail_msg("VA exhaustion failed\n"); + break; + } + } + + chunks = i; + + ret = (chunks < NR_CHUNKS_4G); + ksft_test_result(ret, "mmapped chunks under 4GB\n"); + + /* parse /proc/self/maps, confirm 32 bit VA mappings */ + file = fopen("/proc/self/maps", "r"); + if (!file) + ksft_exit_fail_perror("/proc/self/maps"); + + ret = 0; + while (fgets(line, sizeof(line), file)) { + const char *whitespace_loc, *hyphen_loc; + + hyphen_loc = strchr(line, '-'); + whitespace_loc = strchr(line, ' '); + + if (!(hyphen_loc && whitespace_loc)) + ksft_exit_fail_msg("Unexpected format\n"); + + ret |= ((hyphen_loc - line > 8) || + (whitespace_loc - hyphen_loc > 9)); + } + + ksft_test_result(!ret, "Memory map within 32 bits\n"); + + for (i = 0; i < chunks; ++i) + munmap(ptr[i], MAP_CHUNK_SIZE); + + ksft_finished(); +}
This patch introduces an ELF parsing test. A basic sanity check is required to ensure that we are actually testing a 32-bit build.
Signed-off-by: Dev Jain dev.jain@arm.com --- v2->v3: - Introduce two more testcases
tools/testing/selftests/arm/elf/parse_elf.c | 77 +++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 tools/testing/selftests/arm/elf/parse_elf.c
diff --git a/tools/testing/selftests/arm/elf/parse_elf.c b/tools/testing/selftests/arm/elf/parse_elf.c new file mode 100644 index 000000000000..6eb8fb91f6a7 --- /dev/null +++ b/tools/testing/selftests/arm/elf/parse_elf.c @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2024 ARM Limited + * + * Author : Dev Jain dev.jain@arm.com + * + * Parse elf header to confirm 32-bit process + */ + +#define _GNU_SOURCE +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <elf.h> +#include <stdint.h> + +#include <kselftest.h> + +/* The ELF file header. This appears at the start of every ELF file. */ + +struct elf_header { + unsigned char e_ident[16]; /* Magic number and other info */ + uint16_t e_type; /* Object file type */ + uint16_t e_machine; /* Architecture */ + uint32_t e_version; /* Object file version */ + uint32_t e_entry; /* Entry point virtual address */ + uint32_t e_phoff; /* Program header table file offset */ + uint32_t e_shoff; /* Section header table file offset */ + uint32_t e_flags; /* Processor-specific flags */ + uint16_t e_ehsize; /* ELF header size in bytes */ + uint16_t e_phentsize; /* Program header table entry size */ + uint16_t e_phnum; /* Program header table entry count */ + uint16_t e_shentsize; /* Section header table entry size */ + uint16_t e_shnum; /* Section header table entry count */ + uint16_t e_shstrndx; /* Section header string table index */ +}; + +#define ELFCLASS32 1 +#define EM_ARM 40 + +void read_elf_header(const char *elfFile) +{ + struct elf_header header; + FILE *file; + int ret; + + file = fopen(elfFile, "r"); + if (!file) + ksft_exit_fail_perror("/proc/self/exe"); + + /* store header in struct */ + if (fread(&header, 1, sizeof(header), file) != sizeof(header)) + ksft_exit_fail_perror("fread"); + + if (fclose(file)) + ksft_exit_fail_perror("fclose"); + + /* sanity check: does it really follow ELF format */ + ret = (header.e_ident[0] == 0x7f && header.e_ident[1] == 'E' + && header.e_ident[2] == 'L' && header.e_ident[3] == 'F'); + + ksft_test_result(ret == 1, "Follows ELF format\n"); + + ksft_test_result(header.e_ident[4] == ELFCLASS32, "ELF is 32 bit\n"); + + ksft_test_result(header.e_machine == EM_ARM, "Machine type\n"); +} + +int main(int argc, char *argv[]) +{ + ksft_print_header(); + ksft_set_plan(3); + + read_elf_header("/proc/self/exe"); + + ksft_finished(); +}
Use ifdeffery to guard code chunks meant specifically for arm64, in preparation for putting signal tests in selftests/arm.
Signed-off-by: Dev Jain dev.jain@arm.com --- .../selftests/arm/signal/test_signals.c | 2 + .../selftests/arm/signal/test_signals.h | 2 + .../selftests/arm/signal/test_signals_utils.c | 2 + .../selftests/arm/signal/test_signals_utils.h | 2 + .../selftests/arm64/signal/test_signals.h | 12 +++++ .../arm64/signal/test_signals_utils.c | 51 +++++++++++++++---- .../arm64/signal/test_signals_utils.h | 3 ++ 7 files changed, 65 insertions(+), 9 deletions(-) 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
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..6b47c26ee218 --- /dev/null +++ b/tools/testing/selftests/arm/signal/test_signals.c @@ -0,0 +1,2 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include "../../arm64/signal/test_signals.c" 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..946913d29636 --- /dev/null +++ b/tools/testing/selftests/arm/signal/test_signals.h @@ -0,0 +1,2 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include "../../arm64/signal/test_signals.h" 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..2b16d53545be --- /dev/null +++ b/tools/testing/selftests/arm/signal/test_signals_utils.c @@ -0,0 +1,2 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include "../../arm64/signal/test_signals_utils.c" 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..a4d61697a8dd --- /dev/null +++ b/tools/testing/selftests/arm/signal/test_signals_utils.h @@ -0,0 +1,2 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include "../../arm64/signal/test_signals_utils.h" diff --git a/tools/testing/selftests/arm64/signal/test_signals.h b/tools/testing/selftests/arm64/signal/test_signals.h index 1e6273d81575..9c1bd6560501 100644 --- a/tools/testing/selftests/arm64/signal/test_signals.h +++ b/tools/testing/selftests/arm64/signal/test_signals.h @@ -14,6 +14,8 @@ #include <asm/ptrace.h> #include <asm/hwcap.h>
+/* Not used by selftests/arm */ +#ifdef __aarch64__ #define __stringify_1(x...) #x #define __stringify(x...) __stringify_1(x)
@@ -43,6 +45,7 @@ enum { #define FEAT_SME (1UL << FSME_BIT) #define FEAT_SME_FA64 (1UL << FSME_FA64_BIT) #define FEAT_SME2 (1UL << FSME2_BIT) +#endif
/* * A descriptor used to describe and configure a test case. @@ -56,10 +59,13 @@ struct tdescr { /* just a name for the test-case; manadatory field */ char *name; char *descr; +/* Not used by selftests/arm */ +#ifdef __aarch64__ unsigned long feats_required; unsigned long feats_incompatible; /* bitmask of effectively supported feats: populated at run-time */ unsigned long feats_supported; +#endif bool initialized; unsigned int minsigstksz; /* signum used as a test trigger. Zero if no trigger-signal is used */ @@ -69,8 +75,11 @@ struct tdescr { * Zero when no signal is expected on success */ int sig_ok; +/* Not used by selftests/arm */ +#ifdef __aarch64__ /* signum expected on unsupported CPU features. */ int sig_unsupp; +#endif /* a timeout in second for test completion */ unsigned int timeout; bool triggered; @@ -79,10 +88,13 @@ struct tdescr { /* optional sa_flags for the installed handler */ int sa_flags; ucontext_t saved_uc; +/* Not used by selftests/arm */ +#ifdef __aarch64__ /* used by get_current_ctx() */ size_t live_sz; ucontext_t *live_uc; volatile sig_atomic_t live_uc_valid; +#endif /* optional test private data */ void *priv;
diff --git a/tools/testing/selftests/arm64/signal/test_signals_utils.c b/tools/testing/selftests/arm64/signal/test_signals_utils.c index 0dc948db3a4a..8396d9748b48 100644 --- a/tools/testing/selftests/arm64/signal/test_signals_utils.c +++ b/tools/testing/selftests/arm64/signal/test_signals_utils.c @@ -17,11 +17,16 @@
#include "test_signals.h" #include "test_signals_utils.h" + +#ifdef __aarch64__ #include "testcases/testcases.h" +#endif
extern struct tdescr *current;
+/* Not used by selftests/arm */ +#ifdef __aarch64__ static int sig_copyctx = SIGTRAP;
static char const *const feats_names[FMAX_END] = { @@ -53,6 +58,7 @@ static inline char *feats_to_string(unsigned long feats)
return feats_string; } +#endif
static void unblock_signal(int signum) { @@ -87,6 +93,7 @@ static void default_result(struct tdescr *td, bool force_exit) * take care of such unexpected situations. */
+#ifdef __aarch64__ static bool handle_signal_unsupported(struct tdescr *td, siginfo_t *si, void *uc) { @@ -108,6 +115,7 @@ static bool handle_signal_unsupported(struct tdescr *td,
return true; } +#endif
static bool handle_signal_trigger(struct tdescr *td, siginfo_t *si, void *uc) @@ -127,10 +135,21 @@ static bool handle_signal_ok(struct tdescr *td, * if sig_trig was defined, it must have been used before getting here. */ assert(!td->sig_trig || td->triggered); + +#ifdef __aarch64__ 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); +#else + 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); +#endif + +#ifdef __aarch64__ + /* * 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 @@ -153,6 +172,7 @@ static bool handle_signal_ok(struct tdescr *td, "si_code != SEGV_ACCERR...test is probably broken!\n"); abort(); } +#endif td->pass = 1; /* * Some tests can lead to SEGV loops: in such a case we want to @@ -165,6 +185,7 @@ static bool handle_signal_ok(struct tdescr *td, return true; }
+#ifdef __aarch64__ static bool handle_signal_copyctx(struct tdescr *td, siginfo_t *si, void *uc_in) { @@ -229,22 +250,31 @@ static bool handle_signal_copyctx(struct tdescr *td,
return true; } +#endif
static void default_handler(int signum, siginfo_t *si, void *uc) { +#ifdef __aarch64__ 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 && + } +#endif + + 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 && + } +#ifdef __aarch64__ + else if (signum == sig_copyctx && current->live_uc && handle_signal_copyctx(current, si, uc)) { fprintf(stderr, "Handled SIG_COPYCTX\n"); - } else { + } +#endif + else { if (signum == SIGALRM && current->timeout) { fprintf(stderr, "-- Timeout !\n"); } else { @@ -280,9 +310,10 @@ static int default_setup(struct tdescr *td) unblock_signal(td->sig_trig); if (td->sig_ok) unblock_signal(td->sig_ok); +#ifdef __aarch64__ if (td->sig_unsupp) unblock_signal(td->sig_unsupp); - +#endif if (td->timeout) { unblock_signal(SIGALRM); alarm(td->timeout); @@ -299,6 +330,12 @@ static inline int default_trigger(struct tdescr *td)
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); + +#ifdef __aarch64__ if (td->sig_trig == sig_copyctx) { fprintf(stdout, "Signal %d is RESERVED, cannot be used as a trigger. Aborting\n", @@ -308,11 +345,6 @@ int test_init(struct tdescr *td) /* 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); - if (td->feats_required || td->feats_incompatible) { td->feats_supported = 0; /* @@ -357,6 +389,7 @@ int test_init(struct tdescr *td) return 0; } } +#endif
/* Perform test specific additional initialization */ if (td->init && !td->init(td)) { diff --git a/tools/testing/selftests/arm64/signal/test_signals_utils.h b/tools/testing/selftests/arm64/signal/test_signals_utils.h index 762c8fe9c54a..50acfc1a1692 100644 --- a/tools/testing/selftests/arm64/signal/test_signals_utils.h +++ b/tools/testing/selftests/arm64/signal/test_signals_utils.h @@ -18,6 +18,8 @@ void test_cleanup(struct tdescr *td); int test_run(struct tdescr *td); void test_result(struct tdescr *td);
+/* Not used by selftests/arm */ +#ifdef __aarch64__ static inline bool feats_ok(struct tdescr *td) { if (td->feats_incompatible & td->feats_supported) @@ -146,3 +148,4 @@ static __always_inline bool get_current_context(struct tdescr *td,
int fake_sigreturn(void *sigframe, size_t sz, int misalign_bytes); #endif +#endif
On Tue, Jun 25, 2024 at 05:54:02PM +0530, Dev Jain wrote:
Use ifdeffery to guard code chunks meant specifically for arm64, in preparation for putting signal tests in selftests/arm.
I've got to say I don't love this but I'm not sure how much better we could do if we want to share the code. Though one thing that did jump out at me was:
+/* Not used by selftests/arm */ +#ifdef __aarch64__ #define __stringify_1(x...) #x #define __stringify(x...) __stringify_1(x) @@ -43,6 +45,7 @@ enum { #define FEAT_SME (1UL << FSME_BIT) #define FEAT_SME_FA64 (1UL << FSME_FA64_BIT) #define FEAT_SME2 (1UL << FSME2_BIT) +#endif
There's a lot of ifdefs around feature handling. Could we reduce the ifdeffery by instead of removing all the code just removing the definitions of the features and the bit that initialises them based on the hwcaps? In general a stubbing out approach like that tends to be cleaner and less fragile.
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@arm.com --- .../testcases/mangle_cpsr_invalid_aif_bits.c | 33 +++++++++++++++++++ .../mangle_cpsr_invalid_compat_toggle.c | 29 ++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 tools/testing/selftests/arm/signal/testcases/mangle_cpsr_invalid_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/testcases/mangle_cpsr_invalid_aif_bits.c b/tools/testing/selftests/arm/signal/testcases/mangle_cpsr_invalid_aif_bits.c new file mode 100644 index 000000000000..ea73a96fb229 --- /dev/null +++ b/tools/testing/selftests/arm/signal/testcases/mangle_cpsr_invalid_aif_bits.c @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * 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..f7ccbccb24e5 --- /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-only +/* + * 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, +};
On Tue, Jun 25, 2024 at 05:54:03PM +0530, 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.
Reviewed-by: Mark Brown broonie@kernel.org
"%s" should have been used in ksft_exit_fail_msg(). Anyways, replace that with the recently introduced ksft_exit_fail_perror(). Also fix no mention of type_name in ksft_test_result_skip().
NOTE: This patch can be applied independently of the series, but the next patch depends on this one.
Fixes: ecaf4d3f734f ("kselftest/arm64: Add test coverage for NT_ARM_TLS") Fixes: cb5aa6379438 ("kselftest/arm64: Add a smoke test for ptracing hardware break/watch points") Signed-off-by: Dev Jain dev.jain@arm.com --- tools/testing/selftests/arm64/abi/ptrace.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/tools/testing/selftests/arm64/abi/ptrace.c b/tools/testing/selftests/arm64/abi/ptrace.c index abe4d58d731d..c83f0441e9d0 100644 --- a/tools/testing/selftests/arm64/abi/ptrace.c +++ b/tools/testing/selftests/arm64/abi/ptrace.c @@ -156,17 +156,17 @@ static void test_hw_debug(pid_t child, int type, const char *type_name) /* Zero is not currently architecturally valid */ ksft_test_result(arch, "%s_arch_set\n", type_name); } else { - ksft_test_result_skip("%s_arch_set\n"); + ksft_test_result_skip("%s_arch_set\n", type_name); } }
static int do_child(void) { if (ptrace(PTRACE_TRACEME, -1, NULL, NULL)) - ksft_exit_fail_msg("PTRACE_TRACEME", strerror(errno)); + ksft_exit_fail_perror("PTRACE_TRACEME");
if (raise(SIGSTOP)) - ksft_exit_fail_msg("raise(SIGSTOP)", strerror(errno)); + ksft_exit_fail_perror("raise(SIGSTOP)");
return EXIT_SUCCESS; }
On Tue, Jun 25, 2024 at 05:54:04PM +0530, Dev Jain wrote:
"%s" should have been used in ksft_exit_fail_msg(). Anyways, replace that with the recently introduced ksft_exit_fail_perror(). Also fix no mention of type_name in ksft_test_result_skip().
Reviewed-by: Mark Brown broonie@kernel.org
but a couple of minor notes if this needs resending (it might be best to send indpendently as well to help it get picked up more quickly.)
NOTE: This patch can be applied independently of the series, but the next patch depends on this one.
Admin notes like this should go after the ---.
if (ptrace(PTRACE_TRACEME, -1, NULL, NULL))
ksft_exit_fail_msg("PTRACE_TRACEME", strerror(errno));
ksft_exit_fail_perror("PTRACE_TRACEME");
if (raise(SIGSTOP))
ksft_exit_fail_msg("raise(SIGSTOP)", strerror(errno));
ksft_exit_fail_perror("raise(SIGSTOP)");
This should ideally be a separate patch, there's no overlap.
Split some of the common code to be used by selftests/arm, into ptrace.h.
Signed-off-by: Dev Jain dev.jain@arm.com --- tools/testing/selftests/arm64/abi/ptrace.c | 121 ++---------------- tools/testing/selftests/arm64/abi/ptrace.h | 135 +++++++++++++++++++++ 2 files changed, 145 insertions(+), 111 deletions(-) create mode 100644 tools/testing/selftests/arm64/abi/ptrace.h
diff --git a/tools/testing/selftests/arm64/abi/ptrace.c b/tools/testing/selftests/arm64/abi/ptrace.c index c83f0441e9d0..9e8494d950fe 100644 --- a/tools/testing/selftests/arm64/abi/ptrace.c +++ b/tools/testing/selftests/arm64/abi/ptrace.c @@ -18,15 +18,22 @@ #include <asm/sigcontext.h> #include <asm/ptrace.h>
+#include "ptrace.h" #include "../../kselftest.h"
#define EXPECTED_TESTS 11
#define MAX_TPIDRS 2
-static bool have_sme(void) +static int do_child(void) { - return getauxval(AT_HWCAP2) & HWCAP2_SME; + if (ptrace(PTRACE_TRACEME, -1, NULL, NULL)) + ksft_exit_fail_perror("PTRACE_TRACEME"); + + if (raise(SIGSTOP)) + ksft_exit_fail_perror("raise(SIGSTOP)"); + + return EXIT_SUCCESS; }
static void test_tpidr(pid_t child) @@ -132,119 +139,11 @@ static void test_tpidr(pid_t child) } }
-static void test_hw_debug(pid_t child, int type, const char *type_name) -{ - struct user_hwdebug_state state; - struct iovec iov; - int slots, arch, ret; - - iov.iov_len = sizeof(state); - iov.iov_base = &state; - - /* Should be able to read the values */ - ret = ptrace(PTRACE_GETREGSET, child, type, &iov); - ksft_test_result(ret == 0, "read_%s\n", type_name); - - if (ret == 0) { - /* Low 8 bits is the number of slots, next 4 bits the arch */ - slots = state.dbg_info & 0xff; - arch = (state.dbg_info >> 8) & 0xf; - - ksft_print_msg("%s version %d with %d slots\n", type_name, - arch, slots); - - /* Zero is not currently architecturally valid */ - ksft_test_result(arch, "%s_arch_set\n", type_name); - } else { - ksft_test_result_skip("%s_arch_set\n", type_name); - } -} - -static int do_child(void) -{ - if (ptrace(PTRACE_TRACEME, -1, NULL, NULL)) - ksft_exit_fail_perror("PTRACE_TRACEME"); - - if (raise(SIGSTOP)) - ksft_exit_fail_perror("raise(SIGSTOP)"); - - return EXIT_SUCCESS; -} - -static int do_parent(pid_t child) +static void run_tests(pid_t child) { - int ret = EXIT_FAILURE; - pid_t pid; - int status; - siginfo_t si; - - /* Attach to the child */ - while (1) { - int sig; - - pid = wait(&status); - if (pid == -1) { - perror("wait"); - goto error; - } - - /* - * This should never happen but it's hard to flag in - * the framework. - */ - if (pid != child) - continue; - - if (WIFEXITED(status) || WIFSIGNALED(status)) - ksft_exit_fail_msg("Child died unexpectedly\n"); - - if (!WIFSTOPPED(status)) - goto error; - - sig = WSTOPSIG(status); - - if (ptrace(PTRACE_GETSIGINFO, pid, NULL, &si)) { - if (errno == ESRCH) - goto disappeared; - - if (errno == EINVAL) { - sig = 0; /* bust group-stop */ - goto cont; - } - - ksft_test_result_fail("PTRACE_GETSIGINFO: %s\n", - strerror(errno)); - goto error; - } - - if (sig == SIGSTOP && si.si_code == SI_TKILL && - si.si_pid == pid) - break; - - cont: - if (ptrace(PTRACE_CONT, pid, NULL, sig)) { - if (errno == ESRCH) - goto disappeared; - - ksft_test_result_fail("PTRACE_CONT: %s\n", - strerror(errno)); - goto error; - } - } - - ksft_print_msg("Parent is %d, child is %d\n", getpid(), child); - test_tpidr(child); test_hw_debug(child, NT_ARM_HW_WATCH, "NT_ARM_HW_WATCH"); test_hw_debug(child, NT_ARM_HW_BREAK, "NT_ARM_HW_BREAK"); - - ret = EXIT_SUCCESS; - -error: - kill(child, SIGKILL); - -disappeared: - return ret; }
int main(void) diff --git a/tools/testing/selftests/arm64/abi/ptrace.h b/tools/testing/selftests/arm64/abi/ptrace.h new file mode 100644 index 000000000000..ae65c58cd3bf --- /dev/null +++ b/tools/testing/selftests/arm64/abi/ptrace.h @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2024 ARM Limited. + */ +#include <errno.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/auxv.h> +#include <sys/prctl.h> +#include <sys/ptrace.h> +#include <sys/types.h> +#include <sys/uio.h> +#include <sys/wait.h> +#include <asm/sigcontext.h> +#include <asm/ptrace.h> + +#include "../../kselftest.h" + +static void run_tests(pid_t child); + +static int do_child(void); + +#ifdef __aarch64__ +static bool have_sme(void) +{ + return getauxval(AT_HWCAP2) & HWCAP2_SME; +} + +static void test_hw_debug(pid_t child, int type, const char *type_name) +{ + struct user_hwdebug_state state; + struct iovec iov; + int slots, arch, ret; + + iov.iov_len = sizeof(state); + iov.iov_base = &state; + + /* Should be able to read the values */ + ret = ptrace(PTRACE_GETREGSET, child, type, &iov); + ksft_test_result(ret == 0, "read_%s\n", type_name); + + if (ret == 0) { + /* Low 8 bits is the number of slots, next 4 bits the arch */ + slots = state.dbg_info & 0xff; + arch = (state.dbg_info >> 8) & 0xf; + + ksft_print_msg("%s version %d with %d slots\n", type_name, + arch, slots); + + /* Zero is not currently architecturally valid */ + ksft_test_result(arch, "%s_arch_set\n", type_name); + } else { + ksft_test_result_skip("%s_arch_set\n", type_name); + } +} +#endif + +static int do_parent(pid_t child) +{ + int ret = EXIT_FAILURE; + pid_t pid; + int status; + siginfo_t si; + + /* Attach to the child */ + while (1) { + int sig; + + pid = wait(&status); + if (pid == -1) { + perror("wait"); + goto error; + } + + /* + * This should never happen but it's hard to flag in + * the framework. + */ + if (pid != child) + continue; + + if (WIFEXITED(status) || WIFSIGNALED(status)) + ksft_exit_fail_msg("Child died unexpectedly\n"); + + if (!WIFSTOPPED(status)) + goto error; + + sig = WSTOPSIG(status); + + if (sig == SIGTRAP) + ksft_print_msg("Child received SIGTRAP\n"); + + if (ptrace(PTRACE_GETSIGINFO, pid, NULL, &si)) { + if (errno == ESRCH) + goto disappeared; + + if (errno == EINVAL) + goto cont; + + ksft_test_result_fail("PTRACE_GETSIGINFO: %s\n", + strerror(errno)); + goto error; + } + + if (sig == SIGSTOP && si.si_code == SI_TKILL && + si.si_pid == pid) + break; + +cont: + /* bust group-stop */ + if (ptrace(PTRACE_CONT, pid, NULL, 0)) { + if (errno == ESRCH) + goto disappeared; + + ksft_test_result_fail("PTRACE_CONT: %s\n", + strerror(errno)); + goto error; + } + } + + ksft_print_msg("Parent is %d, child is %d\n", getpid(), child); + + ret = EXIT_SUCCESS; + run_tests(child); + +error: + kill(child, SIGKILL); + +disappeared: + return ret; +}
On Tue, Jun 25, 2024 at 05:54:05PM +0530, Dev Jain wrote:
--- /dev/null +++ b/tools/testing/selftests/arm64/abi/ptrace.h @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0-only +/*
- Copyright (C) 2024 ARM Limited.
- */
+#include "../../kselftest.h"
+static void run_tests(pid_t child);
+static int do_child(void);
+#ifdef __aarch64__ +static bool have_sme(void) +{
- return getauxval(AT_HWCAP2) & HWCAP2_SME;
+}
+static void test_hw_debug(pid_t child, int type, const char *type_name) +{
- struct user_hwdebug_state state;
- struct iovec iov;
- int slots, arch, ret;
- iov.iov_len = sizeof(state);
- iov.iov_base = &state;
- /* Should be able to read the values */
- ret = ptrace(PTRACE_GETREGSET, child, type, &iov);
- ksft_test_result(ret == 0, "read_%s\n", type_name);
This feels like it's pulling way too much stuff out into the header. I think some of this needs to be a C file that gets built for both architectures with some mechanism they can use to run tests, and a library of any shared tests.
- if (ret == 0) {
/* Low 8 bits is the number of slots, next 4 bits the arch */
slots = state.dbg_info & 0xff;
arch = (state.dbg_info >> 8) & 0xf;
ksft_print_msg("%s version %d with %d slots\n", type_name,
arch, slots);
/* Zero is not currently architecturally valid */
ksft_test_result(arch, "%s_arch_set\n", type_name);
- } else {
ksft_test_result_skip("%s_arch_set\n", type_name);
- }
+} +#endif
+static int do_parent(pid_t child) +{
- int ret = EXIT_FAILURE;
- pid_t pid;
- int status;
- siginfo_t si;
- /* Attach to the child */
- while (1) {
int sig;
pid = wait(&status);
if (pid == -1) {
perror("wait");
goto error;
}
/*
* This should never happen but it's hard to flag in
* the framework.
*/
if (pid != child)
continue;
if (WIFEXITED(status) || WIFSIGNALED(status))
ksft_exit_fail_msg("Child died unexpectedly\n");
if (!WIFSTOPPED(status))
goto error;
sig = WSTOPSIG(status);
if (sig == SIGTRAP)
ksft_print_msg("Child received SIGTRAP\n");
if (ptrace(PTRACE_GETSIGINFO, pid, NULL, &si)) {
if (errno == ESRCH)
goto disappeared;
if (errno == EINVAL)
goto cont;
ksft_test_result_fail("PTRACE_GETSIGINFO: %s\n",
strerror(errno));
goto error;
}
if (sig == SIGSTOP && si.si_code == SI_TKILL &&
si.si_pid == pid)
break;
+cont:
/* bust group-stop */
if (ptrace(PTRACE_CONT, pid, NULL, 0)) {
if (errno == ESRCH)
goto disappeared;
ksft_test_result_fail("PTRACE_CONT: %s\n",
strerror(errno));
goto error;
}
- }
- ksft_print_msg("Parent is %d, child is %d\n", getpid(), child);
- ret = EXIT_SUCCESS;
- run_tests(child);
+error:
- kill(child, SIGKILL);
+disappeared:
- return ret;
+}
2.39.2
For a 32-bit parent debugging a 32-bit child, add tests for reading the TLS registers, and mangling with the mode bits in CPSR.
Signed-off-by: Dev Jain dev.jain@arm.com --- tools/testing/selftests/arm/abi/ptrace.c | 82 ++++++++++++++++++++++++ tools/testing/selftests/arm/abi/ptrace.h | 57 ++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 tools/testing/selftests/arm/abi/ptrace.c create mode 100644 tools/testing/selftests/arm/abi/ptrace.h
diff --git a/tools/testing/selftests/arm/abi/ptrace.c b/tools/testing/selftests/arm/abi/ptrace.c new file mode 100644 index 000000000000..2079065c48fd --- /dev/null +++ b/tools/testing/selftests/arm/abi/ptrace.c @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2024 ARM Limited. + */ +#include <errno.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/auxv.h> +#include <sys/prctl.h> +#include <sys/ptrace.h> +#include <sys/types.h> +#include <sys/uio.h> +#include <sys/wait.h> +#include <asm/sigcontext.h> +#include <asm/ptrace.h> + +#include "ptrace.h" +#include "../../kselftest.h" + +#define EXPECTED_TESTS 6 +#define NUM_TLS_REGS 2 + +static void test_tpidr(pid_t child) +{ + unsigned long read_val[NUM_TLS_REGS]; + struct iovec read_iov; + int ret; + + read_iov.iov_base = read_val; + + /* TLS registers must not be accessible */ + read_iov.iov_len = 2 * sizeof(unsigned long); + ret = ptrace(PTRACE_GETREGSET, child, NT_ARM_TLS, &read_iov); + ksft_test_result(ret != 0, "cannot read TLS\n"); +} + +static void run_tests(pid_t child) +{ + test_tpidr(child); + test_user_regs(child); +} + +static int do_child(void) +{ + if (ptrace(PTRACE_TRACEME, -1, NULL, NULL)) + ksft_exit_fail_perror("PTRACE_TRACEME"); + + if (raise(SIGSTOP)) + ksft_exit_fail_perror("raise(SIGSTOP)"); + + if (raise(SIGSTOP)) + ksft_exit_fail_perror("raise(SIGSTOP)"); + + return EXIT_SUCCESS; +} + +int main(void) +{ + int ret = EXIT_SUCCESS; + pid_t child; + + srandom(getpid()); + + ksft_print_header(); + + ksft_set_plan(EXPECTED_TESTS); + + child = fork(); + if (!child) + return do_child(); + + if (do_parent(child)) + ret = EXIT_FAILURE; + + ksft_print_cnts(); + + return ret; +} diff --git a/tools/testing/selftests/arm/abi/ptrace.h b/tools/testing/selftests/arm/abi/ptrace.h new file mode 100644 index 000000000000..17ba8aa32726 --- /dev/null +++ b/tools/testing/selftests/arm/abi/ptrace.h @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include "../../arm64/abi/ptrace.h" + +/* Do not pull from asm/ptrace.h since the macro names change for 32-bit */ +#define PSR_MODE32_BIT 0x00000010 +#define PSR_MODE_EL1t 0x00000004 + +static void test_user_regs(pid_t child) +{ + unsigned int read_val[18]; + struct iovec read_iov; + int status; + int ret; + + read_iov.iov_base = read_val; + read_iov.iov_len = 18 * sizeof(unsigned int); + + ret = ptrace(PTRACE_GETREGSET, child, NT_PRSTATUS, &read_iov); + ksft_test_result(!ret, "read general-purpose registers\n"); + + /* Change a random user register */ + read_val[2] = read_val[2] + 1; + ret = ptrace(PTRACE_SETREGSET, child, NT_PRSTATUS, &read_iov); + ksft_test_result(!ret, "set user register\n"); + + /* 16th register is the CPSR */ + read_val[16] &= (~PSR_MODE32_BIT); + + ret = ptrace(PTRACE_SETREGSET, child, NT_PRSTATUS, &read_iov); + ksft_test_result(ret, "cannot toggle MODE32 bit\n"); + + ret = ptrace(PTRACE_CONT, child, NULL, 0); + if (ret) { + perror("ptrace"); + goto error; + } + + if (wait(&status) == -1) { + perror("wait"); + goto error; + } + + read_val[16] = 0; + + ret = ptrace(PTRACE_GETREGSET, child, NT_PRSTATUS, &read_iov); + ksft_test_result(!ret, "read general-purpose registers again\n"); + + read_val[16] |= PSR_MODE_EL1t; + ret = ptrace(PTRACE_SETREGSET, child, NT_PRSTATUS, &read_iov); + ksft_test_result(ret, "cannot escalate privilege\n"); + return; + +error: + kill(child, SIGKILL); +} + +
For a 64-bit parent debugging a 32-bit child, add tests for reading the TLS registers, and mangling with the mode bits in CPSR.
Signed-off-by: Dev Jain dev.jain@arm.com --- tools/testing/selftests/arm/abi/ptrace_64.c | 91 +++++++++++++++++++ .../selftests/arm/abi/trivial_32bit_program.c | 14 +++ 2 files changed, 105 insertions(+) create mode 100644 tools/testing/selftests/arm/abi/ptrace_64.c create mode 100644 tools/testing/selftests/arm/abi/trivial_32bit_program.c
diff --git a/tools/testing/selftests/arm/abi/ptrace_64.c b/tools/testing/selftests/arm/abi/ptrace_64.c new file mode 100644 index 000000000000..97398cf59052 --- /dev/null +++ b/tools/testing/selftests/arm/abi/ptrace_64.c @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2024 ARM Limited. + * + * Inspired from selftests/arm64/abi/ptrace.c + * + * Author: Dev Jain dev.jain@arm.com + * + */ + +#include <errno.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/auxv.h> +#include <sys/prctl.h> +#include <sys/ptrace.h> +#include <sys/types.h> +#include <sys/uio.h> +#include <sys/wait.h> +#include <asm/sigcontext.h> +#include <asm/ptrace.h> + +#include "ptrace.h" +#include "../../kselftest.h" + +#define EXPECTED_TESTS 12 +#define NUM_TLS_REGS 2 + +static void test_tpidr(pid_t child) +{ + unsigned int read_val[NUM_TLS_REGS]; + struct iovec read_iov; + int ret; + + memset(read_val, 0, sizeof(read_val)); + + read_iov.iov_base = read_val; + + /* Should be able to read a single TLS register... */ + read_iov.iov_len = 2 * sizeof(unsigned int); + ret = ptrace(PTRACE_GETREGSET, child, NT_ARM_TLS, &read_iov); + ksft_test_result(ret == 0, "read_tls\n"); + + ksft_test_result(read_val[0], "read_tls_1\n"); + ksft_test_result(!read_val[1], "cannot read_tls_2\n"); +} + +static void run_tests(pid_t child) +{ + test_tpidr(child); + test_user_regs(child); + test_hw_debug(child, NT_ARM_HW_WATCH, "NT_ARM_HW_WATCH"); + test_hw_debug(child, NT_ARM_HW_BREAK, "NT_ARM_HW_BREAK"); +} + +static int do_child(void) +{ + if (ptrace(PTRACE_TRACEME, -1, NULL, NULL)) + ksft_exit_fail_perror("PTRACE_TRACEME"); + + /* SIGTRAP makes the child stop after exec; do_parent() resumes it */ + execv("trivial_32bit_program", NULL); + return EXIT_SUCCESS; +} + +int main(void) +{ + int ret = EXIT_SUCCESS; + pid_t child; + + srandom(getpid()); + + ksft_print_header(); + + ksft_set_plan(EXPECTED_TESTS); + + child = fork(); + if (!child) + return do_child(); + + if (do_parent(child)) + ret = EXIT_FAILURE; + + ksft_print_cnts(); + + return ret; +} diff --git a/tools/testing/selftests/arm/abi/trivial_32bit_program.c b/tools/testing/selftests/arm/abi/trivial_32bit_program.c new file mode 100644 index 000000000000..c5ad7abb23ed --- /dev/null +++ b/tools/testing/selftests/arm/abi/trivial_32bit_program.c @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2024 ARM Limited. + */ + +#include <stdio.h> +#include <signal.h> + +int main(void) +{ + raise(SIGSTOP); + raise(SIGSTOP); + return 0; +}
On Tue, Jun 25, 2024 at 05:54:07PM +0530, Dev Jain wrote:
+static int do_child(void) +{
- if (ptrace(PTRACE_TRACEME, -1, NULL, NULL))
ksft_exit_fail_perror("PTRACE_TRACEME");
- /* SIGTRAP makes the child stop after exec; do_parent() resumes it */
- execv("trivial_32bit_program", NULL);
- return EXIT_SUCCESS;
+}
We should check that the execv() worked, one thing that can go wrong is partially installing the tests or trying to run the tests in a different directory to the one where everything is installed. I'd be inclined to specify the path as ./ too to avoid surprises.
Add arm target, individual Makefile targets, and instructions to build the tests, along with .gitignore files. All the Makefiles are similar to selftests/arm64, except abi: use TEST_CUSTOM_PROGS to override the make rule from lib.mk. Also, do not build ptrace_64 if we are running a 32-bit kernel.
Signed-off-by: Dev Jain dev.jain@arm.com --- v2->v3: - Add abi Makefile and .gitignore - Remove config file
v1->v2: - Add config file
tools/testing/selftests/Makefile | 1 + tools/testing/selftests/arm/Makefile | 56 +++++++++++++++++++ tools/testing/selftests/arm/README | 32 +++++++++++ tools/testing/selftests/arm/abi/.gitignore | 4 ++ tools/testing/selftests/arm/abi/Makefile | 26 +++++++++ tools/testing/selftests/arm/elf/.gitignore | 2 + tools/testing/selftests/arm/elf/Makefile | 6 ++ tools/testing/selftests/arm/mm/.gitignore | 2 + tools/testing/selftests/arm/mm/Makefile | 6 ++ tools/testing/selftests/arm/signal/.gitignore | 3 + tools/testing/selftests/arm/signal/Makefile | 30 ++++++++++ 11 files changed, 168 insertions(+) create mode 100644 tools/testing/selftests/arm/Makefile create mode 100644 tools/testing/selftests/arm/README create mode 100644 tools/testing/selftests/arm/abi/.gitignore create mode 100644 tools/testing/selftests/arm/abi/Makefile create mode 100644 tools/testing/selftests/arm/elf/.gitignore create mode 100644 tools/testing/selftests/arm/elf/Makefile create mode 100644 tools/testing/selftests/arm/mm/.gitignore create mode 100644 tools/testing/selftests/arm/mm/Makefile create mode 100644 tools/testing/selftests/arm/signal/.gitignore create mode 100644 tools/testing/selftests/arm/signal/Makefile
diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index 9039f3709aff..d7420825f165 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -1,6 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 TARGETS += alsa TARGETS += amd-pstate +TARGETS += arm TARGETS += arm64 TARGETS += bpf TARGETS += breakpoints diff --git a/tools/testing/selftests/arm/Makefile b/tools/testing/selftests/arm/Makefile new file mode 100644 index 000000000000..54e44c4a62bf --- /dev/null +++ b/tools/testing/selftests/arm/Makefile @@ -0,0 +1,56 @@ +# SPDX-License-Identifier: GPL-2.0 + +ARCH ?= $(shell uname -m 2>/dev/null || echo not) + +ifneq (,$(filter $(ARCH),aarch64 arm64 arm armv7l armv8l)) +ARM_SUBTARGETS ?= abi elf mm signal +else +ARM_SUBTARGETS := +endif + +CFLAGS := -Wall -O2 -g -static + +# A proper top_srcdir is needed by KSFT(lib.mk) +top_srcdir = $(realpath ../../../../) + +# Additional include paths needed by kselftest.h and local headers +CFLAGS += -I$(top_srcdir)/tools/testing/selftests/ + +CFLAGS += -I$(top_srcdir)/tools/include + +export CFLAGS +export top_srcdir + +all: + @for DIR in $(ARM_SUBTARGETS); do \ + BUILD_TARGET=$(OUTPUT)/$$DIR; \ + mkdir -p $$BUILD_TARGET; \ + make OUTPUT=$$BUILD_TARGET -C $$DIR $@; \ + done + +install: all + @for DIR in $(ARM_SUBTARGETS); do \ + BUILD_TARGET=$(OUTPUT)/$$DIR; \ + make OUTPUT=$$BUILD_TARGET -C $$DIR $@; \ + done + +run_tests: all + @for DIR in $(ARM_SUBTARGETS); do \ + BUILD_TARGET=$(OUTPUT)/$$DIR; \ + make OUTPUT=$$BUILD_TARGET -C $$DIR $@; \ + done + +# Avoid any output on non arm on emit_tests +emit_tests: + @for DIR in $(ARM_SUBTARGETS); do \ + BUILD_TARGET=$(OUTPUT)/$$DIR; \ + make OUTPUT=$$BUILD_TARGET -C $$DIR $@; \ + done + +clean: + @for DIR in $(ARM_SUBTARGETS); do \ + BUILD_TARGET=$(OUTPUT)/$$DIR; \ + make OUTPUT=$$BUILD_TARGET -C $$DIR $@; \ + done + +.PHONY: all clean install run_tests emit_tests diff --git a/tools/testing/selftests/arm/README b/tools/testing/selftests/arm/README new file mode 100644 index 000000000000..75b7cf4baec3 --- /dev/null +++ b/tools/testing/selftests/arm/README @@ -0,0 +1,32 @@ +KSelfTest ARM +=============== + +- This is a series of compatibility tests, wherein the source files are + built statically into a 32 bit ELF; they should pass on both 32 and 64 + bit kernels. They are not built or run but just skipped completely when + env-variable ARCH is found to be different than 'arm64' or 'arm' and + `uname -m` reports other than 'aarch64', 'armv7l' or 'armv8l'. + +- If building the tests on a 64-bit kernel, please ensure that the kernel is + built with CONFIG_COMPAT enabled. + +- Holding true the above, ARM KSFT tests can be run within the KSelfTest + framework using standard Linux top-level-makefile targets. Please set + $(CROSS_COMPILE) to 'arm-linux-gnueabi-' or 'arm-linux-gnueabihf-'. + + $ make TARGETS=arm kselftest-clean + $ make $(CROSS_COMPILE) TARGETS=arm kselftest + + or + + $ make $(CROSS_COMPILE) -C tools/testing/selftests TARGETS=arm \ + INSTALL_PATH=<your-installation-path> install + + or, alternatively, only specific arm/ subtargets can be picked: + + $ make $(CROSS_COMPILE) -C tools/testing/selftests TARGETS=arm \ + ARM_SUBTARGETS="signal" INSTALL_PATH=<your-installation-path> \ + install + + Further details on building and running KFST can be found in: + Documentation/dev-tools/kselftest.rst diff --git a/tools/testing/selftests/arm/abi/.gitignore b/tools/testing/selftests/arm/abi/.gitignore new file mode 100644 index 000000000000..75af3c416fc3 --- /dev/null +++ b/tools/testing/selftests/arm/abi/.gitignore @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +ptrace +ptrace_64 +trivial_32bit_program diff --git a/tools/testing/selftests/arm/abi/Makefile b/tools/testing/selftests/arm/abi/Makefile new file mode 100644 index 000000000000..160b6aadb064 --- /dev/null +++ b/tools/testing/selftests/arm/abi/Makefile @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: GPL-2.0-only +# Copyright (C) 2024 ARM Limited + +TEST_GEN_PROGS := ptrace + +include ../../lib.mk + +# Do not build 64-bit programs if running on a native 32-bit kernel +UNAME_M := $(shell uname -m) +ifneq (,$(filter $(UNAME_M),aarch64 arm64)) +TEST_CUSTOM_PROGS := $(OUTPUT)/ptrace_64 + +TRIVIAL_32BIT := $(OUTPUT)/trivial_32bit_program + +all: $(TEST_CUSTOM_PROGS) $(TRIVIAL_32BIT) + + +$(TRIVIAL_32BIT): $(OUTPUT)/%: %.c + $(CC) -o $@ $^ -static + +$(OUTPUT)/ptrace_64: ptrace_64.c ptrace.h + gcc -o $@ $^ + +EXTRA_CLEAN := $(TEST_CUSTOM_PROGS) $(TRIVIAL_32BIT) +endif + diff --git a/tools/testing/selftests/arm/elf/.gitignore b/tools/testing/selftests/arm/elf/.gitignore new file mode 100644 index 000000000000..41458ecbcd72 --- /dev/null +++ b/tools/testing/selftests/arm/elf/.gitignore @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +parse_elf diff --git a/tools/testing/selftests/arm/elf/Makefile b/tools/testing/selftests/arm/elf/Makefile new file mode 100644 index 000000000000..86636fe02994 --- /dev/null +++ b/tools/testing/selftests/arm/elf/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2024 ARM Limited + +TEST_GEN_PROGS := parse_elf + +include ../../lib.mk diff --git a/tools/testing/selftests/arm/mm/.gitignore b/tools/testing/selftests/arm/mm/.gitignore new file mode 100644 index 000000000000..eb28169bb1b5 --- /dev/null +++ b/tools/testing/selftests/arm/mm/.gitignore @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +compat_va diff --git a/tools/testing/selftests/arm/mm/Makefile b/tools/testing/selftests/arm/mm/Makefile new file mode 100644 index 000000000000..d8bfa45df98c --- /dev/null +++ b/tools/testing/selftests/arm/mm/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2024 ARM Limited + +TEST_GEN_PROGS := compat_va + +include ../../lib.mk diff --git a/tools/testing/selftests/arm/signal/.gitignore b/tools/testing/selftests/arm/signal/.gitignore new file mode 100644 index 000000000000..85b81356bf41 --- /dev/null +++ b/tools/testing/selftests/arm/signal/.gitignore @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only +mangle_cpsr_invalid_aif_bits +mangle_cpsr_invalid_compat_toggle diff --git a/tools/testing/selftests/arm/signal/Makefile b/tools/testing/selftests/arm/signal/Makefile new file mode 100644 index 000000000000..3540a25de75a --- /dev/null +++ b/tools/testing/selftests/arm/signal/Makefile @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2024 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. + +# 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)/ + +# Common test-unit targets to build common-layout test-cases executables +# Needs secondary expansion to properly include the testcase c-file in pre-reqs +COMMON_SOURCES := test_signals.c test_signals_utils.c +COMMON_HEADERS := test_signals.h test_signals_utils.h + +.SECONDEXPANSION: +$(PROGS): $$@.c ${COMMON_SOURCES} ${COMMON_HEADERS} + $(CC) $(CFLAGS) ${@}.c ${COMMON_SOURCES} -o $@
On Tue, Jun 25, 2024 at 05:54:08PM +0530, Dev Jain wrote:
Add arm target, individual Makefile targets, and instructions to build the tests, along with .gitignore files. All the Makefiles are similar to selftests/arm64, except abi: use TEST_CUSTOM_PROGS to override the make rule from lib.mk. Also, do not build ptrace_64 if we are running a 32-bit kernel.
Also the documentation which could probably go separately.
+- This is a series of compatibility tests, wherein the source files are
- built statically into a 32 bit ELF; they should pass on both 32 and 64
- bit kernels. They are not built or run but just skipped completely when
- env-variable ARCH is found to be different than 'arm64' or 'arm' and
- `uname -m` reports other than 'aarch64', 'armv7l' or 'armv8l'.
These are just plain old tests for arm, the fact that we can use them to test 32 bit mode on a 64 bit host is a nice bonus but the tests are just as useful with actual arm hardware.
+- If building the tests on a 64-bit kernel, please ensure that the kernel is
- built with CONFIG_COMPAT enabled.
Running, not building.
+- Holding true the above, ARM KSFT tests can be run within the KSelfTest
- framework using standard Linux top-level-makefile targets. Please set
- $(CROSS_COMPILE) to 'arm-linux-gnueabi-' or 'arm-linux-gnueabihf-'.
Or whatever your toolchain is set to! We should also consider the LLVM=1 case here for building with clang (which is even easier as it supports all architectures in a single binary).
$ make TARGETS=arm kselftest-clean
$ make $(CROSS_COMPILE) TARGETS=arm kselftest
or
$ make $(CROSS_COMPILE) -C tools/testing/selftests TARGETS=arm \
INSTALL_PATH=<your-installation-path> install
or, alternatively, only specific arm/ subtargets can be picked:
$ make $(CROSS_COMPILE) -C tools/testing/selftests TARGETS=arm \
ARM_SUBTARGETS="signal" INSTALL_PATH=<your-installation-path> \
install
I would expect the Makefile to have machinery which if we're building for arm64 would use CROSS_COMPILE_COMPAT here and force 32 bit builds.
+# Do not build 64-bit programs if running on a native 32-bit kernel +UNAME_M := $(shell uname -m) +ifneq (,$(filter $(UNAME_M),aarch64 arm64)) +TEST_CUSTOM_PROGS := $(OUTPUT)/ptrace_64
Where we're building shouldn't affect what gets run - it is very common especially in CI systems to build on one system and then run the tests on a completely separate machine.
linux-kselftest-mirror@lists.linaro.org