]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
kasan/hw-tags: introduce kasan.write_only option
authorYeoreum Yun <yeoreum.yun@arm.com>
Tue, 16 Sep 2025 22:27:54 +0000 (23:27 +0100)
committerAndrew Morton <akpm@linux-foundation.org>
Sun, 21 Sep 2025 21:22:10 +0000 (14:22 -0700)
Patch series "introduce kasan.write_only option in hw-tags", v8.

Hardware tag based KASAN is implemented using the Memory Tagging Extension
(MTE) feature.

MTE is built on top of the ARMv8.0 virtual address tagging TBI (Top Byte
Ignore) feature and allows software to access a 4-bit allocation tag for
each 16-byte granule in the physical address space.  A logical tag is
derived from bits 59-56 of the virtual address used for the memory access.
A CPU with MTE enabled will compare the logical tag against the
allocation tag and potentially raise an tag check fault on mismatch,
subject to system registers configuration.

Since ARMv8.9, FEAT_MTE_STORE_ONLY can be used to restrict raise of tag
check fault on store operation only.

Using this feature (FEAT_MTE_STORE_ONLY), introduce KASAN write-only mode
which restricts KASAN check write (store) operation only.  This mode omits
KASAN check for read (fetch/load) operation.  Therefore, it might be used
not only debugging purpose but also in normal environment.

This patch (of 2):

Since Armv8.9, FEATURE_MTE_STORE_ONLY feature is introduced to restrict
raise of tag check fault on store operation only.  Introduce 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.

Link: https://lkml.kernel.org/r/20250916222755.466009-1-yeoreum.yun@arm.com
Link: https://lkml.kernel.org/r/20250916222755.466009-2-yeoreum.yun@arm.com
Signed-off-by: Yeoreum Yun <yeoreum.yun@arm.com>
Reviewed-by: Catalin Marinas <catalin.marinas@arm.com>
Reviewed-by: Andrey Ryabinin <ryabinin.a.a@gmail.com>
Cc: Alexander Potapenko <glider@google.com>
Cc: Andrey Konovalov <andreyknvl@gmail.com>
Cc: Ard Biesheuvel <ardb@kernel.org>
Cc: Breno Leitao <leitao@debian.org>
Cc: David Hildenbrand <david@redhat.com>
Cc: Dmitriy Vyukov <dvyukov@google.com>
Cc: D Scott Phillips <scott@os.amperecomputing.com>
Cc: Hardevsinh Palaniya <hardevsinh.palaniya@siliconsignals.io>
Cc: James Morse <james.morse@arm.com>
Cc: John Hubbard <jhubbard@nvidia.com>
Cc: Jonathan Corbet <corbet@lwn.net>
Cc: Kalesh Singh <kaleshsingh@google.com>
Cc: levi.yun <yeoreum.yun@arm.com>
Cc: Marc Zyngier <maz@kernel.org>
Cc: Mark Brown <broonie@kernel.org>
Cc: Oliver Upton <oliver.upton@linux.dev>
Cc: Pankaj Gupta <pankaj.gupta@amd.com>
Cc: Vincenzo Frascino <vincenzo.frascino@arm.com>
Cc: Will Deacon <will@kernel.org>
Cc: Yang Shi <yang@os.amperecomputing.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Documentation/dev-tools/kasan.rst
arch/arm64/include/asm/memory.h
arch/arm64/include/asm/mte-kasan.h
arch/arm64/kernel/cpufeature.c
arch/arm64/kernel/mte.c
mm/kasan/hw_tags.c
mm/kasan/kasan.h

index 0a1418ab72fdfc0ef227549718b627ad1a2e2df0..a034700da7c4634c71d3e9c9fb2e6c5614087bdc 100644 (file)
@@ -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``).
 
index 5213248e081b0a1e26fba62bd52406c914d83817..f1505c4acb389ac2634a73b3d6830a5a8e02ed3f 100644 (file)
@@ -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()
index 2e98028c19658a6e0394b5658bbd9a2e0bb19fbf..0f9b08e8fb8db0cb31da6453d539aa06c1a08b62 100644 (file)
@@ -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__ */
index ef269a5a37e12c53e8e825e947b910f6d3efd296..1f6e8c87aae79ef1250e32e8e2ff144324fca800 100644 (file)
@@ -2945,7 +2945,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)
        },
index e5e773844889a81396e02e2657507520e240d178..54a52dc5c1ae3f40954f2644ab1da3f4e7e26573 100644 (file)
@@ -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 store only mode at EL1\n");
+
+       return 0;
+}
 #endif
 
 #ifdef CONFIG_KASAN_HW_TAGS
index c8289a3feabfd672c823dec8d57c8d097a8cf0ed..1c373cc4b3fa5ad1893c09bffd35b5ffd1a5462f 100644 (file)
@@ -60,6 +60,9 @@ DEFINE_STATIC_KEY_FALSE(kasan_flag_vmalloc);
 #endif
 EXPORT_SYMBOL_GPL(kasan_flag_vmalloc);
 
+/* Whether to check write accesses only. */
+static bool kasan_flag_write_only = false;
+
 #define PAGE_ALLOC_SAMPLE_DEFAULT      1
 #define PAGE_ALLOC_SAMPLE_ORDER_DEFAULT        3
 
@@ -134,6 +137,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_flag_write_only = false;
+       else if (!strcmp(arg, "on"))
+               kasan_flag_write_only = true;
+       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)
@@ -255,10 +275,11 @@ void __init kasan_init_hw_tags(void)
        /* KASAN is now initialized, enable it. */
        kasan_enable();
 
-       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_flag_write_only));
 }
 
 #ifdef CONFIG_KASAN_VMALLOC
@@ -385,6 +406,20 @@ void kasan_enable_hw_tags(void)
                hw_enable_tag_checks_asymm();
        else
                hw_enable_tag_checks_sync();
+
+       /*
+        * CPUs can only be in one of two states:
+        *   - All CPUs support the write_only feature
+        *   - No CPUs support the write_only feature
+        *
+        * If the first CPU attempts hw_enable_tag_checks_write_only() and
+        * finds the feature unsupported, kasan_flag_write_only is set to OFF
+        * to avoid further unnecessary calls on other CPUs.
+        */
+       if (kasan_flag_write_only && hw_enable_tag_checks_write_only()) {
+               kasan_flag_write_only = false;
+               pr_err_once("write-only mode is not supported and thus not enabled\n");
+       }
 }
 
 #if IS_ENABLED(CONFIG_KASAN_KUNIT_TEST)
@@ -397,4 +432,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
index 8a9d8a6ea71773e0839d186f0835f383fff3a4ce..07fa7375a848a88fec42df1d410bd6478fa68491 100644 (file)
@@ -437,6 +437,7 @@ static inline const void *arch_kasan_set_tag(const void *addr, u8 tag)
 #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()
+#define hw_enable_tag_checks_write_only()      arch_enable_tag_checks_write_only()
 #define hw_get_random_tag()                    arch_get_random_tag()
 #define hw_get_mem_tag(addr)                   arch_get_mem_tag(addr)
 #define hw_set_mem_tag_range(addr, size, tag, init) \
@@ -457,11 +458,17 @@ void __init kasan_init_tags(void);
 #if defined(CONFIG_KASAN_HW_TAGS) && IS_ENABLED(CONFIG_KASAN_KUNIT_TEST)
 
 void kasan_force_async_fault(void);
+bool kasan_write_only_enabled(void);
 
 #else /* CONFIG_KASAN_HW_TAGS && CONFIG_KASAN_KUNIT_TEST */
 
 static inline void kasan_force_async_fault(void) { }
 
+static inline bool kasan_write_only_enabled(void)
+{
+       return false;
+}
+
 #endif /* CONFIG_KASAN_HW_TAGS && CONFIG_KASAN_KUNIT_TEST */
 
 #ifdef CONFIG_KASAN_SW_TAGS