mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
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


  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®