Add target state and a static key to KCSAN. When a target is set, only non-atomic instrumented accesses containing that address are eligible for watchpoint setup. Other accesses do not consume the skip interval. Keep report filtering independent from target matching.
Signed-off-by: Hui Su <[email protected]> --- kernel/kcsan/core.c | 37 +++++++++++++++++++++++++++++++++++++ kernel/kcsan/kcsan.h | 10 ++++++++++ 2 files changed, 47 insertions(+) diff --git a/kernel/kcsan/core.c b/kernel/kcsan/core.c index 2db82661cd60..2a58a3a53adb 100644 --- a/kernel/kcsan/core.c +++ b/kernel/kcsan/core.c @@ -12,10 +12,12 @@ #include <linux/delay.h> #include <linux/export.h> #include <linux/init.h> +#include <linux/jump_label.h> #include <linux/kernel.h> #include <linux/list.h> #include <linux/minmax.h> #include <linux/moduleparam.h> +#include <linux/mutex.h> #include <linux/percpu.h> #include <linux/preempt.h> #include <linux/sched.h> @@ -108,9 +110,40 @@ static atomic_long_t watchpoints[CONFIG_KCSAN_NUM_WATCHPOINTS + NUM_SLOTS-1]; */ static DEFINE_PER_CPU(long, kcsan_skip); +static DEFINE_STATIC_KEY_FALSE(kcsan_target_key); +static DEFINE_MUTEX(kcsan_target_lock); +static unsigned long kcsan_target_addr; + /* For kcsan_prandom_u32_max(). */ static DEFINE_PER_CPU(u32, kcsan_rand_state); +void kcsan_target_set(unsigned long addr) +{ + unsigned long old; + + mutex_lock(&kcsan_target_lock); + old = READ_ONCE(kcsan_target_addr); + + if (!old && addr) { + /* Publish the address before enabling the fast-path branch. */ + WRITE_ONCE(kcsan_target_addr, addr); + static_branch_enable(&kcsan_target_key); + } else if (old && !addr) { + /* Disable the fast-path branch before invalidating the target. */ + static_branch_disable(&kcsan_target_key); + WRITE_ONCE(kcsan_target_addr, 0); + } else if (old && addr) { + WRITE_ONCE(kcsan_target_addr, addr); + } + + mutex_unlock(&kcsan_target_lock); +} + +unsigned long kcsan_target_get(void) +{ + return READ_ONCE(kcsan_target_addr); +} + static __always_inline atomic_long_t *find_watchpoint(unsigned long addr, size_t size, bool expect_write, @@ -277,6 +310,10 @@ should_watch(struct kcsan_ctx *ctx, const volatile void *ptr, size_t size, int t if (is_atomic(ctx, ptr, size, type)) return false; + if (static_branch_unlikely(&kcsan_target_key)) + return kcsan_target_matches(READ_ONCE(kcsan_target_addr), + (unsigned long)ptr, size); + if (this_cpu_dec_return(kcsan_skip) >= 0) return false; diff --git a/kernel/kcsan/kcsan.h b/kernel/kcsan/kcsan.h index ae33c2a7f07e..2a36813c3a76 100644 --- a/kernel/kcsan/kcsan.h +++ b/kernel/kcsan/kcsan.h @@ -25,6 +25,16 @@ extern unsigned int kcsan_udelay_interrupt; */ extern bool kcsan_enabled; +void kcsan_target_set(unsigned long addr); +unsigned long kcsan_target_get(void); + +static __always_inline bool +kcsan_target_matches(unsigned long target, unsigned long addr, + size_t size) +{ + return size && target >= addr && target - addr < size; +} + /* * Save/restore IRQ flags state trace dirtied by KCSAN. */ -- 2.55.0

