1625 lines · 51.1 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* * KCSAN test with various race scenarious to test runtime behaviour. Since the * interface with which KCSAN's reports are obtained is via the console, this is * the output we should verify. For each test case checks the presence (or * absence) of generated reports. Relies on 'console' tracepoint to capture * reports as they appear in the kernel log. * * Makes use of KUnit for test organization, and the Torture framework for test * thread control. * * Copyright (C) 2020, Google LLC. * Author: Marco Elver <elver@google.com> */ #define pr_fmt(fmt) "kcsan_test: " fmt #include <kunit/test.h>#include <linux/atomic.h>#include <linux/bitops.h>#include <linux/jiffies.h>#include <linux/kcsan-checks.h>#include <linux/kernel.h>#include <linux/mutex.h>#include <linux/sched.h>#include <linux/seqlock.h>#include <linux/spinlock.h>#include <linux/string.h>#include <linux/timer.h>#include <linux/torture.h>#include <linux/tracepoint.h>#include <linux/types.h>#include <trace/events/printk.h> #define KCSAN_TEST_REQUIRES(test, cond) do {			\	if (!(cond))						\		kunit_skip((test), "Test requires: " #cond);	\} while (0) #ifdef CONFIG_CC_HAS_TSAN_COMPOUND_READ_BEFORE_WRITE#define __KCSAN_ACCESS_RW(alt) (KCSAN_ACCESS_COMPOUND | KCSAN_ACCESS_WRITE)#else#define __KCSAN_ACCESS_RW(alt) (alt)#endif /* Points to current test-case memory access "kernels". */static void (*access_kernels[2])(void); static struct task_struct **threads; /* Lists of threads. */static unsigned long end_time;       /* End time of test. */ /* Report as observed from console. */static struct {	spinlock_t lock;	int nlines;	char lines[3][512];} observed = {	.lock = __SPIN_LOCK_UNLOCKED(observed.lock),}; /* Setup test checking loop. */static __no_kcsan inline voidbegin_test_checks(void (*func1)(void), void (*func2)(void)){	kcsan_disable_current(); 	/*	 * Require at least as long as KCSAN_REPORT_ONCE_IN_MS, to ensure at	 * least one race is reported.	 */	end_time = jiffies + msecs_to_jiffies(CONFIG_KCSAN_REPORT_ONCE_IN_MS + 500); 	/* Signal start; release potential initialization of shared data. */	smp_store_release(&access_kernels[0], func1);	smp_store_release(&access_kernels[1], func2);} /* End test checking loop. */static __no_kcsan inline boolend_test_checks(bool stop){	if (!stop && time_before(jiffies, end_time)) {		/* Continue checking */		might_sleep();		return false;	} 	kcsan_enable_current();	return true;} /* * Probe for console output: checks if a race was reported, and obtains observed * lines of interest. */__no_kcsanstatic void probe_console(void *ignore, const char *buf, size_t len){	unsigned long flags;	int nlines; 	/*	 * Note that KCSAN reports under a global lock, so we do not risk the	 * possibility of having multiple reports interleaved. If that were the	 * case, we'd expect tests to fail.	 */ 	spin_lock_irqsave(&observed.lock, flags);	nlines = observed.nlines; 	if (strnstr(buf, "BUG: KCSAN: ", len) && strnstr(buf, "test_", len)) {		/*		 * KCSAN report and related to the test.		 *		 * The provided @buf is not NUL-terminated; copy no more than		 * @len bytes and let strscpy() add the missing NUL-terminator.		 */		strscpy(observed.lines[0], buf, min(len + 1, sizeof(observed.lines[0])));		nlines = 1;	} else if ((nlines == 1 || nlines == 2) && strnstr(buf, "bytes by", len)) {		strscpy(observed.lines[nlines++], buf, min(len + 1, sizeof(observed.lines[0]))); 		if (strnstr(buf, "race at unknown origin", len)) {			if (WARN_ON(nlines != 2))				goto out; 			/* No second line of interest. */			strscpy(observed.lines[nlines++], "<none>");		}	} out:	WRITE_ONCE(observed.nlines, nlines); /* Publish new nlines. */	spin_unlock_irqrestore(&observed.lock, flags);} /* Check if a report related to the test exists. */__no_kcsanstatic bool report_available(void){	return READ_ONCE(observed.nlines) == ARRAY_SIZE(observed.lines);} /* Report information we expect in a report. */struct expect_report {	/* Access information of both accesses. */	struct {		void *fn;    /* Function pointer to expected function of top frame. */		void *addr;  /* Address of access; unchecked if NULL. */		size_t size; /* Size of access; unchecked if @addr is NULL. */		int type;    /* Access type, see KCSAN_ACCESS definitions. */	} access[2];}; /* Check observed report matches information in @r. */__no_kcsanstatic bool __report_matches(const struct expect_report *r){	const bool is_assert = (r->access[0].type | r->access[1].type) & KCSAN_ACCESS_ASSERT;	bool ret = false;	unsigned long flags;	typeof(*observed.lines) *expect;	const char *end;	char *cur;	int i; 	/* Doubled-checked locking. */	if (!report_available())		return false; 	expect = (typeof(expect))kmalloc_obj(observed.lines);	if (WARN_ON(!expect))		return false; 	/* Generate expected report contents. */ 	/* Title */	cur = expect[0];	end = ARRAY_END(expect[0]);	cur += scnprintf(cur, end - cur, "BUG: KCSAN: %s in ",			 is_assert ? "assert: race" : "data-race");	if (r->access[1].fn) {		char tmp[2][64];		int cmp; 		/* Expect lexographically sorted function names in title. */		scnprintf(tmp[0], sizeof(tmp[0]), "%pS", r->access[0].fn);		scnprintf(tmp[1], sizeof(tmp[1]), "%pS", r->access[1].fn);		cmp = strcmp(tmp[0], tmp[1]);		cur += scnprintf(cur, end - cur, "%ps / %ps",				 cmp < 0 ? r->access[0].fn : r->access[1].fn,				 cmp < 0 ? r->access[1].fn : r->access[0].fn);	} else {		scnprintf(cur, end - cur, "%pS", r->access[0].fn);		/* The exact offset won't match, remove it. */		cur = strchr(expect[0], '+');		if (cur)			*cur = '\0';	} 	/* Access 1 */	cur = expect[1];	end = ARRAY_END(expect[1]);	if (!r->access[1].fn)		cur += scnprintf(cur, end - cur, "race at unknown origin, with "); 	/* Access 1 & 2 */	for (i = 0; i < 2; ++i) {		const int ty = r->access[i].type;		const char *const access_type =			(ty & KCSAN_ACCESS_ASSERT) ?				      ((ty & KCSAN_ACCESS_WRITE) ?					       "assert no accesses" :					       "assert no writes") :				      ((ty & KCSAN_ACCESS_WRITE) ?					       ((ty & KCSAN_ACCESS_COMPOUND) ?							"read-write" :							"write") :					       "read");		const bool is_atomic = (ty & KCSAN_ACCESS_ATOMIC);		const bool is_scoped = (ty & KCSAN_ACCESS_SCOPED);		const char *const access_type_aux =				(is_atomic && is_scoped)	? " (marked, reordered)"				: (is_atomic			? " (marked)"				   : (is_scoped			? " (reordered)" : "")); 		if (i == 1) {			/* Access 2 */			cur = expect[2];			end = &expect[2][sizeof(expect[2]) - 1]; 			if (!r->access[1].fn) {				/* Dummy string if no second access is available. */				strscpy(expect[2], "<none>");				break;			}		} 		cur += scnprintf(cur, end - cur, "%s%s to ", access_type,				 access_type_aux); 		if (r->access[i].addr) /* Address is optional. */			cur += scnprintf(cur, end - cur, "0x%px of %zu bytes",					 r->access[i].addr, r->access[i].size);	} 	spin_lock_irqsave(&observed.lock, flags);	if (!report_available())		goto out; /* A new report is being captured. */ 	/* Finally match expected output to what we actually observed. */	ret = strstr(observed.lines[0], expect[0]) &&	      /* Access info may appear in any order. */	      ((strstr(observed.lines[1], expect[1]) &&		strstr(observed.lines[2], expect[2])) ||	       (strstr(observed.lines[1], expect[2]) &&		strstr(observed.lines[2], expect[1])));out:	spin_unlock_irqrestore(&observed.lock, flags);	kfree(expect);	return ret;} static __always_inline const struct expect_report *__report_set_scoped(struct expect_report *r, int accesses){	BUILD_BUG_ON(accesses > 3); 	if (accesses & 1)		r->access[0].type |= KCSAN_ACCESS_SCOPED;	else		r->access[0].type &= ~KCSAN_ACCESS_SCOPED; 	if (accesses & 2)		r->access[1].type |= KCSAN_ACCESS_SCOPED;	else		r->access[1].type &= ~KCSAN_ACCESS_SCOPED; 	return r;} __no_kcsanstatic bool report_matches_any_reordered(struct expect_report *r){	return __report_matches(__report_set_scoped(r, 0)) ||	       __report_matches(__report_set_scoped(r, 1)) ||	       __report_matches(__report_set_scoped(r, 2)) ||	       __report_matches(__report_set_scoped(r, 3));} #ifdef CONFIG_KCSAN_WEAK_MEMORY/* Due to reordering accesses, any access may appear as "(reordered)". */#define report_matches report_matches_any_reordered#else#define report_matches __report_matches#endif /* ===== Test kernels ===== */ static long test_sink;static long test_var;/* @test_array should be large enough to fall into multiple watchpoint slots. */static long test_array[3 * PAGE_SIZE / sizeof(long)];static struct {	long val[8];} test_struct;static long __data_racy test_data_racy;static DEFINE_SEQLOCK(test_seqlock);static DEFINE_SPINLOCK(test_spinlock);static DEFINE_MUTEX(test_mutex); /* * Helper to avoid compiler optimizing out reads, and to generate source values * for writes. */__no_kcsanstatic noinline void sink_value(long v) { WRITE_ONCE(test_sink, v); } /* * Generates a delay and some accesses that enter the runtime but do not produce * data races. */static noinline void test_delay(int iter){	while (iter--)		sink_value(READ_ONCE(test_sink));} static noinline void test_kernel_read(void) { sink_value(test_var); } static noinline void test_kernel_write(void){	test_var = READ_ONCE_NOCHECK(test_sink) + 1;} static noinline void test_kernel_write_nochange(void) { test_var = 42; } /* Suffixed by value-change exception filter. */static noinline void test_kernel_write_nochange_rcu(void) { test_var = 42; } static noinline void test_kernel_read_atomic(void){	sink_value(READ_ONCE(test_var));} static noinline void test_kernel_write_atomic(void){	WRITE_ONCE(test_var, READ_ONCE_NOCHECK(test_sink) + 1);} static noinline void test_kernel_atomic_rmw(void){	/* Use builtin, so we can set up the "bad" atomic/non-atomic scenario. */	__atomic_fetch_add(&test_var, 1, __ATOMIC_RELAXED);} __no_kcsanstatic noinline void test_kernel_write_uninstrumented(void) { test_var++; } static noinline void test_kernel_data_race(void) { data_race(test_var++); } static noinline void test_kernel_data_racy_qualifier(void) { test_data_racy++; } static noinline void test_kernel_assert_writer(void){	ASSERT_EXCLUSIVE_WRITER(test_var);} static noinline void test_kernel_assert_access(void){	ASSERT_EXCLUSIVE_ACCESS(test_var);} #define TEST_CHANGE_BITS 0xff00ff00 static noinline void test_kernel_change_bits(void){	if (IS_ENABLED(CONFIG_KCSAN_IGNORE_ATOMICS)) {		/*		 * Avoid race of unknown origin for this test, just pretend they		 * are atomic.		 */		kcsan_nestable_atomic_begin();		test_var ^= TEST_CHANGE_BITS;		kcsan_nestable_atomic_end();	} else		WRITE_ONCE(test_var, READ_ONCE(test_var) ^ TEST_CHANGE_BITS);} static noinline void test_kernel_assert_bits_change(void){	ASSERT_EXCLUSIVE_BITS(test_var, TEST_CHANGE_BITS);} static noinline void test_kernel_assert_bits_nochange(void){	ASSERT_EXCLUSIVE_BITS(test_var, ~TEST_CHANGE_BITS);} /* * Scoped assertions do trigger anywhere in scope. However, the report should * still only point at the start of the scope. */static noinline void test_enter_scope(void){	int x = 0; 	/* Unrelated accesses to scoped assert. */	READ_ONCE(test_sink);	kcsan_check_read(&x, sizeof(x));} static noinline void test_kernel_assert_writer_scoped(void){	ASSERT_EXCLUSIVE_WRITER_SCOPED(test_var);	test_enter_scope();} static noinline void test_kernel_assert_access_scoped(void){	ASSERT_EXCLUSIVE_ACCESS_SCOPED(test_var);	test_enter_scope();} static noinline void test_kernel_rmw_array(void){	int i; 	for (i = 0; i < ARRAY_SIZE(test_array); ++i)		test_array[i]++;} static noinline void test_kernel_write_struct(void){	kcsan_check_write(&test_struct, sizeof(test_struct));	kcsan_disable_current();	test_struct.val[3]++; /* induce value change */	kcsan_enable_current();} static noinline void test_kernel_write_struct_part(void){	test_struct.val[3] = 42;} static noinline void test_kernel_read_struct_zero_size(void){	kcsan_check_read(&test_struct.val[3], 0);} static noinline void test_kernel_jiffies_reader(void){	sink_value((long)jiffies);} static noinline void test_kernel_seqlock_reader(void){	unsigned int seq; 	do {		seq = read_seqbegin(&test_seqlock);		sink_value(test_var);	} while (read_seqretry(&test_seqlock, seq));} static noinline void test_kernel_seqlock_writer(void){	unsigned long flags; 	write_seqlock_irqsave(&test_seqlock, flags);	test_var++;	write_sequnlock_irqrestore(&test_seqlock, flags);} static noinline void test_kernel_atomic_builtins(void){	/*	 * Generate concurrent accesses, expecting no reports, ensuring KCSAN	 * treats builtin atomics as actually atomic.	 */	__atomic_load_n(&test_var, __ATOMIC_RELAXED);} static noinline void test_kernel_xor_1bit(void){	/* Do not report data races between the read-writes. */	kcsan_nestable_atomic_begin();	test_var ^= 0x10000;	kcsan_nestable_atomic_end();} #define TEST_KERNEL_LOCKED(name, acquire, release)		\	static noinline void test_kernel_##name(void)		\	{							\		long *flag = &test_struct.val[0];		\		long v = 0;					\		if (!(acquire))					\			return;					\		while (v++ < 100) {				\			test_var++;				\

Lines 1–500 of 1625.