From: "Pierre-Clément Tosi" <ptosi@google.com>
To: Catalin Marinas <catalin.marinas@arm.com>, Will Deacon <will@kernel.org>
Cc: "Mark Rutland" <mark.rutland@arm.com>,
"Vincenzo Frascino" <vincenzo.frascino@arm.com>,
linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org,
"Peter Collingbourne" <peter@pcc.me.uk>,
"Shuah Khan" <shuah@kernel.org>,
linux-kselftest@vger.kernel.org,
"Pierre-Clément Tosi" <ptosi@google.com>
Subject: [PATCH 02/24] kselftest/arm64: Test lazy PSTATE.TCO handling
Date: Sat, 07 Nov 2026 16:36:08 +0100 [thread overview]
Message-ID: <20261107-mte-kasan-split-v1-2-d84ea5f30f94@google.com> (raw)
In-Reply-To: <20261107-mte-kasan-split-v1-0-d84ea5f30f94@google.com>
Reproduce a bug introduced by commit 38ddf7dafaea ("arm64: mte: avoid
clearing PSTATE.TCO on entry unless necessary") where TCO wasn't cleared
when it should have, by detecting that the kernel misses MTE faults when
accessing (tagged) memory of a process running sync MTE.
Cc: Peter Collingbourne <peter@pcc.me.uk>
Cc: Shuah Khan <shuah@kernel.org>
Cc: linux-kselftest@vger.kernel.org
Signed-off-by: Pierre-Clément Tosi <ptosi@google.com>
---
tools/testing/selftests/arm64/mte/.gitignore | 1 +
tools/testing/selftests/arm64/mte/check_lazy_tco.c | 152 +++++++++++++++++++++
2 files changed, 153 insertions(+)
diff --git a/tools/testing/selftests/arm64/mte/.gitignore b/tools/testing/selftests/arm64/mte/.gitignore
index f6937f890039..70ebc3883a26 100644
--- a/tools/testing/selftests/arm64/mte/.gitignore
+++ b/tools/testing/selftests/arm64/mte/.gitignore
@@ -4,6 +4,7 @@ check_tags_inclusion
check_child_memory
check_mmap_options
check_prctl
+check_lazy_tco
check_ksm_options
check_user_mem
check_hugetlb_options
diff --git a/tools/testing/selftests/arm64/mte/check_lazy_tco.c b/tools/testing/selftests/arm64/mte/check_lazy_tco.c
new file mode 100644
index 000000000000..28174495056a
--- /dev/null
+++ b/tools/testing/selftests/arm64/mte/check_lazy_tco.c
@@ -0,0 +1,152 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2026 Google LLC
+/*
+ * Validate that the kernel does not leave PSTATE.TCO set on kernel entry and/or
+ * thread switching, when it should be clear i.e. when kernel accesses (such as
+ * uaccess) are expected to be tag checked.
+ */
+
+#include <sys/wait.h>
+
+#include "kselftest.h"
+#include "mte_common_util.h"
+#include "mte_def.h"
+
+#define BUG_EXIT_CODE 99
+#define MAX_TRIALS 32
+
+static void handle_signal(int sig);
+
+/*
+ * We validate that uaccesses are tag checked by tricking the kernel into
+ * triggering a tag check fault while accessing user (tagged) memory. If the
+ * kernel does not report an error, we assume that PSTATE.TCO was set, silencing
+ * the hardware exception.
+ *
+ * To achieve this with robust userspace-kernel synchronization, we allocate a
+ * signal handler stack in PROT_MTE memory, tagged with 0x0 so that our signal
+ * handler can safely run without triggering tag check violations from SP
+ * (untagged) accesses. We only re-tag (with a non-zero value) the uppermost
+ * granule of the stack, where the kernel must write user->next_frame.
+ *
+ * When the parent process sends SIGUSR1 to its child, the following happens:
+ * - TCO should be cleared before the uaccess
+ * - the kernel writes user->next_frame to the tagged stack granule
+ * - (if MTE is properly enabled, a synchronous tag fault is raised)
+ * - if the fault was detected and handled, SIGSEGV should be delivered
+ * - if it was not detected or raised, the initial SIGUSR1 is received
+ */
+static void setup_sigaltstack_tripwire(int sig)
+{
+ size_t sz = MT_ALIGN_UP((size_t)MINSIGSTKSZ + 2 * (size_t)getpagesize());
+ void *altstack, *top_granule;
+ struct sigaction sa = {};
+ stack_t ss = {};
+
+ /* Enable non-zero IRG, needed for tagging the sigaltstack. */
+ if (mte_switch_mode(MTE_NONE_ERR, MTE_ALLOW_NON_ZERO_TAG, false) != 0)
+ ksft_exit_fail_msg("Failed to enable MTE tag generation\n");
+
+ altstack = mte_allocate_memory(sz, USE_MMAP, 0, false);
+ if (check_allocated_memory(altstack, sz, USE_MMAP, false) != KSFT_PASS)
+ ksft_exit_fail_msg("Failed to allocate MTE sigaltstack\n");
+
+ top_granule = (char *)altstack + sz - MT_GRANULE_SIZE;
+ if (!mte_insert_tags(top_granule, MT_GRANULE_SIZE))
+ ksft_exit_fail_msg("Failed to tag sigaltstack top granule\n");
+
+ ss.ss_sp = altstack;
+ ss.ss_size = sz;
+ if (sigaltstack(&ss, NULL) != 0)
+ ksft_exit_fail_perror("sigaltstack()");
+
+ sa.sa_handler = handle_signal;
+ sa.sa_flags = SA_ONSTACK;
+ sigemptyset(&sa.sa_mask);
+ if (sigaction(sig, &sa, NULL) != 0)
+ ksft_exit_fail_perror("sigaction()");
+}
+
+static __noreturn void stress_test_lazy_tco(void)
+{
+ const unsigned long sync_ctrl =
+ PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC |
+ (MTE_ALLOW_NON_ZERO_TAG << PR_MTE_TAG_SHIFT);
+
+ if (prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0) != 0)
+ _exit(KSFT_FAIL);
+
+ for (;;) {
+ if (prctl(PR_SET_TAGGED_ADDR_CTRL, sync_ctrl, 0, 0, 0) != 0)
+ _exit(KSFT_FAIL);
+ if (prctl(PR_SET_TAGGED_ADDR_CTRL, 0, 0, 0, 0) != 0)
+ _exit(KSFT_FAIL);
+ }
+}
+
+static void handle_signal(int sig __always_unused)
+{
+ if (prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0) & PR_MTE_TCF_SYNC)
+ _exit(BUG_EXIT_CODE);
+}
+
+static int spam_signal_until_child_exit(pid_t pid, int sig)
+{
+ int status = 0;
+ pid_t ret;
+
+ do {
+ if (kill(pid, sig) != 0)
+ ksft_exit_fail_perror("kill()");
+ mte_wait_after_trig();
+ ret = waitpid(pid, &status, WNOHANG);
+ } while (ret == 0);
+ if (ret != pid)
+ ksft_exit_fail_perror("waitpid()");
+
+ if (WIFSIGNALED(status) && WTERMSIG(status) == SIGSEGV)
+ return KSFT_PASS;
+
+ if (WIFEXITED(status) && WEXITSTATUS(status) == BUG_EXIT_CODE)
+ return KSFT_FAIL;
+
+ ksft_exit_fail_msg("Unexpected child wait status %#x\n", status);
+}
+
+static int check_lazy_tco(void)
+{
+ for (int trial = 0; trial < MAX_TRIALS; trial++) {
+ pid_t child = fork();
+
+ if (child < 0)
+ ksft_exit_fail_perror("fork()");
+
+ if (!child)
+ stress_test_lazy_tco();
+
+ if (spam_signal_until_child_exit(child, SIGUSR1) == KSFT_PASS)
+ continue;
+
+ ksft_print_msg("Detected invalid TCO state\n");
+ return KSFT_FAIL;
+ }
+
+ return KSFT_PASS;
+}
+
+int main(void)
+{
+ int err;
+
+ ksft_print_header();
+
+ err = mte_default_setup();
+ if (err)
+ return err;
+
+ ksft_set_plan(1);
+ setup_sigaltstack_tripwire(SIGUSR1);
+ ksft_test_result_report(check_lazy_tco(), "Check lazy TCO clearing\n");
+ mte_restore_setup();
+ ksft_finished();
+}
--
2.56.0.455.g6c5a503b38-goog
next prev parent reply other threads:[~2026-11-07 15:37 UTC|newest]
Thread overview: 19+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-07 15:36 [PATCH 00/24] arm64: Decouple MTE code from KASAN support Pierre-Clément Tosi
2026-11-07 15:36 ` [PATCH 01/24] arm64: Fix lazy TCO clear when enabling user MTE Pierre-Clément Tosi
2026-11-07 15:36 ` Pierre-Clément Tosi [this message]
2026-11-07 15:36 ` [PATCH 03/24] arm64: Fix volatility of IRG inline assembly Pierre-Clément Tosi
2026-11-07 15:36 ` [PATCH 04/24] arm64: Skip TCO on privileged uaccess without kMTE Pierre-Clément Tosi
2026-11-07 15:36 ` [PATCH 05/24] arm64: Check MTE once per fault-handling section Pierre-Clément Tosi
2026-11-07 15:36 ` [PATCH 06/24] arm64: Harden is_ttbr1_addr() on tagged addresses Pierre-Clément Tosi
2026-11-07 15:36 ` [PATCH 07/24] arm64: Skip redundant TCO clear post-kernel entry Pierre-Clément Tosi
2026-11-07 15:36 ` [PATCH 08/24] arm64: Introduce mte_get_reset_tfsr() helpers Pierre-Clément Tosi
2026-11-07 15:36 ` [PATCH 09/24] kasan: rework hw_{force=>flush}_async_tag_fault() Pierre-Clément Tosi
2026-11-07 15:36 ` [PATCH 10/24] kasan: rework hw_{get=>set}_random_tag() Pierre-Clément Tosi
2026-11-07 15:36 ` [PATCH 11/24] kasan: provide empty stub for kasan_report_async() Pierre-Clément Tosi
2026-11-07 15:36 ` [PATCH 12/24] kasan: drop mte-kasan.h from arch-agnostic header Pierre-Clément Tosi
2026-11-07 15:36 ` [PATCH 13/24] arm64: Introduce set_pstate_tco() helper macro Pierre-Clément Tosi
2026-11-07 15:36 ` [PATCH 14/24] arm64: Add mte_{enter,exit}_*_{kernel,user}_mode() Pierre-Clément Tosi
2026-11-07 15:36 ` [PATCH 16/24] arm64: Refactor KERNEL_GCR_EL1 out of <sysreg.h> Pierre-Clément Tosi
2026-11-07 15:36 ` [PATCH 17/24] arm64: Introduce MTE & KASAN tag handling helpers Pierre-Clément Tosi
2026-11-07 15:36 ` [PATCH 19/24] arm64: Rework static branches for kernel MTE Pierre-Clément Tosi
2026-11-07 15:36 ` [PATCH 20/24] arm64: Move MTE insn wrappers to <asm/mte-def.h> Pierre-Clément Tosi
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261107-mte-kasan-split-v1-2-d84ea5f30f94@google.com \
--to=ptosi@google.com \
--cc=catalin.marinas@arm.com \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=mark.rutland@arm.com \
--cc=peter@pcc.me.uk \
--cc=shuah@kernel.org \
--cc=vincenzo.frascino@arm.com \
--cc=will@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®