[PATCH] arm64: Optionally disable EL0 MTE via command-line

From: Pierre-Clément Tosi

Date: Fri Feb 13 2026 - 06:51:29 EST


Although it is currently possible to fully disable MTE on MTE-capable
CPUs (with arm64.nomte or id_aa64pfr1.mte=0) and to only use MTE in
userspace (with kasan=off), there is no way to limit the use of MTE to
the kernel because CPU capabilities are traditionally exposed directly
to userspace.

To address this, introduce a new cmdline argument (inspired by the
existing arm64.nomte) to only expose the MTE capability of the CPU to
the kernel. Combined with KASAN, this results in only the kernel using
the feature, while HWCAP2_MTE and the corresponding MSR ID_AA64PFR1_EL1
field are hidden from userspace.

Implement it as a software-only feature override, similar to nokaslr.

Signed-off-by: Pierre-Clément Tosi <ptosi@xxxxxxxxxx>
---
Documentation/admin-guide/kernel-parameters.txt | 3 +++
arch/arm64/include/asm/cpufeature.h | 1 +
arch/arm64/kernel/cpufeature.c | 8 ++++++++
arch/arm64/kernel/pi/idreg-override.c | 2 ++
4 files changed, 14 insertions(+)

diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 0869294363b3..4d138c1826f0 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -564,6 +564,9 @@ Kernel parameters
arm64.nomte [ARM64] Unconditionally disable Memory Tagging Extension
support

+ arm64.nomte_el0 [ARM64] Unconditionally disable Memory Tagging Extension
+ support for userspace
+
arm64.nopauth [ARM64] Unconditionally disable Pointer Authentication
support

diff --git a/arch/arm64/include/asm/cpufeature.h b/arch/arm64/include/asm/cpufeature.h
index 4de51f8d92cb..0944ff5084a2 100644
--- a/arch/arm64/include/asm/cpufeature.h
+++ b/arch/arm64/include/asm/cpufeature.h
@@ -18,6 +18,7 @@
#define ARM64_SW_FEATURE_OVERRIDE_NOKASLR 0
#define ARM64_SW_FEATURE_OVERRIDE_HVHE 4
#define ARM64_SW_FEATURE_OVERRIDE_RODATA_OFF 8
+#define ARM64_SW_FEATURE_OVERRIDE_NOMTE_EL0 12

#ifndef __ASSEMBLER__

diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c
index 6044d463d3fb..81ea00050e56 100644
--- a/arch/arm64/kernel/cpufeature.c
+++ b/arch/arm64/kernel/cpufeature.c
@@ -2412,6 +2412,14 @@ static void user_feature_fixup(void)
if (regp)
regp->user_mask &= ~ID_AA64PFR1_EL1_SSBS_MASK;
}
+
+ if (arm64_test_sw_feature_override(ARM64_SW_FEATURE_OVERRIDE_NOMTE_EL0)) {
+ struct arm64_ftr_reg *regp;
+
+ regp = get_arm64_ftr_reg(SYS_ID_AA64PFR1_EL1);
+ if (regp)
+ regp->user_mask &= ~ID_AA64PFR1_EL1_MTE_MASK;
+ }
}

static void elf_hwcap_fixup(void)
diff --git a/arch/arm64/kernel/pi/idreg-override.c b/arch/arm64/kernel/pi/idreg-override.c
index bc57b290e5e7..758141bf9e37 100644
--- a/arch/arm64/kernel/pi/idreg-override.c
+++ b/arch/arm64/kernel/pi/idreg-override.c
@@ -211,6 +211,7 @@ static const struct ftr_set_desc sw_features __prel64_initconst = {
FIELD("nokaslr", ARM64_SW_FEATURE_OVERRIDE_NOKASLR, NULL),
FIELD("hvhe", ARM64_SW_FEATURE_OVERRIDE_HVHE, hvhe_filter),
FIELD("rodataoff", ARM64_SW_FEATURE_OVERRIDE_RODATA_OFF, NULL),
+ FIELD("nomte_el0", ARM64_SW_FEATURE_OVERRIDE_NOMTE_EL0, NULL),
{}
},
};
@@ -244,6 +245,7 @@ static const struct {
"id_aa64isar2.gpa3=0 id_aa64isar2.apa3=0" },
{ "arm64.nomops", "id_aa64isar2.mops=0" },
{ "arm64.nomte", "id_aa64pfr1.mte=0" },
+ { "arm64.nomte_el0", "arm64_sw.nomte_el0=1" },
{ "nokaslr", "arm64_sw.nokaslr=1" },
{ "rodata=off", "arm64_sw.rodataoff=1" },
{ "arm64.nolva", "id_aa64mmfr2.varange=0" },
--
2.53.0.273.g2a3d683680-goog


--
Pierre