2024-04-05 08:50:13

by Dev Jain

[permalink] [raw]
Subject: [PATCH 0/4] A new selftests/ directory for arm compatibility testing

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 copied 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 series has been tested on 6.9.0-rc2, on Aarch64 platform. Testing remains
to be done on Aaarch32.

Dev Jain (4):
selftests/arm: Add mm test
selftests/arm: Add signal tests
selftests/arm: Add elf test
selftests: Add build infrastructure along with README

tools/testing/selftests/Makefile | 1 +
tools/testing/selftests/arm/Makefile | 57 ++++
tools/testing/selftests/arm/README | 31 +++
tools/testing/selftests/arm/elf/Makefile | 6 +
tools/testing/selftests/arm/elf/parse_elf.c | 75 +++++
tools/testing/selftests/arm/mm/Makefile | 6 +
tools/testing/selftests/arm/mm/compat_va.c | 94 +++++++
tools/testing/selftests/arm/signal/Makefile | 30 ++
.../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 ++
14 files changed, 848 insertions(+)
create mode 100644 tools/testing/selftests/arm/Makefile
create mode 100644 tools/testing/selftests/arm/README
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/Makefile
create mode 100644 tools/testing/selftests/arm/mm/compat_va.c
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_aif_bits.c
create mode 100644 tools/testing/selftests/arm/signal/testcases/mangle_cpsr_invalid_compat_toggle.c

--
2.39.2



2024-04-05 08:50:14

by Dev Jain

[permalink] [raw]
Subject: [PATCH 1/4] selftests/arm: Add mm test

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 <[email protected]>
---
tools/testing/selftests/arm/mm/compat_va.c | 94 ++++++++++++++++++++++
1 file changed, 94 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..3a78f240bc87
--- /dev/null
+++ b/tools/testing/selftests/arm/mm/compat_va.c
@@ -0,0 +1,94 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2024 ARM Limited
+ *
+ * Author : Dev Jain <[email protected]>
+ *
+ * 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];
+ const char *file_name;
+ int chunks;
+ FILE *file;
+ int i;
+
+ ksft_print_header();
+ ksft_set_plan(1);
+
+ /* 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;
+ if (chunks >= NR_CHUNKS_4G) {
+ ksft_test_result_fail("mmapped chunks beyond 4GB\n");
+ ksft_finished();
+ }
+
+ /* parse /proc/self/maps, confirm 32 bit VA mappings */
+ file_name = "/proc/self/maps";
+ file = fopen(file_name, "r");
+ if (file == NULL)
+ ksft_exit_fail_msg("/proc/self/maps cannot be opened\n");
+
+ 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_test_result_skip("Unexpected format");
+ ksft_finished();
+ }
+
+ if ((hyphen_loc - line > 8) ||
+ (whitespace_loc - hyphen_loc) > 9) {
+ ksft_test_result_fail("Memory map more than 32 bits\n");
+ ksft_finished();
+ }
+ }
+
+ for (int i = 0; i < chunks; ++i)
+ munmap(ptr[i], MAP_CHUNK_SIZE);
+
+ ksft_test_result_pass("Test\n");
+ ksft_finished();
+}
--
2.39.2


2024-04-05 08:50:43

by Dev Jain

[permalink] [raw]
Subject: [PATCH 3/4] selftests/arm: Add elf test

This patch introduces an ELF parsing test; the 5th byte of the ELF header
must be 0x01 for a 32-bit process. A basic sanity check is required to ensure
that we are actually testing a 32-bit build.

Signed-off-by: Dev Jain <[email protected]>
---
tools/testing/selftests/arm/elf/parse_elf.c | 75 +++++++++++++++++++++
1 file changed, 75 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..decd65699858
--- /dev/null
+++ b/tools/testing/selftests/arm/elf/parse_elf.c
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2024 ARM Limited
+ *
+ * Author : Dev Jain <[email protected]>
+ *
+ * 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 */
+ uint64_t e_entry; /* Entry point virtual address */
+ uint64_t e_phoff; /* Program header table file offset */
+ uint64_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 */
+};
+
+static int read_elf_header(const char *elfFile)
+{
+ struct elf_header header;
+ FILE *file;
+
+ file = fopen(elfFile, "r");
+ if (file) {
+
+ /* store header in struct */
+ fread(&header, 1, sizeof(header), file);
+ fclose(file);
+
+ /* sanity check: does it really follow ELF format */
+ if (header.e_ident[0] == 0x7f &&
+ header.e_ident[1] == 'E' &&
+ header.e_ident[2] == 'L' &&
+ header.e_ident[3] == 'F') {
+ if (header.e_ident[4] == 0x01)
+ return 0;
+ return 1;
+ }
+ ksft_exit_fail_msg("Cannot parse /proc/self/exe\n");
+ }
+ ksft_exit_fail_msg("Cannot open /proc/self/exe\n");
+ exit(EXIT_FAILURE);
+}
+
+int main(int argc, char *argv[])
+{
+ const char *file_name;
+
+ ksft_print_header();
+ ksft_set_plan(1);
+
+ file_name = "/proc/self/exe";
+ ksft_test_result(read_elf_header(file_name) == 0, "ELF is 32 bit\n");
+ ksft_finished();
+}
--
2.39.2


2024-04-05 08:50:56

by Dev Jain

[permalink] [raw]
Subject: [PATCH 2/4] selftests/arm: Add signal tests

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 <[email protected]>
---
.../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 ++
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;
+}
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,
+};
--
2.39.2


2024-04-05 08:51:01

by Dev Jain

[permalink] [raw]
Subject: [PATCH 4/4] selftests: Add build infrastructure along with README

Add arm target, individual Makefile targets, and instructions to build the
tests.

Signed-off-by: Dev Jain <[email protected]>
---
tools/testing/selftests/Makefile | 1 +
tools/testing/selftests/arm/Makefile | 57 +++++++++++++++++++++
tools/testing/selftests/arm/README | 31 +++++++++++
tools/testing/selftests/arm/elf/Makefile | 6 +++
tools/testing/selftests/arm/mm/Makefile | 6 +++
tools/testing/selftests/arm/signal/Makefile | 30 +++++++++++
6 files changed, 131 insertions(+)
create mode 100644 tools/testing/selftests/arm/Makefile
create mode 100644 tools/testing/selftests/arm/README
create mode 100644 tools/testing/selftests/arm/elf/Makefile
create mode 100644 tools/testing/selftests/arm/mm/Makefile
create mode 100644 tools/testing/selftests/arm/signal/Makefile

diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index 15b6a111c3be..8478d94cda4c 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..039224bc006e
--- /dev/null
+++ b/tools/testing/selftests/arm/Makefile
@@ -0,0 +1,57 @@
+# SPDX-License-Identifier: GPL-2.0
+
+# When ARCH not overridden for crosscompiling, lookup machine
+ARCH ?= $(shell uname -m 2>/dev/null || echo not)
+
+ifneq (,$(filter $(ARCH),aarch64 arm64 arm armv7l armv8l))
+ARM_SUBTARGETS ?= mm signal elf
+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..1a05c043d7ee
--- /dev/null
+++ b/tools/testing/selftests/arm/README
@@ -0,0 +1,31 @@
+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'.
+
+- Please ensure that the test 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/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/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/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): [email protected] ${COMMON_SOURCES} ${COMMON_HEADERS}
+ $(CC) $(CFLAGS) ${@}.c ${COMMON_SOURCES} -o $@
--
2.39.2


2024-04-05 17:38:09

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH 0/4] A new selftests/ directory for arm compatibility testing

On Fri, Apr 05, 2024 at 02:14:06PM +0530, Dev Jain wrote:
> 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.

You should copy Russell King, the 32 bit architeture maintainer, on this
- I've added him here.

FWIW it was me who suggested this work, like Dev says having some
coverage of the corners of the 32 bit ABI feels especially sensible for
arm64 since compat mode could relatively easily get accidentally broken
making changes for 64 bit.


Attachments:
(No filename) (908.00 B)
signature.asc (499.00 B)
Download all attachments

2024-04-06 21:14:57

by Muhammad Usama Anjum

[permalink] [raw]
Subject: Re: [PATCH 4/4] selftests: Add build infrastructure along with README

On 4/5/24 1:44 PM, Dev Jain wrote:
> Add arm target, individual Makefile targets, and instructions to build the
> tests.
>
> Signed-off-by: Dev Jain <[email protected]>
> ---
> tools/testing/selftests/Makefile | 1 +
> tools/testing/selftests/arm/Makefile | 57 +++++++++++++++++++++
> tools/testing/selftests/arm/README | 31 +++++++++++
> tools/testing/selftests/arm/elf/Makefile | 6 +++
> tools/testing/selftests/arm/mm/Makefile | 6 +++
> tools/testing/selftests/arm/signal/Makefile | 30 +++++++++++
> 6 files changed, 131 insertions(+)
> create mode 100644 tools/testing/selftests/arm/Makefile
> create mode 100644 tools/testing/selftests/arm/README
> create mode 100644 tools/testing/selftests/arm/elf/Makefile
> create mode 100644 tools/testing/selftests/arm/mm/Makefile
> create mode 100644 tools/testing/selftests/arm/signal/Makefile
Add one recursive .gitignore file or multiple .gitignore files and put
generated object files in it to avoid clutter of generated objects in git
history.

>
> diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
> index 15b6a111c3be..8478d94cda4c 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..039224bc006e
> --- /dev/null
> +++ b/tools/testing/selftests/arm/Makefile
> @@ -0,0 +1,57 @@
> +# SPDX-License-Identifier: GPL-2.0
> +
> +# When ARCH not overridden for crosscompiling, lookup machine
> +ARCH ?= $(shell uname -m 2>/dev/null || echo not)
> +
> +ifneq (,$(filter $(ARCH),aarch64 arm64 arm armv7l armv8l))
> +ARM_SUBTARGETS ?= mm signal elf
> +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
Please use KHDR_INCLUDE instead of using absolute path

> +
> +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..1a05c043d7ee
> --- /dev/null
> +++ b/tools/testing/selftests/arm/README
> @@ -0,0 +1,31 @@
> +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'.
> +
> +- Please ensure that the test kernel is built with CONFIG_COMPAT enabled.
Please create a config file and put all the per-requisite configurations in
that. For example, look at tools/testing/selftests/mm/config

> +
> +- 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
Thanks for this well written documentation.

> 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/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/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): [email protected] ${COMMON_SOURCES} ${COMMON_HEADERS}
> + $(CC) $(CFLAGS) ${@}.c ${COMMON_SOURCES} -o $@

--
BR,
Muhammad Usama Anjum

2024-04-06 21:22:56

by Muhammad Usama Anjum

[permalink] [raw]
Subject: Re: [PATCH 1/4] selftests/arm: Add mm test

On 4/5/24 1:44 PM, Dev Jain wrote:
> 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 <[email protected]>
> ---
> tools/testing/selftests/arm/mm/compat_va.c | 94 ++++++++++++++++++++++
> 1 file changed, 94 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..3a78f240bc87
> --- /dev/null
> +++ b/tools/testing/selftests/arm/mm/compat_va.c
> @@ -0,0 +1,94 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2024 ARM Limited
> + *
> + * Author : Dev Jain <[email protected]>
> + *
> + * 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;
Usually we return negative value instead of positive one which indicates
error situation.

> +}
> +
> +int main(int argc, char *argv[])
> +{
> + char *ptr[NR_CHUNKS_4G + 3];
> + char line[1000];
> + const char *file_name;
> + int chunks;
> + FILE *file;
> + int i;
> +
> + ksft_print_header();
> + ksft_set_plan(1);
There are multiple test cases. Instead of saying there is only 1 test.
There should be multiple ksft_test_result{_pass,_fail} statements for each
sub-tests.

> +
> + /* 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;
> + if (chunks >= NR_CHUNKS_4G) {
> + ksft_test_result_fail("mmapped chunks beyond 4GB\n");
> + ksft_finished();
> + }
> +
> + /* parse /proc/self/maps, confirm 32 bit VA mappings */
> + file_name = "/proc/self/maps";
> + file = fopen(file_name, "r");
> + if (file == NULL)
> + ksft_exit_fail_msg("/proc/self/maps cannot be opened\n");
> +
> + 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_test_result_skip("Unexpected format");
> + ksft_finished();
I'm unable to follow as there are too many return statements. If you divide
the test into multiple sub-tests, you can skip/pass/fail each sub-test easily.

> + }
> +
> + if ((hyphen_loc - line > 8) ||
> + (whitespace_loc - hyphen_loc) > 9) {
> + ksft_test_result_fail("Memory map more than 32 bits\n");
> + ksft_finished();
> + }
> + }
> +
> + for (int i = 0; i < chunks; ++i)
> + munmap(ptr[i], MAP_CHUNK_SIZE);
> +
> + ksft_test_result_pass("Test\n");
> + ksft_finished();
> +}

--
BR,
Muhammad Usama Anjum

2024-04-06 21:27:51

by Muhammad Usama Anjum

[permalink] [raw]
Subject: Re: [PATCH 2/4] selftests/arm: Add signal tests

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 <[email protected]>
> ---
> .../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

2024-04-06 21:29:47

by Muhammad Usama Anjum

[permalink] [raw]
Subject: Re: [PATCH 3/4] selftests/arm: Add elf test

On 4/5/24 1:44 PM, Dev Jain wrote:
> This patch introduces an ELF parsing test; the 5th byte of the ELF header
> must be 0x01 for a 32-bit process. A basic sanity check is required to ensure
> that we are actually testing a 32-bit build.
>
> Signed-off-by: Dev Jain <[email protected]>
> ---
> tools/testing/selftests/arm/elf/parse_elf.c | 75 +++++++++++++++++++++
> 1 file changed, 75 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..decd65699858
> --- /dev/null
> +++ b/tools/testing/selftests/arm/elf/parse_elf.c
> @@ -0,0 +1,75 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2024 ARM Limited
> + *
> + * Author : Dev Jain <[email protected]>
> + *
> + * 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 */
> + uint64_t e_entry; /* Entry point virtual address */
> + uint64_t e_phoff; /* Program header table file offset */
> + uint64_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 */
> +};
> +
> +static int read_elf_header(const char *elfFile)
> +{
> + struct elf_header header;
> + FILE *file;
> +
> + file = fopen(elfFile, "r");
> + if (file) {
> +
> + /* store header in struct */
> + fread(&header, 1, sizeof(header), file);
> + fclose(file);
> +
> + /* sanity check: does it really follow ELF format */
> + if (header.e_ident[0] == 0x7f &&
> + header.e_ident[1] == 'E' &&
> + header.e_ident[2] == 'L' &&
> + header.e_ident[3] == 'F') {
> + if (header.e_ident[4] == 0x01)
> + return 0;
> + return 1;
> + }
> + ksft_exit_fail_msg("Cannot parse /proc/self/exe\n");
> + }
> + ksft_exit_fail_msg("Cannot open /proc/self/exe\n");
> + exit(EXIT_FAILURE);
Instead of failing and exiting multiple times here, use ksft_print_msg,
return error or -1 from here and fail the test case in ksft_test_result().

> +}
> +
> +int main(int argc, char *argv[])
> +{
> + const char *file_name;
> +
> + ksft_print_header();
> + ksft_set_plan(1);
> +
> + file_name = "/proc/self/exe";
> + ksft_test_result(read_elf_header(file_name) == 0, "ELF is 32 bit\n");
> + ksft_finished();
> +}

--
BR,
Muhammad Usama Anjum

2024-04-08 12:12:35

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH 2/4] selftests/arm: Add signal tests

On Sun, Apr 07, 2024 at 02:28:06AM +0500, Muhammad Usama Anjum wrote:
> On 4/5/24 1:44 PM, Dev Jain wrote:

> > + 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.

It's based on the 64 bit version of these tests which are also not TAP
compliant. TBH I'm not sure how worthwile it is to fix at all given
that they're all single test executables anyway, if it does get fixed
it'd be good to do the arm64 ones as well.

> > + } else {
> > + fprintf(stdout, "==>> completed. FAIL(0)\n");
> > + td->result = KSFT_FAIL;
> > + }
> > +
> > + if (force_exit)
> > + exit(td->result);
> > +}
> > +

Please delete unneeded context from mails when replying. Doing this
makes it much easier to find your reply in the message, helping ensure
it won't be missed by people scrolling through the irrelevant quoted
material.


Attachments:
(No filename) (1.14 kB)
signature.asc (499.00 B)
Download all attachments

2024-04-08 12:24:25

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH 4/4] selftests: Add build infrastructure along with README

On Sun, Apr 07, 2024 at 02:15:15AM +0500, Muhammad Usama Anjum wrote:
> On 4/5/24 1:44 PM, Dev Jain wrote:

> > @@ -0,0 +1,31 @@
> > +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'.
> > +
> > +- Please ensure that the test kernel is built with CONFIG_COMPAT enabled.

> Please create a config file and put all the per-requisite configurations in
> that. For example, look at tools/testing/selftests/mm/config

Note that arm and arm64 are different architectures, and the kernel
config stuff only applies when building for arm64...


Attachments:
(No filename) (924.00 B)
signature.asc (499.00 B)
Download all attachments

2024-04-10 04:11:18

by Dev Jain

[permalink] [raw]
Subject: Re: [PATCH 3/4] selftests/arm: Add elf test


On 4/7/24 03:00, Muhammad Usama Anjum wrote:
> On 4/5/24 1:44 PM, Dev Jain wrote:
>> This patch introduces an ELF parsing test; the 5th byte of the ELF header
>> must be 0x01 for a 32-bit process. A basic sanity check is required to ensure
>> that we are actually testing a 32-bit build.
>>
>> Signed-off-by: Dev Jain <[email protected]>
>> ---
>> tools/testing/selftests/arm/elf/parse_elf.c | 75 +++++++++++++++++++++
>> 1 file changed, 75 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..decd65699858
>> --- /dev/null
>> +++ b/tools/testing/selftests/arm/elf/parse_elf.c
>> @@ -0,0 +1,75 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Copyright (C) 2024 ARM Limited
>> + *
>> + * Author : Dev Jain <[email protected]>
>> + *
>> + * 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 */
>> + uint64_t e_entry; /* Entry point virtual address */
>> + uint64_t e_phoff; /* Program header table file offset */
>> + uint64_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 */
>> +};
>> +
>> +static int read_elf_header(const char *elfFile)
>> +{
>> + struct elf_header header;
>> + FILE *file;
>> +
>> + file = fopen(elfFile, "r");
>> + if (file) {
>> +
>> + /* store header in struct */
>> + fread(&header, 1, sizeof(header), file);
>> + fclose(file);
>> +
>> + /* sanity check: does it really follow ELF format */
>> + if (header.e_ident[0] == 0x7f &&
>> + header.e_ident[1] == 'E' &&
>> + header.e_ident[2] == 'L' &&
>> + header.e_ident[3] == 'F') {
>> + if (header.e_ident[4] == 0x01)
>> + return 0;
>> + return 1;
>> + }
>> + ksft_exit_fail_msg("Cannot parse /proc/self/exe\n");
>> + }
>> + ksft_exit_fail_msg("Cannot open /proc/self/exe\n");
>> + exit(EXIT_FAILURE);
> Instead of failing and exiting multiple times here, use ksft_print_msg,
> return error or -1 from here and fail the test case in ksft_test_result().
Thanks for the suggestion.
>
>> +}
>> +
>> +int main(int argc, char *argv[])
>> +{
>> + const char *file_name;
>> +
>> + ksft_print_header();
>> + ksft_set_plan(1);
>> +
>> + file_name = "/proc/self/exe";
>> + ksft_test_result(read_elf_header(file_name) == 0, "ELF is 32 bit\n");
>> + ksft_finished();
>> +}

2024-04-10 04:15:58

by Dev Jain

[permalink] [raw]
Subject: Re: [PATCH 1/4] selftests/arm: Add mm test


On 4/7/24 02:53, Muhammad Usama Anjum wrote:
> On 4/5/24 1:44 PM, Dev Jain wrote:
>> 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 <[email protected]>
>> ---
>> tools/testing/selftests/arm/mm/compat_va.c | 94 ++++++++++++++++++++++
>> 1 file changed, 94 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..3a78f240bc87
>> --- /dev/null
>> +++ b/tools/testing/selftests/arm/mm/compat_va.c
>> @@ -0,0 +1,94 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Copyright (C) 2024 ARM Limited
>> + *
>> + * Author : Dev Jain <[email protected]>
>> + *
>> + * 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;
> Usually we return negative value instead of positive one which indicates
> error situation.
>
>> +}
>> +
>> +int main(int argc, char *argv[])
>> +{
>> + char *ptr[NR_CHUNKS_4G + 3];
>> + char line[1000];
>> + const char *file_name;
>> + int chunks;
>> + FILE *file;
>> + int i;
>> +
>> + ksft_print_header();
>> + ksft_set_plan(1);
> There are multiple test cases. Instead of saying there is only 1 test.
> There should be multiple ksft_test_result{_pass,_fail} statements for each
> sub-tests.


My initial idea was to treat this as a single logical test, as I

am asserting the restriction on the number of chunks against

the VMAs. I guess your approach is cleaner; thanks.

>
>> +
>> + /* 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;
>> + if (chunks >= NR_CHUNKS_4G) {
>> + ksft_test_result_fail("mmapped chunks beyond 4GB\n");
>> + ksft_finished();
>> + }
>> +
>> + /* parse /proc/self/maps, confirm 32 bit VA mappings */
>> + file_name = "/proc/self/maps";
>> + file = fopen(file_name, "r");
>> + if (file == NULL)
>> + ksft_exit_fail_msg("/proc/self/maps cannot be opened\n");
>> +
>> + 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_test_result_skip("Unexpected format");
>> + ksft_finished();
> I'm unable to follow as there are too many return statements. If you divide
> the test into multiple sub-tests, you can skip/pass/fail each sub-test easily.
>
>> + }
>> +
>> + if ((hyphen_loc - line > 8) ||
>> + (whitespace_loc - hyphen_loc) > 9) {
>> + ksft_test_result_fail("Memory map more than 32 bits\n");
>> + ksft_finished();
>> + }
>> + }
>> +
>> + for (int i = 0; i < chunks; ++i)
>> + munmap(ptr[i], MAP_CHUNK_SIZE);
>> +
>> + ksft_test_result_pass("Test\n");
>> + ksft_finished();
>> +}

2024-04-10 04:44:04

by Dev Jain

[permalink] [raw]
Subject: Re: [PATCH 2/4] selftests/arm: Add signal tests


On 4/7/24 02:58, Muhammad Usama Anjum wrote:
> 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 <[email protected]>
>> ---
>> .../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.


In this particular case, I am not sure about the utility of doing that.

My idea was to put the wrapper infrastructure and the individual testcases

into a single patch for ease of comparison with selftests/arm64; this will

actually help in pointing out mistakes or suggesting improvements.

>
>> 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

2024-04-11 05:17:00

by Dev Jain

[permalink] [raw]
Subject: Re: [PATCH 4/4] selftests: Add build infrastructure along with README


On 4/7/24 02:45, Muhammad Usama Anjum wrote:
>> diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
>> index 15b6a111c3be..8478d94cda4c 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..039224bc006e
>> --- /dev/null
>> +++ b/tools/testing/selftests/arm/Makefile
>> @@ -0,0 +1,57 @@
>> +# SPDX-License-Identifier: GPL-2.0
>> +
>> +# When ARCH not overridden for crosscompiling, lookup machine
>> +ARCH ?= $(shell uname -m 2>/dev/null || echo not)
>> +
>> +ifneq (,$(filter $(ARCH),aarch64 arm64 arm armv7l armv8l))
>> +ARM_SUBTARGETS ?= mm signal elf
>> +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
> Please use KHDR_INCLUDE instead of using absolute path


The reason I had excluded it was that the signal tests won't

build then. What probably happens is that the kernel headers

collide with the headers included by the compiler.


2024-04-17 04:54:25

by Dev Jain

[permalink] [raw]
Subject: Re: [PATCH 1/4] selftests/arm: Add mm test


On 4/10/24 09:45, Dev Jain wrote:
>
> On 4/7/24 02:53, Muhammad Usama Anjum wrote:
>> On 4/5/24 1:44 PM, Dev Jain wrote:
>>> 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 <[email protected]>
>>> ---
>>>   tools/testing/selftests/arm/mm/compat_va.c | 94
>>> ++++++++++++++++++++++
>>>   1 file changed, 94 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..3a78f240bc87
>>> --- /dev/null
>>> +++ b/tools/testing/selftests/arm/mm/compat_va.c
>>> @@ -0,0 +1,94 @@
>>> +// SPDX-License-Identifier: GPL-2.0
>>> +/*
>>> + * Copyright (C) 2024 ARM Limited
>>> + *
>>> + * Author : Dev Jain <[email protected]>
>>> + *
>>> + * 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;
>> Usually we return negative value instead of positive one which indicates
>> error situation.
>>
>>> +}
>>> +
>>> +int main(int argc, char *argv[])
>>> +{
>>> +    char *ptr[NR_CHUNKS_4G + 3];
>>> +    char line[1000];
>>> +    const char *file_name;
>>> +    int chunks;
>>> +    FILE *file;
>>> +    int i;
>>> +
>>> +    ksft_print_header();
>>> +    ksft_set_plan(1);
>> There are multiple test cases. Instead of saying there is only 1 test.
>> There should be multiple ksft_test_result{_pass,_fail} statements for
>> each
>> sub-tests.
>
>
> My initial idea was to treat this as a single logical test, as I
>
> am asserting the restriction on the number of chunks against
>
> the VMAs. I guess your approach is cleaner; thanks.


Thinking again, using a lot of return statements is in fact making the
code easier

to follow. If I just set a variable ret = 0/1 and use it to pass or
fail, I'll have to

unnecessarily use a lot of if/else statements. Take a look at the
examples below.

>
>>
>>> +
>>> +    /* 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;


I will have to set ret value here, forcing two statements inside the if
block.

>>> +        }
>>> +    }
>>> +
>>> +    chunks = i;
>>> +    if (chunks >= NR_CHUNKS_4G) {
>>> +        ksft_test_result_fail("mmapped chunks beyond 4GB\n");
>>> +        ksft_finished();
>>> +    }
>>> +
>>> +    /* parse /proc/self/maps, confirm 32 bit VA mappings */
>>> +    file_name = "/proc/self/maps";
>>> +    file = fopen(file_name, "r");
>>> +    if (file == NULL)
>>> +        ksft_exit_fail_msg("/proc/self/maps cannot be opened\n");
>>> +


I will have to set ret here, and enclose the below while statement inside

an else block. In short, saving the value of ret will require if/else blocks

if I were to use it in the end in ksft_test_result(). When I use ksft_exit

statements, it is clear that a problem was spotted here, and there is

no need to study the remaining code.

>>> +    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_test_result_skip("Unexpected format");
>>> +            ksft_finished();
>> I'm unable to follow as there are too many return statements. If you
>> divide
>> the test into multiple sub-tests, you can skip/pass/fail each
>> sub-test easily.
>>
>>> +        }
>>> +
>>> +        if ((hyphen_loc - line > 8) ||
>>> +            (whitespace_loc - hyphen_loc) > 9) {
>>> +            ksft_test_result_fail("Memory map more than 32 bits\n");
>>> +            ksft_finished();
>>> +        }
>>> +    }
>>> +
>>> +    for (int i = 0; i < chunks; ++i)
>>> +        munmap(ptr[i], MAP_CHUNK_SIZE);
>>> +
>>> +    ksft_test_result_pass("Test\n");
>>> +    ksft_finished();
>>> +}
>
> _______________________________________________
> linux-arm-kernel mailing list
> [email protected]
> http://lists.infradead.org/mailman/listinfo/linux-arm-kernel