kcsan: Introduce scoped ASSERT_EXCLUSIVE macros

Introduce ASSERT_EXCLUSIVE_*_SCOPED(), which provide an intuitive
interface to use the scoped-access feature, without having to explicitly
mark the start and end of the desired scope. Basing duration of the
checks on scope avoids accidental misuse and resulting false positives,
which may be hard to debug. See added comments for usage.

The macros are implemented using __attribute__((__cleanup__(func))),
which is supported by all compilers that currently support KCSAN.

Suggested-by: Boqun Feng <boqun.feng@gmail.com>
Suggested-by: Paul E. McKenney <paulmck@kernel.org>
Signed-off-by: Marco Elver <elver@google.com>
Signed-off-by: Paul E. McKenney <paulmck@kernel.org>
This commit is contained in:
Marco Elver 2020-03-25 17:41:58 +01:00 committed by Paul E. McKenney
parent 9967683ce5
commit d8949ef1d9
3 changed files with 89 additions and 3 deletions

View File

@ -238,7 +238,8 @@ are defined at the C-language level. The following macros can be used to check
properties of concurrent code where bugs would not manifest as data races.
.. kernel-doc:: include/linux/kcsan-checks.h
:functions: ASSERT_EXCLUSIVE_WRITER ASSERT_EXCLUSIVE_ACCESS
:functions: ASSERT_EXCLUSIVE_WRITER ASSERT_EXCLUSIVE_WRITER_SCOPED
ASSERT_EXCLUSIVE_ACCESS ASSERT_EXCLUSIVE_ACCESS_SCOPED
ASSERT_EXCLUSIVE_BITS
Implementation Details

View File

@ -234,11 +234,63 @@ static inline void kcsan_check_access(const volatile void *ptr, size_t size,
* ... = READ_ONCE(shared_foo);
* }
*
* Note: ASSERT_EXCLUSIVE_WRITER_SCOPED(), if applicable, performs more thorough
* checking if a clear scope where no concurrent writes are expected exists.
*
* @var: variable to assert on
*/
#define ASSERT_EXCLUSIVE_WRITER(var) \
__kcsan_check_access(&(var), sizeof(var), KCSAN_ACCESS_ASSERT)
/*
* Helper macros for implementation of for ASSERT_EXCLUSIVE_*_SCOPED(). @id is
* expected to be unique for the scope in which instances of kcsan_scoped_access
* are declared.
*/
#define __kcsan_scoped_name(c, suffix) __kcsan_scoped_##c##suffix
#define __ASSERT_EXCLUSIVE_SCOPED(var, type, id) \
struct kcsan_scoped_access __kcsan_scoped_name(id, _) \
__kcsan_cleanup_scoped; \
struct kcsan_scoped_access *__kcsan_scoped_name(id, _dummy_p) \
__maybe_unused = kcsan_begin_scoped_access( \
&(var), sizeof(var), KCSAN_ACCESS_SCOPED | (type), \
&__kcsan_scoped_name(id, _))
/**
* ASSERT_EXCLUSIVE_WRITER_SCOPED - assert no concurrent writes to @var in scope
*
* Scoped variant of ASSERT_EXCLUSIVE_WRITER().
*
* Assert that there are no concurrent writes to @var for the duration of the
* scope in which it is introduced. This provides a better way to fully cover
* the enclosing scope, compared to multiple ASSERT_EXCLUSIVE_WRITER(), and
* increases the likelihood for KCSAN to detect racing accesses.
*
* For example, it allows finding race-condition bugs that only occur due to
* state changes within the scope itself:
*
* .. code-block:: c
*
* void writer(void) {
* spin_lock(&update_foo_lock);
* {
* ASSERT_EXCLUSIVE_WRITER_SCOPED(shared_foo);
* WRITE_ONCE(shared_foo, 42);
* ...
* // shared_foo should still be 42 here!
* }
* spin_unlock(&update_foo_lock);
* }
* void buggy(void) {
* if (READ_ONCE(shared_foo) == 42)
* WRITE_ONCE(shared_foo, 1); // bug!
* }
*
* @var: variable to assert on
*/
#define ASSERT_EXCLUSIVE_WRITER_SCOPED(var) \
__ASSERT_EXCLUSIVE_SCOPED(var, KCSAN_ACCESS_ASSERT, __COUNTER__)
/**
* ASSERT_EXCLUSIVE_ACCESS - assert no concurrent accesses to @var
*
@ -258,6 +310,9 @@ static inline void kcsan_check_access(const volatile void *ptr, size_t size,
* release_for_reuse(obj);
* }
*
* Note: ASSERT_EXCLUSIVE_ACCESS_SCOPED(), if applicable, performs more thorough
* checking if a clear scope where no concurrent accesses are expected exists.
*
* Note: For cases where the object is freed, `KASAN <kasan.html>`_ is a better
* fit to detect use-after-free bugs.
*
@ -266,10 +321,26 @@ static inline void kcsan_check_access(const volatile void *ptr, size_t size,
#define ASSERT_EXCLUSIVE_ACCESS(var) \
__kcsan_check_access(&(var), sizeof(var), KCSAN_ACCESS_WRITE | KCSAN_ACCESS_ASSERT)
/**
* ASSERT_EXCLUSIVE_ACCESS_SCOPED - assert no concurrent accesses to @var in scope
*
* Scoped variant of ASSERT_EXCLUSIVE_ACCESS().
*
* Assert that there are no concurrent accesses to @var (no readers nor writers)
* for the entire duration of the scope in which it is introduced. This provides
* a better way to fully cover the enclosing scope, compared to multiple
* ASSERT_EXCLUSIVE_ACCESS(), and increases the likelihood for KCSAN to detect
* racing accesses.
*
* @var: variable to assert on
*/
#define ASSERT_EXCLUSIVE_ACCESS_SCOPED(var) \
__ASSERT_EXCLUSIVE_SCOPED(var, KCSAN_ACCESS_WRITE | KCSAN_ACCESS_ASSERT, __COUNTER__)
/**
* ASSERT_EXCLUSIVE_BITS - assert no concurrent writes to subset of bits in @var
*
* Bit-granular variant of ASSERT_EXCLUSIVE_WRITER(var).
* Bit-granular variant of ASSERT_EXCLUSIVE_WRITER().
*
* Assert that there are no concurrent writes to a subset of bits in @var;
* concurrent readers are permitted. This assertion captures more detailed

View File

@ -110,6 +110,7 @@ static noinline void microbenchmark(unsigned long iters)
*/
static long test_dummy;
static long test_flags;
static long test_scoped;
static noinline void test_thread(unsigned long iters)
{
const long CHANGE_BITS = 0xff00ff00ff00ff00L;
@ -120,7 +121,8 @@ static noinline void test_thread(unsigned long iters)
memset(&current->kcsan_ctx, 0, sizeof(current->kcsan_ctx));
pr_info("KCSAN: %s begin | iters: %lu\n", __func__, iters);
pr_info("test_dummy@%px, test_flags@%px\n", &test_dummy, &test_flags);
pr_info("test_dummy@%px, test_flags@%px, test_scoped@%px,\n",
&test_dummy, &test_flags, &test_scoped);
cycles = get_cycles();
while (iters--) {
@ -141,6 +143,18 @@ static noinline void test_thread(unsigned long iters)
test_flags ^= CHANGE_BITS; /* generate value-change */
__kcsan_check_write(&test_flags, sizeof(test_flags));
BUG_ON(current->kcsan_ctx.scoped_accesses.prev);
{
/* Should generate reports anywhere in this block. */
ASSERT_EXCLUSIVE_WRITER_SCOPED(test_scoped);
ASSERT_EXCLUSIVE_ACCESS_SCOPED(test_scoped);
BUG_ON(!current->kcsan_ctx.scoped_accesses.prev);
/* Unrelated accesses. */
__kcsan_check_access(&cycles, sizeof(cycles), 0);
__kcsan_check_access(&cycles, sizeof(cycles), KCSAN_ACCESS_ATOMIC);
}
BUG_ON(current->kcsan_ctx.scoped_accesses.prev);
}
cycles = get_cycles() - cycles;