* [PATCH 1/2] kselftest/arm64/mte: Introduce MTE safe memory accessors
2026-09-22 10:59 [PATCH 0/2] kselftest: Make MTE memory access robust Vladimir Murzin
@ 2026-09-22 10:59 ` Vladimir Murzin
2026-09-22 10:59 ` [PATCH 2/2] kselftest/arm64/mte: Use " Vladimir Murzin
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Vladimir Murzin @ 2026-09-22 10:59 UTC (permalink / raw)
To: linux-kselftest
Cc: linux-arm-kernel, shuah, broonie, usama.anjum, mark.rutland, will,
catalin.marinas
The MTE selftests assume that they can skip (expected) MTE faults by
advancing the PC by 4 in mte_default_handler(), but this is not
generally safe, as the faults are triggered from arbitrary library
functions (e.g. memset() and memcpy()). For instance, memset() could
be implemented as simple as:
mov x3, x0 // copy pointer
add x4, x0, x2 // calculate end
loop:
strb w1, [x3], #1 // faulting access
cmp x3, x4
b.ne loop
ret
which leads to an infinite loop since X3 could not be updated.
Or, it could be having advanced implementation (FEAT_MOPS):
mov x3, x0 // copy pointer
setp [x3]!, x2!, x1 // prologue
setm [x3]!, x2!, x1 // main
sete [x3]!, x2!, x1 // epilogue
ret
with faulting access at prologue advancing PC to main could lead to
infinite loop due to main could be triggering unaligned access fault
which kernel fixing up by advancing PC back to prologue.
Instead of relying on arbitrary implementations of library functions
introduce helpers to perform the faulting accesses, where those faults
can safely be handled.
Signed-off-by: Vladimir Murzin <vladimir.murzin@arm.com>
---
.../selftests/arm64/mte/mte_common_util.h | 4 ++
.../testing/selftests/arm64/mte/mte_helper.S | 44 +++++++++++++++++++
2 files changed, 48 insertions(+)
diff --git a/tools/testing/selftests/arm64/mte/mte_common_util.h b/tools/testing/selftests/arm64/mte/mte_common_util.h
index 250d671329a5..547ec5659f78 100644
--- a/tools/testing/selftests/arm64/mte/mte_common_util.h
+++ b/tools/testing/selftests/arm64/mte/mte_common_util.h
@@ -64,6 +64,10 @@ void mte_restore_setup(void);
int mte_switch_mode(int mte_option, unsigned long incl_mask, bool stonly);
void mte_initialize_current_context(int mode, uintptr_t ptr, ssize_t range);
+/* Safe memory access functions */
+void *memset_safe(void *s, int c, size_t n);
+void *memcpy_safe(void *dest, const void *src, size_t n);
+
/* Common utility functions */
int create_temp_file(void);
diff --git a/tools/testing/selftests/arm64/mte/mte_helper.S b/tools/testing/selftests/arm64/mte/mte_helper.S
index a55dbbc56ed1..eb62abfa1eb4 100644
--- a/tools/testing/selftests/arm64/mte/mte_helper.S
+++ b/tools/testing/selftests/arm64/mte/mte_helper.S
@@ -128,3 +128,47 @@ ENTRY(mte_get_pstate_tco)
ubfx x0, x0, #MT_PSTATE_TCO_SHIFT, #1
ret
ENDPROC(mte_get_pstate_tco)
+
+/*
+ * memset_safe: Fill memory with a constant byte
+ * Input:
+ * x0 - destination pointer
+ * x1 - constant byte
+ * x2 - number of bytes to fill
+ * Return:
+ * x0 - original destination pointer
+ */
+ENTRY(memset_safe)
+ cbz x2, 2f
+ add x4, x2, x0
+ mov x3, x0
+1:
+ strb w1, [x3]
+ add x3, x3, #0x1
+ cmp x3, x4
+ b.ne 1b
+2:
+ ret
+ENDPROC(memset_safe)
+
+/*
+ * memcpy_safe: Copy memory area
+ * Input:
+ * x0 - destination pointer
+ * x1 - source pointer
+ * x2 - number of bytes to copy
+ * Return:
+ * x0 - destination pointer
+ */
+ENTRY(memcpy_safe)
+ cbz x2, 2f
+ mov x3, #0x0
+1:
+ ldrb w4, [x1, x3]
+ strb w4, [x0, x3]
+ add x3, x3, #0x1
+ cmp x3, x2
+ b.ne 1b
+2:
+ ret
+ENDPROC(memcpy_safe)
--
2.34.1
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH 2/2] kselftest/arm64/mte: Use MTE safe memory accessors
2026-09-22 10:59 [PATCH 0/2] kselftest: Make MTE memory access robust Vladimir Murzin
2026-09-22 10:59 ` [PATCH 1/2] kselftest/arm64/mte: Introduce MTE safe memory accessors Vladimir Murzin
@ 2026-09-22 10:59 ` Vladimir Murzin
2026-09-22 17:10 ` [PATCH 0/2] kselftest: Make MTE memory access robust Muhammad Usama Anjum
2026-10-06 22:56 ` Catalin Marinas
3 siblings, 0 replies; 5+ messages in thread
From: Vladimir Murzin @ 2026-09-22 10:59 UTC (permalink / raw)
To: linux-kselftest
Cc: linux-arm-kernel, shuah, broonie, usama.anjum, mark.rutland, will,
catalin.marinas
Use MTE safe memory accessors instead of library functions with
arbitrary implementations.
Also, for consistency, use accessors even for byte size accesses.
Signed-off-by: Vladimir Murzin <vladimir.murzin@arm.com>
---
tools/testing/selftests/arm64/mte/check_buffer_fill.c | 10 +++++-----
tools/testing/selftests/arm64/mte/check_child_memory.c | 6 +++---
.../selftests/arm64/mte/check_hugetlb_options.c | 4 ++--
tools/testing/selftests/arm64/mte/check_mmap_options.c | 10 +++++-----
.../testing/selftests/arm64/mte/check_tags_inclusion.c | 4 ++--
5 files changed, 17 insertions(+), 17 deletions(-)
diff --git a/tools/testing/selftests/arm64/mte/check_buffer_fill.c b/tools/testing/selftests/arm64/mte/check_buffer_fill.c
index 039b1d7d8566..54e1dcaf52ac 100644
--- a/tools/testing/selftests/arm64/mte/check_buffer_fill.c
+++ b/tools/testing/selftests/arm64/mte/check_buffer_fill.c
@@ -41,7 +41,7 @@ static int check_buffer_by_byte(int mem_type, int mode)
mte_initialize_current_context(mode, (uintptr_t)ptr, sizes[i]);
/* Set some value in tagged memory */
for (j = 0; j < sizes[i]; j++)
- ptr[j] = '1';
+ memset_safe(&ptr[j], '1', 1);
mte_wait_after_trig();
err = cur_mte_cxt.fault_valid;
/* Check the buffer whether it is filled. */
@@ -82,7 +82,7 @@ static int check_buffer_underflow_by_byte(int mem_type, int mode,
/* Set some value in tagged memory and make the buffer underflow */
for (j = sizes[i] - 1; (j >= -underflow_range) &&
(!cur_mte_cxt.fault_valid); j--) {
- ptr[j] = '1';
+ memset_safe(&ptr[j], '1', 1);
last_index = j;
}
mte_wait_after_trig();
@@ -180,7 +180,7 @@ static int check_buffer_overflow_by_byte(int mem_type, int mode,
/* Set some value in tagged memory and make the buffer underflow */
for (j = 0, last_index = 0 ; (j < (sizes[i] + overflow_range)) &&
(cur_mte_cxt.fault_valid == false); j++) {
- ptr[j] = '1';
+ memset_safe(&ptr[j], '1', 1);
last_index = j;
}
mte_wait_after_trig();
@@ -308,8 +308,8 @@ static int check_buffer_by_block_iterate(int mem_type, int mode, size_t size)
result = KSFT_PASS;
mte_initialize_current_context(mode, (uintptr_t)dst, size);
/* Set some value in memory and copy*/
- memset((void *)src, (int)'1', size);
- memcpy((void *)dst, (void *)src, size);
+ memset_safe((void *)src, (int)'1', size);
+ memcpy_safe((void *)dst, (void *)src, size);
mte_wait_after_trig();
if (cur_mte_cxt.fault_valid) {
result = KSFT_FAIL;
diff --git a/tools/testing/selftests/arm64/mte/check_child_memory.c b/tools/testing/selftests/arm64/mte/check_child_memory.c
index e6a8acca2a94..5c62359e5682 100644
--- a/tools/testing/selftests/arm64/mte/check_child_memory.c
+++ b/tools/testing/selftests/arm64/mte/check_child_memory.c
@@ -41,7 +41,7 @@ static int check_child_tag_inheritance(char *ptr, int size, int mode)
} else if (child == 0) {
mte_initialize_current_context(mode, (uintptr_t)ptr, size);
/* Do copy on write */
- memset(ptr, '1', size);
+ memset_safe(ptr, '1', size);
mte_wait_after_trig();
if (cur_mte_cxt.fault_valid == true) {
fault = 1;
@@ -56,14 +56,14 @@ static int check_child_tag_inheritance(char *ptr, int size, int mode)
}
}
mte_initialize_current_context(mode, (uintptr_t)ptr, -UNDERFLOW);
- memset(ptr - UNDERFLOW, '2', UNDERFLOW);
+ memset_safe(ptr - UNDERFLOW, '2', UNDERFLOW);
mte_wait_after_trig();
if (cur_mte_cxt.fault_valid == false) {
fault = 1;
goto check_child_tag_inheritance_err;
}
mte_initialize_current_context(mode, (uintptr_t)ptr, size + OVERFLOW);
- memset(ptr + size, '3', OVERFLOW);
+ memset_safe(ptr + size, '3', OVERFLOW);
mte_wait_after_trig();
if (cur_mte_cxt.fault_valid == false) {
fault = 1;
diff --git a/tools/testing/selftests/arm64/mte/check_hugetlb_options.c b/tools/testing/selftests/arm64/mte/check_hugetlb_options.c
index 23e4a7a9950c..85f1574b88be 100644
--- a/tools/testing/selftests/arm64/mte/check_hugetlb_options.c
+++ b/tools/testing/selftests/arm64/mte/check_hugetlb_options.c
@@ -106,7 +106,7 @@ static int check_child_tag_inheritance(char *ptr, int size, int mode)
} else if (child == 0) {
mte_initialize_current_context(mode, (uintptr_t)ptr, size);
/* Do copy on write */
- memset(ptr, '1', size);
+ memset_safe(ptr, '1', size);
mte_wait_after_trig();
if (cur_mte_cxt.fault_valid == true) {
fault = 1;
@@ -135,7 +135,7 @@ static int check_child_tag_inheritance(char *ptr, int size, int mode)
static int check_mte_memory(char *ptr, int size, int mode, int tag_check)
{
mte_initialize_current_context(mode, (uintptr_t)ptr, size);
- memset(ptr, '1', size);
+ memset_safe(ptr, '1', size);
mte_wait_after_trig();
if (cur_mte_cxt.fault_valid == true)
return KSFT_FAIL;
diff --git a/tools/testing/selftests/arm64/mte/check_mmap_options.c b/tools/testing/selftests/arm64/mte/check_mmap_options.c
index 492f2cd41f43..0f4d43fe80f2 100644
--- a/tools/testing/selftests/arm64/mte/check_mmap_options.c
+++ b/tools/testing/selftests/arm64/mte/check_mmap_options.c
@@ -72,13 +72,13 @@ static int check_mte_memory(char *ptr, int size, int mode,
ptr = mte_insert_atag(ptr);
mte_initialize_current_context(mode, (uintptr_t)ptr, size);
- memset(ptr, '1', size);
+ memset_safe(ptr, '1', size);
mte_wait_after_trig();
if (cur_mte_cxt.fault_valid == true)
return KSFT_FAIL;
mte_initialize_current_context(mode, (uintptr_t)ptr, -UNDERFLOW);
- memset(ptr - UNDERFLOW, '2', UNDERFLOW);
+ memset_safe(ptr - UNDERFLOW, '2', UNDERFLOW);
mte_wait_after_trig();
if (cur_mte_cxt.fault_valid == false && tag_check == TAG_CHECK_ON)
return KSFT_FAIL;
@@ -86,7 +86,7 @@ static int check_mte_memory(char *ptr, int size, int mode,
return KSFT_FAIL;
mte_initialize_current_context(mode, (uintptr_t)ptr, size + OVERFLOW);
- memset(ptr + size, '3', OVERFLOW);
+ memset_safe(ptr + size, '3', OVERFLOW);
mte_wait_after_trig();
if (cur_mte_cxt.fault_valid == false && tag_check == TAG_CHECK_ON)
return KSFT_FAIL;
@@ -95,13 +95,13 @@ static int check_mte_memory(char *ptr, int size, int mode,
if (tag_op == TAG_OP_STONLY) {
mte_initialize_current_context(mode, (uintptr_t)ptr, -UNDERFLOW);
- memcpy(buf, ptr - UNDERFLOW, MT_GRANULE_SIZE);
+ memcpy_safe(buf, ptr - UNDERFLOW, MT_GRANULE_SIZE);
mte_wait_after_trig();
if (cur_mte_cxt.fault_valid == true)
return KSFT_FAIL;
mte_initialize_current_context(mode, (uintptr_t)ptr, size + OVERFLOW);
- memcpy(buf, ptr + size, MT_GRANULE_SIZE);
+ memcpy_safe(buf, ptr + size, MT_GRANULE_SIZE);
mte_wait_after_trig();
if (cur_mte_cxt.fault_valid == true)
return KSFT_FAIL;
diff --git a/tools/testing/selftests/arm64/mte/check_tags_inclusion.c b/tools/testing/selftests/arm64/mte/check_tags_inclusion.c
index 6b4fa6705d7c..bc39be231ae2 100644
--- a/tools/testing/selftests/arm64/mte/check_tags_inclusion.c
+++ b/tools/testing/selftests/arm64/mte/check_tags_inclusion.c
@@ -23,7 +23,7 @@ static int verify_mte_pointer_validity(char *ptr, int mode)
{
mte_initialize_current_context(mode, (uintptr_t)ptr, BUFFER_SIZE);
/* Check the validity of the tagged pointer */
- memset(ptr, '1', BUFFER_SIZE);
+ memset_safe(ptr, '1', BUFFER_SIZE);
mte_wait_after_trig();
if (cur_mte_cxt.fault_valid) {
ksft_print_msg("Unexpected fault recorded for %p-%p in mode %x\n",
@@ -159,7 +159,7 @@ static int check_none_included_tags(int mem_type, int mode)
}
mte_initialize_current_context(mode, (uintptr_t)ptr, BUFFER_SIZE);
/* Check the write validity of the untagged pointer */
- memset(ptr, '1', BUFFER_SIZE);
+ memset_safe(ptr, '1', BUFFER_SIZE);
mte_wait_after_trig();
if (cur_mte_cxt.fault_valid)
break;
--
2.34.1
^ permalink raw reply related [flat|nested] 5+ messages in thread