|
@@ -1,5 +1,7 @@
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
|
|
+#define pr_fmt(fmt) "kcsan: " fmt
|
|
|
+
|
|
|
#include <linux/atomic.h>
|
|
|
#include <linux/bug.h>
|
|
|
#include <linux/delay.h>
|
|
@@ -98,6 +100,9 @@ static atomic_long_t watchpoints[CONFIG_KCSAN_NUM_WATCHPOINTS + NUM_SLOTS-1];
|
|
|
*/
|
|
|
static DEFINE_PER_CPU(long, kcsan_skip);
|
|
|
|
|
|
+/* For kcsan_prandom_u32_max(). */
|
|
|
+static DEFINE_PER_CPU(struct rnd_state, kcsan_rand_state);
|
|
|
+
|
|
|
static __always_inline atomic_long_t *find_watchpoint(unsigned long addr,
|
|
|
size_t size,
|
|
|
bool expect_write,
|
|
@@ -223,7 +228,7 @@ is_atomic(const volatile void *ptr, size_t size, int type, struct kcsan_ctx *ctx
|
|
|
|
|
|
if (IS_ENABLED(CONFIG_KCSAN_ASSUME_PLAIN_WRITES_ATOMIC) &&
|
|
|
(type & KCSAN_ACCESS_WRITE) && size <= sizeof(long) &&
|
|
|
- IS_ALIGNED((unsigned long)ptr, size))
|
|
|
+ !(type & KCSAN_ACCESS_COMPOUND) && IS_ALIGNED((unsigned long)ptr, size))
|
|
|
return true; /* Assume aligned writes up to word size are atomic. */
|
|
|
|
|
|
if (ctx->atomic_next > 0) {
|
|
@@ -269,11 +274,28 @@ should_watch(const volatile void *ptr, size_t size, int type, struct kcsan_ctx *
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
+/*
|
|
|
+ * Returns a pseudo-random number in interval [0, ep_ro). See prandom_u32_max()
|
|
|
+ * for more details.
|
|
|
+ *
|
|
|
+ * The open-coded version here is using only safe primitives for all contexts
|
|
|
+ * where we can have KCSAN instrumentation. In particular, we cannot use
|
|
|
+ * prandom_u32() directly, as its tracepoint could cause recursion.
|
|
|
+ */
|
|
|
+static u32 kcsan_prandom_u32_max(u32 ep_ro)
|
|
|
+{
|
|
|
+ struct rnd_state *state = &get_cpu_var(kcsan_rand_state);
|
|
|
+ const u32 res = prandom_u32_state(state);
|
|
|
+
|
|
|
+ put_cpu_var(kcsan_rand_state);
|
|
|
+ return (u32)(((u64) res * ep_ro) >> 32);
|
|
|
+}
|
|
|
+
|
|
|
static inline void reset_kcsan_skip(void)
|
|
|
{
|
|
|
long skip_count = kcsan_skip_watch -
|
|
|
(IS_ENABLED(CONFIG_KCSAN_SKIP_WATCH_RANDOMIZE) ?
|
|
|
- prandom_u32_max(kcsan_skip_watch) :
|
|
|
+ kcsan_prandom_u32_max(kcsan_skip_watch) :
|
|
|
0);
|
|
|
this_cpu_write(kcsan_skip, skip_count);
|
|
|
}
|
|
@@ -283,12 +305,18 @@ static __always_inline bool kcsan_is_enabled(void)
|
|
|
return READ_ONCE(kcsan_enabled) && get_ctx()->disable_count == 0;
|
|
|
}
|
|
|
|
|
|
-static inline unsigned int get_delay(void)
|
|
|
+/* Introduce delay depending on context and configuration. */
|
|
|
+static void delay_access(int type)
|
|
|
{
|
|
|
unsigned int delay = in_task() ? kcsan_udelay_task : kcsan_udelay_interrupt;
|
|
|
- return delay - (IS_ENABLED(CONFIG_KCSAN_DELAY_RANDOMIZE) ?
|
|
|
- prandom_u32_max(delay) :
|
|
|
- 0);
|
|
|
+ /* For certain access types, skew the random delay to be longer. */
|
|
|
+ unsigned int skew_delay_order =
|
|
|
+ (type & (KCSAN_ACCESS_COMPOUND | KCSAN_ACCESS_ASSERT)) ? 1 : 0;
|
|
|
+
|
|
|
+ delay -= IS_ENABLED(CONFIG_KCSAN_DELAY_RANDOMIZE) ?
|
|
|
+ kcsan_prandom_u32_max(delay >> skew_delay_order) :
|
|
|
+ 0;
|
|
|
+ udelay(delay);
|
|
|
}
|
|
|
|
|
|
void kcsan_save_irqtrace(struct task_struct *task)
|
|
@@ -361,13 +389,13 @@ static noinline void kcsan_found_watchpoint(const volatile void *ptr,
|
|
|
* already removed the watchpoint, or another thread consumed
|
|
|
* the watchpoint before this thread.
|
|
|
*/
|
|
|
- kcsan_counter_inc(KCSAN_COUNTER_REPORT_RACES);
|
|
|
+ atomic_long_inc(&kcsan_counters[KCSAN_COUNTER_REPORT_RACES]);
|
|
|
}
|
|
|
|
|
|
if ((type & KCSAN_ACCESS_ASSERT) != 0)
|
|
|
- kcsan_counter_inc(KCSAN_COUNTER_ASSERT_FAILURES);
|
|
|
+ atomic_long_inc(&kcsan_counters[KCSAN_COUNTER_ASSERT_FAILURES]);
|
|
|
else
|
|
|
- kcsan_counter_inc(KCSAN_COUNTER_DATA_RACES);
|
|
|
+ atomic_long_inc(&kcsan_counters[KCSAN_COUNTER_DATA_RACES]);
|
|
|
|
|
|
user_access_restore(flags);
|
|
|
}
|
|
@@ -408,7 +436,7 @@ kcsan_setup_watchpoint(const volatile void *ptr, size_t size, int type)
|
|
|
goto out;
|
|
|
|
|
|
if (!check_encodable((unsigned long)ptr, size)) {
|
|
|
- kcsan_counter_inc(KCSAN_COUNTER_UNENCODABLE_ACCESSES);
|
|
|
+ atomic_long_inc(&kcsan_counters[KCSAN_COUNTER_UNENCODABLE_ACCESSES]);
|
|
|
goto out;
|
|
|
}
|
|
|
|
|
@@ -428,12 +456,12 @@ kcsan_setup_watchpoint(const volatile void *ptr, size_t size, int type)
|
|
|
* with which should_watch() returns true should be tweaked so
|
|
|
* that this case happens very rarely.
|
|
|
*/
|
|
|
- kcsan_counter_inc(KCSAN_COUNTER_NO_CAPACITY);
|
|
|
+ atomic_long_inc(&kcsan_counters[KCSAN_COUNTER_NO_CAPACITY]);
|
|
|
goto out_unlock;
|
|
|
}
|
|
|
|
|
|
- kcsan_counter_inc(KCSAN_COUNTER_SETUP_WATCHPOINTS);
|
|
|
- kcsan_counter_inc(KCSAN_COUNTER_USED_WATCHPOINTS);
|
|
|
+ atomic_long_inc(&kcsan_counters[KCSAN_COUNTER_SETUP_WATCHPOINTS]);
|
|
|
+ atomic_long_inc(&kcsan_counters[KCSAN_COUNTER_USED_WATCHPOINTS]);
|
|
|
|
|
|
/*
|
|
|
* Read the current value, to later check and infer a race if the data
|
|
@@ -459,7 +487,7 @@ kcsan_setup_watchpoint(const volatile void *ptr, size_t size, int type)
|
|
|
|
|
|
if (IS_ENABLED(CONFIG_KCSAN_DEBUG)) {
|
|
|
kcsan_disable_current();
|
|
|
- pr_err("KCSAN: watching %s, size: %zu, addr: %px [slot: %d, encoded: %lx]\n",
|
|
|
+ pr_err("watching %s, size: %zu, addr: %px [slot: %d, encoded: %lx]\n",
|
|
|
is_write ? "write" : "read", size, ptr,
|
|
|
watchpoint_slot((unsigned long)ptr),
|
|
|
encode_watchpoint((unsigned long)ptr, size, is_write));
|
|
@@ -470,7 +498,7 @@ kcsan_setup_watchpoint(const volatile void *ptr, size_t size, int type)
|
|
|
* Delay this thread, to increase probability of observing a racy
|
|
|
* conflicting access.
|
|
|
*/
|
|
|
- udelay(get_delay());
|
|
|
+ delay_access(type);
|
|
|
|
|
|
/*
|
|
|
* Re-read value, and check if it is as expected; if not, we infer a
|
|
@@ -535,16 +563,16 @@ kcsan_setup_watchpoint(const volatile void *ptr, size_t size, int type)
|
|
|
* increment this counter.
|
|
|
*/
|
|
|
if (is_assert && value_change == KCSAN_VALUE_CHANGE_TRUE)
|
|
|
- kcsan_counter_inc(KCSAN_COUNTER_ASSERT_FAILURES);
|
|
|
+ atomic_long_inc(&kcsan_counters[KCSAN_COUNTER_ASSERT_FAILURES]);
|
|
|
|
|
|
kcsan_report(ptr, size, type, value_change, KCSAN_REPORT_RACE_SIGNAL,
|
|
|
watchpoint - watchpoints);
|
|
|
} else if (value_change == KCSAN_VALUE_CHANGE_TRUE) {
|
|
|
/* Inferring a race, since the value should not have changed. */
|
|
|
|
|
|
- kcsan_counter_inc(KCSAN_COUNTER_RACES_UNKNOWN_ORIGIN);
|
|
|
+ atomic_long_inc(&kcsan_counters[KCSAN_COUNTER_RACES_UNKNOWN_ORIGIN]);
|
|
|
if (is_assert)
|
|
|
- kcsan_counter_inc(KCSAN_COUNTER_ASSERT_FAILURES);
|
|
|
+ atomic_long_inc(&kcsan_counters[KCSAN_COUNTER_ASSERT_FAILURES]);
|
|
|
|
|
|
if (IS_ENABLED(CONFIG_KCSAN_REPORT_RACE_UNKNOWN_ORIGIN) || is_assert)
|
|
|
kcsan_report(ptr, size, type, KCSAN_VALUE_CHANGE_TRUE,
|
|
@@ -557,7 +585,7 @@ kcsan_setup_watchpoint(const volatile void *ptr, size_t size, int type)
|
|
|
* reused after this point.
|
|
|
*/
|
|
|
remove_watchpoint(watchpoint);
|
|
|
- kcsan_counter_dec(KCSAN_COUNTER_USED_WATCHPOINTS);
|
|
|
+ atomic_long_dec(&kcsan_counters[KCSAN_COUNTER_USED_WATCHPOINTS]);
|
|
|
out_unlock:
|
|
|
if (!kcsan_interrupt_watcher)
|
|
|
local_irq_restore(irq_flags);
|
|
@@ -614,13 +642,16 @@ void __init kcsan_init(void)
|
|
|
BUG_ON(!in_task());
|
|
|
|
|
|
kcsan_debugfs_init();
|
|
|
+ prandom_seed_full_state(&kcsan_rand_state);
|
|
|
|
|
|
/*
|
|
|
* We are in the init task, and no other tasks should be running;
|
|
|
* WRITE_ONCE without memory barrier is sufficient.
|
|
|
*/
|
|
|
- if (kcsan_early_enable)
|
|
|
+ if (kcsan_early_enable) {
|
|
|
+ pr_info("enabled early\n");
|
|
|
WRITE_ONCE(kcsan_enabled, true);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/* === Exported interface =================================================== */
|
|
@@ -793,7 +824,17 @@ EXPORT_SYMBOL(__kcsan_check_access);
|
|
|
EXPORT_SYMBOL(__tsan_write##size); \
|
|
|
void __tsan_unaligned_write##size(void *ptr) \
|
|
|
__alias(__tsan_write##size); \
|
|
|
- EXPORT_SYMBOL(__tsan_unaligned_write##size)
|
|
|
+ EXPORT_SYMBOL(__tsan_unaligned_write##size); \
|
|
|
+ void __tsan_read_write##size(void *ptr); \
|
|
|
+ void __tsan_read_write##size(void *ptr) \
|
|
|
+ { \
|
|
|
+ check_access(ptr, size, \
|
|
|
+ KCSAN_ACCESS_COMPOUND | KCSAN_ACCESS_WRITE); \
|
|
|
+ } \
|
|
|
+ EXPORT_SYMBOL(__tsan_read_write##size); \
|
|
|
+ void __tsan_unaligned_read_write##size(void *ptr) \
|
|
|
+ __alias(__tsan_read_write##size); \
|
|
|
+ EXPORT_SYMBOL(__tsan_unaligned_read_write##size)
|
|
|
|
|
|
DEFINE_TSAN_READ_WRITE(1);
|
|
|
DEFINE_TSAN_READ_WRITE(2);
|
|
@@ -879,3 +920,130 @@ void __tsan_init(void)
|
|
|
{
|
|
|
}
|
|
|
EXPORT_SYMBOL(__tsan_init);
|
|
|
+
|
|
|
+/*
|
|
|
+ * Instrumentation for atomic builtins (__atomic_*, __sync_*).
|
|
|
+ *
|
|
|
+ * Normal kernel code _should not_ be using them directly, but some
|
|
|
+ * architectures may implement some or all atomics using the compilers'
|
|
|
+ * builtins.
|
|
|
+ *
|
|
|
+ * Note: If an architecture decides to fully implement atomics using the
|
|
|
+ * builtins, because they are implicitly instrumented by KCSAN (and KASAN,
|
|
|
+ * etc.), implementing the ARCH_ATOMIC interface (to get instrumentation via
|
|
|
+ * atomic-instrumented) is no longer necessary.
|
|
|
+ *
|
|
|
+ * TSAN instrumentation replaces atomic accesses with calls to any of the below
|
|
|
+ * functions, whose job is to also execute the operation itself.
|
|
|
+ */
|
|
|
+
|
|
|
+#define DEFINE_TSAN_ATOMIC_LOAD_STORE(bits) \
|
|
|
+ u##bits __tsan_atomic##bits##_load(const u##bits *ptr, int memorder); \
|
|
|
+ u##bits __tsan_atomic##bits##_load(const u##bits *ptr, int memorder) \
|
|
|
+ { \
|
|
|
+ if (!IS_ENABLED(CONFIG_KCSAN_IGNORE_ATOMICS)) { \
|
|
|
+ check_access(ptr, bits / BITS_PER_BYTE, KCSAN_ACCESS_ATOMIC); \
|
|
|
+ } \
|
|
|
+ return __atomic_load_n(ptr, memorder); \
|
|
|
+ } \
|
|
|
+ EXPORT_SYMBOL(__tsan_atomic##bits##_load); \
|
|
|
+ void __tsan_atomic##bits##_store(u##bits *ptr, u##bits v, int memorder); \
|
|
|
+ void __tsan_atomic##bits##_store(u##bits *ptr, u##bits v, int memorder) \
|
|
|
+ { \
|
|
|
+ if (!IS_ENABLED(CONFIG_KCSAN_IGNORE_ATOMICS)) { \
|
|
|
+ check_access(ptr, bits / BITS_PER_BYTE, \
|
|
|
+ KCSAN_ACCESS_WRITE | KCSAN_ACCESS_ATOMIC); \
|
|
|
+ } \
|
|
|
+ __atomic_store_n(ptr, v, memorder); \
|
|
|
+ } \
|
|
|
+ EXPORT_SYMBOL(__tsan_atomic##bits##_store)
|
|
|
+
|
|
|
+#define DEFINE_TSAN_ATOMIC_RMW(op, bits, suffix) \
|
|
|
+ u##bits __tsan_atomic##bits##_##op(u##bits *ptr, u##bits v, int memorder); \
|
|
|
+ u##bits __tsan_atomic##bits##_##op(u##bits *ptr, u##bits v, int memorder) \
|
|
|
+ { \
|
|
|
+ if (!IS_ENABLED(CONFIG_KCSAN_IGNORE_ATOMICS)) { \
|
|
|
+ check_access(ptr, bits / BITS_PER_BYTE, \
|
|
|
+ KCSAN_ACCESS_COMPOUND | KCSAN_ACCESS_WRITE | \
|
|
|
+ KCSAN_ACCESS_ATOMIC); \
|
|
|
+ } \
|
|
|
+ return __atomic_##op##suffix(ptr, v, memorder); \
|
|
|
+ } \
|
|
|
+ EXPORT_SYMBOL(__tsan_atomic##bits##_##op)
|
|
|
+
|
|
|
+/*
|
|
|
+ * Note: CAS operations are always classified as write, even in case they
|
|
|
+ * fail. We cannot perform check_access() after a write, as it might lead to
|
|
|
+ * false positives, in cases such as:
|
|
|
+ *
|
|
|
+ * T0: __atomic_compare_exchange_n(&p->flag, &old, 1, ...)
|
|
|
+ *
|
|
|
+ * T1: if (__atomic_load_n(&p->flag, ...)) {
|
|
|
+ * modify *p;
|
|
|
+ * p->flag = 0;
|
|
|
+ * }
|
|
|
+ *
|
|
|
+ * The only downside is that, if there are 3 threads, with one CAS that
|
|
|
+ * succeeds, another CAS that fails, and an unmarked racing operation, we may
|
|
|
+ * point at the wrong CAS as the source of the race. However, if we assume that
|
|
|
+ * all CAS can succeed in some other execution, the data race is still valid.
|
|
|
+ */
|
|
|
+#define DEFINE_TSAN_ATOMIC_CMPXCHG(bits, strength, weak) \
|
|
|
+ int __tsan_atomic##bits##_compare_exchange_##strength(u##bits *ptr, u##bits *exp, \
|
|
|
+ u##bits val, int mo, int fail_mo); \
|
|
|
+ int __tsan_atomic##bits##_compare_exchange_##strength(u##bits *ptr, u##bits *exp, \
|
|
|
+ u##bits val, int mo, int fail_mo) \
|
|
|
+ { \
|
|
|
+ if (!IS_ENABLED(CONFIG_KCSAN_IGNORE_ATOMICS)) { \
|
|
|
+ check_access(ptr, bits / BITS_PER_BYTE, \
|
|
|
+ KCSAN_ACCESS_COMPOUND | KCSAN_ACCESS_WRITE | \
|
|
|
+ KCSAN_ACCESS_ATOMIC); \
|
|
|
+ } \
|
|
|
+ return __atomic_compare_exchange_n(ptr, exp, val, weak, mo, fail_mo); \
|
|
|
+ } \
|
|
|
+ EXPORT_SYMBOL(__tsan_atomic##bits##_compare_exchange_##strength)
|
|
|
+
|
|
|
+#define DEFINE_TSAN_ATOMIC_CMPXCHG_VAL(bits) \
|
|
|
+ u##bits __tsan_atomic##bits##_compare_exchange_val(u##bits *ptr, u##bits exp, u##bits val, \
|
|
|
+ int mo, int fail_mo); \
|
|
|
+ u##bits __tsan_atomic##bits##_compare_exchange_val(u##bits *ptr, u##bits exp, u##bits val, \
|
|
|
+ int mo, int fail_mo) \
|
|
|
+ { \
|
|
|
+ if (!IS_ENABLED(CONFIG_KCSAN_IGNORE_ATOMICS)) { \
|
|
|
+ check_access(ptr, bits / BITS_PER_BYTE, \
|
|
|
+ KCSAN_ACCESS_COMPOUND | KCSAN_ACCESS_WRITE | \
|
|
|
+ KCSAN_ACCESS_ATOMIC); \
|
|
|
+ } \
|
|
|
+ __atomic_compare_exchange_n(ptr, &exp, val, 0, mo, fail_mo); \
|
|
|
+ return exp; \
|
|
|
+ } \
|
|
|
+ EXPORT_SYMBOL(__tsan_atomic##bits##_compare_exchange_val)
|
|
|
+
|
|
|
+#define DEFINE_TSAN_ATOMIC_OPS(bits) \
|
|
|
+ DEFINE_TSAN_ATOMIC_LOAD_STORE(bits); \
|
|
|
+ DEFINE_TSAN_ATOMIC_RMW(exchange, bits, _n); \
|
|
|
+ DEFINE_TSAN_ATOMIC_RMW(fetch_add, bits, ); \
|
|
|
+ DEFINE_TSAN_ATOMIC_RMW(fetch_sub, bits, ); \
|
|
|
+ DEFINE_TSAN_ATOMIC_RMW(fetch_and, bits, ); \
|
|
|
+ DEFINE_TSAN_ATOMIC_RMW(fetch_or, bits, ); \
|
|
|
+ DEFINE_TSAN_ATOMIC_RMW(fetch_xor, bits, ); \
|
|
|
+ DEFINE_TSAN_ATOMIC_RMW(fetch_nand, bits, ); \
|
|
|
+ DEFINE_TSAN_ATOMIC_CMPXCHG(bits, strong, 0); \
|
|
|
+ DEFINE_TSAN_ATOMIC_CMPXCHG(bits, weak, 1); \
|
|
|
+ DEFINE_TSAN_ATOMIC_CMPXCHG_VAL(bits)
|
|
|
+
|
|
|
+DEFINE_TSAN_ATOMIC_OPS(8);
|
|
|
+DEFINE_TSAN_ATOMIC_OPS(16);
|
|
|
+DEFINE_TSAN_ATOMIC_OPS(32);
|
|
|
+DEFINE_TSAN_ATOMIC_OPS(64);
|
|
|
+
|
|
|
+void __tsan_atomic_thread_fence(int memorder);
|
|
|
+void __tsan_atomic_thread_fence(int memorder)
|
|
|
+{
|
|
|
+ __atomic_thread_fence(memorder);
|
|
|
+}
|
|
|
+EXPORT_SYMBOL(__tsan_atomic_thread_fence);
|
|
|
+
|
|
|
+void __tsan_atomic_signal_fence(int memorder);
|
|
|
+void __tsan_atomic_signal_fence(int memorder) { }
|
|
|
+EXPORT_SYMBOL(__tsan_atomic_signal_fence);
|