Received: by 2002:a25:1985:0:0:0:0:0 with SMTP id 127csp3068184ybz; Mon, 27 Apr 2020 09:24:28 -0700 (PDT) X-Google-Smtp-Source: APiQypI1qJgWFURyTERyj/gD0zb7h/pWJLrVM5v+abD53CnwyqJmUmi/ShwFdWkwqwRpTdRHhVaQ X-Received: by 2002:a05:6402:221c:: with SMTP id cq28mr18251501edb.50.1588004668728; Mon, 27 Apr 2020 09:24:28 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1588004668; cv=none; d=google.com; s=arc-20160816; b=BjxDVkF2UBGp1HsJifz5gqyQv7Pr+nUNgiOdX5MdySA3UOLoaPw0+ZpFAfrP/5e1Gw zklFBbtC/mrNuaJPOkNboSJU+UNWhXzor9xXdXoA29Q4OoKdX3CWRZUQv9GndQyFTrDr 5nZB9baREo36+NHfWuloBEFPqW1vBCgykkJaq5dfibsvmPHX4w+f642MqLP3BFUYwJg+ KRUZwuVk4z050KWb+Ge7WeHd5XRjVRybfFmzRhvE9aD1OMAxogOzJM2O9aiOZsw06cH+ Qj3V9Zw35h93KSXnBL2VeHGd3IZCnUUPfhm9nnCpS9b8xckjxTEvEWXndvnGw327lBwT ZJ2g== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:user-agent:in-reply-to :content-disposition:mime-version:references:reply-to:message-id :subject:cc:to:from:date:dkim-signature; bh=JphGaZlIE16DZM71mfRhKh0j6ogxPLZM6sqPMJItQc8=; b=F0mVbP7Hv3q9y48Jb40v1+0mhs979a9ksw327IiEIwGmZk1xlK7pWZ21EYXj1uQtHb 6bPl916vMkmx0gPs+FDVBoF2osDdc3U0AxUe6aMIE42x73vdYjPjrGwtGwPNSEj37538 Yb4Mv1siHNxVrwK2t6HdgR1LXdvXsp+014beWWx+kFZfkwu2hXS6XCLJITIyB/P7pHfb AB/6ZauCb3pqs9SPPwI5kJW6+sVzahO3vRk42TgSuO4LClylLAHencGrSt8AsbP/rnLq rmcaD2lKoeb29GOw8RdvanzlUP6/TiTylRsdKK3QPBre7IAoQG+XH594BBbCJdWNDG+7 4aOA== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@kernel.org header.s=default header.b=kBqyfam7; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=kernel.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [23.128.96.18]) by mx.google.com with ESMTP id x16si8792ejw.391.2020.04.27.09.24.04; Mon, 27 Apr 2020 09:24:28 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) client-ip=23.128.96.18; Authentication-Results: mx.google.com; dkim=pass header.i=@kernel.org header.s=default header.b=kBqyfam7; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727949AbgD0QWZ (ORCPT + 99 others); Mon, 27 Apr 2020 12:22:25 -0400 Received: from mail.kernel.org ([198.145.29.99]:46786 "EHLO mail.kernel.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726229AbgD0QWY (ORCPT ); Mon, 27 Apr 2020 12:22:24 -0400 Received: from paulmck-ThinkPad-P72.home (50-39-105-78.bvtn.or.frontiernet.net [50.39.105.78]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPSA id 9AE8D206BF; Mon, 27 Apr 2020 16:22:22 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=default; t=1588004542; bh=fouWSOpEoTwF3h5Vxcqmnd1Tl/BMqkW1L2jXf4DZHZU=; h=Date:From:To:Cc:Subject:Reply-To:References:In-Reply-To:From; b=kBqyfam7FIibXpzbn11hN0hfM7165uY5W8MaHywZmFeOW786VOfWReyGBf1C73fdn xik+gFEbK36EscRpOGHS8zMLTqcuLNE7vyLxHyr47rnJvOocYl+0tlWOq5nMnkteWn vcECttHecVcNKoU+P5Z/Ay6TCmRE6Ta1/8It5usI= Received: by paulmck-ThinkPad-P72.home (Postfix, from userid 1000) id 5F39E35226DB; Mon, 27 Apr 2020 09:22:22 -0700 (PDT) Date: Mon, 27 Apr 2020 09:22:22 -0700 From: "Paul E. McKenney" To: Marco Elver Cc: dvyukov@google.com, glider@google.com, andreyknvl@google.com, kasan-dev@googlegroups.com, linux-kernel@vger.kernel.org Subject: Re: [PATCH] kcsan: Add test suite Message-ID: <20200427162222.GB7560@paulmck-ThinkPad-P72> Reply-To: paulmck@kernel.org References: <20200427143507.49654-1-elver@google.com> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: <20200427143507.49654-1-elver@google.com> User-Agent: Mutt/1.9.4 (2018-02-28) Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org On Mon, Apr 27, 2020 at 04:35:07PM +0200, Marco Elver wrote: > This adds KCSAN test focusing on behaviour of the integrated runtime. > Tests various race scenarios, and verifies the reports generated to > console. Makes use of KUnit for test organization, and the Torture > framework for test thread control. > > Signed-off-by: Marco Elver Queued for review and further testing, thank you! Thanx, Paul > --- > kernel/kcsan/Makefile | 3 + > kernel/kcsan/kcsan-test.c | 1067 +++++++++++++++++++++++++++++++++++++ > lib/Kconfig.kcsan | 23 +- > 3 files changed, 1092 insertions(+), 1 deletion(-) > create mode 100644 kernel/kcsan/kcsan-test.c > > diff --git a/kernel/kcsan/Makefile b/kernel/kcsan/Makefile > index d4999b38d1be..14533cf24bc3 100644 > --- a/kernel/kcsan/Makefile > +++ b/kernel/kcsan/Makefile > @@ -12,3 +12,6 @@ CFLAGS_core.o := $(call cc-option,-fno-conserve-stack,) \ > > obj-y := core.o debugfs.o report.o > obj-$(CONFIG_KCSAN_SELFTEST) += test.o > + > +CFLAGS_kcsan-test.o := $(CFLAGS_KCSAN) -g -fno-omit-frame-pointer > +obj-$(CONFIG_KCSAN_TEST) += kcsan-test.o > diff --git a/kernel/kcsan/kcsan-test.c b/kernel/kcsan/kcsan-test.c > new file mode 100644 > index 000000000000..04326cd5a4b2 > --- /dev/null > +++ b/kernel/kcsan/kcsan-test.c > @@ -0,0 +1,1067 @@ > +// 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 > + */ > + > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +/* 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_or_inline void > +begin_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 + 300); > + > + /* 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_or_inline bool > +end_test_checks(bool stop) > +{ > + if (!stop && time_before(jiffies, end_time)) { > + /* Continue checking */ > + return false; > + } > + > + kcsan_enable_current(); > + return true; > +} > + > +/* > + * Probe for console output: checks if a race was reported, and obtains observed > + * lines of interest. > + */ > +__no_kcsan > +static 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. */ > + strcpy(observed.lines[nlines++], ""); > + } > + } > + > +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_kcsan > +static 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_kcsan > +static 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; > + > + /* Generate expected report contents. */ > + > + /* Title */ > + cur = expect[0]; > + end = &expect[0][sizeof(expect[0]) - 1]; > + 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 = &expect[1][sizeof(expect[1]) - 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 char *const access_type = > + (r->access[i].type & KCSAN_ACCESS_ASSERT) ? > + ((r->access[i].type & KCSAN_ACCESS_WRITE) ? > + "assert no accesses" : > + "assert no writes") : > + ((r->access[i].type & KCSAN_ACCESS_WRITE) ? > + "write" : > + "read"); > + const char *const access_type_aux = > + (r->access[i].type & KCSAN_ACCESS_ATOMIC) ? > + " (marked)" : > + ((r->access[i].type & KCSAN_ACCESS_SCOPED) ? > + " (scoped)" : > + ""); > + > + 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. */ > + strcpy(cur, ""); > + 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); > + return ret; > +} > + > +/* ===== 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 DEFINE_SEQLOCK(test_seqlock); > + > +/* > + * Helper to avoid compiler optimizing out reads, and to generate source values > + * for writes. > + */ > +__no_kcsan > +static noinline void sink_value(long v) { WRITE_ONCE(test_sink, v); } > + > +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); > +} > + > +__no_kcsan > +static 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_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); > +} > + > +/* To check that scoped assertions do trigger anywhere in 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_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); > +} > + > +/* ===== Test cases ===== */ > + > +/* Simple test with normal data race. */ > +__no_kcsan > +static void test_basic(struct kunit *test) > +{ > + const struct expect_report expect = { > + .access = { > + { test_kernel_write, &test_var, sizeof(test_var), KCSAN_ACCESS_WRITE }, > + { test_kernel_read, &test_var, sizeof(test_var), 0 }, > + }, > + }; > + static const struct expect_report never = { > + .access = { > + { test_kernel_read, &test_var, sizeof(test_var), 0 }, > + { test_kernel_read, &test_var, sizeof(test_var), 0 }, > + }, > + }; > + bool match_expect = false; > + bool match_never = false; > + > + begin_test_checks(test_kernel_write, test_kernel_read); > + do { > + match_expect |= report_matches(&expect); > + match_never = report_matches(&never); > + } while (!end_test_checks(match_never)); > + KUNIT_EXPECT_TRUE(test, match_expect); > + KUNIT_EXPECT_FALSE(test, match_never); > +} > + > +/* > + * Stress KCSAN with lots of concurrent races on different addresses until > + * timeout. > + */ > +__no_kcsan > +static void test_concurrent_races(struct kunit *test) > +{ > + const struct expect_report expect = { > + .access = { > + /* NULL will match any address. */ > + { test_kernel_rmw_array, NULL, 0, KCSAN_ACCESS_WRITE }, > + { test_kernel_rmw_array, NULL, 0, 0 }, > + }, > + }; > + static const struct expect_report never = { > + .access = { > + { test_kernel_rmw_array, NULL, 0, 0 }, > + { test_kernel_rmw_array, NULL, 0, 0 }, > + }, > + }; > + bool match_expect = false; > + bool match_never = false; > + > + begin_test_checks(test_kernel_rmw_array, test_kernel_rmw_array); > + do { > + match_expect |= report_matches(&expect); > + match_never |= report_matches(&never); > + } while (!end_test_checks(false)); > + KUNIT_EXPECT_TRUE(test, match_expect); /* Sanity check matches exist. */ > + KUNIT_EXPECT_FALSE(test, match_never); > +} > + > +/* Test the KCSAN_REPORT_VALUE_CHANGE_ONLY option. */ > +__no_kcsan > +static void test_novalue_change(struct kunit *test) > +{ > + const struct expect_report expect = { > + .access = { > + { test_kernel_write_nochange, &test_var, sizeof(test_var), KCSAN_ACCESS_WRITE }, > + { test_kernel_read, &test_var, sizeof(test_var), 0 }, > + }, > + }; > + bool match_expect = false; > + > + begin_test_checks(test_kernel_write_nochange, test_kernel_read); > + do { > + match_expect = report_matches(&expect); > + } while (!end_test_checks(match_expect)); > + if (IS_ENABLED(CONFIG_KCSAN_REPORT_VALUE_CHANGE_ONLY)) > + KUNIT_EXPECT_FALSE(test, match_expect); > + else > + KUNIT_EXPECT_TRUE(test, match_expect); > +} > + > +/* > + * Test that the rules where the KCSAN_REPORT_VALUE_CHANGE_ONLY option should > + * never apply work. > + */ > +__no_kcsan > +static void test_novalue_change_exception(struct kunit *test) > +{ > + const struct expect_report expect = { > + .access = { > + { test_kernel_write_nochange_rcu, &test_var, sizeof(test_var), KCSAN_ACCESS_WRITE }, > + { test_kernel_read, &test_var, sizeof(test_var), 0 }, > + }, > + }; > + bool match_expect = false; > + > + begin_test_checks(test_kernel_write_nochange_rcu, test_kernel_read); > + do { > + match_expect = report_matches(&expect); > + } while (!end_test_checks(match_expect)); > + KUNIT_EXPECT_TRUE(test, match_expect); > +} > + > +/* Test that data races of unknown origin are reported. */ > +__no_kcsan > +static void test_unknown_origin(struct kunit *test) > +{ > + const struct expect_report expect = { > + .access = { > + { test_kernel_read, &test_var, sizeof(test_var), 0 }, > + { NULL }, > + }, > + }; > + bool match_expect = false; > + > + begin_test_checks(test_kernel_write_uninstrumented, test_kernel_read); > + do { > + match_expect = report_matches(&expect); > + } while (!end_test_checks(match_expect)); > + if (IS_ENABLED(CONFIG_KCSAN_REPORT_RACE_UNKNOWN_ORIGIN)) > + KUNIT_EXPECT_TRUE(test, match_expect); > + else > + KUNIT_EXPECT_FALSE(test, match_expect); > +} > + > +/* Test KCSAN_ASSUME_PLAIN_WRITES_ATOMIC if it is selected. */ > +__no_kcsan > +static void test_write_write_assume_atomic(struct kunit *test) > +{ > + const struct expect_report expect = { > + .access = { > + { test_kernel_write, &test_var, sizeof(test_var), KCSAN_ACCESS_WRITE }, > + { test_kernel_write, &test_var, sizeof(test_var), KCSAN_ACCESS_WRITE }, > + }, > + }; > + bool match_expect = false; > + > + begin_test_checks(test_kernel_write, test_kernel_write); > + do { > + sink_value(READ_ONCE(test_var)); /* induce value-change */ > + match_expect = report_matches(&expect); > + } while (!end_test_checks(match_expect)); > + if (IS_ENABLED(CONFIG_KCSAN_ASSUME_PLAIN_WRITES_ATOMIC)) > + KUNIT_EXPECT_FALSE(test, match_expect); > + else > + KUNIT_EXPECT_TRUE(test, match_expect); > +} > + > +/* > + * Test that data races with writes larger than word-size are always reported, > + * even if KCSAN_ASSUME_PLAIN_WRITES_ATOMIC is selected. > + */ > +__no_kcsan > +static void test_write_write_struct(struct kunit *test) > +{ > + const struct expect_report expect = { > + .access = { > + { test_kernel_write_struct, &test_struct, sizeof(test_struct), KCSAN_ACCESS_WRITE }, > + { test_kernel_write_struct, &test_struct, sizeof(test_struct), KCSAN_ACCESS_WRITE }, > + }, > + }; > + bool match_expect = false; > + > + begin_test_checks(test_kernel_write_struct, test_kernel_write_struct); > + do { > + match_expect = report_matches(&expect); > + } while (!end_test_checks(match_expect)); > + KUNIT_EXPECT_TRUE(test, match_expect); > +} > + > +/* > + * Test that data races where only one write is larger than word-size are always > + * reported, even if KCSAN_ASSUME_PLAIN_WRITES_ATOMIC is selected. > + */ > +__no_kcsan > +static void test_write_write_struct_part(struct kunit *test) > +{ > + const struct expect_report expect = { > + .access = { > + { test_kernel_write_struct, &test_struct, sizeof(test_struct), KCSAN_ACCESS_WRITE }, > + { test_kernel_write_struct_part, &test_struct.val[3], sizeof(test_struct.val[3]), KCSAN_ACCESS_WRITE }, > + }, > + }; > + bool match_expect = false; > + > + begin_test_checks(test_kernel_write_struct, test_kernel_write_struct_part); > + do { > + match_expect = report_matches(&expect); > + } while (!end_test_checks(match_expect)); > + KUNIT_EXPECT_TRUE(test, match_expect); > +} > + > +/* Test that races with atomic accesses never result in reports. */ > +__no_kcsan > +static void test_read_atomic_write_atomic(struct kunit *test) > +{ > + bool match_never = false; > + > + begin_test_checks(test_kernel_read_atomic, test_kernel_write_atomic); > + do { > + match_never = report_available(); > + } while (!end_test_checks(match_never)); > + KUNIT_EXPECT_FALSE(test, match_never); > +} > + > +/* Test that a race with an atomic and plain access result in reports. */ > +__no_kcsan > +static void test_read_plain_atomic_write(struct kunit *test) > +{ > + const struct expect_report expect = { > + .access = { > + { test_kernel_read, &test_var, sizeof(test_var), 0 }, > + { test_kernel_write_atomic, &test_var, sizeof(test_var), KCSAN_ACCESS_WRITE | KCSAN_ACCESS_ATOMIC }, > + }, > + }; > + bool match_expect = false; > + > + if (IS_ENABLED(CONFIG_KCSAN_IGNORE_ATOMICS)) > + return; > + > + begin_test_checks(test_kernel_read, test_kernel_write_atomic); > + do { > + match_expect = report_matches(&expect); > + } while (!end_test_checks(match_expect)); > + KUNIT_EXPECT_TRUE(test, match_expect); > +} > + > +/* Zero-sized accesses should never cause data race reports. */ > +__no_kcsan > +static void test_zero_size_access(struct kunit *test) > +{ > + const struct expect_report expect = { > + .access = { > + { test_kernel_write_struct, &test_struct, sizeof(test_struct), KCSAN_ACCESS_WRITE }, > + { test_kernel_write_struct, &test_struct, sizeof(test_struct), KCSAN_ACCESS_WRITE }, > + }, > + }; > + const struct expect_report never = { > + .access = { > + { test_kernel_write_struct, &test_struct, sizeof(test_struct), KCSAN_ACCESS_WRITE }, > + { test_kernel_read_struct_zero_size, &test_struct.val[3], 0, 0 }, > + }, > + }; > + bool match_expect = false; > + bool match_never = false; > + > + begin_test_checks(test_kernel_write_struct, test_kernel_read_struct_zero_size); > + do { > + match_expect |= report_matches(&expect); > + match_never = report_matches(&never); > + } while (!end_test_checks(match_never)); > + KUNIT_EXPECT_TRUE(test, match_expect); /* Sanity check. */ > + KUNIT_EXPECT_FALSE(test, match_never); > +} > + > +/* Test the data_race() macro. */ > +__no_kcsan > +static void test_data_race(struct kunit *test) > +{ > + bool match_never = false; > + > + begin_test_checks(test_kernel_data_race, test_kernel_data_race); > + do { > + match_never = report_available(); > + } while (!end_test_checks(match_never)); > + KUNIT_EXPECT_FALSE(test, match_never); > +} > + > +__no_kcsan > +static void test_assert_exclusive_writer(struct kunit *test) > +{ > + const struct expect_report expect = { > + .access = { > + { test_kernel_assert_writer, &test_var, sizeof(test_var), KCSAN_ACCESS_ASSERT }, > + { test_kernel_write_nochange, &test_var, sizeof(test_var), KCSAN_ACCESS_WRITE }, > + }, > + }; > + bool match_expect = false; > + > + begin_test_checks(test_kernel_assert_writer, test_kernel_write_nochange); > + do { > + match_expect = report_matches(&expect); > + } while (!end_test_checks(match_expect)); > + KUNIT_EXPECT_TRUE(test, match_expect); > +} > + > +__no_kcsan > +static void test_assert_exclusive_access(struct kunit *test) > +{ > + const struct expect_report expect = { > + .access = { > + { test_kernel_assert_access, &test_var, sizeof(test_var), KCSAN_ACCESS_ASSERT | KCSAN_ACCESS_WRITE }, > + { test_kernel_read, &test_var, sizeof(test_var), 0 }, > + }, > + }; > + bool match_expect = false; > + > + begin_test_checks(test_kernel_assert_access, test_kernel_read); > + do { > + match_expect = report_matches(&expect); > + } while (!end_test_checks(match_expect)); > + KUNIT_EXPECT_TRUE(test, match_expect); > +} > + > +__no_kcsan > +static void test_assert_exclusive_access_writer(struct kunit *test) > +{ > + const struct expect_report expect_access_writer = { > + .access = { > + { test_kernel_assert_access, &test_var, sizeof(test_var), KCSAN_ACCESS_ASSERT | KCSAN_ACCESS_WRITE }, > + { test_kernel_assert_writer, &test_var, sizeof(test_var), KCSAN_ACCESS_ASSERT }, > + }, > + }; > + const struct expect_report expect_access_access = { > + .access = { > + { test_kernel_assert_access, &test_var, sizeof(test_var), KCSAN_ACCESS_ASSERT | KCSAN_ACCESS_WRITE }, > + { test_kernel_assert_access, &test_var, sizeof(test_var), KCSAN_ACCESS_ASSERT | KCSAN_ACCESS_WRITE }, > + }, > + }; > + const struct expect_report never = { > + .access = { > + { test_kernel_assert_writer, &test_var, sizeof(test_var), KCSAN_ACCESS_ASSERT }, > + { test_kernel_assert_writer, &test_var, sizeof(test_var), KCSAN_ACCESS_ASSERT }, > + }, > + }; > + bool match_expect_access_writer = false; > + bool match_expect_access_access = false; > + bool match_never = false; > + > + begin_test_checks(test_kernel_assert_access, test_kernel_assert_writer); > + do { > + match_expect_access_writer |= report_matches(&expect_access_writer); > + match_expect_access_access |= report_matches(&expect_access_access); > + match_never |= report_matches(&never); > + } while (!end_test_checks(match_never)); > + KUNIT_EXPECT_TRUE(test, match_expect_access_writer); > + KUNIT_EXPECT_TRUE(test, match_expect_access_access); > + KUNIT_EXPECT_FALSE(test, match_never); > +} > + > +__no_kcsan > +static void test_assert_exclusive_bits_change(struct kunit *test) > +{ > + const struct expect_report expect = { > + .access = { > + { test_kernel_assert_bits_change, &test_var, sizeof(test_var), KCSAN_ACCESS_ASSERT }, > + { test_kernel_change_bits, &test_var, sizeof(test_var), > + KCSAN_ACCESS_WRITE | (IS_ENABLED(CONFIG_KCSAN_IGNORE_ATOMICS) ? 0 : KCSAN_ACCESS_ATOMIC) }, > + }, > + }; > + bool match_expect = false; > + > + begin_test_checks(test_kernel_assert_bits_change, test_kernel_change_bits); > + do { > + match_expect = report_matches(&expect); > + } while (!end_test_checks(match_expect)); > + KUNIT_EXPECT_TRUE(test, match_expect); > +} > + > +__no_kcsan > +static void test_assert_exclusive_bits_nochange(struct kunit *test) > +{ > + bool match_never = false; > + > + begin_test_checks(test_kernel_assert_bits_nochange, test_kernel_change_bits); > + do { > + match_never = report_available(); > + } while (!end_test_checks(match_never)); > + KUNIT_EXPECT_FALSE(test, match_never); > +} > + > +__no_kcsan > +static void test_assert_exclusive_writer_scoped(struct kunit *test) > +{ > + const struct expect_report expect_start = { > + .access = { > + { test_kernel_assert_writer_scoped, &test_var, sizeof(test_var), KCSAN_ACCESS_ASSERT | KCSAN_ACCESS_SCOPED }, > + { test_kernel_write_nochange, &test_var, sizeof(test_var), KCSAN_ACCESS_WRITE }, > + }, > + }; > + const struct expect_report expect_anywhere = { > + .access = { > + { test_enter_scope, &test_var, sizeof(test_var), KCSAN_ACCESS_ASSERT | KCSAN_ACCESS_SCOPED }, > + { test_kernel_write_nochange, &test_var, sizeof(test_var), KCSAN_ACCESS_WRITE }, > + }, > + }; > + bool match_expect_start = false; > + bool match_expect_anywhere = false; > + > + begin_test_checks(test_kernel_assert_writer_scoped, test_kernel_write_nochange); > + do { > + match_expect_start |= report_matches(&expect_start); > + match_expect_anywhere |= report_matches(&expect_anywhere); > + } while (!end_test_checks(match_expect_start && match_expect_anywhere)); > + KUNIT_EXPECT_TRUE(test, match_expect_start); > + KUNIT_EXPECT_TRUE(test, match_expect_anywhere); > +} > + > +__no_kcsan > +static void test_assert_exclusive_access_scoped(struct kunit *test) > +{ > + const struct expect_report expect_start1 = { > + .access = { > + { test_kernel_assert_access_scoped, &test_var, sizeof(test_var), KCSAN_ACCESS_ASSERT | KCSAN_ACCESS_WRITE | KCSAN_ACCESS_SCOPED }, > + { test_kernel_read, &test_var, sizeof(test_var), 0 }, > + }, > + }; > + const struct expect_report expect_start2 = { > + .access = { expect_start1.access[0], expect_start1.access[0] }, > + }; > + const struct expect_report expect_inscope = { > + .access = { > + { test_enter_scope, &test_var, sizeof(test_var), KCSAN_ACCESS_ASSERT | KCSAN_ACCESS_WRITE | KCSAN_ACCESS_SCOPED }, > + { test_kernel_read, &test_var, sizeof(test_var), 0 }, > + }, > + }; > + bool match_expect_start = false; > + bool match_expect_inscope = false; > + > + begin_test_checks(test_kernel_assert_access_scoped, test_kernel_read); > + end_time += msecs_to_jiffies(1000); /* This test requires a bit more time. */ > + do { > + match_expect_start |= report_matches(&expect_start1) || report_matches(&expect_start2); > + match_expect_inscope |= report_matches(&expect_inscope); > + } while (!end_test_checks(match_expect_start && match_expect_inscope)); > + KUNIT_EXPECT_TRUE(test, match_expect_start); > + KUNIT_EXPECT_TRUE(test, match_expect_inscope); > +} > + > +/* Test that racing accesses in seqlock critical sections are not reported. */ > +__no_kcsan > +static void test_seqlock_noreport(struct kunit *test) > +{ > + bool match_never = false; > + > + begin_test_checks(test_kernel_seqlock_reader, test_kernel_seqlock_writer); > + do { > + match_never = report_available(); > + } while (!end_test_checks(match_never)); > + KUNIT_EXPECT_FALSE(test, match_never); > +} > + > +/* > + * Each test case is run with different numbers of threads. Until KUnit supports > + * passing arguments for each test case, we encode #threads in the test case > + * name (read by get_num_threads()). [The '-' was chosen as a stylistic > + * preference to separate test name and #threads.] > + * > + * The thread counts are chosen to cover potentially interesting boundaries and > + * corner cases (range 2-5), and then stress the system with larger counts. > + */ > +#define KCSAN_KUNIT_CASE(test_name) \ > + { .run_case = test_name, .name = #test_name "-02" }, \ > + { .run_case = test_name, .name = #test_name "-03" }, \ > + { .run_case = test_name, .name = #test_name "-04" }, \ > + { .run_case = test_name, .name = #test_name "-05" }, \ > + { .run_case = test_name, .name = #test_name "-08" }, \ > + { .run_case = test_name, .name = #test_name "-16" } > + > +static struct kunit_case kcsan_test_cases[] = { > + KCSAN_KUNIT_CASE(test_basic), > + KCSAN_KUNIT_CASE(test_concurrent_races), > + KCSAN_KUNIT_CASE(test_novalue_change), > + KCSAN_KUNIT_CASE(test_novalue_change_exception), > + KCSAN_KUNIT_CASE(test_unknown_origin), > + KCSAN_KUNIT_CASE(test_write_write_assume_atomic), > + KCSAN_KUNIT_CASE(test_write_write_struct), > + KCSAN_KUNIT_CASE(test_write_write_struct_part), > + KCSAN_KUNIT_CASE(test_read_atomic_write_atomic), > + KCSAN_KUNIT_CASE(test_read_plain_atomic_write), > + KCSAN_KUNIT_CASE(test_zero_size_access), > + KCSAN_KUNIT_CASE(test_data_race), > + KCSAN_KUNIT_CASE(test_assert_exclusive_writer), > + KCSAN_KUNIT_CASE(test_assert_exclusive_access), > + KCSAN_KUNIT_CASE(test_assert_exclusive_access_writer), > + KCSAN_KUNIT_CASE(test_assert_exclusive_bits_change), > + KCSAN_KUNIT_CASE(test_assert_exclusive_bits_nochange), > + KCSAN_KUNIT_CASE(test_assert_exclusive_writer_scoped), > + KCSAN_KUNIT_CASE(test_assert_exclusive_access_scoped), > + KCSAN_KUNIT_CASE(test_seqlock_noreport), > + {}, > +}; > + > +/* ===== End test cases ===== */ > + > +/* Get number of threads encoded in test name. */ > +static bool __no_kcsan > +get_num_threads(const char *test, int *nthreads) > +{ > + int len = strlen(test); > + > + if (WARN_ON(len < 3)) > + return false; > + > + *nthreads = test[len - 1] - '0'; > + *nthreads += (test[len - 2] - '0') * 10; > + > + if (WARN_ON(*nthreads < 0)) > + return false; > + > + return true; > +} > + > +/* Concurrent accesses from interrupts. */ > +__no_kcsan > +static void access_thread_timer(struct timer_list *timer) > +{ > + static atomic_t cnt = ATOMIC_INIT(0); > + unsigned int idx; > + void (*func)(void); > + > + idx = (unsigned int)atomic_inc_return(&cnt) % ARRAY_SIZE(access_kernels); > + /* Acquire potential initialization. */ > + func = smp_load_acquire(&access_kernels[idx]); > + if (func) > + func(); > +} > + > +/* The main loop for each thread. */ > +__no_kcsan > +static int access_thread(void *arg) > +{ > + struct timer_list timer; > + unsigned int cnt = 0; > + unsigned int idx; > + void (*func)(void); > + > + timer_setup_on_stack(&timer, access_thread_timer, 0); > + do { > + if (!timer_pending(&timer)) > + mod_timer(&timer, jiffies + 1); > + else { > + /* Iterate through all kernels. */ > + idx = cnt++ % ARRAY_SIZE(access_kernels); > + /* Acquire potential initialization. */ > + func = smp_load_acquire(&access_kernels[idx]); > + if (func) > + func(); > + } > + } while (!torture_must_stop()); > + del_timer_sync(&timer); > + destroy_timer_on_stack(&timer); > + > + torture_kthread_stopping("access_thread"); > + return 0; > +} > + > +__no_kcsan > +static int test_init(struct kunit *test) > +{ > + unsigned long flags; > + int nthreads; > + int i; > + > + spin_lock_irqsave(&observed.lock, flags); > + for (i = 0; i < ARRAY_SIZE(observed.lines); ++i) > + observed.lines[i][0] = '\0'; > + observed.nlines = 0; > + spin_unlock_irqrestore(&observed.lock, flags); > + > + if (!torture_init_begin((char *)test->name, 1)) > + return -EBUSY; > + > + if (!get_num_threads(test->name, &nthreads)) > + goto err; > + > + if (WARN_ON(threads)) > + goto err; > + > + for (i = 0; i < ARRAY_SIZE(access_kernels); ++i) { > + if (WARN_ON(access_kernels[i])) > + goto err; > + } > + > + if (!IS_ENABLED(CONFIG_PREEMPT) && nthreads > num_online_cpus() - 2) { > + nthreads = num_online_cpus() - 2; > + pr_info("%s: limiting number of threads to %d\n", test->name, > + nthreads); > + } > + > + if (nthreads) { > + threads = kcalloc(nthreads + 1, sizeof(struct task_struct *), > + GFP_KERNEL); > + if (WARN_ON(!threads)) > + goto err; > + > + threads[nthreads] = NULL; > + for (i = 0; i < nthreads; ++i) { > + if (torture_create_kthread(access_thread, NULL, > + threads[i])) > + goto err; > + } > + } > + > + torture_init_end(); > + > + return 0; > + > +err: > + kfree(threads); > + threads = NULL; > + torture_init_end(); > + return -EINVAL; > +} > + > +__no_kcsan > +static void test_exit(struct kunit *test) > +{ > + struct task_struct **stop_thread; > + int i; > + > + if (torture_cleanup_begin()) > + return; > + > + for (i = 0; i < ARRAY_SIZE(access_kernels); ++i) > + WRITE_ONCE(access_kernels[i], NULL); > + > + if (threads) { > + for (stop_thread = threads; *stop_thread; stop_thread++) > + torture_stop_kthread(reader_thread, *stop_thread); > + > + kfree(threads); > + threads = NULL; > + } > + > + torture_cleanup_end(); > +} > + > +static struct kunit_suite kcsan_test_suite = { > + .name = "kcsan-test", > + .test_cases = kcsan_test_cases, > + .init = test_init, > + .exit = test_exit, > +}; > +static struct kunit_suite *kcsan_test_suites[] = { &kcsan_test_suite, NULL }; > + > +__no_kcsan > +static void register_tracepoints(struct tracepoint *tp, void *ignore) > +{ > + check_trace_callback_type_console(probe_console); > + if (!strcmp(tp->name, "console")) > + WARN_ON(tracepoint_probe_register(tp, probe_console, NULL)); > +} > + > +__no_kcsan > +static void unregister_tracepoints(struct tracepoint *tp, void *ignore) > +{ > + if (!strcmp(tp->name, "console")) > + tracepoint_probe_unregister(tp, probe_console, NULL); > +} > + > +/* > + * We only want to do tracepoints setup and teardown once, therefore we have to > + * customize the init and exit functions and cannot rely on kunit_test_suite(). > + */ > +static int __init kcsan_test_init(void) > +{ > + /* > + * Because we want to be able to build the test as a module, we need to > + * iterate through all known tracepoints, since the static registration > + * won't work here. > + */ > + for_each_kernel_tracepoint(register_tracepoints, NULL); > + return __kunit_test_suites_init(kcsan_test_suites); > +} > + > +static void kcsan_test_exit(void) > +{ > + __kunit_test_suites_exit(kcsan_test_suites); > + for_each_kernel_tracepoint(unregister_tracepoints, NULL); > + tracepoint_synchronize_unregister(); > +} > + > +late_initcall(kcsan_test_init); > +module_exit(kcsan_test_exit); > + > +MODULE_LICENSE("GPL v2"); > +MODULE_AUTHOR("Marco Elver "); > diff --git a/lib/Kconfig.kcsan b/lib/Kconfig.kcsan > index 689b6b81f272..ea28245c6c1d 100644 > --- a/lib/Kconfig.kcsan > +++ b/lib/Kconfig.kcsan > @@ -41,7 +41,28 @@ config KCSAN_SELFTEST > bool "Perform short selftests on boot" > default y > help > - Run KCSAN selftests on boot. On test failure, causes the kernel to panic. > + Run KCSAN selftests on boot. On test failure, causes the kernel to > + panic. Recommended to be enabled, ensuring critical functionality > + works as intended. > + > +config KCSAN_TEST > + tristate "KCSAN test for integrated runtime behaviour" > + depends on TRACEPOINTS && KUNIT > + select TORTURE_TEST > + help > + KCSAN test focusing on behaviour of the integrated runtime. Tests > + various race scenarios, and verifies the reports generated to > + console. Makes use of KUnit for test organization, and the Torture > + framework for test thread control. > + > + Each test case may run at least up to KCSAN_REPORT_ONCE_IN_MS > + milliseconds. Test run duration may be optimized by building the > + kernel and KCSAN test with KCSAN_REPORT_ONCE_IN_MS set to a lower > + than default value. > + > + Say Y here if you want the test to be built into the kernel and run > + during boot; say M if you want the test to build as a module; say N > + if you are unsure. > > config KCSAN_EARLY_ENABLE > bool "Early enable during boot" > -- > 2.26.2.303.gf8c07b1a785-goog >