[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-Id: <20250804-kasan-via-kcsan-v1-3-823a6d5b5f84@google.com>
Date: Mon, 04 Aug 2025 21:17:07 +0200
From: Jann Horn <jannh@...gle.com>
To: Masahiro Yamada <masahiroy@...nel.org>,
Nathan Chancellor <nathan@...nel.org>,
Nicolas Schier <nicolas.schier@...ux.dev>,
Andrey Ryabinin <ryabinin.a.a@...il.com>,
Alexander Potapenko <glider@...gle.com>,
Andrey Konovalov <andreyknvl@...il.com>, Dmitry Vyukov <dvyukov@...gle.com>,
Vincenzo Frascino <vincenzo.frascino@....com>,
Andrew Morton <akpm@...ux-foundation.org>, Marco Elver <elver@...gle.com>,
Christoph Lameter <cl@...two.org>, David Rientjes <rientjes@...gle.com>,
Vlastimil Babka <vbabka@...e.cz>, Roman Gushchin <roman.gushchin@...ux.dev>,
Harry Yoo <harry.yoo@...cle.com>
Cc: linux-kbuild@...r.kernel.org, linux-kernel@...r.kernel.org,
kasan-dev@...glegroups.com, linux-mm@...ck.org,
Jann Horn <jannh@...gle.com>
Subject: [PATCH early RFC 3/4] kasan: add support for running via KCSAN
hooks
Inserting ASAN and TSAN instrumentation at the same time is not
supported by gcc/clang, and so the kernel currently does not support
enabling KASAN (which uses ASAN) and KCSAN (which uses TSAN) at the same
time.
But luckily, the TSAN hooks provide a large part of what we get from ASAN
hooks; so it is possible to hook up KASAN indirectly through KCSAN.
There are some trade-offs with this - in particular:
- Since OOB detection for stack and globals relies on ASAN-specific
redzone creation in the compiler, it won't be available when using
TSAN instrumentation (because the compiler thinks we only want
instrumentation for catching UAF).
- Unlike KASAN, KCSAN does not have instrumentation for functions like
memcpy(), and this KASAN mode inherits this issue from KCSAN.
- It makes it impossible to selectively disable KCSAN without also
disabling KASAN, or the other way around. To be safe, this mode only
enables KCSAN instrumentation in files in which both KASAN and KCSAN
are allowed.
(There are currently some places in the kernel that disable KASAN
without disabling KCSAN - I think that's probably unintentional, and
we might want to refactor that at some point such that either KASAN
and KCSAN are enabled in the same files, or files covered by KCSAN
are a subset of files covered by KASAN if that's somehow problematic.
Opting out of every compiler instrumentation individually in makefiles
seems suboptimal to me.)
- I expect its performance to be significantly worse than normal KASAN,
but have not tested that; performance is not really something I care
about for my usecase.
NOTE: instrument_read() and such call both KASAN and KCSAN, so KASAN
will see duplicate accesses from instrument_read().
Signed-off-by: Jann Horn <jannh@...gle.com>
---
include/linux/kasan.h | 14 ++++++++++++++
kernel/kcsan/core.c | 13 +++++++++++++
lib/Kconfig.kasan | 17 +++++++++++++++++
lib/Kconfig.kcsan | 2 +-
mm/kasan/kasan.h | 11 -----------
mm/kasan/kasan_test_c.c | 4 ++++
mm/kasan/shadow.c | 3 ++-
scripts/Makefile.lib | 6 +++++-
8 files changed, 56 insertions(+), 14 deletions(-)
diff --git a/include/linux/kasan.h b/include/linux/kasan.h
index 890011071f2b..818c53707e72 100644
--- a/include/linux/kasan.h
+++ b/include/linux/kasan.h
@@ -75,6 +75,20 @@ extern void kasan_enable_current(void);
/* Disable reporting bugs for current task */
extern void kasan_disable_current(void);
+/**
+ * kasan_check_range - Check memory region, and report if invalid access.
+ * @addr: the accessed address
+ * @size: the accessed size
+ * @write: true if access is a write access
+ * @ret_ip: return address
+ * @return: true if access was valid, false if invalid
+ *
+ * This function is intended for KASAN-internal use and for integration with
+ * KCSAN.
+ */
+bool kasan_check_range(const void *addr, size_t size, bool write,
+ unsigned long ret_ip);
+
#else /* CONFIG_KASAN_GENERIC || CONFIG_KASAN_SW_TAGS */
static inline int kasan_add_zero_shadow(void *start, unsigned long size)
diff --git a/kernel/kcsan/core.c b/kernel/kcsan/core.c
index 8a7baf4e332e..aaa9bf0141a8 100644
--- a/kernel/kcsan/core.c
+++ b/kernel/kcsan/core.c
@@ -728,6 +728,19 @@ check_access(const volatile void *ptr, size_t size, int type, unsigned long ip)
if (unlikely(size == 0))
return;
+#ifdef CONFIG_KASAN_KCSAN
+ /*
+ * Use the KCSAN infrastructure to inform KASAN about memory accesses.
+ * Do this only for real memory access, not for KCSAN assertions - in
+ * particular, SLUB makes KCSAN assertions that can cross into ASAN
+ * redzones, which would KASAN think that an OOB access occurred.
+ */
+ if ((type & KCSAN_ACCESS_ASSERT) == 0) {
+ kasan_check_range((const void *)ptr, size,
+ (type & (KCSAN_ACCESS_WRITE|KCSAN_ACCESS_COMPOUND)) != 0, ip);
+ }
+#endif
+
again:
/*
* Avoid user_access_save in fast-path: find_watchpoint is safe without
diff --git a/lib/Kconfig.kasan b/lib/Kconfig.kasan
index f82889a830fa..0ee9f2196448 100644
--- a/lib/Kconfig.kasan
+++ b/lib/Kconfig.kasan
@@ -133,6 +133,7 @@ choice
config KASAN_OUTLINE
bool "Outline instrumentation"
+ depends on !KCSAN
help
Makes the compiler insert function calls that check whether the memory
is accessible before each memory access. Slower than KASAN_INLINE, but
@@ -141,17 +142,33 @@ config KASAN_OUTLINE
config KASAN_INLINE
bool "Inline instrumentation"
depends on !ARCH_DISABLE_KASAN_INLINE
+ depends on !KCSAN
help
Makes the compiler directly insert memory accessibility checks before
each memory access. Faster than KASAN_OUTLINE (gives ~x2 boost for
some workloads), but makes the kernel's .text size much bigger.
+config KASAN_KCSAN
+ bool "Piggyback on KCSAN (EXPERIMENTAL)"
+ depends on KASAN_GENERIC
+ depends on KCSAN
+ help
+ Let KASAN piggyback on KCSAN instrumentation callbacks instead of
+ using KASAN-specific compiler instrumentation.
+
+ This limits coverage of KASAN and KCSAN to files that are supported by
+ *both* KASAN and KCSAN.
+
+ This is only useful if you want to run both the KASAN and KCSAN
+ subsystems at the same time.
+
endchoice
config KASAN_STACK
bool "Stack instrumentation (unsafe)" if CC_IS_CLANG && !COMPILE_TEST
depends on KASAN_GENERIC || KASAN_SW_TAGS
depends on !ARCH_DISABLE_KASAN_INLINE
+ depends on !KASAN_KCSAN
default y if CC_IS_GCC
help
Disables stack instrumentation and thus KASAN's ability to detect
diff --git a/lib/Kconfig.kcsan b/lib/Kconfig.kcsan
index 609ddfc73de5..86bf8f2da0a8 100644
--- a/lib/Kconfig.kcsan
+++ b/lib/Kconfig.kcsan
@@ -13,7 +13,7 @@ config HAVE_KCSAN_COMPILER
menuconfig KCSAN
bool "KCSAN: dynamic data race detector"
depends on HAVE_ARCH_KCSAN && HAVE_KCSAN_COMPILER
- depends on DEBUG_KERNEL && !KASAN
+ depends on DEBUG_KERNEL
select CONSTRUCTORS
select STACKTRACE
help
diff --git a/mm/kasan/kasan.h b/mm/kasan/kasan.h
index 129178be5e64..ec191ff1fc83 100644
--- a/mm/kasan/kasan.h
+++ b/mm/kasan/kasan.h
@@ -335,17 +335,6 @@ static __always_inline bool addr_has_metadata(const void *addr)
}
#endif
-/**
- * kasan_check_range - Check memory region, and report if invalid access.
- * @addr: the accessed address
- * @size: the accessed size
- * @write: true if access is a write access
- * @ret_ip: return address
- * @return: true if access was valid, false if invalid
- */
-bool kasan_check_range(const void *addr, size_t size, bool write,
- unsigned long ret_ip);
-
#else /* CONFIG_KASAN_GENERIC || CONFIG_KASAN_SW_TAGS */
static __always_inline bool addr_has_metadata(const void *addr)
diff --git a/mm/kasan/kasan_test_c.c b/mm/kasan/kasan_test_c.c
index 5f922dd38ffa..c4826c67aa33 100644
--- a/mm/kasan/kasan_test_c.c
+++ b/mm/kasan/kasan_test_c.c
@@ -154,6 +154,8 @@ static void kasan_test_exit(struct kunit *test)
#define KASAN_TEST_NEEDS_CHECKED_MEMINTRINSICS(test) do { \
if (IS_ENABLED(CONFIG_KASAN_HW_TAGS)) \
break; /* No compiler instrumentation. */ \
+ if (IS_ENABLED(CONFIG_KASAN_KCSAN)) \
+ kunit_skip((test), "No checked mem*() with KCSAN"); \
if (IS_ENABLED(CONFIG_CC_HAS_KASAN_MEMINTRINSIC_PREFIX)) \
break; /* Should always be instrumented! */ \
if (IS_ENABLED(CONFIG_GENERIC_ENTRY)) \
@@ -1453,6 +1455,7 @@ static void kasan_global_oob_right(struct kunit *test)
/* Only generic mode instruments globals. */
KASAN_TEST_NEEDS_CONFIG_ON(test, CONFIG_KASAN_GENERIC);
+ KASAN_TEST_NEEDS_CONFIG_OFF(test, CONFIG_KASAN_KCSAN);
KUNIT_EXPECT_KASAN_FAIL(test, *(volatile char *)p);
}
@@ -1468,6 +1471,7 @@ static void kasan_global_oob_left(struct kunit *test)
*/
KASAN_TEST_NEEDS_CONFIG_ON(test, CONFIG_CC_IS_CLANG);
KASAN_TEST_NEEDS_CONFIG_ON(test, CONFIG_KASAN_GENERIC);
+ KASAN_TEST_NEEDS_CONFIG_OFF(test, CONFIG_KASAN_KCSAN);
KUNIT_EXPECT_KASAN_FAIL(test, *(volatile char *)p);
}
diff --git a/mm/kasan/shadow.c b/mm/kasan/shadow.c
index d2c70cd2afb1..136be8e6c98d 100644
--- a/mm/kasan/shadow.c
+++ b/mm/kasan/shadow.c
@@ -38,7 +38,8 @@ bool __kasan_check_write(const volatile void *p, unsigned int size)
}
EXPORT_SYMBOL(__kasan_check_write);
-#if !defined(CONFIG_CC_HAS_KASAN_MEMINTRINSIC_PREFIX) && !defined(CONFIG_GENERIC_ENTRY)
+#if !defined(CONFIG_CC_HAS_KASAN_MEMINTRINSIC_PREFIX) && \
+ !defined(CONFIG_GENERIC_ENTRY) && !defined(CONFIG_KASAN_KCSAN)
/*
* CONFIG_GENERIC_ENTRY relies on compiler emitted mem*() calls to not be
* instrumented. KASAN enabled toolchains should emit __asan_mem*() functions
diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib
index 017c9801b6bb..2572fcc0bf50 100644
--- a/scripts/Makefile.lib
+++ b/scripts/Makefile.lib
@@ -56,10 +56,13 @@ is-kasan-compatible = $(patsubst n%,, \
$(KASAN_SANITIZE_$(target-stem).o)$(KASAN_SANITIZE)$(is-kernel-object))
ifeq ($(CONFIG_KASAN),y)
ifneq ($(CONFIG_KASAN_HW_TAGS),y)
+# Disable ASAN instrumentation if KASAN is running off the KCSAN hooks.
+ifneq ($(CONFIG_KASAN_KCSAN),y)
_c_flags += $(if $(is-kasan-compatible), $(CFLAGS_KASAN), $(CFLAGS_KASAN_NOSANITIZE))
_rust_flags += $(if $(is-kasan-compatible), $(RUSTFLAGS_KASAN))
endif
endif
+endif
ifeq ($(CONFIG_KMSAN),y)
_c_flags += $(if $(patsubst n%,, \
@@ -95,7 +98,8 @@ endif
is-kcsan-compatible = $(patsubst n%,, \
$(KCSAN_SANITIZE_$(target-stem).o)$(KCSAN_SANITIZE)$(is-kernel-object))
ifeq ($(CONFIG_KCSAN),y)
-_c_flags += $(if $(is-kcsan-compatible), $(CFLAGS_KCSAN))
+enable-kcsan-instr = $(and $(is-kcsan-compatible), $(if $(CONFIG_KASAN_KCSAN),$(is-kasan-compatible),y))
+_c_flags += $(if $(enable-kcsan-instr), $(CFLAGS_KCSAN))
# Some uninstrumented files provide implied barriers required to avoid false
# positives: set KCSAN_INSTRUMENT_BARRIERS for barrier instrumentation only.
_c_flags += $(if $(patsubst n%,, \
--
2.50.1.565.gc32cd1483b-goog
Powered by blists - more mailing lists