[PATCH v4 1/2] kasan/hw-tags: introduce kasan.write_only option

Yeoreum Yun posted 2 patches 1 month, 2 weeks ago
There is a newer version of this series
[PATCH v4 1/2] kasan/hw-tags: introduce kasan.write_only option
Posted by Yeoreum Yun 1 month, 2 weeks ago
Since Armv8.9, FEATURE_MTE_STORE_ONLY feature is introduced to restrict
raise of tag check fault on store operation only.
Introcude KASAN write only mode based on this feature.

KASAN write only mode restricts KASAN checks operation for write only and
omits the checks for fetch/read operations when accessing memory.
So it might be used not only debugging enviroment but also normal
enviroment to check memory safty.

This features can be controlled with "kasan.write_only" arguments.
When "kasan.write_only=on", KASAN checks write operation only otherwise
KASAN checks all operations.

This changes the MTE_STORE_ONLY feature as BOOT_CPU_FEATURE like
ARM64_MTE_ASYMM so that makes it initialise in kasan_init_hw_tags()
with other function together.

Signed-off-by: Yeoreum Yun <yeoreum.yun@arm.com>
---
 Documentation/dev-tools/kasan.rst  |  3 ++
 arch/arm64/include/asm/memory.h    |  1 +
 arch/arm64/include/asm/mte-kasan.h |  6 ++++
 arch/arm64/kernel/cpufeature.c     |  2 +-
 arch/arm64/kernel/mte.c            | 18 ++++++++++
 mm/kasan/hw_tags.c                 | 54 ++++++++++++++++++++++++++++--
 mm/kasan/kasan.h                   |  7 ++++
 7 files changed, 88 insertions(+), 3 deletions(-)

diff --git a/Documentation/dev-tools/kasan.rst b/Documentation/dev-tools/kasan.rst
index 0a1418ab72fd..fe1a1e152275 100644
--- a/Documentation/dev-tools/kasan.rst
+++ b/Documentation/dev-tools/kasan.rst
@@ -143,6 +143,9 @@ disabling KASAN altogether or controlling its features:
   Asymmetric mode: a bad access is detected synchronously on reads and
   asynchronously on writes.
 
+- ``kasan.write_only=off`` or ``kasan.write_only=on`` controls whether KASAN
+  checks the write (store) accesses only or all accesses (default: ``off``)
+
 - ``kasan.vmalloc=off`` or ``=on`` disables or enables tagging of vmalloc
   allocations (default: ``on``).
 
diff --git a/arch/arm64/include/asm/memory.h b/arch/arm64/include/asm/memory.h
index 5213248e081b..f1505c4acb38 100644
--- a/arch/arm64/include/asm/memory.h
+++ b/arch/arm64/include/asm/memory.h
@@ -308,6 +308,7 @@ static inline const void *__tag_set(const void *addr, u8 tag)
 #define arch_enable_tag_checks_sync()		mte_enable_kernel_sync()
 #define arch_enable_tag_checks_async()		mte_enable_kernel_async()
 #define arch_enable_tag_checks_asymm()		mte_enable_kernel_asymm()
+#define arch_enable_tag_checks_write_only()	mte_enable_kernel_store_only()
 #define arch_suppress_tag_checks_start()	mte_enable_tco()
 #define arch_suppress_tag_checks_stop()		mte_disable_tco()
 #define arch_force_async_tag_fault()		mte_check_tfsr_exit()
diff --git a/arch/arm64/include/asm/mte-kasan.h b/arch/arm64/include/asm/mte-kasan.h
index 2e98028c1965..0f9b08e8fb8d 100644
--- a/arch/arm64/include/asm/mte-kasan.h
+++ b/arch/arm64/include/asm/mte-kasan.h
@@ -200,6 +200,7 @@ static inline void mte_set_mem_tag_range(void *addr, size_t size, u8 tag,
 void mte_enable_kernel_sync(void);
 void mte_enable_kernel_async(void);
 void mte_enable_kernel_asymm(void);
+int mte_enable_kernel_store_only(void);
 
 #else /* CONFIG_ARM64_MTE */
 
@@ -251,6 +252,11 @@ static inline void mte_enable_kernel_asymm(void)
 {
 }
 
+static inline int mte_enable_kernel_store_only(void)
+{
+	return -EINVAL;
+}
+
 #endif /* CONFIG_ARM64_MTE */
 
 #endif /* __ASSEMBLY__ */
diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c
index 9ad065f15f1d..505bd56e21a2 100644
--- a/arch/arm64/kernel/cpufeature.c
+++ b/arch/arm64/kernel/cpufeature.c
@@ -2920,7 +2920,7 @@ static const struct arm64_cpu_capabilities arm64_features[] = {
 	{
 		.desc = "Store Only MTE Tag Check",
 		.capability = ARM64_MTE_STORE_ONLY,
-		.type = ARM64_CPUCAP_SYSTEM_FEATURE,
+		.type = ARM64_CPUCAP_BOOT_CPU_FEATURE,
 		.matches = has_cpuid_feature,
 		ARM64_CPUID_FIELDS(ID_AA64PFR2_EL1, MTESTOREONLY, IMP)
 	},
diff --git a/arch/arm64/kernel/mte.c b/arch/arm64/kernel/mte.c
index e5e773844889..cd5452eb7486 100644
--- a/arch/arm64/kernel/mte.c
+++ b/arch/arm64/kernel/mte.c
@@ -157,6 +157,24 @@ void mte_enable_kernel_asymm(void)
 		mte_enable_kernel_sync();
 	}
 }
+
+int mte_enable_kernel_store_only(void)
+{
+	/*
+	 * If the CPU does not support MTE store only,
+	 * the kernel checks all operations.
+	 */
+	if (!cpus_have_cap(ARM64_MTE_STORE_ONLY))
+		return -EINVAL;
+
+	sysreg_clear_set(sctlr_el1, SCTLR_EL1_TCSO_MASK,
+			 SYS_FIELD_PREP(SCTLR_EL1, TCSO, 1));
+	isb();
+
+	pr_info_once("MTE: enabled stonly mode at EL1\n");
+
+	return 0;
+}
 #endif
 
 #ifdef CONFIG_KASAN_HW_TAGS
diff --git a/mm/kasan/hw_tags.c b/mm/kasan/hw_tags.c
index 9a6927394b54..df67b48739b4 100644
--- a/mm/kasan/hw_tags.c
+++ b/mm/kasan/hw_tags.c
@@ -41,9 +41,16 @@ enum kasan_arg_vmalloc {
 	KASAN_ARG_VMALLOC_ON,
 };
 
+enum kasan_arg_write_only {
+	KASAN_ARG_WRITE_ONLY_DEFAULT,
+	KASAN_ARG_WRITE_ONLY_OFF,
+	KASAN_ARG_WRITE_ONLY_ON,
+};
+
 static enum kasan_arg kasan_arg __ro_after_init;
 static enum kasan_arg_mode kasan_arg_mode __ro_after_init;
 static enum kasan_arg_vmalloc kasan_arg_vmalloc __initdata;
+static enum kasan_arg_write_only kasan_arg_write_only __ro_after_init;
 
 /*
  * Whether KASAN is enabled at all.
@@ -67,6 +74,8 @@ DEFINE_STATIC_KEY_FALSE(kasan_flag_vmalloc);
 #endif
 EXPORT_SYMBOL_GPL(kasan_flag_vmalloc);
 
+static bool kasan_flag_write_only;
+
 #define PAGE_ALLOC_SAMPLE_DEFAULT	1
 #define PAGE_ALLOC_SAMPLE_ORDER_DEFAULT	3
 
@@ -141,6 +150,23 @@ static int __init early_kasan_flag_vmalloc(char *arg)
 }
 early_param("kasan.vmalloc", early_kasan_flag_vmalloc);
 
+/* kasan.write_only=off/on */
+static int __init early_kasan_flag_write_only(char *arg)
+{
+	if (!arg)
+		return -EINVAL;
+
+	if (!strcmp(arg, "off"))
+		kasan_arg_write_only = KASAN_ARG_WRITE_ONLY_OFF;
+	else if (!strcmp(arg, "on"))
+		kasan_arg_write_only = KASAN_ARG_WRITE_ONLY_ON;
+	else
+		return -EINVAL;
+
+	return 0;
+}
+early_param("kasan.write_only", early_kasan_flag_write_only);
+
 static inline const char *kasan_mode_info(void)
 {
 	if (kasan_mode == KASAN_MODE_ASYNC)
@@ -257,15 +283,26 @@ void __init kasan_init_hw_tags(void)
 		break;
 	}
 
+	switch (kasan_arg_write_only) {
+	case KASAN_ARG_WRITE_ONLY_DEFAULT:
+	case KASAN_ARG_WRITE_ONLY_OFF:
+		kasan_flag_write_only = false;
+		break;
+	case KASAN_ARG_WRITE_ONLY_ON:
+		kasan_flag_write_only = true;
+		break;
+	}
+
 	kasan_init_tags();
 
 	/* KASAN is now initialized, enable it. */
 	static_branch_enable(&kasan_flag_enabled);
 
-	pr_info("KernelAddressSanitizer initialized (hw-tags, mode=%s, vmalloc=%s, stacktrace=%s)\n",
+	pr_info("KernelAddressSanitizer initialized (hw-tags, mode=%s, vmalloc=%s, stacktrace=%s, write_only=%s\n",
 		kasan_mode_info(),
 		str_on_off(kasan_vmalloc_enabled()),
-		str_on_off(kasan_stack_collection_enabled()));
+		str_on_off(kasan_stack_collection_enabled()),
+		str_on_off(kasan_arg_write_only));
 }
 
 #ifdef CONFIG_KASAN_VMALLOC
@@ -392,6 +429,13 @@ void kasan_enable_hw_tags(void)
 		hw_enable_tag_checks_asymm();
 	else
 		hw_enable_tag_checks_sync();
+
+	if (kasan_arg_write_only == KASAN_ARG_WRITE_ONLY_ON &&
+	    hw_enable_tag_checks_write_only()) {
+		kasan_arg_write_only == KASAN_ARG_WRITE_ONLY_OFF;
+		kasan_flag_write_only = false;
+		pr_warn_once("System doesn't support write-only option. Disable it\n");
+	}
 }
 
 #if IS_ENABLED(CONFIG_KASAN_KUNIT_TEST)
@@ -404,4 +448,10 @@ VISIBLE_IF_KUNIT void kasan_force_async_fault(void)
 }
 EXPORT_SYMBOL_IF_KUNIT(kasan_force_async_fault);
 
+VISIBLE_IF_KUNIT bool kasan_write_only_enabled(void)
+{
+	return kasan_flag_write_only;
+}
+EXPORT_SYMBOL_IF_KUNIT(kasan_write_only_enabled);
+
 #endif
diff --git a/mm/kasan/kasan.h b/mm/kasan/kasan.h
index 129178be5e64..c1490136c96b 100644
--- a/mm/kasan/kasan.h
+++ b/mm/kasan/kasan.h
@@ -428,6 +428,7 @@ static inline const void *arch_kasan_set_tag(const void *addr, u8 tag)
 #define hw_enable_tag_checks_sync()		arch_enable_tag_checks_sync()
 #define hw_enable_tag_checks_async()		arch_enable_tag_checks_async()
 #define hw_enable_tag_checks_asymm()		arch_enable_tag_checks_asymm()
+#define hw_enable_tag_checks_write_only()	arch_enable_tag_checks_write_only()
 #define hw_suppress_tag_checks_start()		arch_suppress_tag_checks_start()
 #define hw_suppress_tag_checks_stop()		arch_suppress_tag_checks_stop()
 #define hw_force_async_tag_fault()		arch_force_async_tag_fault()
@@ -437,11 +438,17 @@ static inline const void *arch_kasan_set_tag(const void *addr, u8 tag)
 			arch_set_mem_tag_range((addr), (size), (tag), (init))
 
 void kasan_enable_hw_tags(void);
+bool kasan_write_only_enabled(void);
 
 #else /* CONFIG_KASAN_HW_TAGS */
 
 static inline void kasan_enable_hw_tags(void) { }
 
+static inline bool kasan_write_only_enabled(void)
+{
+	return false;
+}
+
 #endif /* CONFIG_KASAN_HW_TAGS */
 
 #if defined(CONFIG_KASAN_SW_TAGS) || defined(CONFIG_KASAN_HW_TAGS)
-- 
LEVI:{C3F47F37-75D8-414A-A8BA-3980EC8A46D7}
Re: [PATCH v4 1/2] kasan/hw-tags: introduce kasan.write_only option
Posted by Andrey Konovalov 1 month, 2 weeks ago
On Mon, Aug 18, 2025 at 9:51 AM Yeoreum Yun <yeoreum.yun@arm.com> wrote:
>
> Since Armv8.9, FEATURE_MTE_STORE_ONLY feature is introduced to restrict
> raise of tag check fault on store operation only.
> Introcude KASAN write only mode based on this feature.
>
> KASAN write only mode restricts KASAN checks operation for write only and
> omits the checks for fetch/read operations when accessing memory.
> So it might be used not only debugging enviroment but also normal
> enviroment to check memory safty.
>
> This features can be controlled with "kasan.write_only" arguments.
> When "kasan.write_only=on", KASAN checks write operation only otherwise
> KASAN checks all operations.
>
> This changes the MTE_STORE_ONLY feature as BOOT_CPU_FEATURE like
> ARM64_MTE_ASYMM so that makes it initialise in kasan_init_hw_tags()
> with other function together.
>
> Signed-off-by: Yeoreum Yun <yeoreum.yun@arm.com>
> ---
>  Documentation/dev-tools/kasan.rst  |  3 ++
>  arch/arm64/include/asm/memory.h    |  1 +
>  arch/arm64/include/asm/mte-kasan.h |  6 ++++
>  arch/arm64/kernel/cpufeature.c     |  2 +-
>  arch/arm64/kernel/mte.c            | 18 ++++++++++
>  mm/kasan/hw_tags.c                 | 54 ++++++++++++++++++++++++++++--
>  mm/kasan/kasan.h                   |  7 ++++
>  7 files changed, 88 insertions(+), 3 deletions(-)
>
> diff --git a/Documentation/dev-tools/kasan.rst b/Documentation/dev-tools/kasan.rst
> index 0a1418ab72fd..fe1a1e152275 100644
> --- a/Documentation/dev-tools/kasan.rst
> +++ b/Documentation/dev-tools/kasan.rst
> @@ -143,6 +143,9 @@ disabling KASAN altogether or controlling its features:
>    Asymmetric mode: a bad access is detected synchronously on reads and
>    asynchronously on writes.
>
> +- ``kasan.write_only=off`` or ``kasan.write_only=on`` controls whether KASAN
> +  checks the write (store) accesses only or all accesses (default: ``off``)
> +
>  - ``kasan.vmalloc=off`` or ``=on`` disables or enables tagging of vmalloc
>    allocations (default: ``on``).
>
> diff --git a/arch/arm64/include/asm/memory.h b/arch/arm64/include/asm/memory.h
> index 5213248e081b..f1505c4acb38 100644
> --- a/arch/arm64/include/asm/memory.h
> +++ b/arch/arm64/include/asm/memory.h
> @@ -308,6 +308,7 @@ static inline const void *__tag_set(const void *addr, u8 tag)
>  #define arch_enable_tag_checks_sync()          mte_enable_kernel_sync()
>  #define arch_enable_tag_checks_async()         mte_enable_kernel_async()
>  #define arch_enable_tag_checks_asymm()         mte_enable_kernel_asymm()
> +#define arch_enable_tag_checks_write_only()    mte_enable_kernel_store_only()
>  #define arch_suppress_tag_checks_start()       mte_enable_tco()
>  #define arch_suppress_tag_checks_stop()                mte_disable_tco()
>  #define arch_force_async_tag_fault()           mte_check_tfsr_exit()
> diff --git a/arch/arm64/include/asm/mte-kasan.h b/arch/arm64/include/asm/mte-kasan.h
> index 2e98028c1965..0f9b08e8fb8d 100644
> --- a/arch/arm64/include/asm/mte-kasan.h
> +++ b/arch/arm64/include/asm/mte-kasan.h
> @@ -200,6 +200,7 @@ static inline void mte_set_mem_tag_range(void *addr, size_t size, u8 tag,
>  void mte_enable_kernel_sync(void);
>  void mte_enable_kernel_async(void);
>  void mte_enable_kernel_asymm(void);
> +int mte_enable_kernel_store_only(void);
>
>  #else /* CONFIG_ARM64_MTE */
>
> @@ -251,6 +252,11 @@ static inline void mte_enable_kernel_asymm(void)
>  {
>  }
>
> +static inline int mte_enable_kernel_store_only(void)
> +{
> +       return -EINVAL;
> +}
> +
>  #endif /* CONFIG_ARM64_MTE */
>
>  #endif /* __ASSEMBLY__ */
> diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c
> index 9ad065f15f1d..505bd56e21a2 100644
> --- a/arch/arm64/kernel/cpufeature.c
> +++ b/arch/arm64/kernel/cpufeature.c
> @@ -2920,7 +2920,7 @@ static const struct arm64_cpu_capabilities arm64_features[] = {
>         {
>                 .desc = "Store Only MTE Tag Check",
>                 .capability = ARM64_MTE_STORE_ONLY,
> -               .type = ARM64_CPUCAP_SYSTEM_FEATURE,
> +               .type = ARM64_CPUCAP_BOOT_CPU_FEATURE,
>                 .matches = has_cpuid_feature,
>                 ARM64_CPUID_FIELDS(ID_AA64PFR2_EL1, MTESTOREONLY, IMP)
>         },
> diff --git a/arch/arm64/kernel/mte.c b/arch/arm64/kernel/mte.c
> index e5e773844889..cd5452eb7486 100644
> --- a/arch/arm64/kernel/mte.c
> +++ b/arch/arm64/kernel/mte.c
> @@ -157,6 +157,24 @@ void mte_enable_kernel_asymm(void)
>                 mte_enable_kernel_sync();
>         }
>  }
> +
> +int mte_enable_kernel_store_only(void)
> +{
> +       /*
> +        * If the CPU does not support MTE store only,
> +        * the kernel checks all operations.
> +        */
> +       if (!cpus_have_cap(ARM64_MTE_STORE_ONLY))
> +               return -EINVAL;
> +
> +       sysreg_clear_set(sctlr_el1, SCTLR_EL1_TCSO_MASK,
> +                        SYS_FIELD_PREP(SCTLR_EL1, TCSO, 1));
> +       isb();
> +
> +       pr_info_once("MTE: enabled stonly mode at EL1\n");
> +
> +       return 0;
> +}
>  #endif
>
>  #ifdef CONFIG_KASAN_HW_TAGS
> diff --git a/mm/kasan/hw_tags.c b/mm/kasan/hw_tags.c
> index 9a6927394b54..df67b48739b4 100644
> --- a/mm/kasan/hw_tags.c
> +++ b/mm/kasan/hw_tags.c
> @@ -41,9 +41,16 @@ enum kasan_arg_vmalloc {
>         KASAN_ARG_VMALLOC_ON,
>  };
>
> +enum kasan_arg_write_only {
> +       KASAN_ARG_WRITE_ONLY_DEFAULT,
> +       KASAN_ARG_WRITE_ONLY_OFF,
> +       KASAN_ARG_WRITE_ONLY_ON,
> +};
> +
>  static enum kasan_arg kasan_arg __ro_after_init;
>  static enum kasan_arg_mode kasan_arg_mode __ro_after_init;
>  static enum kasan_arg_vmalloc kasan_arg_vmalloc __initdata;
> +static enum kasan_arg_write_only kasan_arg_write_only __ro_after_init;
>
>  /*
>   * Whether KASAN is enabled at all.
> @@ -67,6 +74,8 @@ DEFINE_STATIC_KEY_FALSE(kasan_flag_vmalloc);
>  #endif
>  EXPORT_SYMBOL_GPL(kasan_flag_vmalloc);
>
> +static bool kasan_flag_write_only;
> +
>  #define PAGE_ALLOC_SAMPLE_DEFAULT      1
>  #define PAGE_ALLOC_SAMPLE_ORDER_DEFAULT        3
>
> @@ -141,6 +150,23 @@ static int __init early_kasan_flag_vmalloc(char *arg)
>  }
>  early_param("kasan.vmalloc", early_kasan_flag_vmalloc);
>
> +/* kasan.write_only=off/on */
> +static int __init early_kasan_flag_write_only(char *arg)
> +{
> +       if (!arg)
> +               return -EINVAL;
> +
> +       if (!strcmp(arg, "off"))
> +               kasan_arg_write_only = KASAN_ARG_WRITE_ONLY_OFF;
> +       else if (!strcmp(arg, "on"))
> +               kasan_arg_write_only = KASAN_ARG_WRITE_ONLY_ON;
> +       else
> +               return -EINVAL;
> +
> +       return 0;
> +}
> +early_param("kasan.write_only", early_kasan_flag_write_only);
> +
>  static inline const char *kasan_mode_info(void)
>  {
>         if (kasan_mode == KASAN_MODE_ASYNC)
> @@ -257,15 +283,26 @@ void __init kasan_init_hw_tags(void)
>                 break;
>         }
>
> +       switch (kasan_arg_write_only) {
> +       case KASAN_ARG_WRITE_ONLY_DEFAULT:

Let's keep this part similar to the other parameters for consistency:

/* Default is specified by kasan_flag_write_only definition. */
break;

> +       case KASAN_ARG_WRITE_ONLY_OFF:
> +               kasan_flag_write_only = false;
> +               break;
> +       case KASAN_ARG_WRITE_ONLY_ON:
> +               kasan_flag_write_only = true;
> +               break;
> +       }
> +
>         kasan_init_tags();
>
>         /* KASAN is now initialized, enable it. */
>         static_branch_enable(&kasan_flag_enabled);
>
> -       pr_info("KernelAddressSanitizer initialized (hw-tags, mode=%s, vmalloc=%s, stacktrace=%s)\n",
> +       pr_info("KernelAddressSanitizer initialized (hw-tags, mode=%s, vmalloc=%s, stacktrace=%s, write_only=%s\n",
>                 kasan_mode_info(),
>                 str_on_off(kasan_vmalloc_enabled()),
> -               str_on_off(kasan_stack_collection_enabled()));
> +               str_on_off(kasan_stack_collection_enabled()),
> +               str_on_off(kasan_arg_write_only));
>  }
>
>  #ifdef CONFIG_KASAN_VMALLOC
> @@ -392,6 +429,13 @@ void kasan_enable_hw_tags(void)
>                 hw_enable_tag_checks_asymm();
>         else
>                 hw_enable_tag_checks_sync();
> +
> +       if (kasan_arg_write_only == KASAN_ARG_WRITE_ONLY_ON &&

We already set kasan_flag_write_only by this point, right? Let's check
that one then.

> +           hw_enable_tag_checks_write_only()) {
> +               kasan_arg_write_only == KASAN_ARG_WRITE_ONLY_OFF;

Typo in == in the line above. But also I think we can just drop the
line: kasan_arg_write_only is KASAN_ARG_WRITE_ONLY_ON after all, it's
just not supported and thus kasan_flag_write_only is set to false to
reflect that.

> +               kasan_flag_write_only = false;
> +               pr_warn_once("System doesn't support write-only option. Disable it\n");

Let's do pr_err like the rest of KASAN code. And:

pr_err_once("write-only mode is not supported and thus not enabled\n");



> +       }
>  }
>
>  #if IS_ENABLED(CONFIG_KASAN_KUNIT_TEST)
> @@ -404,4 +448,10 @@ VISIBLE_IF_KUNIT void kasan_force_async_fault(void)
>  }
>  EXPORT_SYMBOL_IF_KUNIT(kasan_force_async_fault);
>
> +VISIBLE_IF_KUNIT bool kasan_write_only_enabled(void)
> +{
> +       return kasan_flag_write_only;
> +}
> +EXPORT_SYMBOL_IF_KUNIT(kasan_write_only_enabled);
> +
>  #endif
> diff --git a/mm/kasan/kasan.h b/mm/kasan/kasan.h
> index 129178be5e64..c1490136c96b 100644
> --- a/mm/kasan/kasan.h
> +++ b/mm/kasan/kasan.h
> @@ -428,6 +428,7 @@ static inline const void *arch_kasan_set_tag(const void *addr, u8 tag)
>  #define hw_enable_tag_checks_sync()            arch_enable_tag_checks_sync()
>  #define hw_enable_tag_checks_async()           arch_enable_tag_checks_async()
>  #define hw_enable_tag_checks_asymm()           arch_enable_tag_checks_asymm()
> +#define hw_enable_tag_checks_write_only()      arch_enable_tag_checks_write_only()
>  #define hw_suppress_tag_checks_start()         arch_suppress_tag_checks_start()
>  #define hw_suppress_tag_checks_stop()          arch_suppress_tag_checks_stop()
>  #define hw_force_async_tag_fault()             arch_force_async_tag_fault()
> @@ -437,11 +438,17 @@ static inline const void *arch_kasan_set_tag(const void *addr, u8 tag)
>                         arch_set_mem_tag_range((addr), (size), (tag), (init))
>
>  void kasan_enable_hw_tags(void);
> +bool kasan_write_only_enabled(void);

This should go next to kasan_force_async_fault().

>
>  #else /* CONFIG_KASAN_HW_TAGS */
>
>  static inline void kasan_enable_hw_tags(void) { }
>
> +static inline bool kasan_write_only_enabled(void)
> +{
> +       return false;
> +}

And this too.


> +
>  #endif /* CONFIG_KASAN_HW_TAGS */
>
>  #if defined(CONFIG_KASAN_SW_TAGS) || defined(CONFIG_KASAN_HW_TAGS)
> --
> LEVI:{C3F47F37-75D8-414A-A8BA-3980EC8A46D7}
>
Re: [PATCH v4 1/2] kasan/hw-tags: introduce kasan.write_only option
Posted by Yeoreum Yun 1 month, 2 weeks ago
Hi Andrey,

[...]

> >  static inline const char *kasan_mode_info(void)
> >  {
> >         if (kasan_mode == KASAN_MODE_ASYNC)
> > @@ -257,15 +283,26 @@ void __init kasan_init_hw_tags(void)
> >                 break;
> >         }
> >
> > +       switch (kasan_arg_write_only) {
> > +       case KASAN_ARG_WRITE_ONLY_DEFAULT:
>
> Let's keep this part similar to the other parameters for consistency:
>
> /* Default is specified by kasan_flag_write_only definition. */
> break;

Okay.

> > +       case KASAN_ARG_WRITE_ONLY_OFF:
> > +               kasan_flag_write_only = false;
> > +               break;
> > +       case KASAN_ARG_WRITE_ONLY_ON:
> > +               kasan_flag_write_only = true;
> > +               break;
> > +       }
> > +
> >         kasan_init_tags();
> >
> >         /* KASAN is now initialized, enable it. */
> >         static_branch_enable(&kasan_flag_enabled);
> >
> > -       pr_info("KernelAddressSanitizer initialized (hw-tags, mode=%s, vmalloc=%s, stacktrace=%s)\n",
> > +       pr_info("KernelAddressSanitizer initialized (hw-tags, mode=%s, vmalloc=%s, stacktrace=%s, write_only=%s\n",
> >                 kasan_mode_info(),
> >                 str_on_off(kasan_vmalloc_enabled()),
> > -               str_on_off(kasan_stack_collection_enabled()));
> > +               str_on_off(kasan_stack_collection_enabled()),
> > +               str_on_off(kasan_arg_write_only));
> >  }
> >
> >  #ifdef CONFIG_KASAN_VMALLOC
> > @@ -392,6 +429,13 @@ void kasan_enable_hw_tags(void)
> >                 hw_enable_tag_checks_asymm();
> >         else
> >                 hw_enable_tag_checks_sync();
> > +
> > +       if (kasan_arg_write_only == KASAN_ARG_WRITE_ONLY_ON &&
>
> We already set kasan_flag_write_only by this point, right? Let's check
> that one then.

Yes. if user specifies the write_only == on.
>
> > +           hw_enable_tag_checks_write_only()) {
> > +               kasan_arg_write_only == KASAN_ARG_WRITE_ONLY_OFF;
>
> Typo in == in the line above. But also I think we can just drop the
> line: kasan_arg_write_only is KASAN_ARG_WRITE_ONLY_ON after all, it's
> just not supported and thus kasan_flag_write_only is set to false to
> reflect that.

Sorry :\ I've missed this fix from patch 3... this should be == to =.

However, we couldn't remove kasan_arg_write_only check in condition.
If one of cpu get failed to hw_enable_tag_checks_write_only() then
By changing this with KASAN_ARG_WRITE_ONLY_OFF, It prevent to call
hw_eanble_tag_checks_write_only() in other cpu.

As you said, kasan_flag_write_only reflects the state.
But like other option, I keep the condition to call the hw_enable_xxx()
by checking the "argments" and keep the "hw enable state" with
kasan_flag_write_only.

so let me change == to = only in here.

>
> > +               kasan_flag_write_only = false;
> > +               pr_warn_once("System doesn't support write-only option. Disable it\n");
>
> Let's do pr_err like the rest of KASAN code. And:
>
> pr_err_once("write-only mode is not supported and thus not enabled\n");

Okay. Thanks for suggestion.

>
>
>
> > +       }
> >  }
> >
> >  #if IS_ENABLED(CONFIG_KASAN_KUNIT_TEST)
> > @@ -404,4 +448,10 @@ VISIBLE_IF_KUNIT void kasan_force_async_fault(void)
> >  }
> >  EXPORT_SYMBOL_IF_KUNIT(kasan_force_async_fault);
> >
> > +VISIBLE_IF_KUNIT bool kasan_write_only_enabled(void)
> > +{
> > +       return kasan_flag_write_only;
> > +}
> > +EXPORT_SYMBOL_IF_KUNIT(kasan_write_only_enabled);
> > +
> >  #endif
> > diff --git a/mm/kasan/kasan.h b/mm/kasan/kasan.h
> > index 129178be5e64..c1490136c96b 100644
> > --- a/mm/kasan/kasan.h
> > +++ b/mm/kasan/kasan.h
> > @@ -428,6 +428,7 @@ static inline const void *arch_kasan_set_tag(const void *addr, u8 tag)
> >  #define hw_enable_tag_checks_sync()            arch_enable_tag_checks_sync()
> >  #define hw_enable_tag_checks_async()           arch_enable_tag_checks_async()
> >  #define hw_enable_tag_checks_asymm()           arch_enable_tag_checks_asymm()
> > +#define hw_enable_tag_checks_write_only()      arch_enable_tag_checks_write_only()
> >  #define hw_suppress_tag_checks_start()         arch_suppress_tag_checks_start()
> >  #define hw_suppress_tag_checks_stop()          arch_suppress_tag_checks_stop()
> >  #define hw_force_async_tag_fault()             arch_force_async_tag_fault()
> > @@ -437,11 +438,17 @@ static inline const void *arch_kasan_set_tag(const void *addr, u8 tag)
> >                         arch_set_mem_tag_range((addr), (size), (tag), (init))
> >
> >  void kasan_enable_hw_tags(void);
> > +bool kasan_write_only_enabled(void);
>
> This should go next to kasan_force_async_fault().
>
> >
> >  #else /* CONFIG_KASAN_HW_TAGS */
> >
> >  static inline void kasan_enable_hw_tags(void) { }
> >
> > +static inline bool kasan_write_only_enabled(void)
> > +{
> > +       return false;
> > +}
>
> And this too.

Right. I'll move them. Thanks!

Thanks!

--
Sincerely,
Yeoreum Yun
Re: [PATCH v4 1/2] kasan/hw-tags: introduce kasan.write_only option
Posted by Andrey Konovalov 1 month, 2 weeks ago
On Mon, Aug 18, 2025 at 3:11 PM Yeoreum Yun <yeoreum.yun@arm.com> wrote:
>
> > > +           hw_enable_tag_checks_write_only()) {
> > > +               kasan_arg_write_only == KASAN_ARG_WRITE_ONLY_OFF;
> >
> > Typo in == in the line above. But also I think we can just drop the
> > line: kasan_arg_write_only is KASAN_ARG_WRITE_ONLY_ON after all, it's
> > just not supported and thus kasan_flag_write_only is set to false to
> > reflect that.
>
> Sorry :\ I've missed this fix from patch 3... this should be == to =.
>
> However, we couldn't remove kasan_arg_write_only check in condition.
> If one of cpu get failed to hw_enable_tag_checks_write_only() then
> By changing this with KASAN_ARG_WRITE_ONLY_OFF, It prevent to call
> hw_eanble_tag_checks_write_only() in other cpu.

Is it possible that the write-only mode will fail to be enabled on one
CPU but then get enabled successfully for another?

What would happen with the current code if the first CPU succeeds in
enabling the write-only mode, and the second one fails?

> As you said, kasan_flag_write_only reflects the state.
> But like other option, I keep the condition to call the hw_enable_xxx()
> by checking the "argments" and keep the "hw enable state" with
> kasan_flag_write_only.

Assuming we keep this behavior, please add a comment explaining all this.
Re: [PATCH v4 1/2] kasan/hw-tags: introduce kasan.write_only option
Posted by Yeoreum Yun 1 month, 2 weeks ago
Hi Andery,
> On Mon, Aug 18, 2025 at 3:11 PM Yeoreum Yun <yeoreum.yun@arm.com> wrote:
> >
> > > > +           hw_enable_tag_checks_write_only()) {
> > > > +               kasan_arg_write_only == KASAN_ARG_WRITE_ONLY_OFF;
> > >
> > > Typo in == in the line above. But also I think we can just drop the
> > > line: kasan_arg_write_only is KASAN_ARG_WRITE_ONLY_ON after all, it's
> > > just not supported and thus kasan_flag_write_only is set to false to
> > > reflect that.
> >
> > Sorry :\ I've missed this fix from patch 3... this should be == to =.
> >
> > However, we couldn't remove kasan_arg_write_only check in condition.
> > If one of cpu get failed to hw_enable_tag_checks_write_only() then
> > By changing this with KASAN_ARG_WRITE_ONLY_OFF, It prevent to call
> > hw_eanble_tag_checks_write_only() in other cpu.
>
> Is it possible that the write-only mode will fail to be enabled on one
> CPU but then get enabled successfully for another?
>
> What would happen with the current code if the first CPU succeeds in
> enabling the write-only mode, and the second one fails?

The only failure case is when CPU doesn't support the MTE_STORE_ONLY feature.
Since MTE_STORE_ONLY is BOOT CPU feature, the CPUs have two state --
all CPUs have a MTE_STORE_ONLY feature or they doesn't have it.

So when boot-cpu try to enable the write-only option according to
"write_only=on" boot argument but the cpu doesn't have this feature,
hw_enable_tag_checks_write_only() will fail and prevent the calling
hw_enable_tag_checks_write_only() in other cpu by kasan_arg_write_only
as OFF to prevent other cpu call this function since it'll be failed
anyway.

So there is no case for failure -- the first CPU succeeds but second one
fails. if first one succeeds, all cpus will success ans vice versa.

This condition just to prevent to call
hw_enable_tags_checks_write_only() by other cpu if first cpu found it
doesn't support MTE_STORE_ONLY feature since other doesn't need to call
hw_eanble_tag_checks_write_only() function -- it'll be failed
so it is meaningless call.

>
> > As you said, kasan_flag_write_only reflects the state.
> > But like other option, I keep the condition to call the hw_enable_xxx()
> > by checking the "argments" and keep the "hw enable state" with
> > kasan_flag_write_only.
>
> Assuming we keep this behavior, please add a comment explaining all this.

Okay. :)

--
Sincerely,
Yeoreum Yun
Re: [PATCH v4 1/2] kasan/hw-tags: introduce kasan.write_only option
Posted by Ben Horgan 1 month, 2 weeks ago
Hi Yeoreum,

On 8/18/25 08:50, Yeoreum Yun wrote:
> Since Armv8.9, FEATURE_MTE_STORE_ONLY feature is introduced to restrict
> raise of tag check fault on store operation only.
> Introcude KASAN write only mode based on this feature.
> 
> KASAN write only mode restricts KASAN checks operation for write only and
> omits the checks for fetch/read operations when accessing memory.
> So it might be used not only debugging enviroment but also normal
> enviroment to check memory safty.
> 
> This features can be controlled with "kasan.write_only" arguments.
> When "kasan.write_only=on", KASAN checks write operation only otherwise
> KASAN checks all operations.
> 
> This changes the MTE_STORE_ONLY feature as BOOT_CPU_FEATURE like
> ARM64_MTE_ASYMM so that makes it initialise in kasan_init_hw_tags()
> with other function together.
> 
> Signed-off-by: Yeoreum Yun <yeoreum.yun@arm.com>
> ---
>   Documentation/dev-tools/kasan.rst  |  3 ++
>   arch/arm64/include/asm/memory.h    |  1 +
>   arch/arm64/include/asm/mte-kasan.h |  6 ++++
>   arch/arm64/kernel/cpufeature.c     |  2 +-
>   arch/arm64/kernel/mte.c            | 18 ++++++++++
>   mm/kasan/hw_tags.c                 | 54 ++++++++++++++++++++++++++++--
>   mm/kasan/kasan.h                   |  7 ++++
>   7 files changed, 88 insertions(+), 3 deletions(-)
> 
> diff --git a/Documentation/dev-tools/kasan.rst b/Documentation/dev-tools/kasan.rst
> index 0a1418ab72fd..fe1a1e152275 100644
> --- a/Documentation/dev-tools/kasan.rst
> +++ b/Documentation/dev-tools/kasan.rst
> @@ -143,6 +143,9 @@ disabling KASAN altogether or controlling its features:
>     Asymmetric mode: a bad access is detected synchronously on reads and
>     asynchronously on writes.
>   
> +- ``kasan.write_only=off`` or ``kasan.write_only=on`` controls whether KASAN
> +  checks the write (store) accesses only or all accesses (default: ``off``)
> +
>   - ``kasan.vmalloc=off`` or ``=on`` disables or enables tagging of vmalloc
>     allocations (default: ``on``).
>   
> diff --git a/arch/arm64/include/asm/memory.h b/arch/arm64/include/asm/memory.h
> index 5213248e081b..f1505c4acb38 100644
> --- a/arch/arm64/include/asm/memory.h
> +++ b/arch/arm64/include/asm/memory.h
> @@ -308,6 +308,7 @@ static inline const void *__tag_set(const void *addr, u8 tag)
>   #define arch_enable_tag_checks_sync()		mte_enable_kernel_sync()
>   #define arch_enable_tag_checks_async()		mte_enable_kernel_async()
>   #define arch_enable_tag_checks_asymm()		mte_enable_kernel_asymm()
> +#define arch_enable_tag_checks_write_only()	mte_enable_kernel_store_only()
>   #define arch_suppress_tag_checks_start()	mte_enable_tco()
>   #define arch_suppress_tag_checks_stop()		mte_disable_tco()
>   #define arch_force_async_tag_fault()		mte_check_tfsr_exit()
> diff --git a/arch/arm64/include/asm/mte-kasan.h b/arch/arm64/include/asm/mte-kasan.h
> index 2e98028c1965..0f9b08e8fb8d 100644
> --- a/arch/arm64/include/asm/mte-kasan.h
> +++ b/arch/arm64/include/asm/mte-kasan.h
> @@ -200,6 +200,7 @@ static inline void mte_set_mem_tag_range(void *addr, size_t size, u8 tag,
>   void mte_enable_kernel_sync(void);
>   void mte_enable_kernel_async(void);
>   void mte_enable_kernel_asymm(void);
> +int mte_enable_kernel_store_only(void);
>   
>   #else /* CONFIG_ARM64_MTE */
>   
> @@ -251,6 +252,11 @@ static inline void mte_enable_kernel_asymm(void)
>   {
>   }
>   
> +static inline int mte_enable_kernel_store_only(void)
> +{
> +	return -EINVAL;
> +}
> +
>   #endif /* CONFIG_ARM64_MTE */
>   
>   #endif /* __ASSEMBLY__ */
> diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c
> index 9ad065f15f1d..505bd56e21a2 100644
> --- a/arch/arm64/kernel/cpufeature.c
> +++ b/arch/arm64/kernel/cpufeature.c
> @@ -2920,7 +2920,7 @@ static const struct arm64_cpu_capabilities arm64_features[] = {
>   	{
>   		.desc = "Store Only MTE Tag Check",
>   		.capability = ARM64_MTE_STORE_ONLY,
> -		.type = ARM64_CPUCAP_SYSTEM_FEATURE,
> +		.type = ARM64_CPUCAP_BOOT_CPU_FEATURE,
>   		.matches = has_cpuid_feature,
>   		ARM64_CPUID_FIELDS(ID_AA64PFR2_EL1, MTESTOREONLY, IMP)
>   	},
> diff --git a/arch/arm64/kernel/mte.c b/arch/arm64/kernel/mte.c
> index e5e773844889..cd5452eb7486 100644
> --- a/arch/arm64/kernel/mte.c
> +++ b/arch/arm64/kernel/mte.c
> @@ -157,6 +157,24 @@ void mte_enable_kernel_asymm(void)
>   		mte_enable_kernel_sync();
>   	}
>   }
> +
> +int mte_enable_kernel_store_only(void)
> +{
> +	/*
> +	 * If the CPU does not support MTE store only,
> +	 * the kernel checks all operations.
> +	 */
> +	if (!cpus_have_cap(ARM64_MTE_STORE_ONLY))
> +		return -EINVAL;
Would it be better to make this function return void and add a static 
key in the manner of mte_async_or_asymm_mode, perhaps 
mte_store_only_mode? This information could then be used to help 
determine whether it is required to enable and disable tco in 
__get_kernel_nofault() and load_unaligned_zeropad(). The function 
signature would also match that of the other hw_enable_tag_...().

> +
> +	sysreg_clear_set(sctlr_el1, SCTLR_EL1_TCSO_MASK,
> +			 SYS_FIELD_PREP(SCTLR_EL1, TCSO, 1));
> +	isb();
> +
> +	pr_info_once("MTE: enabled stonly mode at EL1\n");
nit: stonly can be expanded to store only
> +
> +	return 0;
> +}
>   #endif
>   
>   #ifdef CONFIG_KASAN_HW_TAGS
> diff --git a/mm/kasan/hw_tags.c b/mm/kasan/hw_tags.c
> index 9a6927394b54..df67b48739b4 100644
> --- a/mm/kasan/hw_tags.c
> +++ b/mm/kasan/hw_tags.c
> @@ -41,9 +41,16 @@ enum kasan_arg_vmalloc {
>   	KASAN_ARG_VMALLOC_ON,
>   };
>   
> +enum kasan_arg_write_only {
> +	KASAN_ARG_WRITE_ONLY_DEFAULT,
> +	KASAN_ARG_WRITE_ONLY_OFF,
> +	KASAN_ARG_WRITE_ONLY_ON,
> +};
> +
>   static enum kasan_arg kasan_arg __ro_after_init;
>   static enum kasan_arg_mode kasan_arg_mode __ro_after_init;
>   static enum kasan_arg_vmalloc kasan_arg_vmalloc __initdata;
> +static enum kasan_arg_write_only kasan_arg_write_only __ro_after_init;
>   
>   /*
>    * Whether KASAN is enabled at all.
> @@ -67,6 +74,8 @@ DEFINE_STATIC_KEY_FALSE(kasan_flag_vmalloc);
>   #endif
>   EXPORT_SYMBOL_GPL(kasan_flag_vmalloc);
>   
> +static bool kasan_flag_write_only;
> +
>   #define PAGE_ALLOC_SAMPLE_DEFAULT	1
>   #define PAGE_ALLOC_SAMPLE_ORDER_DEFAULT	3
>   
> @@ -141,6 +150,23 @@ static int __init early_kasan_flag_vmalloc(char *arg)
>   }
>   early_param("kasan.vmalloc", early_kasan_flag_vmalloc);
>   
> +/* kasan.write_only=off/on */
> +static int __init early_kasan_flag_write_only(char *arg)
> +{
> +	if (!arg)
> +		return -EINVAL;
> +
> +	if (!strcmp(arg, "off"))
> +		kasan_arg_write_only = KASAN_ARG_WRITE_ONLY_OFF;
> +	else if (!strcmp(arg, "on"))
> +		kasan_arg_write_only = KASAN_ARG_WRITE_ONLY_ON;
> +	else
> +		return -EINVAL;
> +
> +	return 0;
> +}
> +early_param("kasan.write_only", early_kasan_flag_write_only);
> +
>   static inline const char *kasan_mode_info(void)
>   {
>   	if (kasan_mode == KASAN_MODE_ASYNC)
> @@ -257,15 +283,26 @@ void __init kasan_init_hw_tags(void)
>   		break;
>   	}
>   
> +	switch (kasan_arg_write_only) {
> +	case KASAN_ARG_WRITE_ONLY_DEFAULT:
> +	case KASAN_ARG_WRITE_ONLY_OFF:
> +		kasan_flag_write_only = false;
> +		break;
> +	case KASAN_ARG_WRITE_ONLY_ON:
> +		kasan_flag_write_only = true;
> +		break;
> +	}
> +
>   	kasan_init_tags();
>   
>   	/* KASAN is now initialized, enable it. */
>   	static_branch_enable(&kasan_flag_enabled);
>   
> -	pr_info("KernelAddressSanitizer initialized (hw-tags, mode=%s, vmalloc=%s, stacktrace=%s)\n",
> +	pr_info("KernelAddressSanitizer initialized (hw-tags, mode=%s, vmalloc=%s, stacktrace=%s, write_only=%s\n",
>   		kasan_mode_info(),
>   		str_on_off(kasan_vmalloc_enabled()),
> -		str_on_off(kasan_stack_collection_enabled()));
> +		str_on_off(kasan_stack_collection_enabled()),
> +		str_on_off(kasan_arg_write_only));
>   }
>   
>   #ifdef CONFIG_KASAN_VMALLOC
> @@ -392,6 +429,13 @@ void kasan_enable_hw_tags(void)
>   		hw_enable_tag_checks_asymm();
>   	else
>   		hw_enable_tag_checks_sync();
> +
> +	if (kasan_arg_write_only == KASAN_ARG_WRITE_ONLY_ON &&
> +	    hw_enable_tag_checks_write_only()) {
> +		kasan_arg_write_only == KASAN_ARG_WRITE_ONLY_OFF;
> +		kasan_flag_write_only = false;
> +		pr_warn_once("System doesn't support write-only option. Disable it\n");
> +	}
>   }
>   
>   #if IS_ENABLED(CONFIG_KASAN_KUNIT_TEST)
> @@ -404,4 +448,10 @@ VISIBLE_IF_KUNIT void kasan_force_async_fault(void)
>   }
>   EXPORT_SYMBOL_IF_KUNIT(kasan_force_async_fault);
>   
> +VISIBLE_IF_KUNIT bool kasan_write_only_enabled(void)
> +{
> +	return kasan_flag_write_only;
> +}
> +EXPORT_SYMBOL_IF_KUNIT(kasan_write_only_enabled);
> +
>   #endif
> diff --git a/mm/kasan/kasan.h b/mm/kasan/kasan.h
> index 129178be5e64..c1490136c96b 100644
> --- a/mm/kasan/kasan.h
> +++ b/mm/kasan/kasan.h
> @@ -428,6 +428,7 @@ static inline const void *arch_kasan_set_tag(const void *addr, u8 tag)
>   #define hw_enable_tag_checks_sync()		arch_enable_tag_checks_sync()
>   #define hw_enable_tag_checks_async()		arch_enable_tag_checks_async()
>   #define hw_enable_tag_checks_asymm()		arch_enable_tag_checks_asymm()
> +#define hw_enable_tag_checks_write_only()	arch_enable_tag_checks_write_only()
>   #define hw_suppress_tag_checks_start()		arch_suppress_tag_checks_start()
>   #define hw_suppress_tag_checks_stop()		arch_suppress_tag_checks_stop()
>   #define hw_force_async_tag_fault()		arch_force_async_tag_fault()
> @@ -437,11 +438,17 @@ static inline const void *arch_kasan_set_tag(const void *addr, u8 tag)
>   			arch_set_mem_tag_range((addr), (size), (tag), (init))
>   
>   void kasan_enable_hw_tags(void);
> +bool kasan_write_only_enabled(void);
>   
>   #else /* CONFIG_KASAN_HW_TAGS */
>   
>   static inline void kasan_enable_hw_tags(void) { }
>   
> +static inline bool kasan_write_only_enabled(void)
> +{
> +	return false;
> +}
> +
>   #endif /* CONFIG_KASAN_HW_TAGS */
>   
>   #if defined(CONFIG_KASAN_SW_TAGS) || defined(CONFIG_KASAN_HW_TAGS)

Thanks,

Ben
Re: [PATCH v4 1/2] kasan/hw-tags: introduce kasan.write_only option
Posted by Yeoreum Yun 1 month, 2 weeks ago
Hi Ben,

[..]

> > --- a/arch/arm64/kernel/cpufeature.c
> > +++ b/arch/arm64/kernel/cpufeature.c
> > @@ -2920,7 +2920,7 @@ static const struct arm64_cpu_capabilities arm64_features[] = {
> >   	{
> >   		.desc = "Store Only MTE Tag Check",
> >   		.capability = ARM64_MTE_STORE_ONLY,
> > -		.type = ARM64_CPUCAP_SYSTEM_FEATURE,
> > +		.type = ARM64_CPUCAP_BOOT_CPU_FEATURE,
> >   		.matches = has_cpuid_feature,
> >   		ARM64_CPUID_FIELDS(ID_AA64PFR2_EL1, MTESTOREONLY, IMP)
> >   	},
> > diff --git a/arch/arm64/kernel/mte.c b/arch/arm64/kernel/mte.c
> > index e5e773844889..cd5452eb7486 100644
> > --- a/arch/arm64/kernel/mte.c
> > +++ b/arch/arm64/kernel/mte.c
> > @@ -157,6 +157,24 @@ void mte_enable_kernel_asymm(void)
> >   		mte_enable_kernel_sync();
> >   	}
> >   }
> > +
> > +int mte_enable_kernel_store_only(void)
> > +{
> > +	/*
> > +	 * If the CPU does not support MTE store only,
> > +	 * the kernel checks all operations.
> > +	 */
> > +	if (!cpus_have_cap(ARM64_MTE_STORE_ONLY))
> > +		return -EINVAL;

> Would it be better to make this function return void

This is the same point Catalin points out from patch v2.
But for usage of kunit test, it need to keep return as int.

> and add a static key in
> the manner of mte_async_or_asymm_mode, perhaps mte_store_only_mode? This
> information could then be used to help determine whether it is required to
> enable and disable tco in __get_kernel_nofault() and
> load_unaligned_zeropad().

Yes. Since the mte_store_only enabled, it doesn't need to enable tco
since load/fetch doesn't increase the TSFR.
However This sounds like an over optimisation.
I think it would be enough to check when mte_async_or_asymm_mode()
otherwise in for __get_kernel_nofault() or load_unaligned_zeropad(),
it should use like:
  - __mte_enable_tco_async_and_store_only()
  or
  - __mte_enable_tco_async(op) // whether op is load or store (boolean or enum)

But this seems ugly too.

So I think it woule be better to remain as it is --
without static_key for store only since there is no usage.

>
> > +
> > +	sysreg_clear_set(sctlr_el1, SCTLR_EL1_TCSO_MASK,
> > +			 SYS_FIELD_PREP(SCTLR_EL1, TCSO, 1));
> > +	isb();
> > +
> > +	pr_info_once("MTE: enabled stonly mode at EL1\n");
> nit: stonly can be expanded to store only

Thanks. I'll change it.


> > +
> > +	return 0;
> > +}
> >   #endif
> >   #ifdef CONFIG_KASAN_HW_TAGS
> > diff --git a/mm/kasan/hw_tags.c b/mm/kasan/hw_tags.c
> > index 9a6927394b54..df67b48739b4 100644
> > --- a/mm/kasan/hw_tags.c
> > +++ b/mm/kasan/hw_tags.c
> > @@ -41,9 +41,16 @@ enum kasan_arg_vmalloc {
> >   	KASAN_ARG_VMALLOC_ON,
> >   };
> > +enum kasan_arg_write_only {
> > +	KASAN_ARG_WRITE_ONLY_DEFAULT,
> > +	KASAN_ARG_WRITE_ONLY_OFF,
> > +	KASAN_ARG_WRITE_ONLY_ON,
> > +};
> > +
> >   static enum kasan_arg kasan_arg __ro_after_init;
> >   static enum kasan_arg_mode kasan_arg_mode __ro_after_init;
> >   static enum kasan_arg_vmalloc kasan_arg_vmalloc __initdata;
> > +static enum kasan_arg_write_only kasan_arg_write_only __ro_after_init;
> >   /*
> >    * Whether KASAN is enabled at all.
> > @@ -67,6 +74,8 @@ DEFINE_STATIC_KEY_FALSE(kasan_flag_vmalloc);
> >   #endif
> >   EXPORT_SYMBOL_GPL(kasan_flag_vmalloc);
> > +static bool kasan_flag_write_only;
> > +
> >   #define PAGE_ALLOC_SAMPLE_DEFAULT	1
> >   #define PAGE_ALLOC_SAMPLE_ORDER_DEFAULT	3
> > @@ -141,6 +150,23 @@ static int __init early_kasan_flag_vmalloc(char *arg)
> >   }
> >   early_param("kasan.vmalloc", early_kasan_flag_vmalloc);
> > +/* kasan.write_only=off/on */
> > +static int __init early_kasan_flag_write_only(char *arg)
> > +{
> > +	if (!arg)
> > +		return -EINVAL;
> > +
> > +	if (!strcmp(arg, "off"))
> > +		kasan_arg_write_only = KASAN_ARG_WRITE_ONLY_OFF;
> > +	else if (!strcmp(arg, "on"))
> > +		kasan_arg_write_only = KASAN_ARG_WRITE_ONLY_ON;
> > +	else
> > +		return -EINVAL;
> > +
> > +	return 0;
> > +}
> > +early_param("kasan.write_only", early_kasan_flag_write_only);
> > +
> >   static inline const char *kasan_mode_info(void)
> >   {
> >   	if (kasan_mode == KASAN_MODE_ASYNC)
> > @@ -257,15 +283,26 @@ void __init kasan_init_hw_tags(void)
> >   		break;
> >   	}
> > +	switch (kasan_arg_write_only) {
> > +	case KASAN_ARG_WRITE_ONLY_DEFAULT:
> > +	case KASAN_ARG_WRITE_ONLY_OFF:
> > +		kasan_flag_write_only = false;
> > +		break;
> > +	case KASAN_ARG_WRITE_ONLY_ON:
> > +		kasan_flag_write_only = true;
> > +		break;
> > +	}
> > +
> >   	kasan_init_tags();
> >   	/* KASAN is now initialized, enable it. */
> >   	static_branch_enable(&kasan_flag_enabled);
> > -	pr_info("KernelAddressSanitizer initialized (hw-tags, mode=%s, vmalloc=%s, stacktrace=%s)\n",
> > +	pr_info("KernelAddressSanitizer initialized (hw-tags, mode=%s, vmalloc=%s, stacktrace=%s, write_only=%s\n",
> >   		kasan_mode_info(),
> >   		str_on_off(kasan_vmalloc_enabled()),
> > -		str_on_off(kasan_stack_collection_enabled()));
> > +		str_on_off(kasan_stack_collection_enabled()),
> > +		str_on_off(kasan_arg_write_only));
> >   }
> >   #ifdef CONFIG_KASAN_VMALLOC
> > @@ -392,6 +429,13 @@ void kasan_enable_hw_tags(void)
> >   		hw_enable_tag_checks_asymm();
> >   	else
> >   		hw_enable_tag_checks_sync();
> > +
> > +	if (kasan_arg_write_only == KASAN_ARG_WRITE_ONLY_ON &&
> > +	    hw_enable_tag_checks_write_only()) {
> > +		kasan_arg_write_only == KASAN_ARG_WRITE_ONLY_OFF;
> > +		kasan_flag_write_only = false;
> > +		pr_warn_once("System doesn't support write-only option. Disable it\n");
> > +	}
> >   }
> >   #if IS_ENABLED(CONFIG_KASAN_KUNIT_TEST)
> > @@ -404,4 +448,10 @@ VISIBLE_IF_KUNIT void kasan_force_async_fault(void)
> >   }
> >   EXPORT_SYMBOL_IF_KUNIT(kasan_force_async_fault);
> > +VISIBLE_IF_KUNIT bool kasan_write_only_enabled(void)
> > +{
> > +	return kasan_flag_write_only;
> > +}
> > +EXPORT_SYMBOL_IF_KUNIT(kasan_write_only_enabled);
> > +
> >   #endif
> > diff --git a/mm/kasan/kasan.h b/mm/kasan/kasan.h
> > index 129178be5e64..c1490136c96b 100644
> > --- a/mm/kasan/kasan.h
> > +++ b/mm/kasan/kasan.h
> > @@ -428,6 +428,7 @@ static inline const void *arch_kasan_set_tag(const void *addr, u8 tag)
> >   #define hw_enable_tag_checks_sync()		arch_enable_tag_checks_sync()
> >   #define hw_enable_tag_checks_async()		arch_enable_tag_checks_async()
> >   #define hw_enable_tag_checks_asymm()		arch_enable_tag_checks_asymm()
> > +#define hw_enable_tag_checks_write_only()	arch_enable_tag_checks_write_only()
> >   #define hw_suppress_tag_checks_start()		arch_suppress_tag_checks_start()
> >   #define hw_suppress_tag_checks_stop()		arch_suppress_tag_checks_stop()
> >   #define hw_force_async_tag_fault()		arch_force_async_tag_fault()
> > @@ -437,11 +438,17 @@ static inline const void *arch_kasan_set_tag(const void *addr, u8 tag)
> >   			arch_set_mem_tag_range((addr), (size), (tag), (init))
> >   void kasan_enable_hw_tags(void);
> > +bool kasan_write_only_enabled(void);
> >   #else /* CONFIG_KASAN_HW_TAGS */
> >   static inline void kasan_enable_hw_tags(void) { }
> > +static inline bool kasan_write_only_enabled(void)
> > +{
> > +	return false;
> > +}
> > +
> >   #endif /* CONFIG_KASAN_HW_TAGS */
> >   #if defined(CONFIG_KASAN_SW_TAGS) || defined(CONFIG_KASAN_HW_TAGS)
>
> Thanks,
>
> Ben
>

--
Sincerely,
Yeoreum Yun