[PATCH v3 00/35] Compiler-Based Capability- and Locking-Analysis

0 views
Skip to first unread message

Marco Elver

unread,
Sep 18, 2025, 10:05:30 AM (6 days ago) Sep 18
to el...@google.com, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
Capability analysis is a C language extension, which enables statically
checking that user-definable "capabilities" are acquired and released where
required. An obvious application is lock-safety checking for the kernel's
various synchronization primitives (each of which represents a "capability"),
and checking that locking rules are not violated.

Clang originally called the feature "Thread Safety Analysis" [1], with
some terminology still using the thread-safety-analysis-only names. This
was later changed and the feature became more flexible, gaining the
ability to define custom "capabilities". Its foundations can be found in
"capability systems" [2], used to specify the permissibility of
operations to depend on some capability being held (or not held).

Because the feature is not just able to express capabilities related to
synchronization primitives, the naming chosen for the kernel departs
from Clang's initial "Thread Safety" nomenclature and refers to the
feature as "Capability Analysis" to avoid confusion. The implementation
still makes references to the older terminology in some places, such as
`-Wthread-safety` being the warning enabled option that also still
appears in diagnostic messages.

Enabling capability analysis can be seen as enabling a dialect of Linux
C with a Capability System.

Additional details can be found in the added kernel-doc documentation.
An LWN article covered v2 of the series: https://lwn.net/Articles/1012990/

[1] https://clang.llvm.org/docs/ThreadSafetyAnalysis.html
[2] https://www.cs.cornell.edu/talc/papers/capabilities.pdf

=== Development Approach ===

Prior art exists in the form of Sparse's context tracking. Locking
annotations on functions exist, so the concept of analyzing locking rules
is not foreign to the kernel's codebase.

However, Clang's analysis is more complete vs. Sparse's, with the
typical trade-offs in static analysis: improved completeness is
sacrificed for more possible false positives or additional annotations
required by the programmer. Numerous options exist to disable or opt out
certain code from analysis.

This series initially aimed to retain compatibility with Sparse, which
can provide tree-wide analysis of a subset of the capability analysis
introduced, but it was later decided to drop Sparse compatibility. For
the most part, the new (and old) keywords used for annotations remain
the same, and many of the pre-existing annotations remain valid.

One big question is how to enable this feature, given we end up with a
new dialect of C -- 2 approaches have been considered:

A. Tree-wide all-or-nothing approach. This approach requires tree-wide
changes, adding annotations or selective opt-outs. Making additional
primitives capability-enabled increases churn, esp. where maintainers
are unaware of the feature's existence and how to use it.

Because we can't change the programming language (even if from one C
dialect to another) of the kernel overnight, a different approach might
cause less friction.

B. A selective, incremental, and much less intrusive approach.
Maintainers of subsystems opt in their modules or directories into
"capability analysis" (via Makefile):

CAPABILITY_ANALYSIS_foo.o := y # foo.o only
CAPABILITY_ANALYSIS := y # all TUs

Most (eventually all) synchronization primitives and more
capabilities (including ones that could track "irq disabled",
"preemption" disabled, etc.) could be supported.

The approach taken by this series is B. This ensures that only
subsystems where maintainers are willing to deal with any warnings are
opted-in. Introducing the feature can be done incrementally, without
large tree-wide changes and adding numerous opt-outs and annotations to
the majority of code.

Note: Bart Van Assche concurrently worked on enabling -Wthread-safety:
https://lore.kernel.org/all/20250206175114.19...@acm.org/
Bart's work has shown what it might take to go with approach A
(tree-wide, restricted to 'mutex' usage). This has shown that the
analysis finds real issues when applied to enough subsystems! We hope
this serves as motivation to eventually enable the analysis in as many
subsystems as possible, particularly subsystems that are not as easily
tested by CI systems and test robots.

=== Initial Uses ===

With this initial series, the following synchronization primitives are
supported: `raw_spinlock_t`, `spinlock_t`, `rwlock_t`, `mutex`,
`seqlock_t`, `bit_spinlock`, RCU, SRCU (`srcu_struct`), `rw_semaphore`,
`local_lock_t`, `ww_mutex`.

To demonstrate use of the feature on real kernel code, the series also
enables capability analysis for the following subsystems:

* kernel/kcov
* kernel/kcsan
* kernel/sched/
* lib/rhashtable
* lib/stackdepot
* mm/kfence
* security/tomoyo
* crypto/

The initial benefits are static detection of violations of locking
rules. As more capabilities are added, we would see more static checking
beyond what regular C can provide, all while remaining easy (read quick)
to use via the Clang compiler.

Note: The kernel already provides dynamic analysis tools Lockdep and
KCSAN for lock-safety checking and data-race detection respectively.
Unlike those, Clang's capability analysis is a compile-time static
analysis with no runtime impact. The static analysis complements
existing dynamic analysis tools, as it may catch some issues before
even getting into a running kernel, but is *not* a replacement for
whole-kernel testing with the dynamic analysis tools enabled!

=== Appendix ===

A Clang version that supports `-Wthread-safety-pointer` and the new
alias-analysis of capability pointers is required (from this version
onwards):

https://github.com/llvm/llvm-project/commit/b4c98fcbe1504841203e610c351a3227f36c92a4 [3]

This series is also available at this Git tree:

https://git.kernel.org/pub/scm/linux/kernel/git/melver/linux.git/log/?h=cap-analysis/dev

=== Changelog ===

v3:

- Bump min. Clang version to 22+ (unreleased), which now supports:

* re-entrancy via __attribute__((reentrant_capability));
* basic form of capability alias analysis [3] - which is the
biggest improvement since v2.

This was the result of conclusions from this discussion:
https://lore.kernel.org/all/CANpmjNPquO=W1JAh1FNQb8pMQjgeZAKCPQUAd7qUg=5pjJ6x=Q...@mail.gmail.com/

- Rename __asserts_cap/__assert_cap to __assumes_cap/__assume_cap.

- Switch to DECLARE_LOCK_GUARD_1_ATTRS().

- Add __acquire_ret and __acquire_shared_ret helper macros - can be
used to define function-like macros that return objects which
contains a held capabilities. Works now because of capability alias
analysis.

- Add capability_unsafe_alias() helper, where the analysis rightfully
points out we're doing strange things with aliases but we don't
care.

- Support multi-argument attributes.

- Enable for kernel/sched/{core,fair}.c, kernel/kcsan.
- Drop drivers/tty changes (revisit later).

v2: https://lore.kernel.org/all/20250304092417....@google.com/

- Remove Sparse context tracking support - after the introduction of
Clang support, so that backports can skip removal of Sparse support.

- Remove __cond_lock() function-like helper.

- ww_mutex support.

- -Wthread-safety-addressof was reworked and committed in upstream
Clang as -Wthread-safety-pointer.

- Make __cond_acquires() and __cond_acquires_shared() take abstract
value, since compiler only cares about zero and non-zero.

- Rename __var_guarded_by to simply __guarded_by. Initially the idea
was to be explicit about if the variable itself or the pointed-to
data is guarded, but in the long-term, making this shorter might be
better.

- Likewise rename __ref_guarded_by to __pt_guarded_by.

- Introduce common header warning suppressions - this is a better
solution than guarding header inclusions with disable_ +
enable_capability_analysis(). Header suppressions are disabled when
selecting CONFIG_WARN_CAPABILITY_ANALYSIS_ALL=y. This bumps the
minimum Clang version required to 20+.

- Make the data_race() macro imply disabled capability analysis.
Writing capability_unsafe(data_race(..)) is unnecessarily verbose
and data_race() on its own already indicates something subtly unsafe
is happening. This change was made after analysis of a finding in
security/tomoyo.

- Enable analysis in the following subsystems as additional examples
of larger subsystem. Where it was obvious, the __guarded_by
attribute was added to lock-guarded variables to improve coverage.

* drivers/tty
* security/tomoyo
* crypto/

RFC v1: https://lore.kernel.org/lkml/20250206181711....@google.com

Marco Elver (35):
compiler_types: Move lock checking attributes to
compiler-capability-analysis.h
compiler-capability-analysis: Add infrastructure for Clang's
capability analysis
compiler-capability-analysis: Add test stub
Documentation: Add documentation for Compiler-Based Capability
Analysis
checkpatch: Warn about capability_unsafe() without comment
cleanup: Basic compatibility with capability analysis
lockdep: Annotate lockdep assertions for capability analysis
locking/rwlock, spinlock: Support Clang's capability analysis
compiler-capability-analysis: Change __cond_acquires to take return
value
locking/mutex: Support Clang's capability analysis
locking/seqlock: Support Clang's capability analysis
bit_spinlock: Include missing <asm/processor.h>
bit_spinlock: Support Clang's capability analysis
rcu: Support Clang's capability analysis
srcu: Support Clang's capability analysis
kref: Add capability-analysis annotations
locking/rwsem: Support Clang's capability analysis
locking/local_lock: Include missing headers
locking/local_lock: Support Clang's capability analysis
locking/ww_mutex: Support Clang's capability analysis
debugfs: Make debugfs_cancellation a capability struct
compiler-capability-analysis: Remove Sparse support
compiler-capability-analysis: Remove __cond_lock() function-like
helper
compiler-capability-analysis: Introduce header suppressions
compiler: Let data_race() imply disabled capability analysis
MAINTAINERS: Add entry for Capability Analysis
kfence: Enable capability analysis
kcov: Enable capability analysis
kcsan: Enable capability analysis
stackdepot: Enable capability analysis
rhashtable: Enable capability analysis
printk: Move locking annotation to printk.c
security/tomoyo: Enable capability analysis
crypto: Enable capability analysis
sched: Enable capability analysis for core.c and fair.c

.../dev-tools/capability-analysis.rst | 148 +++++
Documentation/dev-tools/index.rst | 1 +
Documentation/dev-tools/sparse.rst | 19 -
Documentation/mm/process_addrs.rst | 6 +-
MAINTAINERS | 11 +
Makefile | 1 +
crypto/Makefile | 2 +
crypto/acompress.c | 6 +-
crypto/algapi.c | 2 +
crypto/api.c | 1 +
crypto/crypto_engine.c | 2 +-
crypto/drbg.c | 5 +
crypto/internal.h | 2 +-
crypto/proc.c | 3 +
crypto/scompress.c | 24 +-
.../net/wireless/intel/iwlwifi/iwl-trans.c | 4 +-
.../net/wireless/intel/iwlwifi/iwl-trans.h | 6 +-
.../intel/iwlwifi/pcie/gen1_2/internal.h | 5 +-
.../intel/iwlwifi/pcie/gen1_2/trans.c | 4 +-
fs/dlm/lock.c | 2 +-
include/crypto/internal/acompress.h | 7 +-
include/crypto/internal/engine.h | 2 +-
include/linux/bit_spinlock.h | 24 +-
include/linux/cleanup.h | 17 +
include/linux/compiler-capability-analysis.h | 423 +++++++++++++
include/linux/compiler.h | 2 +
include/linux/compiler_types.h | 18 +-
include/linux/console.h | 4 +-
include/linux/debugfs.h | 12 +-
include/linux/kref.h | 2 +
include/linux/list_bl.h | 2 +
include/linux/local_lock.h | 45 +-
include/linux/local_lock_internal.h | 73 ++-
include/linux/lockdep.h | 12 +-
include/linux/mm.h | 33 +-
include/linux/mutex.h | 35 +-
include/linux/mutex_types.h | 4 +-
include/linux/rcupdate.h | 86 +--
include/linux/refcount.h | 6 +-
include/linux/rhashtable.h | 14 +-
include/linux/rwlock.h | 22 +-
include/linux/rwlock_api_smp.h | 43 +-
include/linux/rwlock_rt.h | 44 +-
include/linux/rwlock_types.h | 10 +-
include/linux/rwsem.h | 66 +-
include/linux/sched.h | 6 +-
include/linux/sched/signal.h | 16 +-
include/linux/sched/task.h | 5 +-
include/linux/sched/wake_q.h | 3 +
include/linux/seqlock.h | 24 +
include/linux/seqlock_types.h | 5 +-
include/linux/spinlock.h | 89 ++-
include/linux/spinlock_api_smp.h | 34 +-
include/linux/spinlock_api_up.h | 112 +++-
include/linux/spinlock_rt.h | 37 +-
include/linux/spinlock_types.h | 10 +-
include/linux/spinlock_types_raw.h | 5 +-
include/linux/srcu.h | 60 +-
include/linux/srcutiny.h | 4 +
include/linux/srcutree.h | 6 +-
include/linux/ww_mutex.h | 22 +-
kernel/Makefile | 2 +
kernel/kcov.c | 36 +-
kernel/kcsan/Makefile | 2 +
kernel/kcsan/report.c | 11 +-
kernel/printk/printk.c | 2 +
kernel/sched/Makefile | 3 +
kernel/sched/core.c | 89 ++-
kernel/sched/fair.c | 9 +-
kernel/sched/sched.h | 110 +++-
kernel/signal.c | 4 +-
kernel/time/posix-timers.c | 13 +-
lib/Kconfig.debug | 45 ++
lib/Makefile | 6 +
lib/dec_and_lock.c | 8 +-
lib/rhashtable.c | 5 +-
lib/stackdepot.c | 20 +-
lib/test_capability-analysis.c | 596 ++++++++++++++++++
mm/kfence/Makefile | 2 +
mm/kfence/core.c | 20 +-
mm/kfence/kfence.h | 14 +-
mm/kfence/report.c | 4 +-
mm/memory.c | 4 +-
mm/pgtable-generic.c | 19 +-
net/ipv4/tcp_sigpool.c | 2 +-
scripts/Makefile.capability-analysis | 11 +
scripts/Makefile.lib | 10 +
scripts/capability-analysis-suppression.txt | 33 +
scripts/checkpatch.pl | 8 +
security/tomoyo/Makefile | 2 +
security/tomoyo/common.c | 52 +-
security/tomoyo/common.h | 77 +--
security/tomoyo/domain.c | 1 +
security/tomoyo/environ.c | 1 +
security/tomoyo/file.c | 5 +
security/tomoyo/gc.c | 28 +-
security/tomoyo/mount.c | 2 +
security/tomoyo/network.c | 3 +
tools/include/linux/compiler_types.h | 2 -
99 files changed, 2370 insertions(+), 589 deletions(-)
create mode 100644 Documentation/dev-tools/capability-analysis.rst
create mode 100644 include/linux/compiler-capability-analysis.h
create mode 100644 lib/test_capability-analysis.c
create mode 100644 scripts/Makefile.capability-analysis
create mode 100644 scripts/capability-analysis-suppression.txt

--
2.51.0.384.g4c02a37b29-goog

Marco Elver

unread,
Sep 18, 2025, 10:05:33 AM (6 days ago) Sep 18
to el...@google.com, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
The conditional definition of lock checking macros and attributes is
about to become more complex. Factor them out into their own header for
better readability, and to make it obvious which features are supported
by which mode (currently only Sparse). This is the first step towards
generalizing towards "capability analysis".

No functional change intended.

Signed-off-by: Marco Elver <el...@google.com>
Reviewed-by: Bart Van Assche <bvana...@acm.org>
---
include/linux/compiler-capability-analysis.h | 32 ++++++++++++++++++++
include/linux/compiler_types.h | 18 ++---------
2 files changed, 34 insertions(+), 16 deletions(-)
create mode 100644 include/linux/compiler-capability-analysis.h

diff --git a/include/linux/compiler-capability-analysis.h b/include/linux/compiler-capability-analysis.h
new file mode 100644
index 000000000000..7546ddb83f86
--- /dev/null
+++ b/include/linux/compiler-capability-analysis.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Macros and attributes for compiler-based static capability analysis.
+ */
+
+#ifndef _LINUX_COMPILER_CAPABILITY_ANALYSIS_H
+#define _LINUX_COMPILER_CAPABILITY_ANALYSIS_H
+
+#ifdef __CHECKER__
+
+/* Sparse context/lock checking support. */
+# define __must_hold(x) __attribute__((context(x,1,1)))
+# define __acquires(x) __attribute__((context(x,0,1)))
+# define __cond_acquires(x) __attribute__((context(x,0,-1)))
+# define __releases(x) __attribute__((context(x,1,0)))
+# define __acquire(x) __context__(x,1)
+# define __release(x) __context__(x,-1)
+# define __cond_lock(x, c) ((c) ? ({ __acquire(x); 1; }) : 0)
+
+#else /* !__CHECKER__ */
+
+# define __must_hold(x)
+# define __acquires(x)
+# define __cond_acquires(x)
+# define __releases(x)
+# define __acquire(x) (void)0
+# define __release(x) (void)0
+# define __cond_lock(x, c) (c)
+
+#endif /* __CHECKER__ */
+
+#endif /* _LINUX_COMPILER_CAPABILITY_ANALYSIS_H */
diff --git a/include/linux/compiler_types.h b/include/linux/compiler_types.h
index 16755431fc11..c24e60e75f36 100644
--- a/include/linux/compiler_types.h
+++ b/include/linux/compiler_types.h
@@ -24,6 +24,8 @@
# define BTF_TYPE_TAG(value) /* nothing */
#endif

+#include <linux/compiler-capability-analysis.h>
+
/* sparse defines __CHECKER__; see Documentation/dev-tools/sparse.rst */
#ifdef __CHECKER__
/* address spaces */
@@ -34,14 +36,6 @@
# define __rcu __attribute__((noderef, address_space(__rcu)))
static inline void __chk_user_ptr(const volatile void __user *ptr) { }
static inline void __chk_io_ptr(const volatile void __iomem *ptr) { }
-/* context/locking */
-# define __must_hold(x) __attribute__((context(x,1,1)))
-# define __acquires(x) __attribute__((context(x,0,1)))
-# define __cond_acquires(x) __attribute__((context(x,0,-1)))
-# define __releases(x) __attribute__((context(x,1,0)))
-# define __acquire(x) __context__(x,1)
-# define __release(x) __context__(x,-1)
-# define __cond_lock(x,c) ((c) ? ({ __acquire(x); 1; }) : 0)
/* other */
# define __force __attribute__((force))
# define __nocast __attribute__((nocast))
@@ -62,14 +56,6 @@ static inline void __chk_io_ptr(const volatile void __iomem *ptr) { }

# define __chk_user_ptr(x) (void)0
# define __chk_io_ptr(x) (void)0
-/* context/locking */
-# define __must_hold(x)
-# define __acquires(x)
-# define __cond_acquires(x)
-# define __releases(x)
-# define __acquire(x) (void)0
-# define __release(x) (void)0
-# define __cond_lock(x,c) (c)
/* other */
# define __force
# define __nocast
--
2.51.0.384.g4c02a37b29-goog

Marco Elver

unread,
Sep 18, 2025, 10:05:36 AM (6 days ago) Sep 18
to el...@google.com, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
Capability analysis is a C language extension, which enables statically
checking that user-definable "capabilities" are acquired and released where
required. An obvious application is lock-safety checking for the kernel's
various synchronization primitives (each of which represents a "capability"),
and checking that locking rules are not violated.

Clang originally called the feature "Thread Safety Analysis" [1], with
some terminology still using the thread-safety-analysis-only names. This
was later changed and the feature became more flexible, gaining the
ability to define custom "capabilities". Its foundations can be found in
"capability systems", used to specify the permissibility of operations
to depend on some capability being held (or not held).

Because the feature is not just able to express capabilities related to
synchronization primitives, the naming chosen for the kernel departs
from Clang's initial "Thread Safety" nomenclature and refers to the
feature as "Capability Analysis" to avoid confusion. The implementation
still makes references to the older terminology in some places, such as
`-Wthread-safety` being the warning enabled option that also still
appears in diagnostic messages.

See more details in the kernel-doc documentation added in this and the
subsequent changes.

Clang version 22+ is required.

Signed-off-by: Marco Elver <el...@google.com>
---
v3:
* Require Clang 22 or later (reentrant capabilities, basic alias analysis).
* Rename __assert_cap/__asserts_cap -> __assume_cap/__assumes_cap (suggested by Peter).
* Add __acquire_ret and __acquire_shared_ret helper macros - can be used
to define function-like macros that return objects which contains a
held capabilities. Works now because of capability alias analysis.
* Add capability_unsafe_alias() helper, where the analysis rightfully
points out we're doing strange things with aliases but we don't care.
* Support multi-argument attributes.

v2:
* New -Wthread-safety feature rename to -Wthread-safety-pointer (was
-Wthread-safety-addressof).
* Introduce __capability_unsafe() function attribute.
* Rename __var_guarded_by to simply __guarded_by. The initial idea was
to be explicit if the variable or pointed-to data is guarded by, but
having a shorter attribute name is likely better long-term.
* Rename __ref_guarded_by to __pt_guarded_by (pointed-to guarded by).
---
Makefile | 1 +
include/linux/compiler-capability-analysis.h | 449 ++++++++++++++++++-
lib/Kconfig.debug | 31 ++
scripts/Makefile.capability-analysis | 7 +
scripts/Makefile.lib | 10 +
5 files changed, 491 insertions(+), 7 deletions(-)
create mode 100644 scripts/Makefile.capability-analysis

diff --git a/Makefile b/Makefile
index cf37b9407821..2c91730e513b 100644
--- a/Makefile
+++ b/Makefile
@@ -1096,6 +1096,7 @@ include-$(CONFIG_RANDSTRUCT) += scripts/Makefile.randstruct
include-$(CONFIG_KSTACK_ERASE) += scripts/Makefile.kstack_erase
include-$(CONFIG_AUTOFDO_CLANG) += scripts/Makefile.autofdo
include-$(CONFIG_PROPELLER_CLANG) += scripts/Makefile.propeller
+include-$(CONFIG_WARN_CAPABILITY_ANALYSIS) += scripts/Makefile.capability-analysis
include-$(CONFIG_GCC_PLUGINS) += scripts/Makefile.gcc-plugins

include $(addprefix $(srctree)/, $(include-y))
diff --git a/include/linux/compiler-capability-analysis.h b/include/linux/compiler-capability-analysis.h
index 7546ddb83f86..6f3f185478bc 100644
--- a/include/linux/compiler-capability-analysis.h
+++ b/include/linux/compiler-capability-analysis.h
@@ -6,27 +6,462 @@
#ifndef _LINUX_COMPILER_CAPABILITY_ANALYSIS_H
#define _LINUX_COMPILER_CAPABILITY_ANALYSIS_H

+#if defined(WARN_CAPABILITY_ANALYSIS)
+
+/*
+ * The below attributes are used to define new capability types. Internal only.
+ */
+# define __cap_type(name) __attribute__((capability(#name)))
+# define __reentrant_cap __attribute__((reentrant_capability))
+# define __acquires_cap(...) __attribute__((acquire_capability(__VA_ARGS__)))
+# define __acquires_shared_cap(...) __attribute__((acquire_shared_capability(__VA_ARGS__)))
+# define __try_acquires_cap(ret, var) __attribute__((try_acquire_capability(ret, var)))
+# define __try_acquires_shared_cap(ret, var) __attribute__((try_acquire_shared_capability(ret, var)))
+# define __releases_cap(...) __attribute__((release_capability(__VA_ARGS__)))
+# define __releases_shared_cap(...) __attribute__((release_shared_capability(__VA_ARGS__)))
+# define __assumes_cap(...) __attribute__((assert_capability(__VA_ARGS__)))
+# define __assumes_shared_cap(...) __attribute__((assert_shared_capability(__VA_ARGS__)))
+# define __returns_cap(var) __attribute__((lock_returned(var)))
+
+/*
+ * The below are used to annotate code being checked. Internal only.
+ */
+# define __excludes_cap(...) __attribute__((locks_excluded(__VA_ARGS__)))
+# define __requires_cap(...) __attribute__((requires_capability(__VA_ARGS__)))
+# define __requires_shared_cap(...) __attribute__((requires_shared_capability(__VA_ARGS__)))
+
+/**
+ * __guarded_by - struct member and globals attribute, declares variable
+ * protected by capability
+ *
+ * Declares that the struct member or global variable must be guarded by the
+ * given capabilities. Read operations on the data require shared access,
+ * while write operations require exclusive access.
+ *
+ * .. code-block:: c
+ *
+ * struct some_state {
+ * spinlock_t lock;
+ * long counter __guarded_by(&lock);
+ * };
+ */
+# define __guarded_by(...) __attribute__((guarded_by(__VA_ARGS__)))
+
+/**
+ * __pt_guarded_by - struct member and globals attribute, declares pointed-to
+ * data is protected by capability
+ *
+ * Declares that the data pointed to by the struct member pointer or global
+ * pointer must be guarded by the given capabilities. Read operations on the
+ * data require shared access, while write operations require exclusive access.
+ *
+ * .. code-block:: c
+ *
+ * struct some_state {
+ * spinlock_t lock;
+ * long *counter __pt_guarded_by(&lock);
+ * };
+ */
+# define __pt_guarded_by(...) __attribute__((pt_guarded_by(__VA_ARGS__)))
+
+/**
+ * struct_with_capability() - declare or define a capability struct
+ * @name: struct name
+ *
+ * Helper to declare or define a struct type with capability of the same name.
+ *
+ * .. code-block:: c
+ *
+ * struct_with_capability(my_handle) {
+ * int foo;
+ * long bar;
+ * };
+ *
+ * struct some_state {
+ * ...
+ * };
+ * // ... declared elsewhere ...
+ * struct_with_capability(some_state);
+ *
+ * Note: The implementation defines several helper functions that can acquire,
+ * release, and assert the capability.
+ */
+# define struct_with_capability(name, ...) \
+ struct __cap_type(name) __VA_ARGS__ name; \
+ static __always_inline void __acquire_cap(const struct name *var) \
+ __attribute__((overloadable)) __no_capability_analysis __acquires_cap(var) { } \
+ static __always_inline void __acquire_shared_cap(const struct name *var) \
+ __attribute__((overloadable)) __no_capability_analysis __acquires_shared_cap(var) { } \
+ static __always_inline bool __try_acquire_cap(const struct name *var, bool ret) \
+ __attribute__((overloadable)) __no_capability_analysis __try_acquires_cap(1, var) \
+ { return ret; } \
+ static __always_inline bool __try_acquire_shared_cap(const struct name *var, bool ret) \
+ __attribute__((overloadable)) __no_capability_analysis __try_acquires_shared_cap(1, var) \
+ { return ret; } \
+ static __always_inline void __release_cap(const struct name *var) \
+ __attribute__((overloadable)) __no_capability_analysis __releases_cap(var) { } \
+ static __always_inline void __release_shared_cap(const struct name *var) \
+ __attribute__((overloadable)) __no_capability_analysis __releases_shared_cap(var) { } \
+ static __always_inline void __assume_cap(const struct name *var) \
+ __attribute__((overloadable)) __assumes_cap(var) { } \
+ static __always_inline void __assume_shared_cap(const struct name *var) \
+ __attribute__((overloadable)) __assumes_shared_cap(var) { } \
+ struct name
+
+/**
+ * disable_capability_analysis() - disables capability analysis
+ *
+ * Disables capability analysis. Must be paired with a later
+ * enable_capability_analysis().
+ */
+# define disable_capability_analysis() \
+ __diag_push(); \
+ __diag_ignore_all("-Wunknown-warning-option", "") \
+ __diag_ignore_all("-Wthread-safety", "") \
+ __diag_ignore_all("-Wthread-safety-pointer", "")
+
+/**
+ * enable_capability_analysis() - re-enables capability analysis
+ *
+ * Re-enables capability analysis. Must be paired with a prior
+ * disable_capability_analysis().
+ */
+# define enable_capability_analysis() __diag_pop()
+
+/**
+ * __no_capability_analysis - function attribute, disables capability analysis
+ *
+ * Function attribute denoting that capability analysis is disabled for the
+ * whole function. Prefer use of `capability_unsafe()` where possible.
+ */
+# define __no_capability_analysis __attribute__((no_thread_safety_analysis))
+
+#else /* !WARN_CAPABILITY_ANALYSIS */
+
+# define __cap_type(name)
+# define __reentrant_cap
+# define __acquires_cap(...)
+# define __acquires_shared_cap(...)
+# define __try_acquires_cap(ret, var)
+# define __try_acquires_shared_cap(ret, var)
+# define __releases_cap(...)
+# define __releases_shared_cap(...)
+# define __assumes_cap(...)
+# define __assumes_shared_cap(...)
+# define __returns_cap(var)
+# define __guarded_by(...)
+# define __pt_guarded_by(...)
+# define __excludes_cap(...)
+# define __requires_cap(...)
+# define __requires_shared_cap(...)
+# define __acquire_cap(var) do { } while (0)
+# define __acquire_shared_cap(var) do { } while (0)
+# define __try_acquire_cap(var, ret) (ret)
+# define __try_acquire_shared_cap(var, ret) (ret)
+# define __release_cap(var) do { } while (0)
+# define __release_shared_cap(var) do { } while (0)
+# define __assume_cap(var) do { (void)(var); } while (0)
+# define __assume_shared_cap(var) do { (void)(var); } while (0)
+# define struct_with_capability(name, ...) struct __VA_ARGS__ name
+# define disable_capability_analysis()
+# define enable_capability_analysis()
+# define __no_capability_analysis
+
+#endif /* WARN_CAPABILITY_ANALYSIS */
+
+/**
+ * capability_unsafe() - disable capability checking for contained code
+ *
+ * Disables capability checking for contained statements or expression.
+ *
+ * .. code-block:: c
+ *
+ * struct some_data {
+ * spinlock_t lock;
+ * int counter __guarded_by(&lock);
+ * };
+ *
+ * int foo(struct some_data *d)
+ * {
+ * // ...
+ * // other code that is still checked ...
+ * // ...
+ * return capability_unsafe(d->counter);
+ * }
+ */
+#define capability_unsafe(...) \
+({ \
+ disable_capability_analysis(); \
+ __VA_ARGS__; \
+ enable_capability_analysis() \
+})
+
+/**
+ * __capability_unsafe() - function attribute, disable capability checking
+ * @comment: comment explaining why opt-out is safe
+ *
+ * Function attribute denoting that capability analysis is disabled for the
+ * whole function. Forces adding an inline comment as argument.
+ */
+#define __capability_unsafe(comment) __no_capability_analysis
+
+/**
+ * capability_unsafe_alias() - helper to insert a capability "alias barrier"
+ * @p: pointer aliasing a capability or object containing capabilities
+ *
+ * No-op function that acts as a "capability alias barrier", where the analysis
+ * rightfully detects that we're switching aliases, but the switch is considered
+ * safe but beyond the analysis reasoning abilities.
+ *
+ * This should be inserted before the first use of such an alias.
+ *
+ * Implementation Note: The compiler ignores aliases that may be reassigned but
+ * their value cannot be determined (e.g. when passing a non-const pointer to an
+ * alias as a function argument).
+ */
+#define capability_unsafe_alias(p) _capability_unsafe_alias((void **)&(p))
+static inline void _capability_unsafe_alias(void **p) { }
+
+/**
+ * token_capability() - declare an abstract global capability instance
+ * @name: token capability name
+ *
+ * Helper that declares an abstract global capability instance @name that can be
+ * used as a token capability, but not backed by a real data structure (linker
+ * error if accidentally referenced). The type name is `__capability_@name`.
+ */
+#define token_capability(name, ...) \
+ struct_with_capability(__capability_##name, ##__VA_ARGS__) {}; \
+ extern const struct __capability_##name *name
+
+/**
+ * token_capability_instance() - declare another instance of a global capability
+ * @cap: token capability previously declared with token_capability()
+ * @name: name of additional global capability instance
+ *
+ * Helper that declares an additional instance @name of the same token
+ * capability class @name. This is helpful where multiple related token
+ * capabilities are declared, as it also allows using the same underlying type
+ * (`__capability_@cap`) as function arguments.
+ */
+#define token_capability_instance(cap, name) \
+ extern const struct __capability_##cap *name
+
+/*
+ * Common keywords for static capability analysis. Both Clang's capability
+ * analysis and Sparse's context tracking are currently supported.
+ */
#ifdef __CHECKER__

/* Sparse context/lock checking support. */
# define __must_hold(x) __attribute__((context(x,1,1)))
+# define __must_not_hold(x)
# define __acquires(x) __attribute__((context(x,0,1)))
# define __cond_acquires(x) __attribute__((context(x,0,-1)))
# define __releases(x) __attribute__((context(x,1,0)))
# define __acquire(x) __context__(x,1)
# define __release(x) __context__(x,-1)
# define __cond_lock(x, c) ((c) ? ({ __acquire(x); 1; }) : 0)
+/* For Sparse, there's no distinction between exclusive and shared locks. */
+# define __must_hold_shared __must_hold
+# define __acquires_shared __acquires
+# define __cond_acquires_shared __cond_acquires
+# define __releases_shared __releases
+# define __acquire_shared __acquire
+# define __release_shared __release
+# define __cond_lock_shared __cond_acquire

#else /* !__CHECKER__ */

-# define __must_hold(x)
-# define __acquires(x)
-# define __cond_acquires(x)
-# define __releases(x)
-# define __acquire(x) (void)0
-# define __release(x) (void)0
-# define __cond_lock(x, c) (c)
+/**
+ * __must_hold() - function attribute, caller must hold exclusive capability
+ * @x: capability instance pointer
+ *
+ * Function attribute declaring that the caller must hold the given capability
+ * instance @x exclusively.
+ */
+# define __must_hold(x) __requires_cap(x)
+
+/**
+ * __must_not_hold() - function attribute, caller must not hold capability
+ * @x: capability instance pointer
+ *
+ * Function attribute declaring that the caller must not hold the given
+ * capability instance @x.
+ */
+# define __must_not_hold(x) __excludes_cap(x)
+
+/**
+ * __acquires() - function attribute, function acquires capability exclusively
+ * @x: capability instance pointer
+ *
+ * Function attribute declaring that the function acquires the given
+ * capability instance @x exclusively, but does not release it.
+ */
+# define __acquires(x) __acquires_cap(x)
+
+/**
+ * __cond_acquires() - function attribute, function conditionally
+ * acquires a capability exclusively
+ * @x: capability instance pointer
+ *
+ * Function attribute declaring that the function conditionally acquires the
+ * given capability instance @x exclusively, but does not release it.
+ */
+# define __cond_acquires(x) __try_acquires_cap(1, x)
+
+/**
+ * __releases() - function attribute, function releases a capability exclusively
+ * @x: capability instance pointer
+ *
+ * Function attribute declaring that the function releases the given capability
+ * instance @x exclusively. The capability must be held on entry.
+ */
+# define __releases(x) __releases_cap(x)
+
+/**
+ * __acquire() - function to acquire capability exclusively
+ * @x: capability instance pointer
+ *
+ * No-op function that acquires the given capability instance @x exclusively.
+ */
+# define __acquire(x) __acquire_cap(x)
+
+/**
+ * __release() - function to release capability exclusively
+ * @x: capability instance pointer
+ *
+ * No-op function that releases the given capability instance @x.
+ */
+# define __release(x) __release_cap(x)
+
+/**
+ * __cond_lock() - function that conditionally acquires a capability
+ * exclusively
+ * @x: capability instance pinter
+ * @c: boolean expression
+ *
+ * Return: result of @c
+ *
+ * No-op function that conditionally acquires capability instance @x
+ * exclusively, if the boolean expression @c is true. The result of @c is the
+ * return value, to be able to create a capability-enabled interface; for
+ * example:
+ *
+ * .. code-block:: c
+ *
+ * #define spin_trylock(l) __cond_lock(&lock, _spin_trylock(&lock))
+ */
+# define __cond_lock(x, c) __try_acquire_cap(x, c)
+
+/**
+ * __must_hold_shared() - function attribute, caller must hold shared capability
+ * @x: capability instance pointer
+ *
+ * Function attribute declaring that the caller must hold the given capability
+ * instance @x with shared access.
+ */
+# define __must_hold_shared(x) __requires_shared_cap(x)
+
+/**
+ * __acquires_shared() - function attribute, function acquires capability shared
+ * @x: capability instance pointer
+ *
+ * Function attribute declaring that the function acquires the given
+ * capability instance @x with shared access, but does not release it.
+ */
+# define __acquires_shared(x) __acquires_shared_cap(x)
+
+/**
+ * __cond_acquires_shared() - function attribute, function conditionally
+ * acquires a capability shared
+ * @x: capability instance pointer
+ *
+ * Function attribute declaring that the function conditionally acquires the
+ * given capability instance @x with shared access, but does not release it.
+ */
+# define __cond_acquires_shared(x) __try_acquires_shared_cap(1, x)
+
+/**
+ * __releases_shared() - function attribute, function releases a
+ * capability shared
+ * @x: capability instance pointer
+ *
+ * Function attribute declaring that the function releases the given capability
+ * instance @x with shared access. The capability must be held on entry.
+ */
+# define __releases_shared(x) __releases_shared_cap(x)
+
+/**
+ * __acquire_shared() - function to acquire capability shared
+ * @x: capability instance pointer
+ *
+ * No-op function that acquires the given capability instance @x with shared
+ * access.
+ */
+# define __acquire_shared(x) __acquire_shared_cap(x)
+
+/**
+ * __release_shared() - function to release capability shared
+ * @x: capability instance pointer
+ *
+ * No-op function that releases the given capability instance @x with shared
+ * access.
+ */
+# define __release_shared(x) __release_shared_cap(x)
+
+/**
+ * __cond_lock_shared() - function that conditionally acquires a capability
+ * shared
+ * @x: capability instance pinter
+ * @c: boolean expression
+ *
+ * Return: result of @c
+ *
+ * No-op function that conditionally acquires capability instance @x with shared
+ * access, if the boolean expression @c is true. The result of @c is the return
+ * value, to be able to create a capability-enabled interface.
+ */
+# define __cond_lock_shared(x, c) __try_acquire_shared_cap(x, c)

#endif /* __CHECKER__ */

+/**
+ * __acquire_ret() - helper to acquire capability of return value
+ * @call: call expression
+ * @ret_expr: acquire expression that uses __ret
+ */
+#define __acquire_ret(call, ret_expr) \
+ ({ \
+ __auto_type __ret = call; \
+ __acquire(ret_expr); \
+ __ret; \
+ })
+
+/**
+ * __acquire_shared_ret() - helper to acquire capability shared of return value
+ * @call: call expression
+ * @ret_expr: acquire shared expression that uses __ret
+ */
+#define __acquire_shared_ret(call, ret_expr) \
+ ({ \
+ __auto_type __ret = call; \
+ __acquire_shared(ret_expr); \
+ __ret; \
+ })
+
+/*
+ * Attributes to mark functions returning acquired capabilities. This is purely
+ * cosmetic to help readability, and should be used with the above macros as
+ * follows:
+ *
+ * struct foo { spinlock_t lock; ... };
+ * ...
+ * #define myfunc(...) __acquire_ret(_myfunc(__VA_ARGS__), &__ret->lock)
+ * struct foo *_myfunc(int bar) __acquires_ret;
+ * ...
+ */
+#define __acquires_ret __no_capability_analysis
+#define __acquires_shared_ret __no_capability_analysis
+
#endif /* _LINUX_COMPILER_CAPABILITY_ANALYSIS_H */
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index dc0e0c6ed075..57e09615f88d 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -613,6 +613,37 @@ config DEBUG_FORCE_WEAK_PER_CPU
To ensure that generic code follows the above rules, this
option forces all percpu variables to be defined as weak.

+config WARN_CAPABILITY_ANALYSIS
+ bool "Compiler capability-analysis warnings"
+ depends on CC_IS_CLANG && CLANG_VERSION >= 220000
+ # Branch profiling re-defines "if", which messes with the compiler's
+ # ability to analyze __cond_acquires(..), resulting in false positives.
+ depends on !TRACE_BRANCH_PROFILING
+ default y
+ help
+ Capability analysis is a C language extension, which enables
+ statically checking that user-definable "capabilities" are acquired
+ and released where required.
+
+ Clang's name of the feature ("Thread Safety Analysis") refers to
+ the original name of the feature; it was later expanded to be a
+ generic "Capability Analysis" framework.
+
+ Requires Clang 22 or later.
+
+ Produces warnings by default. Select CONFIG_WERROR if you wish to
+ turn these warnings into errors.
+
+config WARN_CAPABILITY_ANALYSIS_ALL
+ bool "Enable capability analysis for all source files"
+ depends on WARN_CAPABILITY_ANALYSIS
+ depends on EXPERT && !COMPILE_TEST
+ help
+ Enable tree-wide capability analysis. This is likely to produce a
+ large number of false positives - enable at your own risk.
+
+ If unsure, say N.
+
endmenu # "Compiler options"

menu "Generic Kernel Debugging Instruments"
diff --git a/scripts/Makefile.capability-analysis b/scripts/Makefile.capability-analysis
new file mode 100644
index 000000000000..e137751a4c9a
--- /dev/null
+++ b/scripts/Makefile.capability-analysis
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0
+
+capability-analysis-cflags := -DWARN_CAPABILITY_ANALYSIS \
+ -fexperimental-late-parse-attributes -Wthread-safety \
+ -Wthread-safety-pointer -Wthread-safety-beta
+
+export CFLAGS_CAPABILITY_ANALYSIS := $(capability-analysis-cflags)
diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib
index 1d581ba5df66..e0ac273bf9eb 100644
--- a/scripts/Makefile.lib
+++ b/scripts/Makefile.lib
@@ -105,6 +105,16 @@ _c_flags += $(if $(patsubst n%,, \
-D__KCSAN_INSTRUMENT_BARRIERS__)
endif

+#
+# Enable capability analysis flags only where explicitly opted in.
+# (depends on variables CAPABILITY_ANALYSIS_obj.o, CAPABILITY_ANALYSIS)
+#
+ifeq ($(CONFIG_WARN_CAPABILITY_ANALYSIS),y)
+_c_flags += $(if $(patsubst n%,, \
+ $(CAPABILITY_ANALYSIS_$(target-stem).o)$(CAPABILITY_ANALYSIS)$(if $(is-kernel-object),$(CONFIG_WARN_CAPABILITY_ANALYSIS_ALL))), \
+ $(CFLAGS_CAPABILITY_ANALYSIS))
+endif
+
#
# Enable AutoFDO build flags except some files or directories we don't want to
# enable (depends on variables AUTOFDO_PROFILE_obj.o and AUTOFDO_PROFILE).
--
2.51.0.384.g4c02a37b29-goog

Marco Elver

unread,
Sep 18, 2025, 10:05:38 AM (6 days ago) Sep 18
to el...@google.com, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
Add a simple test stub where we will add common supported patterns that
should not generate false positive of each new supported capability.

Signed-off-by: Marco Elver <el...@google.com>
---
lib/Kconfig.debug | 14 ++++++++++++++
lib/Makefile | 3 +++
lib/test_capability-analysis.c | 18 ++++++++++++++++++
3 files changed, 35 insertions(+)
create mode 100644 lib/test_capability-analysis.c

diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 57e09615f88d..ac024861930f 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -2813,6 +2813,20 @@ config LINEAR_RANGES_TEST

If unsure, say N.

+config CAPABILITY_ANALYSIS_TEST
+ bool "Compiler capability-analysis warnings test"
+ depends on EXPERT
+ help
+ This builds the test for compiler-based capability analysis. The test
+ does not add executable code to the kernel, but is meant to test that
+ common patterns supported by the analysis do not result in false
+ positive warnings.
+
+ When adding support for new capabilities, it is strongly recommended
+ to add supported patterns to this test.
+
+ If unsure, say N.
+
config CMDLINE_KUNIT_TEST
tristate "KUnit test for cmdline API" if !KUNIT_ALL_TESTS
depends on KUNIT
diff --git a/lib/Makefile b/lib/Makefile
index 392ff808c9b9..e677cb5cc777 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -332,4 +332,7 @@ obj-$(CONFIG_GENERIC_LIB_DEVMEM_IS_ALLOWED) += devmem_is_allowed.o

obj-$(CONFIG_FIRMWARE_TABLE) += fw_table.o

+CAPABILITY_ANALYSIS_test_capability-analysis.o := y
+obj-$(CONFIG_CAPABILITY_ANALYSIS_TEST) += test_capability-analysis.o
+
subdir-$(CONFIG_FORTIFY_SOURCE) += test_fortify
diff --git a/lib/test_capability-analysis.c b/lib/test_capability-analysis.c
new file mode 100644
index 000000000000..a0adacce30ff
--- /dev/null
+++ b/lib/test_capability-analysis.c
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Compile-only tests for common patterns that should not generate false
+ * positive errors when compiled with Clang's capability analysis.
+ */
+
+#include <linux/build_bug.h>
+
+/*
+ * Test that helper macros work as expected.
+ */
+static void __used test_common_helpers(void)
+{
+ BUILD_BUG_ON(capability_unsafe(3) != 3); /* plain expression */
+ BUILD_BUG_ON(capability_unsafe((void)2; 3;) != 3); /* does not swallow semi-colon */
+ BUILD_BUG_ON(capability_unsafe((void)2, 3) != 3); /* does not swallow commas */
+ capability_unsafe(do { } while (0)); /* works with void statements */
+}
--
2.51.0.384.g4c02a37b29-goog

Marco Elver

unread,
Sep 18, 2025, 10:05:43 AM (6 days ago) Sep 18
to el...@google.com, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
Warn about applications of capability_unsafe() without a comment, to
encourage documenting the reasoning behind why it was deemed safe.

Signed-off-by: Marco Elver <el...@google.com>
---
scripts/checkpatch.pl | 8 ++++++++
1 file changed, 8 insertions(+)

diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl
index e722dd6fa8ef..532075e67a96 100755
--- a/scripts/checkpatch.pl
+++ b/scripts/checkpatch.pl
@@ -6717,6 +6717,14 @@ sub process {
}
}

+# check for capability_unsafe without a comment.
+ if ($line =~ /\bcapability_unsafe\b/) {
+ if (!ctx_has_comment($first_line, $linenr)) {
+ WARN("CAPABILITY_UNSAFE",
+ "capability_unsafe without comment\n" . $herecurr);
+ }
+ }
+
# check of hardware specific defines
if ($line =~ m@^.\s*\#\s*if.*\b(__i386__|__powerpc64__|__sun__|__s390x__)\b@ && $realfile !~ m@include/asm-@) {
CHK("ARCH_DEFINES",
--
2.51.0.384.g4c02a37b29-goog

Marco Elver

unread,
Sep 18, 2025, 10:05:46 AM (6 days ago) Sep 18
to el...@google.com, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
Introduce basic compatibility with cleanup.h infrastructure: introduce
DECLARE_LOCK_GUARD_*_ATTRS() helpers to add attributes to constructors
and destructors respectively.

Note: Due to the scoped cleanup helpers used for lock guards wrapping
acquire and release around their own constructors/destructors that store
pointers to the passed locks in a separate struct, we currently cannot
accurately annotate *destructors* which lock was released. While it's
possible to annotate the constructor to say which lock was acquired,
that alone would result in false positives claiming the lock was not
released on function return.

Instead, to avoid false positives, we can claim that the constructor
"assumes" that the taken lock is held via __assumes_cap().

This will ensure we can still benefit from the analysis where scoped
guards are used to protect access to guarded variables, while avoiding
false positives. The only downside are false negatives where we might
accidentally lock the same lock again:

raw_spin_lock(&my_lock);
...
guard(raw_spinlock)(&my_lock); // no warning

Arguably, lockdep will immediately catch issues like this.

While Clang's analysis supports scoped guards in C++ [1], there's no way
to apply this to C right now. Better support for Linux's scoped guard
design could be added in future if deemed critical.

[1] https://clang.llvm.org/docs/ThreadSafetyAnalysis.html#scoped-capability

Signed-off-by: Marco Elver <el...@google.com>
---
v3:
* Add *_ATTRS helpers instead of implicit __assumes_cap (suggested by Peter)
* __assert -> __assume rename
---
include/linux/cleanup.h | 17 +++++++++++++++++
1 file changed, 17 insertions(+)

diff --git a/include/linux/cleanup.h b/include/linux/cleanup.h
index 2573585b7f06..54fc70d8da27 100644
--- a/include/linux/cleanup.h
+++ b/include/linux/cleanup.h
@@ -274,16 +274,21 @@ const volatile void * __must_check_fn(const volatile void *val)

#define DEFINE_CLASS(_name, _type, _exit, _init, _init_args...) \
typedef _type class_##_name##_t; \
+typedef _type lock_##_name##_t; \
static inline void class_##_name##_destructor(_type *p) \
+ __no_capability_analysis \
{ _type _T = *p; _exit; } \
static inline _type class_##_name##_constructor(_init_args) \
+ __no_capability_analysis \
{ _type t = _init; return t; }

#define EXTEND_CLASS(_name, ext, _init, _init_args...) \
+typedef lock_##_name##_t lock_##_name##ext##_t; \
typedef class_##_name##_t class_##_name##ext##_t; \
static inline void class_##_name##ext##_destructor(class_##_name##_t *p)\
{ class_##_name##_destructor(p); } \
static inline class_##_name##_t class_##_name##ext##_constructor(_init_args) \
+ __no_capability_analysis \
{ class_##_name##_t t = _init; return t; }

#define CLASS(_name, var) \
@@ -461,12 +466,14 @@ _label: \
*/

#define __DEFINE_UNLOCK_GUARD(_name, _type, _unlock, ...) \
+typedef _type lock_##_name##_t; \
typedef struct { \
_type *lock; \
__VA_ARGS__; \
} class_##_name##_t; \
\
static inline void class_##_name##_destructor(class_##_name##_t *_T) \
+ __no_capability_analysis \
{ \
if (!__GUARD_IS_ERR(_T->lock)) { _unlock; } \
} \
@@ -475,6 +482,7 @@ __DEFINE_GUARD_LOCK_PTR(_name, &_T->lock)

#define __DEFINE_LOCK_GUARD_1(_name, _type, _lock) \
static inline class_##_name##_t class_##_name##_constructor(_type *l) \
+ __no_capability_analysis \
{ \
class_##_name##_t _t = { .lock = l }, *_T = &_t; \
_lock; \
@@ -483,6 +491,7 @@ static inline class_##_name##_t class_##_name##_constructor(_type *l) \

#define __DEFINE_LOCK_GUARD_0(_name, _lock) \
static inline class_##_name##_t class_##_name##_constructor(void) \
+ __no_capability_analysis \
{ \
class_##_name##_t _t = { .lock = (void*)1 }, \
*_T __maybe_unused = &_t; \
@@ -490,6 +499,14 @@ static inline class_##_name##_t class_##_name##_constructor(void) \
return _t; \
}

+#define DECLARE_LOCK_GUARD_0_ATTRS(_name, _lock, _unlock) \
+static inline class_##_name##_t class_##_name##_constructor(void) _lock;\
+static inline void class_##_name##_destructor(class_##_name##_t *_T) _unlock;
+
+#define DECLARE_LOCK_GUARD_1_ATTRS(_name, _lock, _unlock) \
+static inline class_##_name##_t class_##_name##_constructor(lock_##_name##_t *_T) _lock;\
+static inline void class_##_name##_destructor(class_##_name##_t *_T) _unlock;
+
#define DEFINE_LOCK_GUARD_1(_name, _type, _lock, _unlock, ...) \
__DEFINE_CLASS_IS_CONDITIONAL(_name, false); \
__DEFINE_UNLOCK_GUARD(_name, _type, _unlock, __VA_ARGS__) \
--
2.51.0.384.g4c02a37b29-goog

Marco Elver

unread,
Sep 18, 2025, 10:05:51 AM (6 days ago) Sep 18
to el...@google.com, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
Adds documentation in Documentation/dev-tools/capability-analysis.rst,
and adds it to the index and cross-references from Sparse's document.

Signed-off-by: Marco Elver <el...@google.com>
---
v2:
* Remove cross-reference to Sparse, since we plan to remove Sparse
support anyway.
* Mention __no_capability_analysis should be avoided.
---
.../dev-tools/capability-analysis.rst | 147 ++++++++++++++++++
Documentation/dev-tools/index.rst | 1 +
2 files changed, 148 insertions(+)
create mode 100644 Documentation/dev-tools/capability-analysis.rst

diff --git a/Documentation/dev-tools/capability-analysis.rst b/Documentation/dev-tools/capability-analysis.rst
new file mode 100644
index 000000000000..1287f792f6cd
--- /dev/null
+++ b/Documentation/dev-tools/capability-analysis.rst
@@ -0,0 +1,147 @@
+.. SPDX-License-Identifier: GPL-2.0
+.. Copyright (C) 2025, Google LLC.
+
+.. _capability-analysis:
+
+Compiler-Based Capability Analysis
+==================================
+
+Capability analysis is a C language extension, which enables statically
+checking that user-definable "capabilities" are acquired and released where
+required. An obvious application is lock-safety checking for the kernel's
+various synchronization primitives (each of which represents a "capability"),
+and checking that locking rules are not violated.
+
+The Clang compiler currently supports the full set of capability analysis
+features. To enable for Clang, configure the kernel with::
+
+ CONFIG_WARN_CAPABILITY_ANALYSIS=y
+
+The feature requires Clang 22 or later.
+
+The analysis is *opt-in by default*, and requires declaring which modules and
+subsystems should be analyzed in the respective `Makefile`::
+
+ CAPABILITY_ANALYSIS_mymodule.o := y
+
+Or for all translation units in the directory::
+
+ CAPABILITY_ANALYSIS := y
+
+It is possible to enable the analysis tree-wide, however, which will result in
+numerous false positive warnings currently and is *not* generally recommended::
+
+ CONFIG_WARN_CAPABILITY_ANALYSIS_ALL=y
+
+Programming Model
+-----------------
+
+The below describes the programming model around using capability-enabled
+types.
+
+.. note::
+ Enabling capability analysis can be seen as enabling a dialect of Linux C with
+ a Capability System. Some valid patterns involving complex control-flow are
+ constrained (such as conditional acquisition and later conditional release
+ in the same function, or returning pointers to capabilities from functions.
+
+Capability analysis is a way to specify permissibility of operations to depend
+on capabilities being held (or not held). Typically we are interested in
+protecting data and code by requiring some capability to be held, for example a
+specific lock. The analysis ensures that the caller cannot perform the
+operation without holding the appropriate capability.
+
+Capabilities are associated with named structs, along with functions that
+operate on capability-enabled struct instances to acquire and release the
+associated capability.
+
+Capabilities can be held either exclusively or shared. This mechanism allows
+assign more precise privileges when holding a capability, typically to
+distinguish where a thread may only read (shared) or also write (exclusive) to
+guarded data.
+
+The set of capabilities that are actually held by a given thread at a given
+point in program execution is a run-time concept. The static analysis works by
+calculating an approximation of that set, called the capability environment.
+The capability environment is calculated for every program point, and describes
+the set of capabilities that are statically known to be held, or not held, at
+that particular point. This environment is a conservative approximation of the
+full set of capabilities that will actually held by a thread at run-time.
+
+More details are also documented `here
+<https://clang.llvm.org/docs/ThreadSafetyAnalysis.html>`_.
+
+.. note::
+ Clang's analysis explicitly does not infer capabilities acquired or released
+ by inline functions. It requires explicit annotations to (a) assert that
+ it's not a bug if a capability is released or acquired, and (b) to retain
+ consistency between inline and non-inline function declarations.
+
+Supported Kernel Primitives
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. Currently the following synchronization primitives are supported:
+
+For capabilities with an initialization function (e.g., `spin_lock_init()`),
+calling this function on the capability instance before initializing any
+guarded members or globals prevents the compiler from issuing warnings about
+unguarded initialization.
+
+Lockdep assertions, such as `lockdep_assert_held()`, inform the compiler's
+capability analysis that the associated synchronization primitive is held after
+the assertion. This avoids false positives in complex control-flow scenarios
+and encourages the use of Lockdep where static analysis is limited. For
+example, this is useful when a function doesn't *always* require a lock, making
+`__must_hold()` inappropriate.
+
+Keywords
+~~~~~~~~
+
+.. kernel-doc:: include/linux/compiler-capability-analysis.h
+ :identifiers: struct_with_capability
+ token_capability token_capability_instance
+ __guarded_by __pt_guarded_by
+ __must_hold
+ __must_not_hold
+ __acquires
+ __cond_acquires
+ __releases
+ __must_hold_shared
+ __acquires_shared
+ __cond_acquires_shared
+ __releases_shared
+ __acquire
+ __release
+ __cond_lock
+ __acquire_shared
+ __release_shared
+ __cond_lock_shared
+ capability_unsafe
+ __capability_unsafe
+ disable_capability_analysis enable_capability_analysis
+
+.. note::
+ The function attribute `__no_capability_analysis` is reserved for internal
+ implementation of capability-enabled primitives, and should be avoided in
+ normal code.
+
+Background
+----------
+
+Clang originally called the feature `Thread Safety Analysis
+<https://clang.llvm.org/docs/ThreadSafetyAnalysis.html>`_, with some
+terminology still using the thread-safety-analysis-only names. This was later
+changed and the feature became more flexible, gaining the ability to define
+custom "capabilities".
+
+Indeed, its foundations can be found in `capability systems
+<https://www.cs.cornell.edu/talc/papers/capabilities.pdf>`_, used to specify
+the permissibility of operations to depend on some capability being held (or
+not held).
+
+Because the feature is not just able to express capabilities related to
+synchronization primitives, the naming chosen for the kernel departs from
+Clang's initial "Thread Safety" nomenclature and refers to the feature as
+"Capability Analysis" to avoid confusion. The implementation still makes
+references to the older terminology in some places, such as `-Wthread-safety`
+being the warning option that also still appears in diagnostic messages.
diff --git a/Documentation/dev-tools/index.rst b/Documentation/dev-tools/index.rst
index 65c54b27a60b..62ac23f797cd 100644
--- a/Documentation/dev-tools/index.rst
+++ b/Documentation/dev-tools/index.rst
@@ -18,6 +18,7 @@ Documentation/process/debugging/index.rst
:maxdepth: 2

testing-overview
+ capability-analysis
checkpatch
clang-format
coccinelle
--
2.51.0.384.g4c02a37b29-goog

Marco Elver

unread,
Sep 18, 2025, 10:05:51 AM (6 days ago) Sep 18
to el...@google.com, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
Clang's capability analysis can be made aware of functions that assert
that capabilities/locks are held.

Presence of these annotations causes the analysis to assume the
capability is held after calls to the annotated function, and avoid
false positives with complex control-flow; for example, where not all
control-flow paths in a function require a held lock, and therefore
marking the function with __must_hold(..) is inappropriate.

Signed-off-by: Marco Elver <el...@google.com>
---
v3:
* __assert -> __assume rename
---
include/linux/lockdep.h | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/include/linux/lockdep.h b/include/linux/lockdep.h
index 67964dc4db95..11b3d22555ff 100644
--- a/include/linux/lockdep.h
+++ b/include/linux/lockdep.h
@@ -282,16 +282,16 @@ extern void lock_unpin_lock(struct lockdep_map *lock, struct pin_cookie);
do { WARN_ON_ONCE(debug_locks && !(cond)); } while (0)

#define lockdep_assert_held(l) \
- lockdep_assert(lockdep_is_held(l) != LOCK_STATE_NOT_HELD)
+ do { lockdep_assert(lockdep_is_held(l) != LOCK_STATE_NOT_HELD); __assume_cap(l); } while (0)

#define lockdep_assert_not_held(l) \
lockdep_assert(lockdep_is_held(l) != LOCK_STATE_HELD)

#define lockdep_assert_held_write(l) \
- lockdep_assert(lockdep_is_held_type(l, 0))
+ do { lockdep_assert(lockdep_is_held_type(l, 0)); __assume_cap(l); } while (0)

#define lockdep_assert_held_read(l) \
- lockdep_assert(lockdep_is_held_type(l, 1))
+ do { lockdep_assert(lockdep_is_held_type(l, 1)); __assume_shared_cap(l); } while (0)

#define lockdep_assert_held_once(l) \
lockdep_assert_once(lockdep_is_held(l) != LOCK_STATE_NOT_HELD)
@@ -389,10 +389,10 @@ extern int lockdep_is_held(const void *);
#define lockdep_assert(c) do { } while (0)
#define lockdep_assert_once(c) do { } while (0)

-#define lockdep_assert_held(l) do { (void)(l); } while (0)
+#define lockdep_assert_held(l) __assume_cap(l)
#define lockdep_assert_not_held(l) do { (void)(l); } while (0)
-#define lockdep_assert_held_write(l) do { (void)(l); } while (0)
-#define lockdep_assert_held_read(l) do { (void)(l); } while (0)
+#define lockdep_assert_held_write(l) __assume_cap(l)
+#define lockdep_assert_held_read(l) __assume_shared_cap(l)
#define lockdep_assert_held_once(l) do { (void)(l); } while (0)
#define lockdep_assert_none_held_once() do { } while (0)

--
2.51.0.384.g4c02a37b29-goog

Marco Elver

unread,
Sep 18, 2025, 10:05:52 AM (6 days ago) Sep 18
to el...@google.com, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
Add support for Clang's capability analysis for raw_spinlock_t,
spinlock_t, and rwlock. This wholesale conversion is required because
all three of them are interdependent.

To avoid warnings in constructors, the initialization functions mark a
capability as acquired when initialized before guarded variables.

The test verifies that common patterns do not generate false positives.

Signed-off-by: Marco Elver <el...@google.com>
---
v3:
* Switch to DECLARE_LOCK_GUARD_1_ATTRS() (suggested by Peter)
* __assert -> __assume rename
---
.../dev-tools/capability-analysis.rst | 3 +-
include/linux/rwlock.h | 25 ++--
include/linux/rwlock_api_smp.h | 29 +++-
include/linux/rwlock_rt.h | 35 +++--
include/linux/rwlock_types.h | 10 +-
include/linux/spinlock.h | 70 +++++++---
include/linux/spinlock_api_smp.h | 14 +-
include/linux/spinlock_api_up.h | 71 +++++-----
include/linux/spinlock_rt.h | 21 +--
include/linux/spinlock_types.h | 10 +-
include/linux/spinlock_types_raw.h | 5 +-
lib/test_capability-analysis.c | 128 ++++++++++++++++++
12 files changed, 324 insertions(+), 97 deletions(-)

diff --git a/Documentation/dev-tools/capability-analysis.rst b/Documentation/dev-tools/capability-analysis.rst
index 1287f792f6cd..9abd7f62cf4e 100644
--- a/Documentation/dev-tools/capability-analysis.rst
+++ b/Documentation/dev-tools/capability-analysis.rst
@@ -80,7 +80,8 @@ More details are also documented `here
Supported Kernel Primitives
~~~~~~~~~~~~~~~~~~~~~~~~~~~

-.. Currently the following synchronization primitives are supported:
+Currently the following synchronization primitives are supported:
+`raw_spinlock_t`, `spinlock_t`, `rwlock_t`.

For capabilities with an initialization function (e.g., `spin_lock_init()`),
calling this function on the capability instance before initializing any
diff --git a/include/linux/rwlock.h b/include/linux/rwlock.h
index 5b87c6f4a243..78e4d02ee2c6 100644
--- a/include/linux/rwlock.h
+++ b/include/linux/rwlock.h
@@ -22,23 +22,24 @@ do { \
static struct lock_class_key __key; \
\
__rwlock_init((lock), #lock, &__key); \
+ __assume_cap(lock); \
} while (0)
#else
# define rwlock_init(lock) \
- do { *(lock) = __RW_LOCK_UNLOCKED(lock); } while (0)
+ do { *(lock) = __RW_LOCK_UNLOCKED(lock); __assume_cap(lock); } while (0)
#endif

#ifdef CONFIG_DEBUG_SPINLOCK
- extern void do_raw_read_lock(rwlock_t *lock) __acquires(lock);
+ extern void do_raw_read_lock(rwlock_t *lock) __acquires_shared(lock);
extern int do_raw_read_trylock(rwlock_t *lock);
- extern void do_raw_read_unlock(rwlock_t *lock) __releases(lock);
+ extern void do_raw_read_unlock(rwlock_t *lock) __releases_shared(lock);
extern void do_raw_write_lock(rwlock_t *lock) __acquires(lock);
extern int do_raw_write_trylock(rwlock_t *lock);
extern void do_raw_write_unlock(rwlock_t *lock) __releases(lock);
#else
-# define do_raw_read_lock(rwlock) do {__acquire(lock); arch_read_lock(&(rwlock)->raw_lock); } while (0)
+# define do_raw_read_lock(rwlock) do {__acquire_shared(lock); arch_read_lock(&(rwlock)->raw_lock); } while (0)
# define do_raw_read_trylock(rwlock) arch_read_trylock(&(rwlock)->raw_lock)
-# define do_raw_read_unlock(rwlock) do {arch_read_unlock(&(rwlock)->raw_lock); __release(lock); } while (0)
+# define do_raw_read_unlock(rwlock) do {arch_read_unlock(&(rwlock)->raw_lock); __release_shared(lock); } while (0)
# define do_raw_write_lock(rwlock) do {__acquire(lock); arch_write_lock(&(rwlock)->raw_lock); } while (0)
# define do_raw_write_trylock(rwlock) arch_write_trylock(&(rwlock)->raw_lock)
# define do_raw_write_unlock(rwlock) do {arch_write_unlock(&(rwlock)->raw_lock); __release(lock); } while (0)
@@ -49,7 +50,7 @@ do { \
* regardless of whether CONFIG_SMP or CONFIG_PREEMPT are set. The various
* methods are defined as nops in the case they are not required.
*/
-#define read_trylock(lock) __cond_lock(lock, _raw_read_trylock(lock))
+#define read_trylock(lock) __cond_lock_shared(lock, _raw_read_trylock(lock))
#define write_trylock(lock) __cond_lock(lock, _raw_write_trylock(lock))

#define write_lock(lock) _raw_write_lock(lock)
@@ -112,12 +113,12 @@ do { \
} while (0)
#define write_unlock_bh(lock) _raw_write_unlock_bh(lock)

-#define write_trylock_irqsave(lock, flags) \
-({ \
- local_irq_save(flags); \
- write_trylock(lock) ? \
- 1 : ({ local_irq_restore(flags); 0; }); \
-})
+#define write_trylock_irqsave(lock, flags) \
+ __cond_lock(lock, ({ \
+ local_irq_save(flags); \
+ _raw_write_trylock(lock) ? \
+ 1 : ({ local_irq_restore(flags); 0; }); \
+ }))

#ifdef arch_rwlock_is_contended
#define rwlock_is_contended(lock) \
diff --git a/include/linux/rwlock_api_smp.h b/include/linux/rwlock_api_smp.h
index 31d3d1116323..3e975105a606 100644
--- a/include/linux/rwlock_api_smp.h
+++ b/include/linux/rwlock_api_smp.h
@@ -15,12 +15,12 @@
* Released under the General Public License (GPL).
*/

-void __lockfunc _raw_read_lock(rwlock_t *lock) __acquires(lock);
+void __lockfunc _raw_read_lock(rwlock_t *lock) __acquires_shared(lock);
void __lockfunc _raw_write_lock(rwlock_t *lock) __acquires(lock);
void __lockfunc _raw_write_lock_nested(rwlock_t *lock, int subclass) __acquires(lock);
-void __lockfunc _raw_read_lock_bh(rwlock_t *lock) __acquires(lock);
+void __lockfunc _raw_read_lock_bh(rwlock_t *lock) __acquires_shared(lock);
void __lockfunc _raw_write_lock_bh(rwlock_t *lock) __acquires(lock);
-void __lockfunc _raw_read_lock_irq(rwlock_t *lock) __acquires(lock);
+void __lockfunc _raw_read_lock_irq(rwlock_t *lock) __acquires_shared(lock);
void __lockfunc _raw_write_lock_irq(rwlock_t *lock) __acquires(lock);
unsigned long __lockfunc _raw_read_lock_irqsave(rwlock_t *lock)
__acquires(lock);
@@ -28,11 +28,11 @@ unsigned long __lockfunc _raw_write_lock_irqsave(rwlock_t *lock)
__acquires(lock);
int __lockfunc _raw_read_trylock(rwlock_t *lock);
int __lockfunc _raw_write_trylock(rwlock_t *lock);
-void __lockfunc _raw_read_unlock(rwlock_t *lock) __releases(lock);
+void __lockfunc _raw_read_unlock(rwlock_t *lock) __releases_shared(lock);
void __lockfunc _raw_write_unlock(rwlock_t *lock) __releases(lock);
-void __lockfunc _raw_read_unlock_bh(rwlock_t *lock) __releases(lock);
+void __lockfunc _raw_read_unlock_bh(rwlock_t *lock) __releases_shared(lock);
void __lockfunc _raw_write_unlock_bh(rwlock_t *lock) __releases(lock);
-void __lockfunc _raw_read_unlock_irq(rwlock_t *lock) __releases(lock);
+void __lockfunc _raw_read_unlock_irq(rwlock_t *lock) __releases_shared(lock);
void __lockfunc _raw_write_unlock_irq(rwlock_t *lock) __releases(lock);
void __lockfunc
_raw_read_unlock_irqrestore(rwlock_t *lock, unsigned long flags)
@@ -145,6 +145,7 @@ static inline int __raw_write_trylock(rwlock_t *lock)
#if !defined(CONFIG_GENERIC_LOCKBREAK) || defined(CONFIG_DEBUG_LOCK_ALLOC)

static inline void __raw_read_lock(rwlock_t *lock)
+ __acquires_shared(lock) __no_capability_analysis
{
preempt_disable();
rwlock_acquire_read(&lock->dep_map, 0, 0, _RET_IP_);
@@ -152,6 +153,7 @@ static inline void __raw_read_lock(rwlock_t *lock)
}

static inline unsigned long __raw_read_lock_irqsave(rwlock_t *lock)
+ __acquires_shared(lock) __no_capability_analysis
{
unsigned long flags;

@@ -163,6 +165,7 @@ static inline unsigned long __raw_read_lock_irqsave(rwlock_t *lock)
}

static inline void __raw_read_lock_irq(rwlock_t *lock)
+ __acquires_shared(lock) __no_capability_analysis
{
local_irq_disable();
preempt_disable();
@@ -171,6 +174,7 @@ static inline void __raw_read_lock_irq(rwlock_t *lock)
}

static inline void __raw_read_lock_bh(rwlock_t *lock)
+ __acquires_shared(lock) __no_capability_analysis
{
__local_bh_disable_ip(_RET_IP_, SOFTIRQ_LOCK_OFFSET);
rwlock_acquire_read(&lock->dep_map, 0, 0, _RET_IP_);
@@ -178,6 +182,7 @@ static inline void __raw_read_lock_bh(rwlock_t *lock)
}

static inline unsigned long __raw_write_lock_irqsave(rwlock_t *lock)
+ __acquires(lock) __no_capability_analysis
{
unsigned long flags;

@@ -189,6 +194,7 @@ static inline unsigned long __raw_write_lock_irqsave(rwlock_t *lock)
}

static inline void __raw_write_lock_irq(rwlock_t *lock)
+ __acquires(lock) __no_capability_analysis
{
local_irq_disable();
preempt_disable();
@@ -197,6 +203,7 @@ static inline void __raw_write_lock_irq(rwlock_t *lock)
}

static inline void __raw_write_lock_bh(rwlock_t *lock)
+ __acquires(lock) __no_capability_analysis
{
__local_bh_disable_ip(_RET_IP_, SOFTIRQ_LOCK_OFFSET);
rwlock_acquire(&lock->dep_map, 0, 0, _RET_IP_);
@@ -204,6 +211,7 @@ static inline void __raw_write_lock_bh(rwlock_t *lock)
}

static inline void __raw_write_lock(rwlock_t *lock)
+ __acquires(lock) __no_capability_analysis
{
preempt_disable();
rwlock_acquire(&lock->dep_map, 0, 0, _RET_IP_);
@@ -211,6 +219,7 @@ static inline void __raw_write_lock(rwlock_t *lock)
}

static inline void __raw_write_lock_nested(rwlock_t *lock, int subclass)
+ __acquires(lock) __no_capability_analysis
{
preempt_disable();
rwlock_acquire(&lock->dep_map, subclass, 0, _RET_IP_);
@@ -220,6 +229,7 @@ static inline void __raw_write_lock_nested(rwlock_t *lock, int subclass)
#endif /* !CONFIG_GENERIC_LOCKBREAK || CONFIG_DEBUG_LOCK_ALLOC */

static inline void __raw_write_unlock(rwlock_t *lock)
+ __releases(lock)
{
rwlock_release(&lock->dep_map, _RET_IP_);
do_raw_write_unlock(lock);
@@ -227,6 +237,7 @@ static inline void __raw_write_unlock(rwlock_t *lock)
}

static inline void __raw_read_unlock(rwlock_t *lock)
+ __releases_shared(lock)
{
rwlock_release(&lock->dep_map, _RET_IP_);
do_raw_read_unlock(lock);
@@ -235,6 +246,7 @@ static inline void __raw_read_unlock(rwlock_t *lock)

static inline void
__raw_read_unlock_irqrestore(rwlock_t *lock, unsigned long flags)
+ __releases_shared(lock)
{
rwlock_release(&lock->dep_map, _RET_IP_);
do_raw_read_unlock(lock);
@@ -243,6 +255,7 @@ __raw_read_unlock_irqrestore(rwlock_t *lock, unsigned long flags)
}

static inline void __raw_read_unlock_irq(rwlock_t *lock)
+ __releases_shared(lock)
{
rwlock_release(&lock->dep_map, _RET_IP_);
do_raw_read_unlock(lock);
@@ -251,6 +264,7 @@ static inline void __raw_read_unlock_irq(rwlock_t *lock)
}

static inline void __raw_read_unlock_bh(rwlock_t *lock)
+ __releases_shared(lock)
{
rwlock_release(&lock->dep_map, _RET_IP_);
do_raw_read_unlock(lock);
@@ -259,6 +273,7 @@ static inline void __raw_read_unlock_bh(rwlock_t *lock)

static inline void __raw_write_unlock_irqrestore(rwlock_t *lock,
unsigned long flags)
+ __releases(lock)
{
rwlock_release(&lock->dep_map, _RET_IP_);
do_raw_write_unlock(lock);
@@ -267,6 +282,7 @@ static inline void __raw_write_unlock_irqrestore(rwlock_t *lock,
}

static inline void __raw_write_unlock_irq(rwlock_t *lock)
+ __releases(lock)
{
rwlock_release(&lock->dep_map, _RET_IP_);
do_raw_write_unlock(lock);
@@ -275,6 +291,7 @@ static inline void __raw_write_unlock_irq(rwlock_t *lock)
}

static inline void __raw_write_unlock_bh(rwlock_t *lock)
+ __releases(lock)
{
rwlock_release(&lock->dep_map, _RET_IP_);
do_raw_write_unlock(lock);
diff --git a/include/linux/rwlock_rt.h b/include/linux/rwlock_rt.h
index 7d81fc6918ee..52ef2dc63a96 100644
--- a/include/linux/rwlock_rt.h
+++ b/include/linux/rwlock_rt.h
@@ -22,28 +22,32 @@ do { \
\
init_rwbase_rt(&(rwl)->rwbase); \
__rt_rwlock_init(rwl, #rwl, &__key); \
+ __assume_cap(rwl); \
} while (0)

-extern void rt_read_lock(rwlock_t *rwlock) __acquires(rwlock);
+extern void rt_read_lock(rwlock_t *rwlock) __acquires_shared(rwlock);
extern int rt_read_trylock(rwlock_t *rwlock);
-extern void rt_read_unlock(rwlock_t *rwlock) __releases(rwlock);
+extern void rt_read_unlock(rwlock_t *rwlock) __releases_shared(rwlock);
extern void rt_write_lock(rwlock_t *rwlock) __acquires(rwlock);
extern void rt_write_lock_nested(rwlock_t *rwlock, int subclass) __acquires(rwlock);
extern int rt_write_trylock(rwlock_t *rwlock);
extern void rt_write_unlock(rwlock_t *rwlock) __releases(rwlock);

static __always_inline void read_lock(rwlock_t *rwlock)
+ __acquires_shared(rwlock)
{
rt_read_lock(rwlock);
}

static __always_inline void read_lock_bh(rwlock_t *rwlock)
+ __acquires_shared(rwlock)
{
local_bh_disable();
rt_read_lock(rwlock);
}

static __always_inline void read_lock_irq(rwlock_t *rwlock)
+ __acquires_shared(rwlock)
{
rt_read_lock(rwlock);
}
@@ -55,37 +59,43 @@ static __always_inline void read_lock_irq(rwlock_t *rwlock)
flags = 0; \
} while (0)

-#define read_trylock(lock) __cond_lock(lock, rt_read_trylock(lock))
+#define read_trylock(lock) __cond_lock_shared(lock, rt_read_trylock(lock))

static __always_inline void read_unlock(rwlock_t *rwlock)
+ __releases_shared(rwlock)
{
rt_read_unlock(rwlock);
}

static __always_inline void read_unlock_bh(rwlock_t *rwlock)
+ __releases_shared(rwlock)
{
rt_read_unlock(rwlock);
local_bh_enable();
}

static __always_inline void read_unlock_irq(rwlock_t *rwlock)
+ __releases_shared(rwlock)
{
rt_read_unlock(rwlock);
}

static __always_inline void read_unlock_irqrestore(rwlock_t *rwlock,
unsigned long flags)
+ __releases_shared(rwlock)
{
rt_read_unlock(rwlock);
}

static __always_inline void write_lock(rwlock_t *rwlock)
+ __acquires(rwlock)
{
rt_write_lock(rwlock);
}

#ifdef CONFIG_DEBUG_LOCK_ALLOC
static __always_inline void write_lock_nested(rwlock_t *rwlock, int subclass)
+ __acquires(rwlock)
{
rt_write_lock_nested(rwlock, subclass);
}
@@ -94,12 +104,14 @@ static __always_inline void write_lock_nested(rwlock_t *rwlock, int subclass)
#endif

static __always_inline void write_lock_bh(rwlock_t *rwlock)
+ __acquires(rwlock)
{
local_bh_disable();
rt_write_lock(rwlock);
}

static __always_inline void write_lock_irq(rwlock_t *rwlock)
+ __acquires(rwlock)
{
rt_write_lock(rwlock);
}
@@ -114,33 +126,34 @@ static __always_inline void write_lock_irq(rwlock_t *rwlock)
#define write_trylock(lock) __cond_lock(lock, rt_write_trylock(lock))

#define write_trylock_irqsave(lock, flags) \
-({ \
- int __locked; \
- \
- typecheck(unsigned long, flags); \
- flags = 0; \
- __locked = write_trylock(lock); \
- __locked; \
-})
+ __cond_lock(lock, ({ \
+ typecheck(unsigned long, flags); \
+ flags = 0; \
+ rt_write_trylock(lock); \
+ }))

static __always_inline void write_unlock(rwlock_t *rwlock)
+ __releases(rwlock)
{
rt_write_unlock(rwlock);
}

static __always_inline void write_unlock_bh(rwlock_t *rwlock)
+ __releases(rwlock)
{
rt_write_unlock(rwlock);
local_bh_enable();
}

static __always_inline void write_unlock_irq(rwlock_t *rwlock)
+ __releases(rwlock)
{
rt_write_unlock(rwlock);
}

static __always_inline void write_unlock_irqrestore(rwlock_t *rwlock,
unsigned long flags)
+ __releases(rwlock)
{
rt_write_unlock(rwlock);
}
diff --git a/include/linux/rwlock_types.h b/include/linux/rwlock_types.h
index 1948442e7750..231489cc30f2 100644
--- a/include/linux/rwlock_types.h
+++ b/include/linux/rwlock_types.h
@@ -22,7 +22,7 @@
* portions Copyright 2005, Red Hat, Inc., Ingo Molnar
* Released under the General Public License (GPL).
*/
-typedef struct {
+struct_with_capability(rwlock) {
arch_rwlock_t raw_lock;
#ifdef CONFIG_DEBUG_SPINLOCK
unsigned int magic, owner_cpu;
@@ -31,7 +31,8 @@ typedef struct {
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
-} rwlock_t;
+};
+typedef struct rwlock rwlock_t;

#define RWLOCK_MAGIC 0xdeaf1eed

@@ -54,13 +55,14 @@ typedef struct {

#include <linux/rwbase_rt.h>

-typedef struct {
+struct_with_capability(rwlock) {
struct rwbase_rt rwbase;
atomic_t readers;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
-} rwlock_t;
+};
+typedef struct rwlock rwlock_t;

#define __RWLOCK_RT_INITIALIZER(name) \
{ \
diff --git a/include/linux/spinlock.h b/include/linux/spinlock.h
index d3561c4a080e..7679f39071e9 100644
--- a/include/linux/spinlock.h
+++ b/include/linux/spinlock.h
@@ -106,11 +106,12 @@ do { \
static struct lock_class_key __key; \
\
__raw_spin_lock_init((lock), #lock, &__key, LD_WAIT_SPIN); \
+ __assume_cap(lock); \
} while (0)

#else
# define raw_spin_lock_init(lock) \
- do { *(lock) = __RAW_SPIN_LOCK_UNLOCKED(lock); } while (0)
+ do { *(lock) = __RAW_SPIN_LOCK_UNLOCKED(lock); __assume_cap(lock); } while (0)
#endif

#define raw_spin_is_locked(lock) arch_spin_is_locked(&(lock)->raw_lock)
@@ -286,19 +287,19 @@ static inline void do_raw_spin_unlock(raw_spinlock_t *lock) __releases(lock)
#define raw_spin_trylock_bh(lock) \
__cond_lock(lock, _raw_spin_trylock_bh(lock))

-#define raw_spin_trylock_irq(lock) \
-({ \
- local_irq_disable(); \
- raw_spin_trylock(lock) ? \
- 1 : ({ local_irq_enable(); 0; }); \
-})
+#define raw_spin_trylock_irq(lock) \
+ __cond_lock(lock, ({ \
+ local_irq_disable(); \
+ _raw_spin_trylock(lock) ? \
+ 1 : ({ local_irq_enable(); 0; }); \
+ }))

-#define raw_spin_trylock_irqsave(lock, flags) \
-({ \
- local_irq_save(flags); \
- raw_spin_trylock(lock) ? \
- 1 : ({ local_irq_restore(flags); 0; }); \
-})
+#define raw_spin_trylock_irqsave(lock, flags) \
+ __cond_lock(lock, ({ \
+ local_irq_save(flags); \
+ _raw_spin_trylock(lock) ? \
+ 1 : ({ local_irq_restore(flags); 0; }); \
+ }))

#ifndef CONFIG_PREEMPT_RT
/* Include rwlock functions for !RT */
@@ -334,6 +335,7 @@ do { \
\
__raw_spin_lock_init(spinlock_check(lock), \
#lock, &__key, LD_WAIT_CONFIG); \
+ __assume_cap(lock); \
} while (0)

#else
@@ -342,21 +344,25 @@ do { \
do { \
spinlock_check(_lock); \
*(_lock) = __SPIN_LOCK_UNLOCKED(_lock); \
+ __assume_cap(_lock); \
} while (0)

#endif

static __always_inline void spin_lock(spinlock_t *lock)
+ __acquires(lock) __no_capability_analysis
{
raw_spin_lock(&lock->rlock);
}

static __always_inline void spin_lock_bh(spinlock_t *lock)
+ __acquires(lock) __no_capability_analysis
{
raw_spin_lock_bh(&lock->rlock);
}

static __always_inline int spin_trylock(spinlock_t *lock)
+ __cond_acquires(lock) __no_capability_analysis
{
return raw_spin_trylock(&lock->rlock);
}
@@ -364,14 +370,17 @@ static __always_inline int spin_trylock(spinlock_t *lock)
#define spin_lock_nested(lock, subclass) \
do { \
raw_spin_lock_nested(spinlock_check(lock), subclass); \
+ __release(spinlock_check(lock)); __acquire(lock); \
} while (0)

#define spin_lock_nest_lock(lock, nest_lock) \
do { \
raw_spin_lock_nest_lock(spinlock_check(lock), nest_lock); \
+ __release(spinlock_check(lock)); __acquire(lock); \
} while (0)

static __always_inline void spin_lock_irq(spinlock_t *lock)
+ __acquires(lock) __no_capability_analysis
{
raw_spin_lock_irq(&lock->rlock);
}
@@ -379,47 +388,53 @@ static __always_inline void spin_lock_irq(spinlock_t *lock)
#define spin_lock_irqsave(lock, flags) \
do { \
raw_spin_lock_irqsave(spinlock_check(lock), flags); \
+ __release(spinlock_check(lock)); __acquire(lock); \
} while (0)

#define spin_lock_irqsave_nested(lock, flags, subclass) \
do { \
raw_spin_lock_irqsave_nested(spinlock_check(lock), flags, subclass); \
+ __release(spinlock_check(lock)); __acquire(lock); \
} while (0)

static __always_inline void spin_unlock(spinlock_t *lock)
+ __releases(lock) __no_capability_analysis
{
raw_spin_unlock(&lock->rlock);
}

static __always_inline void spin_unlock_bh(spinlock_t *lock)
+ __releases(lock) __no_capability_analysis
{
raw_spin_unlock_bh(&lock->rlock);
}

static __always_inline void spin_unlock_irq(spinlock_t *lock)
+ __releases(lock) __no_capability_analysis
{
raw_spin_unlock_irq(&lock->rlock);
}

static __always_inline void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)
+ __releases(lock) __no_capability_analysis
{
raw_spin_unlock_irqrestore(&lock->rlock, flags);
}

static __always_inline int spin_trylock_bh(spinlock_t *lock)
+ __cond_acquires(lock) __no_capability_analysis
{
return raw_spin_trylock_bh(&lock->rlock);
}

static __always_inline int spin_trylock_irq(spinlock_t *lock)
+ __cond_acquires(lock) __no_capability_analysis
{
return raw_spin_trylock_irq(&lock->rlock);
}

#define spin_trylock_irqsave(lock, flags) \
-({ \
- raw_spin_trylock_irqsave(spinlock_check(lock), flags); \
-})
+ __cond_lock(lock, raw_spin_trylock_irqsave(spinlock_check(lock), flags))

/**
* spin_is_locked() - Check whether a spinlock is locked.
@@ -535,86 +550,109 @@ void free_bucket_spinlocks(spinlock_t *locks);
DEFINE_LOCK_GUARD_1(raw_spinlock, raw_spinlock_t,
raw_spin_lock(_T->lock),
raw_spin_unlock(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(raw_spinlock, __assumes_cap(_T), /* */)

DEFINE_LOCK_GUARD_1_COND(raw_spinlock, _try, raw_spin_trylock(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(raw_spinlock_try, __assumes_cap(_T), /* */)

DEFINE_LOCK_GUARD_1(raw_spinlock_nested, raw_spinlock_t,
raw_spin_lock_nested(_T->lock, SINGLE_DEPTH_NESTING),
raw_spin_unlock(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(raw_spinlock_nested, __assumes_cap(_T), /* */)

DEFINE_LOCK_GUARD_1(raw_spinlock_irq, raw_spinlock_t,
raw_spin_lock_irq(_T->lock),
raw_spin_unlock_irq(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(raw_spinlock_irq, __assumes_cap(_T), /* */)

DEFINE_LOCK_GUARD_1_COND(raw_spinlock_irq, _try, raw_spin_trylock_irq(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(raw_spinlock_irq_try, __assumes_cap(_T), /* */)

DEFINE_LOCK_GUARD_1(raw_spinlock_bh, raw_spinlock_t,
raw_spin_lock_bh(_T->lock),
raw_spin_unlock_bh(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(raw_spinlock_bh, __assumes_cap(_T), /* */)

DEFINE_LOCK_GUARD_1_COND(raw_spinlock_bh, _try, raw_spin_trylock_bh(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(raw_spinlock_bh_try, __assumes_cap(_T), /* */)

DEFINE_LOCK_GUARD_1(raw_spinlock_irqsave, raw_spinlock_t,
raw_spin_lock_irqsave(_T->lock, _T->flags),
raw_spin_unlock_irqrestore(_T->lock, _T->flags),
unsigned long flags)
+DECLARE_LOCK_GUARD_1_ATTRS(raw_spinlock_irqsave, __assumes_cap(_T), /* */)

DEFINE_LOCK_GUARD_1_COND(raw_spinlock_irqsave, _try,
raw_spin_trylock_irqsave(_T->lock, _T->flags))
+DECLARE_LOCK_GUARD_1_ATTRS(raw_spinlock_irqsave_try, __assumes_cap(_T), /* */)

DEFINE_LOCK_GUARD_1(spinlock, spinlock_t,
spin_lock(_T->lock),
spin_unlock(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(spinlock, __assumes_cap(_T), /* */)

DEFINE_LOCK_GUARD_1_COND(spinlock, _try, spin_trylock(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(spinlock_try, __assumes_cap(_T), /* */)

DEFINE_LOCK_GUARD_1(spinlock_irq, spinlock_t,
spin_lock_irq(_T->lock),
spin_unlock_irq(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(spinlock_irq, __assumes_cap(_T), /* */)

DEFINE_LOCK_GUARD_1_COND(spinlock_irq, _try,
spin_trylock_irq(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(spinlock_irq_try, __assumes_cap(_T), /* */)

DEFINE_LOCK_GUARD_1(spinlock_bh, spinlock_t,
spin_lock_bh(_T->lock),
spin_unlock_bh(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(spinlock_bh, __assumes_cap(_T), /* */)

DEFINE_LOCK_GUARD_1_COND(spinlock_bh, _try,
spin_trylock_bh(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(spinlock_bh_try, __assumes_cap(_T), /* */)

DEFINE_LOCK_GUARD_1(spinlock_irqsave, spinlock_t,
spin_lock_irqsave(_T->lock, _T->flags),
spin_unlock_irqrestore(_T->lock, _T->flags),
unsigned long flags)
+DECLARE_LOCK_GUARD_1_ATTRS(spinlock_irqsave, __assumes_cap(_T), /* */)

DEFINE_LOCK_GUARD_1_COND(spinlock_irqsave, _try,
spin_trylock_irqsave(_T->lock, _T->flags))
+DECLARE_LOCK_GUARD_1_ATTRS(spinlock_irqsave_try, __assumes_cap(_T), /* */)

DEFINE_LOCK_GUARD_1(read_lock, rwlock_t,
read_lock(_T->lock),
read_unlock(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(read_lock, __assumes_cap(_T), /* */)

DEFINE_LOCK_GUARD_1(read_lock_irq, rwlock_t,
read_lock_irq(_T->lock),
read_unlock_irq(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(read_lock_irq, __assumes_cap(_T), /* */)

DEFINE_LOCK_GUARD_1(read_lock_irqsave, rwlock_t,
read_lock_irqsave(_T->lock, _T->flags),
read_unlock_irqrestore(_T->lock, _T->flags),
unsigned long flags)
+DECLARE_LOCK_GUARD_1_ATTRS(read_lock_irqsave, __assumes_cap(_T), /* */)

DEFINE_LOCK_GUARD_1(write_lock, rwlock_t,
write_lock(_T->lock),
write_unlock(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(write_lock, __assumes_cap(_T), /* */)

DEFINE_LOCK_GUARD_1(write_lock_irq, rwlock_t,
write_lock_irq(_T->lock),
write_unlock_irq(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(write_lock_irq, __assumes_cap(_T), /* */)

DEFINE_LOCK_GUARD_1(write_lock_irqsave, rwlock_t,
write_lock_irqsave(_T->lock, _T->flags),
write_unlock_irqrestore(_T->lock, _T->flags),
unsigned long flags)
+DECLARE_LOCK_GUARD_1_ATTRS(write_lock_irqsave, __assumes_cap(_T), /* */)

#undef __LINUX_INSIDE_SPINLOCK_H
#endif /* __LINUX_SPINLOCK_H */
diff --git a/include/linux/spinlock_api_smp.h b/include/linux/spinlock_api_smp.h
index 9ecb0ab504e3..fab02d8bf0c9 100644
--- a/include/linux/spinlock_api_smp.h
+++ b/include/linux/spinlock_api_smp.h
@@ -34,8 +34,8 @@ unsigned long __lockfunc _raw_spin_lock_irqsave(raw_spinlock_t *lock)
unsigned long __lockfunc
_raw_spin_lock_irqsave_nested(raw_spinlock_t *lock, int subclass)
__acquires(lock);
-int __lockfunc _raw_spin_trylock(raw_spinlock_t *lock);
-int __lockfunc _raw_spin_trylock_bh(raw_spinlock_t *lock);
+int __lockfunc _raw_spin_trylock(raw_spinlock_t *lock) __cond_acquires(lock);
+int __lockfunc _raw_spin_trylock_bh(raw_spinlock_t *lock) __cond_acquires(lock);
void __lockfunc _raw_spin_unlock(raw_spinlock_t *lock) __releases(lock);
void __lockfunc _raw_spin_unlock_bh(raw_spinlock_t *lock) __releases(lock);
void __lockfunc _raw_spin_unlock_irq(raw_spinlock_t *lock) __releases(lock);
@@ -84,6 +84,7 @@ _raw_spin_unlock_irqrestore(raw_spinlock_t *lock, unsigned long flags)
#endif

static inline int __raw_spin_trylock(raw_spinlock_t *lock)
+ __cond_acquires(lock)
{
preempt_disable();
if (do_raw_spin_trylock(lock)) {
@@ -102,6 +103,7 @@ static inline int __raw_spin_trylock(raw_spinlock_t *lock)
#if !defined(CONFIG_GENERIC_LOCKBREAK) || defined(CONFIG_DEBUG_LOCK_ALLOC)

static inline unsigned long __raw_spin_lock_irqsave(raw_spinlock_t *lock)
+ __acquires(lock) __no_capability_analysis
{
unsigned long flags;

@@ -113,6 +115,7 @@ static inline unsigned long __raw_spin_lock_irqsave(raw_spinlock_t *lock)
}

static inline void __raw_spin_lock_irq(raw_spinlock_t *lock)
+ __acquires(lock) __no_capability_analysis
{
local_irq_disable();
preempt_disable();
@@ -121,6 +124,7 @@ static inline void __raw_spin_lock_irq(raw_spinlock_t *lock)
}

static inline void __raw_spin_lock_bh(raw_spinlock_t *lock)
+ __acquires(lock) __no_capability_analysis
{
__local_bh_disable_ip(_RET_IP_, SOFTIRQ_LOCK_OFFSET);
spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
@@ -128,6 +132,7 @@ static inline void __raw_spin_lock_bh(raw_spinlock_t *lock)
}

static inline void __raw_spin_lock(raw_spinlock_t *lock)
+ __acquires(lock) __no_capability_analysis
{
preempt_disable();
spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
@@ -137,6 +142,7 @@ static inline void __raw_spin_lock(raw_spinlock_t *lock)
#endif /* !CONFIG_GENERIC_LOCKBREAK || CONFIG_DEBUG_LOCK_ALLOC */

static inline void __raw_spin_unlock(raw_spinlock_t *lock)
+ __releases(lock)
{
spin_release(&lock->dep_map, _RET_IP_);
do_raw_spin_unlock(lock);
@@ -145,6 +151,7 @@ static inline void __raw_spin_unlock(raw_spinlock_t *lock)

static inline void __raw_spin_unlock_irqrestore(raw_spinlock_t *lock,
unsigned long flags)
+ __releases(lock)
{
spin_release(&lock->dep_map, _RET_IP_);
do_raw_spin_unlock(lock);
@@ -153,6 +160,7 @@ static inline void __raw_spin_unlock_irqrestore(raw_spinlock_t *lock,
}

static inline void __raw_spin_unlock_irq(raw_spinlock_t *lock)
+ __releases(lock)
{
spin_release(&lock->dep_map, _RET_IP_);
do_raw_spin_unlock(lock);
@@ -161,6 +169,7 @@ static inline void __raw_spin_unlock_irq(raw_spinlock_t *lock)
}

static inline void __raw_spin_unlock_bh(raw_spinlock_t *lock)
+ __releases(lock)
{
spin_release(&lock->dep_map, _RET_IP_);
do_raw_spin_unlock(lock);
@@ -168,6 +177,7 @@ static inline void __raw_spin_unlock_bh(raw_spinlock_t *lock)
}

static inline int __raw_spin_trylock_bh(raw_spinlock_t *lock)
+ __cond_acquires(lock)
{
__local_bh_disable_ip(_RET_IP_, SOFTIRQ_LOCK_OFFSET);
if (do_raw_spin_trylock(lock)) {
diff --git a/include/linux/spinlock_api_up.h b/include/linux/spinlock_api_up.h
index 819aeba1c87e..018f5aabc1be 100644
--- a/include/linux/spinlock_api_up.h
+++ b/include/linux/spinlock_api_up.h
@@ -24,68 +24,77 @@
* flags straight, to suppress compiler warnings of unused lock
* variables, and to add the proper checker annotations:
*/
-#define ___LOCK(lock) \
- do { __acquire(lock); (void)(lock); } while (0)
+#define ___LOCK_void(lock) \
+ do { (void)(lock); } while (0)

-#define __LOCK(lock) \
- do { preempt_disable(); ___LOCK(lock); } while (0)
+#define ___LOCK_(lock) \
+ do { __acquire(lock); ___LOCK_void(lock); } while (0)

-#define __LOCK_BH(lock) \
- do { __local_bh_disable_ip(_THIS_IP_, SOFTIRQ_LOCK_OFFSET); ___LOCK(lock); } while (0)
+#define ___LOCK_shared(lock) \
+ do { __acquire_shared(lock); ___LOCK_void(lock); } while (0)

-#define __LOCK_IRQ(lock) \
- do { local_irq_disable(); __LOCK(lock); } while (0)
+#define __LOCK(lock, ...) \
+ do { preempt_disable(); ___LOCK_##__VA_ARGS__(lock); } while (0)

-#define __LOCK_IRQSAVE(lock, flags) \
- do { local_irq_save(flags); __LOCK(lock); } while (0)
+#define __LOCK_BH(lock, ...) \
+ do { __local_bh_disable_ip(_THIS_IP_, SOFTIRQ_LOCK_OFFSET); ___LOCK_##__VA_ARGS__(lock); } while (0)

-#define ___UNLOCK(lock) \
+#define __LOCK_IRQ(lock, ...) \
+ do { local_irq_disable(); __LOCK(lock, ##__VA_ARGS__); } while (0)
+
+#define __LOCK_IRQSAVE(lock, flags, ...) \
+ do { local_irq_save(flags); __LOCK(lock, ##__VA_ARGS__); } while (0)
+
+#define ___UNLOCK_(lock) \
do { __release(lock); (void)(lock); } while (0)

-#define __UNLOCK(lock) \
- do { preempt_enable(); ___UNLOCK(lock); } while (0)
+#define ___UNLOCK_shared(lock) \
+ do { __release_shared(lock); (void)(lock); } while (0)

-#define __UNLOCK_BH(lock) \
+#define __UNLOCK(lock, ...) \
+ do { preempt_enable(); ___UNLOCK_##__VA_ARGS__(lock); } while (0)
+
+#define __UNLOCK_BH(lock, ...) \
do { __local_bh_enable_ip(_THIS_IP_, SOFTIRQ_LOCK_OFFSET); \
- ___UNLOCK(lock); } while (0)
+ ___UNLOCK_##__VA_ARGS__(lock); } while (0)

-#define __UNLOCK_IRQ(lock) \
- do { local_irq_enable(); __UNLOCK(lock); } while (0)
+#define __UNLOCK_IRQ(lock, ...) \
+ do { local_irq_enable(); __UNLOCK(lock, ##__VA_ARGS__); } while (0)

-#define __UNLOCK_IRQRESTORE(lock, flags) \
- do { local_irq_restore(flags); __UNLOCK(lock); } while (0)
+#define __UNLOCK_IRQRESTORE(lock, flags, ...) \
+ do { local_irq_restore(flags); __UNLOCK(lock, ##__VA_ARGS__); } while (0)

#define _raw_spin_lock(lock) __LOCK(lock)
#define _raw_spin_lock_nested(lock, subclass) __LOCK(lock)
-#define _raw_read_lock(lock) __LOCK(lock)
+#define _raw_read_lock(lock) __LOCK(lock, shared)
#define _raw_write_lock(lock) __LOCK(lock)
#define _raw_write_lock_nested(lock, subclass) __LOCK(lock)
#define _raw_spin_lock_bh(lock) __LOCK_BH(lock)
-#define _raw_read_lock_bh(lock) __LOCK_BH(lock)
+#define _raw_read_lock_bh(lock) __LOCK_BH(lock, shared)
#define _raw_write_lock_bh(lock) __LOCK_BH(lock)
#define _raw_spin_lock_irq(lock) __LOCK_IRQ(lock)
-#define _raw_read_lock_irq(lock) __LOCK_IRQ(lock)
+#define _raw_read_lock_irq(lock) __LOCK_IRQ(lock, shared)
#define _raw_write_lock_irq(lock) __LOCK_IRQ(lock)
#define _raw_spin_lock_irqsave(lock, flags) __LOCK_IRQSAVE(lock, flags)
-#define _raw_read_lock_irqsave(lock, flags) __LOCK_IRQSAVE(lock, flags)
+#define _raw_read_lock_irqsave(lock, flags) __LOCK_IRQSAVE(lock, flags, shared)
#define _raw_write_lock_irqsave(lock, flags) __LOCK_IRQSAVE(lock, flags)
-#define _raw_spin_trylock(lock) ({ __LOCK(lock); 1; })
-#define _raw_read_trylock(lock) ({ __LOCK(lock); 1; })
-#define _raw_write_trylock(lock) ({ __LOCK(lock); 1; })
-#define _raw_spin_trylock_bh(lock) ({ __LOCK_BH(lock); 1; })
+#define _raw_spin_trylock(lock) ({ __LOCK(lock, void); 1; })
+#define _raw_read_trylock(lock) ({ __LOCK(lock, void); 1; })
+#define _raw_write_trylock(lock) ({ __LOCK(lock, void); 1; })
+#define _raw_spin_trylock_bh(lock) ({ __LOCK_BH(lock, void); 1; })
#define _raw_spin_unlock(lock) __UNLOCK(lock)
-#define _raw_read_unlock(lock) __UNLOCK(lock)
+#define _raw_read_unlock(lock) __UNLOCK(lock, shared)
#define _raw_write_unlock(lock) __UNLOCK(lock)
#define _raw_spin_unlock_bh(lock) __UNLOCK_BH(lock)
#define _raw_write_unlock_bh(lock) __UNLOCK_BH(lock)
-#define _raw_read_unlock_bh(lock) __UNLOCK_BH(lock)
+#define _raw_read_unlock_bh(lock) __UNLOCK_BH(lock, shared)
#define _raw_spin_unlock_irq(lock) __UNLOCK_IRQ(lock)
-#define _raw_read_unlock_irq(lock) __UNLOCK_IRQ(lock)
+#define _raw_read_unlock_irq(lock) __UNLOCK_IRQ(lock, shared)
#define _raw_write_unlock_irq(lock) __UNLOCK_IRQ(lock)
#define _raw_spin_unlock_irqrestore(lock, flags) \
__UNLOCK_IRQRESTORE(lock, flags)
#define _raw_read_unlock_irqrestore(lock, flags) \
- __UNLOCK_IRQRESTORE(lock, flags)
+ __UNLOCK_IRQRESTORE(lock, flags, shared)
#define _raw_write_unlock_irqrestore(lock, flags) \
__UNLOCK_IRQRESTORE(lock, flags)

diff --git a/include/linux/spinlock_rt.h b/include/linux/spinlock_rt.h
index f6499c37157d..9688675b7536 100644
--- a/include/linux/spinlock_rt.h
+++ b/include/linux/spinlock_rt.h
@@ -20,6 +20,7 @@ static inline void __rt_spin_lock_init(spinlock_t *lock, const char *name,
do { \
rt_mutex_base_init(&(slock)->lock); \
__rt_spin_lock_init(slock, name, key, percpu); \
+ __assume_cap(slock); \
} while (0)

#define _spin_lock_init(slock, percpu) \
@@ -40,6 +41,7 @@ extern int rt_spin_trylock_bh(spinlock_t *lock);
extern int rt_spin_trylock(spinlock_t *lock);

static __always_inline void spin_lock(spinlock_t *lock)
+ __acquires(lock)
{
rt_spin_lock(lock);
}
@@ -82,6 +84,7 @@ static __always_inline void spin_lock(spinlock_t *lock)
__spin_lock_irqsave_nested(lock, flags, subclass)

static __always_inline void spin_lock_bh(spinlock_t *lock)
+ __acquires(lock)
{
/* Investigate: Drop bh when blocking ? */
local_bh_disable();
@@ -89,6 +92,7 @@ static __always_inline void spin_lock_bh(spinlock_t *lock)
}

static __always_inline void spin_lock_irq(spinlock_t *lock)
+ __acquires(lock)
{
rt_spin_lock(lock);
}
@@ -101,23 +105,27 @@ static __always_inline void spin_lock_irq(spinlock_t *lock)
} while (0)

static __always_inline void spin_unlock(spinlock_t *lock)
+ __releases(lock)
{
rt_spin_unlock(lock);
}

static __always_inline void spin_unlock_bh(spinlock_t *lock)
+ __releases(lock)
{
rt_spin_unlock(lock);
local_bh_enable();
}

static __always_inline void spin_unlock_irq(spinlock_t *lock)
+ __releases(lock)
{
rt_spin_unlock(lock);
}

static __always_inline void spin_unlock_irqrestore(spinlock_t *lock,
unsigned long flags)
+ __releases(lock)
{
rt_spin_unlock(lock);
}
@@ -132,14 +140,11 @@ static __always_inline void spin_unlock_irqrestore(spinlock_t *lock,
__cond_lock(lock, rt_spin_trylock(lock))

#define spin_trylock_irqsave(lock, flags) \
-({ \
- int __locked; \
- \
- typecheck(unsigned long, flags); \
- flags = 0; \
- __locked = spin_trylock(lock); \
- __locked; \
-})
+ __cond_lock(lock, ({ \
+ typecheck(unsigned long, flags); \
+ flags = 0; \
+ rt_spin_trylock(lock); \
+ }))

#define spin_is_contended(lock) (((void)(lock), 0))

diff --git a/include/linux/spinlock_types.h b/include/linux/spinlock_types.h
index 2dfa35ffec76..2c5db5b5b990 100644
--- a/include/linux/spinlock_types.h
+++ b/include/linux/spinlock_types.h
@@ -14,7 +14,7 @@
#ifndef CONFIG_PREEMPT_RT

/* Non PREEMPT_RT kernels map spinlock to raw_spinlock */
-typedef struct spinlock {
+struct_with_capability(spinlock) {
union {
struct raw_spinlock rlock;

@@ -26,7 +26,8 @@ typedef struct spinlock {
};
#endif
};
-} spinlock_t;
+};
+typedef struct spinlock spinlock_t;

#define ___SPIN_LOCK_INITIALIZER(lockname) \
{ \
@@ -47,12 +48,13 @@ typedef struct spinlock {
/* PREEMPT_RT kernels map spinlock to rt_mutex */
#include <linux/rtmutex.h>

-typedef struct spinlock {
+struct_with_capability(spinlock) {
struct rt_mutex_base lock;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
-} spinlock_t;
+};
+typedef struct spinlock spinlock_t;

#define __SPIN_LOCK_UNLOCKED(name) \
{ \
diff --git a/include/linux/spinlock_types_raw.h b/include/linux/spinlock_types_raw.h
index 91cb36b65a17..07792ff2c2b5 100644
--- a/include/linux/spinlock_types_raw.h
+++ b/include/linux/spinlock_types_raw.h
@@ -11,7 +11,7 @@

#include <linux/lockdep_types.h>

-typedef struct raw_spinlock {
+struct_with_capability(raw_spinlock) {
arch_spinlock_t raw_lock;
#ifdef CONFIG_DEBUG_SPINLOCK
unsigned int magic, owner_cpu;
@@ -20,7 +20,8 @@ typedef struct raw_spinlock {
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
-} raw_spinlock_t;
+};
+typedef struct raw_spinlock raw_spinlock_t;

#define SPINLOCK_MAGIC 0xdead4ead

diff --git a/lib/test_capability-analysis.c b/lib/test_capability-analysis.c
index a0adacce30ff..84060bace61d 100644
--- a/lib/test_capability-analysis.c
+++ b/lib/test_capability-analysis.c
@@ -5,6 +5,7 @@
*/

#include <linux/build_bug.h>
+#include <linux/spinlock.h>

/*
* Test that helper macros work as expected.
@@ -16,3 +17,130 @@ static void __used test_common_helpers(void)
BUILD_BUG_ON(capability_unsafe((void)2, 3) != 3); /* does not swallow commas */
capability_unsafe(do { } while (0)); /* works with void statements */
}
+
+#define TEST_SPINLOCK_COMMON(class, type, type_init, type_lock, type_unlock, type_trylock, op) \
+ struct test_##class##_data { \
+ type lock; \
+ int counter __guarded_by(&lock); \
+ int *pointer __pt_guarded_by(&lock); \
+ }; \
+ static void __used test_##class##_init(struct test_##class##_data *d) \
+ { \
+ type_init(&d->lock); \
+ d->counter = 0; \
+ } \
+ static void __used test_##class(struct test_##class##_data *d) \
+ { \
+ unsigned long flags; \
+ d->pointer++; \
+ type_lock(&d->lock); \
+ op(d->counter); \
+ op(*d->pointer); \
+ type_unlock(&d->lock); \
+ type_lock##_irq(&d->lock); \
+ op(d->counter); \
+ op(*d->pointer); \
+ type_unlock##_irq(&d->lock); \
+ type_lock##_bh(&d->lock); \
+ op(d->counter); \
+ op(*d->pointer); \
+ type_unlock##_bh(&d->lock); \
+ type_lock##_irqsave(&d->lock, flags); \
+ op(d->counter); \
+ op(*d->pointer); \
+ type_unlock##_irqrestore(&d->lock, flags); \
+ } \
+ static void __used test_##class##_trylock(struct test_##class##_data *d) \
+ { \
+ if (type_trylock(&d->lock)) { \
+ op(d->counter); \
+ type_unlock(&d->lock); \
+ } \
+ } \
+ static void __used test_##class##_assert(struct test_##class##_data *d) \
+ { \
+ lockdep_assert_held(&d->lock); \
+ op(d->counter); \
+ } \
+ static void __used test_##class##_guard(struct test_##class##_data *d) \
+ { \
+ { guard(class)(&d->lock); op(d->counter); } \
+ { guard(class##_irq)(&d->lock); op(d->counter); } \
+ { guard(class##_irqsave)(&d->lock); op(d->counter); } \
+ }
+
+#define TEST_OP_RW(x) (x)++
+#define TEST_OP_RO(x) ((void)(x))
+
+TEST_SPINLOCK_COMMON(raw_spinlock,
+ raw_spinlock_t,
+ raw_spin_lock_init,
+ raw_spin_lock,
+ raw_spin_unlock,
+ raw_spin_trylock,
+ TEST_OP_RW);
+static void __used test_raw_spinlock_trylock_extra(struct test_raw_spinlock_data *d)
+{
+ unsigned long flags;
+
+ if (raw_spin_trylock_irq(&d->lock)) {
+ d->counter++;
+ raw_spin_unlock_irq(&d->lock);
+ }
+ if (raw_spin_trylock_irqsave(&d->lock, flags)) {
+ d->counter++;
+ raw_spin_unlock_irqrestore(&d->lock, flags);
+ }
+ scoped_cond_guard(raw_spinlock_try, return, &d->lock) {
+ d->counter++;
+ }
+}
+
+TEST_SPINLOCK_COMMON(spinlock,
+ spinlock_t,
+ spin_lock_init,
+ spin_lock,
+ spin_unlock,
+ spin_trylock,
+ TEST_OP_RW);
+static void __used test_spinlock_trylock_extra(struct test_spinlock_data *d)
+{
+ unsigned long flags;
+
+ if (spin_trylock_irq(&d->lock)) {
+ d->counter++;
+ spin_unlock_irq(&d->lock);
+ }
+ if (spin_trylock_irqsave(&d->lock, flags)) {
+ d->counter++;
+ spin_unlock_irqrestore(&d->lock, flags);
+ }
+ scoped_cond_guard(spinlock_try, return, &d->lock) {
+ d->counter++;
+ }
+}
+
+TEST_SPINLOCK_COMMON(write_lock,
+ rwlock_t,
+ rwlock_init,
+ write_lock,
+ write_unlock,
+ write_trylock,
+ TEST_OP_RW);
+static void __used test_write_trylock_extra(struct test_write_lock_data *d)
+{
+ unsigned long flags;
+
+ if (write_trylock_irqsave(&d->lock, flags)) {
+ d->counter++;
+ write_unlock_irqrestore(&d->lock, flags);
+ }
+}
+
+TEST_SPINLOCK_COMMON(read_lock,
+ rwlock_t,
+ rwlock_init,
+ read_lock,
+ read_unlock,
+ read_trylock,
+ TEST_OP_RO);
--
2.51.0.384.g4c02a37b29-goog

Marco Elver

unread,
Sep 18, 2025, 10:05:55 AM (6 days ago) Sep 18
to el...@google.com, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
While Sparse is oblivious to the return value of conditional acquire
functions, Clang's capability analysis needs to know the return value
which indicates successful acquisition.

Add the additional argument, and convert existing uses.

Notably, Clang's interpretation of the value merely relates to the use
in a later conditional branch, i.e. 1 ==> capability acquired in branch
taken if condition non-zero, and 0 ==> capability acquired in branch
taken if condition is zero. Given the precise value does not matter,
introduce symbolic variants to use instead of either 0 or 1, which
should be more intuitive.

No functional change intended.

Signed-off-by: Marco Elver <el...@google.com>
---
v2:
* Use symbolic values for __cond_acquires() and __cond_acquires_shared()
(suggested by Bart).
---
fs/dlm/lock.c | 2 +-
include/linux/compiler-capability-analysis.h | 31 ++++++++++++++++----
include/linux/refcount.h | 6 ++--
include/linux/spinlock.h | 6 ++--
include/linux/spinlock_api_smp.h | 8 ++---
net/ipv4/tcp_sigpool.c | 2 +-
6 files changed, 38 insertions(+), 17 deletions(-)

diff --git a/fs/dlm/lock.c b/fs/dlm/lock.c
index 6dd3a524cd35..006eb284c8a7 100644
--- a/fs/dlm/lock.c
+++ b/fs/dlm/lock.c
@@ -343,7 +343,7 @@ void dlm_hold_rsb(struct dlm_rsb *r)
/* TODO move this to lib/refcount.c */
static __must_check bool
dlm_refcount_dec_and_write_lock_bh(refcount_t *r, rwlock_t *lock)
-__cond_acquires(lock)
+ __cond_acquires(true, lock)
{
if (refcount_dec_not_one(r))
return false;
diff --git a/include/linux/compiler-capability-analysis.h b/include/linux/compiler-capability-analysis.h
index 6f3f185478bc..ccd312dbbf06 100644
--- a/include/linux/compiler-capability-analysis.h
+++ b/include/linux/compiler-capability-analysis.h
@@ -257,7 +257,7 @@ static inline void _capability_unsafe_alias(void **p) { }
# define __must_hold(x) __attribute__((context(x,1,1)))
# define __must_not_hold(x)
# define __acquires(x) __attribute__((context(x,0,1)))
-# define __cond_acquires(x) __attribute__((context(x,0,-1)))
+# define __cond_acquires(ret, x) __attribute__((context(x,0,-1)))
# define __releases(x) __attribute__((context(x,1,0)))
# define __acquire(x) __context__(x,1)
# define __release(x) __context__(x,-1)
@@ -300,15 +300,32 @@ static inline void _capability_unsafe_alias(void **p) { }
*/
# define __acquires(x) __acquires_cap(x)

+/*
+ * Clang's analysis does not care precisely about the value, only that it is
+ * either zero or non-zero. So the __cond_acquires() interface might be
+ * misleading if we say that @ret is the value returned if acquired. Instead,
+ * provide symbolic variants which we translate.
+ */
+#define __cond_acquires_impl_true(x, ...) __try_acquires##__VA_ARGS__##_cap(1, x)
+#define __cond_acquires_impl_false(x, ...) __try_acquires##__VA_ARGS__##_cap(0, x)
+#define __cond_acquires_impl_nonzero(x, ...) __try_acquires##__VA_ARGS__##_cap(1, x)
+#define __cond_acquires_impl_0(x, ...) __try_acquires##__VA_ARGS__##_cap(0, x)
+#define __cond_acquires_impl_nonnull(x, ...) __try_acquires##__VA_ARGS__##_cap(1, x)
+#define __cond_acquires_impl_NULL(x, ...) __try_acquires##__VA_ARGS__##_cap(0, x)
+
/**
* __cond_acquires() - function attribute, function conditionally
* acquires a capability exclusively
+ * @ret: abstract value returned by function if capability acquired
* @x: capability instance pointer
*
* Function attribute declaring that the function conditionally acquires the
- * given capability instance @x exclusively, but does not release it.
+ * given capability instance @x exclusively, but does not release it. The
+ * function return value @ret denotes when the capability is acquired.
+ *
+ * @ret may be one of: true, false, nonzero, 0, nonnull, NULL.
*/
-# define __cond_acquires(x) __try_acquires_cap(1, x)
+# define __cond_acquires(ret, x) __cond_acquires_impl_##ret(x)

/**
* __releases() - function attribute, function releases a capability exclusively
@@ -375,12 +392,16 @@ static inline void _capability_unsafe_alias(void **p) { }
/**
* __cond_acquires_shared() - function attribute, function conditionally
* acquires a capability shared
+ * @ret: abstract value returned by function if capability acquired
* @x: capability instance pointer
*
* Function attribute declaring that the function conditionally acquires the
- * given capability instance @x with shared access, but does not release it.
+ * given capability instance @x with shared access, but does not release it. The
+ * function return value @ret denotes when the capability is acquired.
+ *
+ * @ret may be one of: true, false, nonzero, 0, nonnull, NULL.
*/
-# define __cond_acquires_shared(x) __try_acquires_shared_cap(1, x)
+# define __cond_acquires_shared(ret, x) __cond_acquires_impl_##ret(x, _shared)

/**
* __releases_shared() - function attribute, function releases a
diff --git a/include/linux/refcount.h b/include/linux/refcount.h
index 80dc023ac2bf..3da377ffb0c2 100644
--- a/include/linux/refcount.h
+++ b/include/linux/refcount.h
@@ -478,9 +478,9 @@ static inline void refcount_dec(refcount_t *r)

extern __must_check bool refcount_dec_if_one(refcount_t *r);
extern __must_check bool refcount_dec_not_one(refcount_t *r);
-extern __must_check bool refcount_dec_and_mutex_lock(refcount_t *r, struct mutex *lock) __cond_acquires(lock);
-extern __must_check bool refcount_dec_and_lock(refcount_t *r, spinlock_t *lock) __cond_acquires(lock);
+extern __must_check bool refcount_dec_and_mutex_lock(refcount_t *r, struct mutex *lock) __cond_acquires(true, lock);
+extern __must_check bool refcount_dec_and_lock(refcount_t *r, spinlock_t *lock) __cond_acquires(true, lock);
extern __must_check bool refcount_dec_and_lock_irqsave(refcount_t *r,
spinlock_t *lock,
- unsigned long *flags) __cond_acquires(lock);
+ unsigned long *flags) __cond_acquires(true, lock);
#endif /* _LINUX_REFCOUNT_H */
diff --git a/include/linux/spinlock.h b/include/linux/spinlock.h
index 7679f39071e9..22295a126c3a 100644
--- a/include/linux/spinlock.h
+++ b/include/linux/spinlock.h
@@ -362,7 +362,7 @@ static __always_inline void spin_lock_bh(spinlock_t *lock)
}

static __always_inline int spin_trylock(spinlock_t *lock)
- __cond_acquires(lock) __no_capability_analysis
+ __cond_acquires(true, lock) __no_capability_analysis
{
return raw_spin_trylock(&lock->rlock);
}
@@ -422,13 +422,13 @@ static __always_inline void spin_unlock_irqrestore(spinlock_t *lock, unsigned lo
}

static __always_inline int spin_trylock_bh(spinlock_t *lock)
- __cond_acquires(lock) __no_capability_analysis
+ __cond_acquires(true, lock) __no_capability_analysis
{
return raw_spin_trylock_bh(&lock->rlock);
}

static __always_inline int spin_trylock_irq(spinlock_t *lock)
- __cond_acquires(lock) __no_capability_analysis
+ __cond_acquires(true, lock) __no_capability_analysis
{
return raw_spin_trylock_irq(&lock->rlock);
}
diff --git a/include/linux/spinlock_api_smp.h b/include/linux/spinlock_api_smp.h
index fab02d8bf0c9..a77b76003ebb 100644
--- a/include/linux/spinlock_api_smp.h
+++ b/include/linux/spinlock_api_smp.h
@@ -34,8 +34,8 @@ unsigned long __lockfunc _raw_spin_lock_irqsave(raw_spinlock_t *lock)
unsigned long __lockfunc
_raw_spin_lock_irqsave_nested(raw_spinlock_t *lock, int subclass)
__acquires(lock);
-int __lockfunc _raw_spin_trylock(raw_spinlock_t *lock) __cond_acquires(lock);
-int __lockfunc _raw_spin_trylock_bh(raw_spinlock_t *lock) __cond_acquires(lock);
+int __lockfunc _raw_spin_trylock(raw_spinlock_t *lock) __cond_acquires(true, lock);
+int __lockfunc _raw_spin_trylock_bh(raw_spinlock_t *lock) __cond_acquires(true, lock);
void __lockfunc _raw_spin_unlock(raw_spinlock_t *lock) __releases(lock);
void __lockfunc _raw_spin_unlock_bh(raw_spinlock_t *lock) __releases(lock);
void __lockfunc _raw_spin_unlock_irq(raw_spinlock_t *lock) __releases(lock);
@@ -84,7 +84,7 @@ _raw_spin_unlock_irqrestore(raw_spinlock_t *lock, unsigned long flags)
#endif

static inline int __raw_spin_trylock(raw_spinlock_t *lock)
- __cond_acquires(lock)
+ __cond_acquires(true, lock)
{
preempt_disable();
if (do_raw_spin_trylock(lock)) {
@@ -177,7 +177,7 @@ static inline void __raw_spin_unlock_bh(raw_spinlock_t *lock)
}

static inline int __raw_spin_trylock_bh(raw_spinlock_t *lock)
- __cond_acquires(lock)
+ __cond_acquires(true, lock)
{
__local_bh_disable_ip(_RET_IP_, SOFTIRQ_LOCK_OFFSET);
if (do_raw_spin_trylock(lock)) {
diff --git a/net/ipv4/tcp_sigpool.c b/net/ipv4/tcp_sigpool.c
index d8a4f192873a..10b2e5970c40 100644
--- a/net/ipv4/tcp_sigpool.c
+++ b/net/ipv4/tcp_sigpool.c
@@ -257,7 +257,7 @@ void tcp_sigpool_get(unsigned int id)
}
EXPORT_SYMBOL_GPL(tcp_sigpool_get);

-int tcp_sigpool_start(unsigned int id, struct tcp_sigpool *c) __cond_acquires(RCU_BH)
+int tcp_sigpool_start(unsigned int id, struct tcp_sigpool *c) __cond_acquires(0, RCU_BH)
{
struct crypto_ahash *hash;

--
2.51.0.384.g4c02a37b29-goog

Marco Elver

unread,
Sep 18, 2025, 10:05:58 AM (6 days ago) Sep 18
to el...@google.com, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
Add support for Clang's capability analysis for mutex.

Signed-off-by: Marco Elver <el...@google.com>
---
v3:
* Switch to DECLARE_LOCK_GUARD_1_ATTRS() (suggested by Peter)
* __assert -> __assume rename
---
.../dev-tools/capability-analysis.rst | 2 +-
include/linux/mutex.h | 35 +++++-----
include/linux/mutex_types.h | 4 +-
lib/test_capability-analysis.c | 64 +++++++++++++++++++
4 files changed, 87 insertions(+), 18 deletions(-)

diff --git a/Documentation/dev-tools/capability-analysis.rst b/Documentation/dev-tools/capability-analysis.rst
index 9abd7f62cf4e..89f9c991f7cf 100644
--- a/Documentation/dev-tools/capability-analysis.rst
+++ b/Documentation/dev-tools/capability-analysis.rst
@@ -81,7 +81,7 @@ Supported Kernel Primitives
~~~~~~~~~~~~~~~~~~~~~~~~~~~

Currently the following synchronization primitives are supported:
-`raw_spinlock_t`, `spinlock_t`, `rwlock_t`.
+`raw_spinlock_t`, `spinlock_t`, `rwlock_t`, `mutex`.

For capabilities with an initialization function (e.g., `spin_lock_init()`),
calling this function on the capability instance before initializing any
diff --git a/include/linux/mutex.h b/include/linux/mutex.h
index 847b81ca6436..7e4eb778d269 100644
--- a/include/linux/mutex.h
+++ b/include/linux/mutex.h
@@ -62,6 +62,7 @@ do { \
static struct lock_class_key __key; \
\
__mutex_init((mutex), #mutex, &__key); \
+ __assume_cap(mutex); \
} while (0)

/**
@@ -157,13 +158,13 @@ static inline int __must_check __devm_mutex_init(struct device *dev, struct mute
* Also see Documentation/locking/mutex-design.rst.
*/
#ifdef CONFIG_DEBUG_LOCK_ALLOC
-extern void mutex_lock_nested(struct mutex *lock, unsigned int subclass);
+extern void mutex_lock_nested(struct mutex *lock, unsigned int subclass) __acquires(lock);
extern void _mutex_lock_nest_lock(struct mutex *lock, struct lockdep_map *nest_lock);
extern int __must_check mutex_lock_interruptible_nested(struct mutex *lock,
- unsigned int subclass);
+ unsigned int subclass) __cond_acquires(0, lock);
extern int __must_check _mutex_lock_killable(struct mutex *lock,
- unsigned int subclass, struct lockdep_map *nest_lock);
-extern void mutex_lock_io_nested(struct mutex *lock, unsigned int subclass);
+ unsigned int subclass, struct lockdep_map *nest_lock) __cond_acquires(0, lock);
+extern void mutex_lock_io_nested(struct mutex *lock, unsigned int subclass) __acquires(lock);

#define mutex_lock(lock) mutex_lock_nested(lock, 0)
#define mutex_lock_interruptible(lock) mutex_lock_interruptible_nested(lock, 0)
@@ -186,10 +187,10 @@ do { \
_mutex_lock_killable(lock, subclass, NULL)

#else
-extern void mutex_lock(struct mutex *lock);
-extern int __must_check mutex_lock_interruptible(struct mutex *lock);
-extern int __must_check mutex_lock_killable(struct mutex *lock);
-extern void mutex_lock_io(struct mutex *lock);
+extern void mutex_lock(struct mutex *lock) __acquires(lock);
+extern int __must_check mutex_lock_interruptible(struct mutex *lock) __cond_acquires(0, lock);
+extern int __must_check mutex_lock_killable(struct mutex *lock) __cond_acquires(0, lock);
+extern void mutex_lock_io(struct mutex *lock) __acquires(lock);

# define mutex_lock_nested(lock, subclass) mutex_lock(lock)
# define mutex_lock_interruptible_nested(lock, subclass) mutex_lock_interruptible(lock)
@@ -207,7 +208,7 @@ extern void mutex_lock_io(struct mutex *lock);
*/

#ifdef CONFIG_DEBUG_LOCK_ALLOC
-extern int _mutex_trylock_nest_lock(struct mutex *lock, struct lockdep_map *nest_lock);
+extern int _mutex_trylock_nest_lock(struct mutex *lock, struct lockdep_map *nest_lock) __cond_acquires(true, lock);

#define mutex_trylock_nest_lock(lock, nest_lock) \
( \
@@ -217,17 +218,21 @@ extern int _mutex_trylock_nest_lock(struct mutex *lock, struct lockdep_map *nest

#define mutex_trylock(lock) _mutex_trylock_nest_lock(lock, NULL)
#else
-extern int mutex_trylock(struct mutex *lock);
+extern int mutex_trylock(struct mutex *lock) __cond_acquires(true, lock);
#define mutex_trylock_nest_lock(lock, nest_lock) mutex_trylock(lock)
#endif

-extern void mutex_unlock(struct mutex *lock);
+extern void mutex_unlock(struct mutex *lock) __releases(lock);

-extern int atomic_dec_and_mutex_lock(atomic_t *cnt, struct mutex *lock);
+extern int atomic_dec_and_mutex_lock(atomic_t *cnt, struct mutex *lock) __cond_acquires(true, lock);

-DEFINE_GUARD(mutex, struct mutex *, mutex_lock(_T), mutex_unlock(_T))
-DEFINE_GUARD_COND(mutex, _try, mutex_trylock(_T))
-DEFINE_GUARD_COND(mutex, _intr, mutex_lock_interruptible(_T), _RET == 0)
+DEFINE_LOCK_GUARD_1(mutex, struct mutex, mutex_lock(_T->lock), mutex_unlock(_T->lock))
+DEFINE_LOCK_GUARD_1_COND(mutex, _try, mutex_trylock(_T->lock))
+DEFINE_LOCK_GUARD_1_COND(mutex, _intr, mutex_lock_interruptible(_T->lock), _RET == 0)
+
+DECLARE_LOCK_GUARD_1_ATTRS(mutex, __assumes_cap(_T), /* */)
+DECLARE_LOCK_GUARD_1_ATTRS(mutex_try, __assumes_cap(_T), /* */)
+DECLARE_LOCK_GUARD_1_ATTRS(mutex_intr, __assumes_cap(_T), /* */)

extern unsigned long mutex_get_owner(struct mutex *lock);

diff --git a/include/linux/mutex_types.h b/include/linux/mutex_types.h
index fdf7f515fde8..e1a5ea12d53c 100644
--- a/include/linux/mutex_types.h
+++ b/include/linux/mutex_types.h
@@ -38,7 +38,7 @@
* - detects multi-task circular deadlocks and prints out all affected
* locks and tasks (and only those tasks)
*/
-struct mutex {
+struct_with_capability(mutex) {
atomic_long_t owner;
raw_spinlock_t wait_lock;
#ifdef CONFIG_MUTEX_SPIN_ON_OWNER
@@ -59,7 +59,7 @@ struct mutex {
*/
#include <linux/rtmutex.h>

-struct mutex {
+struct_with_capability(mutex) {
struct rt_mutex_base rtmutex;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
diff --git a/lib/test_capability-analysis.c b/lib/test_capability-analysis.c
index 84060bace61d..286723b47328 100644
--- a/lib/test_capability-analysis.c
+++ b/lib/test_capability-analysis.c
@@ -5,6 +5,7 @@
*/

#include <linux/build_bug.h>
+#include <linux/mutex.h>
#include <linux/spinlock.h>

/*
@@ -144,3 +145,66 @@ TEST_SPINLOCK_COMMON(read_lock,
read_unlock,
read_trylock,
TEST_OP_RO);
+
+struct test_mutex_data {
+ struct mutex mtx;
+ int counter __guarded_by(&mtx);
+};
+
+static void __used test_mutex_init(struct test_mutex_data *d)
+{
+ mutex_init(&d->mtx);
+ d->counter = 0;
+}
+
+static void __used test_mutex_lock(struct test_mutex_data *d)
+{
+ mutex_lock(&d->mtx);
+ d->counter++;
+ mutex_unlock(&d->mtx);
+ mutex_lock_io(&d->mtx);
+ d->counter++;
+ mutex_unlock(&d->mtx);
+}
+
+static void __used test_mutex_trylock(struct test_mutex_data *d, atomic_t *a)
+{
+ if (!mutex_lock_interruptible(&d->mtx)) {
+ d->counter++;
+ mutex_unlock(&d->mtx);
+ }
+ if (!mutex_lock_killable(&d->mtx)) {
+ d->counter++;
+ mutex_unlock(&d->mtx);
+ }
+ if (mutex_trylock(&d->mtx)) {
+ d->counter++;
+ mutex_unlock(&d->mtx);
+ }
+ if (atomic_dec_and_mutex_lock(a, &d->mtx)) {
+ d->counter++;
+ mutex_unlock(&d->mtx);
+ }
+}
+
+static void __used test_mutex_assert(struct test_mutex_data *d)
+{
+ lockdep_assert_held(&d->mtx);
+ d->counter++;
+}
+
+static void __used test_mutex_guard(struct test_mutex_data *d)
+{
+ guard(mutex)(&d->mtx);
+ d->counter++;
+}
+
+static void __used test_mutex_cond_guard(struct test_mutex_data *d)
+{
+ scoped_cond_guard(mutex_try, return, &d->mtx) {
+ d->counter++;
+ }
+ scoped_cond_guard(mutex_intr, return, &d->mtx) {
+ d->counter++;
+ }
+}
--
2.51.0.384.g4c02a37b29-goog

Marco Elver

unread,
Sep 18, 2025, 10:06:00 AM (6 days ago) Sep 18
to el...@google.com, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
Add support for Clang's capability analysis for seqlock_t.

Signed-off-by: Marco Elver <el...@google.com>
---
v3:
* __assert -> __assume rename
---
.../dev-tools/capability-analysis.rst | 2 +-
include/linux/seqlock.h | 24 +++++++++++
include/linux/seqlock_types.h | 5 ++-
lib/test_capability-analysis.c | 43 +++++++++++++++++++
4 files changed, 71 insertions(+), 3 deletions(-)

diff --git a/Documentation/dev-tools/capability-analysis.rst b/Documentation/dev-tools/capability-analysis.rst
index 89f9c991f7cf..4789de7b019a 100644
--- a/Documentation/dev-tools/capability-analysis.rst
+++ b/Documentation/dev-tools/capability-analysis.rst
@@ -81,7 +81,7 @@ Supported Kernel Primitives
~~~~~~~~~~~~~~~~~~~~~~~~~~~

Currently the following synchronization primitives are supported:
-`raw_spinlock_t`, `spinlock_t`, `rwlock_t`, `mutex`.
+`raw_spinlock_t`, `spinlock_t`, `rwlock_t`, `mutex`, `seqlock_t`.

For capabilities with an initialization function (e.g., `spin_lock_init()`),
calling this function on the capability instance before initializing any
diff --git a/include/linux/seqlock.h b/include/linux/seqlock.h
index 5ce48eab7a2a..2c7a02b727de 100644
--- a/include/linux/seqlock.h
+++ b/include/linux/seqlock.h
@@ -816,6 +816,7 @@ static __always_inline void write_seqcount_latch_end(seqcount_latch_t *s)
do { \
spin_lock_init(&(sl)->lock); \
seqcount_spinlock_init(&(sl)->seqcount, &(sl)->lock); \
+ __assume_cap(sl); \
} while (0)

/**
@@ -832,6 +833,7 @@ static __always_inline void write_seqcount_latch_end(seqcount_latch_t *s)
* Return: count, to be passed to read_seqretry()
*/
static inline unsigned read_seqbegin(const seqlock_t *sl)
+ __acquires_shared(sl) __no_capability_analysis
{
return read_seqcount_begin(&sl->seqcount);
}
@@ -848,6 +850,7 @@ static inline unsigned read_seqbegin(const seqlock_t *sl)
* Return: true if a read section retry is required, else false
*/
static inline unsigned read_seqretry(const seqlock_t *sl, unsigned start)
+ __releases_shared(sl) __no_capability_analysis
{
return read_seqcount_retry(&sl->seqcount, start);
}
@@ -872,6 +875,7 @@ static inline unsigned read_seqretry(const seqlock_t *sl, unsigned start)
* _irqsave or _bh variants of this function instead.
*/
static inline void write_seqlock(seqlock_t *sl)
+ __acquires(sl) __no_capability_analysis
{
spin_lock(&sl->lock);
do_write_seqcount_begin(&sl->seqcount.seqcount);
@@ -885,6 +889,7 @@ static inline void write_seqlock(seqlock_t *sl)
* critical section of given seqlock_t.
*/
static inline void write_sequnlock(seqlock_t *sl)
+ __releases(sl) __no_capability_analysis
{
do_write_seqcount_end(&sl->seqcount.seqcount);
spin_unlock(&sl->lock);
@@ -898,6 +903,7 @@ static inline void write_sequnlock(seqlock_t *sl)
* other write side sections, can be invoked from softirq contexts.
*/
static inline void write_seqlock_bh(seqlock_t *sl)
+ __acquires(sl) __no_capability_analysis
{
spin_lock_bh(&sl->lock);
do_write_seqcount_begin(&sl->seqcount.seqcount);
@@ -912,6 +918,7 @@ static inline void write_seqlock_bh(seqlock_t *sl)
* write_seqlock_bh().
*/
static inline void write_sequnlock_bh(seqlock_t *sl)
+ __releases(sl) __no_capability_analysis
{
do_write_seqcount_end(&sl->seqcount.seqcount);
spin_unlock_bh(&sl->lock);
@@ -925,6 +932,7 @@ static inline void write_sequnlock_bh(seqlock_t *sl)
* other write sections, can be invoked from hardirq contexts.
*/
static inline void write_seqlock_irq(seqlock_t *sl)
+ __acquires(sl) __no_capability_analysis
{
spin_lock_irq(&sl->lock);
do_write_seqcount_begin(&sl->seqcount.seqcount);
@@ -938,12 +946,14 @@ static inline void write_seqlock_irq(seqlock_t *sl)
* seqlock_t write side section opened with write_seqlock_irq().
*/
static inline void write_sequnlock_irq(seqlock_t *sl)
+ __releases(sl) __no_capability_analysis
{
do_write_seqcount_end(&sl->seqcount.seqcount);
spin_unlock_irq(&sl->lock);
}

static inline unsigned long __write_seqlock_irqsave(seqlock_t *sl)
+ __acquires(sl) __no_capability_analysis
{
unsigned long flags;

@@ -976,6 +986,7 @@ static inline unsigned long __write_seqlock_irqsave(seqlock_t *sl)
*/
static inline void
write_sequnlock_irqrestore(seqlock_t *sl, unsigned long flags)
+ __releases(sl) __no_capability_analysis
{
do_write_seqcount_end(&sl->seqcount.seqcount);
spin_unlock_irqrestore(&sl->lock, flags);
@@ -998,6 +1009,7 @@ write_sequnlock_irqrestore(seqlock_t *sl, unsigned long flags)
* The opened read section must be closed with read_sequnlock_excl().
*/
static inline void read_seqlock_excl(seqlock_t *sl)
+ __acquires_shared(sl) __no_capability_analysis
{
spin_lock(&sl->lock);
}
@@ -1007,6 +1019,7 @@ static inline void read_seqlock_excl(seqlock_t *sl)
* @sl: Pointer to seqlock_t
*/
static inline void read_sequnlock_excl(seqlock_t *sl)
+ __releases_shared(sl) __no_capability_analysis
{
spin_unlock(&sl->lock);
}
@@ -1021,6 +1034,7 @@ static inline void read_sequnlock_excl(seqlock_t *sl)
* from softirq contexts.
*/
static inline void read_seqlock_excl_bh(seqlock_t *sl)
+ __acquires_shared(sl) __no_capability_analysis
{
spin_lock_bh(&sl->lock);
}
@@ -1031,6 +1045,7 @@ static inline void read_seqlock_excl_bh(seqlock_t *sl)
* @sl: Pointer to seqlock_t
*/
static inline void read_sequnlock_excl_bh(seqlock_t *sl)
+ __releases_shared(sl) __no_capability_analysis
{
spin_unlock_bh(&sl->lock);
}
@@ -1045,6 +1060,7 @@ static inline void read_sequnlock_excl_bh(seqlock_t *sl)
* hardirq context.
*/
static inline void read_seqlock_excl_irq(seqlock_t *sl)
+ __acquires_shared(sl) __no_capability_analysis
{
spin_lock_irq(&sl->lock);
}
@@ -1055,11 +1071,13 @@ static inline void read_seqlock_excl_irq(seqlock_t *sl)
* @sl: Pointer to seqlock_t
*/
static inline void read_sequnlock_excl_irq(seqlock_t *sl)
+ __releases_shared(sl) __no_capability_analysis
{
spin_unlock_irq(&sl->lock);
}

static inline unsigned long __read_seqlock_excl_irqsave(seqlock_t *sl)
+ __acquires_shared(sl) __no_capability_analysis
{
unsigned long flags;

@@ -1089,6 +1107,7 @@ static inline unsigned long __read_seqlock_excl_irqsave(seqlock_t *sl)
*/
static inline void
read_sequnlock_excl_irqrestore(seqlock_t *sl, unsigned long flags)
+ __releases_shared(sl) __no_capability_analysis
{
spin_unlock_irqrestore(&sl->lock, flags);
}
@@ -1125,6 +1144,7 @@ read_sequnlock_excl_irqrestore(seqlock_t *sl, unsigned long flags)
* parameter of the next read_seqbegin_or_lock() iteration.
*/
static inline void read_seqbegin_or_lock(seqlock_t *lock, int *seq)
+ __acquires_shared(lock) __no_capability_analysis
{
if (!(*seq & 1)) /* Even */
*seq = read_seqbegin(lock);
@@ -1140,6 +1160,7 @@ static inline void read_seqbegin_or_lock(seqlock_t *lock, int *seq)
* Return: true if a read section retry is required, false otherwise
*/
static inline int need_seqretry(seqlock_t *lock, int seq)
+ __releases_shared(lock) __no_capability_analysis
{
return !(seq & 1) && read_seqretry(lock, seq);
}
@@ -1153,6 +1174,7 @@ static inline int need_seqretry(seqlock_t *lock, int seq)
* with read_seqbegin_or_lock() and validated by need_seqretry().
*/
static inline void done_seqretry(seqlock_t *lock, int seq)
+ __no_capability_analysis
{
if (seq & 1)
read_sequnlock_excl(lock);
@@ -1180,6 +1202,7 @@ static inline void done_seqretry(seqlock_t *lock, int seq)
*/
static inline unsigned long
read_seqbegin_or_lock_irqsave(seqlock_t *lock, int *seq)
+ __acquires_shared(lock) __no_capability_analysis
{
unsigned long flags = 0;

@@ -1205,6 +1228,7 @@ read_seqbegin_or_lock_irqsave(seqlock_t *lock, int *seq)
*/
static inline void
done_seqretry_irqrestore(seqlock_t *lock, int seq, unsigned long flags)
+ __no_capability_analysis
{
if (seq & 1)
read_sequnlock_excl_irqrestore(lock, flags);
diff --git a/include/linux/seqlock_types.h b/include/linux/seqlock_types.h
index dfdf43e3fa3d..9775d6f1a234 100644
--- a/include/linux/seqlock_types.h
+++ b/include/linux/seqlock_types.h
@@ -81,13 +81,14 @@ SEQCOUNT_LOCKNAME(mutex, struct mutex, true, mutex)
* - Comments on top of seqcount_t
* - Documentation/locking/seqlock.rst
*/
-typedef struct {
+struct_with_capability(seqlock) {
/*
* Make sure that readers don't starve writers on PREEMPT_RT: use
* seqcount_spinlock_t instead of seqcount_t. Check __SEQ_LOCK().
*/
seqcount_spinlock_t seqcount;
spinlock_t lock;
-} seqlock_t;
+};
+typedef struct seqlock seqlock_t;

#endif /* __LINUX_SEQLOCK_TYPES_H */
diff --git a/lib/test_capability-analysis.c b/lib/test_capability-analysis.c
index 286723b47328..74d287740bb8 100644
--- a/lib/test_capability-analysis.c
+++ b/lib/test_capability-analysis.c
@@ -6,6 +6,7 @@

#include <linux/build_bug.h>
#include <linux/mutex.h>
+#include <linux/seqlock.h>
#include <linux/spinlock.h>

/*
@@ -208,3 +209,45 @@ static void __used test_mutex_cond_guard(struct test_mutex_data *d)
d->counter++;
}
}
+
+struct test_seqlock_data {
+ seqlock_t sl;
+ int counter __guarded_by(&sl);
+};
+
+static void __used test_seqlock_init(struct test_seqlock_data *d)
+{
+ seqlock_init(&d->sl);
+ d->counter = 0;
+}
+
+static void __used test_seqlock_reader(struct test_seqlock_data *d)
+{
+ unsigned int seq;
+
+ do {
+ seq = read_seqbegin(&d->sl);
+ (void)d->counter;
+ } while (read_seqretry(&d->sl, seq));
+}
+
+static void __used test_seqlock_writer(struct test_seqlock_data *d)
+{
+ unsigned long flags;
+
+ write_seqlock(&d->sl);
+ d->counter++;
+ write_sequnlock(&d->sl);
+
+ write_seqlock_irq(&d->sl);
+ d->counter++;
+ write_sequnlock_irq(&d->sl);
+
+ write_seqlock_bh(&d->sl);
+ d->counter++;
+ write_sequnlock_bh(&d->sl);
+
+ write_seqlock_irqsave(&d->sl, flags);
+ d->counter++;
+ write_sequnlock_irqrestore(&d->sl, flags);
+}
--
2.51.0.384.g4c02a37b29-goog

Marco Elver

unread,
Sep 18, 2025, 10:06:05 AM (6 days ago) Sep 18
to el...@google.com, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
Including <linux/bit_spinlock.h> into an empty TU will result in the
compiler complaining:

./include/linux/bit_spinlock.h:34:4: error: call to undeclared function 'cpu_relax'; <...>
34 | cpu_relax();
| ^
1 error generated.

Include <asm/processor.h> to allow including bit_spinlock.h where
<asm/processor.h> is not otherwise included.

Signed-off-by: Marco Elver <el...@google.com>
---
include/linux/bit_spinlock.h | 2 ++
1 file changed, 2 insertions(+)

diff --git a/include/linux/bit_spinlock.h b/include/linux/bit_spinlock.h
index c0989b5b0407..59e345f74b0e 100644
--- a/include/linux/bit_spinlock.h
+++ b/include/linux/bit_spinlock.h
@@ -7,6 +7,8 @@
#include <linux/atomic.h>
#include <linux/bug.h>

+#include <asm/processor.h> /* for cpu_relax() */
+
/*
* bit-based spin_lock()
*
--
2.51.0.384.g4c02a37b29-goog

Marco Elver

unread,
Sep 18, 2025, 10:06:06 AM (6 days ago) Sep 18
to el...@google.com, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
The annotations for bit_spinlock.h have simply been using "bitlock" as
the token. For Sparse, that was likely sufficient in most cases. But
Clang's capability analysis is more precise, and we need to ensure we
can distinguish different bitlocks.

To do so, add a token capability, and a macro __bitlock(bitnum, addr)
that is used to construct unique per-bitlock tokens.

Add the appropriate test.

<linux/list_bl.h> is implicitly included through other includes, and
requires 2 annotations to indicate that acquisition (without release)
and release (without prior acquisition) of its bitlock is intended.

Signed-off-by: Marco Elver <el...@google.com>
---
.../dev-tools/capability-analysis.rst | 3 ++-
include/linux/bit_spinlock.h | 22 +++++++++++++---
include/linux/list_bl.h | 2 ++
lib/test_capability-analysis.c | 26 +++++++++++++++++++
4 files changed, 48 insertions(+), 5 deletions(-)

diff --git a/Documentation/dev-tools/capability-analysis.rst b/Documentation/dev-tools/capability-analysis.rst
index 4789de7b019a..56c6ba7205aa 100644
--- a/Documentation/dev-tools/capability-analysis.rst
+++ b/Documentation/dev-tools/capability-analysis.rst
@@ -81,7 +81,8 @@ Supported Kernel Primitives
~~~~~~~~~~~~~~~~~~~~~~~~~~~

Currently the following synchronization primitives are supported:
-`raw_spinlock_t`, `spinlock_t`, `rwlock_t`, `mutex`, `seqlock_t`.
+`raw_spinlock_t`, `spinlock_t`, `rwlock_t`, `mutex`, `seqlock_t`,
+`bit_spinlock`.

For capabilities with an initialization function (e.g., `spin_lock_init()`),
calling this function on the capability instance before initializing any
diff --git a/include/linux/bit_spinlock.h b/include/linux/bit_spinlock.h
index 59e345f74b0e..ba3a67f39f0c 100644
--- a/include/linux/bit_spinlock.h
+++ b/include/linux/bit_spinlock.h
@@ -9,6 +9,16 @@

#include <asm/processor.h> /* for cpu_relax() */

+/*
+ * For static capability analysis, we need a unique token for each possible bit
+ * that can be used as a bit_spinlock. The easiest way to do that is to create a
+ * fake capability that we can cast to with the __bitlock(bitnum, addr) macro
+ * below, which will give us unique instances for each (bit, addr) pair that the
+ * static analysis can use.
+ */
+struct_with_capability(__capability_bitlock) { };
+#define __bitlock(bitnum, addr) (struct __capability_bitlock *)(bitnum + (addr))
+
/*
* bit-based spin_lock()
*
@@ -16,6 +26,7 @@
* are significantly faster.
*/
static __always_inline void bit_spin_lock(int bitnum, unsigned long *addr)
+ __acquires(__bitlock(bitnum, addr))
{
/*
* Assuming the lock is uncontended, this never enters
@@ -34,13 +45,14 @@ static __always_inline void bit_spin_lock(int bitnum, unsigned long *addr)
preempt_disable();
}
#endif
- __acquire(bitlock);
+ __acquire(__bitlock(bitnum, addr));
}

/*
* Return true if it was acquired
*/
static __always_inline int bit_spin_trylock(int bitnum, unsigned long *addr)
+ __cond_acquires(true, __bitlock(bitnum, addr))
{
preempt_disable();
#if defined(CONFIG_SMP) || defined(CONFIG_DEBUG_SPINLOCK)
@@ -49,7 +61,7 @@ static __always_inline int bit_spin_trylock(int bitnum, unsigned long *addr)
return 0;
}
#endif
- __acquire(bitlock);
+ __acquire(__bitlock(bitnum, addr));
return 1;
}

@@ -57,6 +69,7 @@ static __always_inline int bit_spin_trylock(int bitnum, unsigned long *addr)
* bit-based spin_unlock()
*/
static __always_inline void bit_spin_unlock(int bitnum, unsigned long *addr)
+ __releases(__bitlock(bitnum, addr))
{
#ifdef CONFIG_DEBUG_SPINLOCK
BUG_ON(!test_bit(bitnum, addr));
@@ -65,7 +78,7 @@ static __always_inline void bit_spin_unlock(int bitnum, unsigned long *addr)
clear_bit_unlock(bitnum, addr);
#endif
preempt_enable();
- __release(bitlock);
+ __release(__bitlock(bitnum, addr));
}

/*
@@ -74,6 +87,7 @@ static __always_inline void bit_spin_unlock(int bitnum, unsigned long *addr)
* protecting the rest of the flags in the word.
*/
static __always_inline void __bit_spin_unlock(int bitnum, unsigned long *addr)
+ __releases(__bitlock(bitnum, addr))
{
#ifdef CONFIG_DEBUG_SPINLOCK
BUG_ON(!test_bit(bitnum, addr));
@@ -82,7 +96,7 @@ static __always_inline void __bit_spin_unlock(int bitnum, unsigned long *addr)
__clear_bit_unlock(bitnum, addr);
#endif
preempt_enable();
- __release(bitlock);
+ __release(__bitlock(bitnum, addr));
}

/*
diff --git a/include/linux/list_bl.h b/include/linux/list_bl.h
index ae1b541446c9..df9eebe6afca 100644
--- a/include/linux/list_bl.h
+++ b/include/linux/list_bl.h
@@ -144,11 +144,13 @@ static inline void hlist_bl_del_init(struct hlist_bl_node *n)
}

static inline void hlist_bl_lock(struct hlist_bl_head *b)
+ __acquires(__bitlock(0, b))
{
bit_spin_lock(0, (unsigned long *)b);
}

static inline void hlist_bl_unlock(struct hlist_bl_head *b)
+ __releases(__bitlock(0, b))
{
__bit_spin_unlock(0, (unsigned long *)b);
}
diff --git a/lib/test_capability-analysis.c b/lib/test_capability-analysis.c
index 74d287740bb8..ad362d5a7916 100644
--- a/lib/test_capability-analysis.c
+++ b/lib/test_capability-analysis.c
@@ -4,6 +4,7 @@
* positive errors when compiled with Clang's capability analysis.
*/

+#include <linux/bit_spinlock.h>
#include <linux/build_bug.h>
#include <linux/mutex.h>
#include <linux/seqlock.h>
@@ -251,3 +252,28 @@ static void __used test_seqlock_writer(struct test_seqlock_data *d)
d->counter++;
write_sequnlock_irqrestore(&d->sl, flags);
}
+
+struct test_bit_spinlock_data {
+ unsigned long bits;
+ int counter __guarded_by(__bitlock(3, &bits));
+};
+
+static void __used test_bit_spin_lock(struct test_bit_spinlock_data *d)
+{
+ /*
+ * Note, the analysis seems to have false negatives, because it won't
+ * precisely recognize the bit of the fake __bitlock() token.
+ */
+ bit_spin_lock(3, &d->bits);
+ d->counter++;
+ bit_spin_unlock(3, &d->bits);
+
+ bit_spin_lock(3, &d->bits);
+ d->counter++;
+ __bit_spin_unlock(3, &d->bits);
+
+ if (bit_spin_trylock(3, &d->bits)) {
+ d->counter++;
+ bit_spin_unlock(3, &d->bits);
+ }
+}
--
2.51.0.384.g4c02a37b29-goog

Marco Elver

unread,
Sep 18, 2025, 10:06:10 AM (6 days ago) Sep 18
to el...@google.com, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
Improve the existing annotations to properly support Clang's capability
analysis.

The old annotations distinguished between RCU, RCU_BH, and RCU_SCHED;
however, to more easily be able to express that "hold the RCU read lock"
without caring if the normal, _bh(), or _sched() variant was used we'd
have to remove the distinction of the latter variants: change the _bh()
and _sched() variants to also acquire "RCU".

When (and if) we introduce capabilities to denote more generally that
"IRQ", "BH", "PREEMPT" are disabled, it would make sense to acquire
these capabilities instead of RCU_BH and RCU_SCHED respectively.

The above change also simplified introducing __guarded_by support, where
only the "RCU" capability needs to be held: introduce __rcu_guarded,
where Clang's capability analysis warns if a pointer is dereferenced
without any of the RCU locks held, or updated without the appropriate
helpers.

The primitives rcu_assign_pointer() and friends are wrapped with
capability_unsafe(), which enforces using them to update RCU-protected
pointers marked with __rcu_guarded.

Signed-off-by: Marco Elver <el...@google.com>
---
v3:
* Properly support reentrancy via new compiler support.

v2:
* Reword commit message and point out reentrancy caveat.
---
.../dev-tools/capability-analysis.rst | 2 +-
include/linux/rcupdate.h | 73 +++++++++++-----
lib/test_capability-analysis.c | 85 +++++++++++++++++++
3 files changed, 136 insertions(+), 24 deletions(-)

diff --git a/Documentation/dev-tools/capability-analysis.rst b/Documentation/dev-tools/capability-analysis.rst
index 56c6ba7205aa..fdacc7f73da8 100644
--- a/Documentation/dev-tools/capability-analysis.rst
+++ b/Documentation/dev-tools/capability-analysis.rst
@@ -82,7 +82,7 @@ Supported Kernel Primitives

Currently the following synchronization primitives are supported:
`raw_spinlock_t`, `spinlock_t`, `rwlock_t`, `mutex`, `seqlock_t`,
-`bit_spinlock`.
+`bit_spinlock`, RCU.

For capabilities with an initialization function (e.g., `spin_lock_init()`),
calling this function on the capability instance before initializing any
diff --git a/include/linux/rcupdate.h b/include/linux/rcupdate.h
index 120536f4c6eb..8eeece72492c 100644
--- a/include/linux/rcupdate.h
+++ b/include/linux/rcupdate.h
@@ -31,6 +31,16 @@
#include <asm/processor.h>
#include <linux/context_tracking_irq.h>

+token_capability(RCU, __reentrant_cap);
+token_capability_instance(RCU, RCU_SCHED);
+token_capability_instance(RCU, RCU_BH);
+
+/*
+ * A convenience macro that can be used for RCU-protected globals or struct
+ * members; adds type qualifier __rcu, and also enforces __guarded_by(RCU).
+ */
+#define __rcu_guarded __rcu __guarded_by(RCU)
+
#define ULONG_CMP_GE(a, b) (ULONG_MAX / 2 >= (a) - (b))
#define ULONG_CMP_LT(a, b) (ULONG_MAX / 2 < (a) - (b))

@@ -425,7 +435,8 @@ static inline void rcu_preempt_sleep_check(void) { }

// See RCU_LOCKDEP_WARN() for an explanation of the double call to
// debug_lockdep_rcu_enabled().
-static inline bool lockdep_assert_rcu_helper(bool c)
+static inline bool lockdep_assert_rcu_helper(bool c, const struct __capability_RCU *cap)
+ __assumes_shared_cap(RCU) __assumes_shared_cap(cap)
{
return debug_lockdep_rcu_enabled() &&
(c || !rcu_is_watching() || !rcu_lockdep_current_cpu_online()) &&
@@ -438,7 +449,7 @@ static inline bool lockdep_assert_rcu_helper(bool c)
* Splats if lockdep is enabled and there is no rcu_read_lock() in effect.
*/
#define lockdep_assert_in_rcu_read_lock() \
- WARN_ON_ONCE(lockdep_assert_rcu_helper(!lock_is_held(&rcu_lock_map)))
+ WARN_ON_ONCE(lockdep_assert_rcu_helper(!lock_is_held(&rcu_lock_map), RCU))

/**
* lockdep_assert_in_rcu_read_lock_bh - WARN if not protected by rcu_read_lock_bh()
@@ -448,7 +459,7 @@ static inline bool lockdep_assert_rcu_helper(bool c)
* actual rcu_read_lock_bh() is required.
*/
#define lockdep_assert_in_rcu_read_lock_bh() \
- WARN_ON_ONCE(lockdep_assert_rcu_helper(!lock_is_held(&rcu_bh_lock_map)))
+ WARN_ON_ONCE(lockdep_assert_rcu_helper(!lock_is_held(&rcu_bh_lock_map), RCU_BH))

/**
* lockdep_assert_in_rcu_read_lock_sched - WARN if not protected by rcu_read_lock_sched()
@@ -458,7 +469,7 @@ static inline bool lockdep_assert_rcu_helper(bool c)
* instead an actual rcu_read_lock_sched() is required.
*/
#define lockdep_assert_in_rcu_read_lock_sched() \
- WARN_ON_ONCE(lockdep_assert_rcu_helper(!lock_is_held(&rcu_sched_lock_map)))
+ WARN_ON_ONCE(lockdep_assert_rcu_helper(!lock_is_held(&rcu_sched_lock_map), RCU_SCHED))

/**
* lockdep_assert_in_rcu_reader - WARN if not within some type of RCU reader
@@ -476,17 +487,17 @@ static inline bool lockdep_assert_rcu_helper(bool c)
WARN_ON_ONCE(lockdep_assert_rcu_helper(!lock_is_held(&rcu_lock_map) && \
!lock_is_held(&rcu_bh_lock_map) && \
!lock_is_held(&rcu_sched_lock_map) && \
- preemptible()))
+ preemptible(), RCU))

#else /* #ifdef CONFIG_PROVE_RCU */

#define RCU_LOCKDEP_WARN(c, s) do { } while (0 && (c))
#define rcu_sleep_check() do { } while (0)

-#define lockdep_assert_in_rcu_read_lock() do { } while (0)
-#define lockdep_assert_in_rcu_read_lock_bh() do { } while (0)
-#define lockdep_assert_in_rcu_read_lock_sched() do { } while (0)
-#define lockdep_assert_in_rcu_reader() do { } while (0)
+#define lockdep_assert_in_rcu_read_lock() __assume_shared_cap(RCU)
+#define lockdep_assert_in_rcu_read_lock_bh() __assume_shared_cap(RCU_BH)
+#define lockdep_assert_in_rcu_read_lock_sched() __assume_shared_cap(RCU_SCHED)
+#define lockdep_assert_in_rcu_reader() __assume_shared_cap(RCU)

#endif /* #else #ifdef CONFIG_PROVE_RCU */

@@ -506,11 +517,11 @@ static inline bool lockdep_assert_rcu_helper(bool c)
#endif /* #else #ifdef __CHECKER__ */

#define __unrcu_pointer(p, local) \
-({ \
+capability_unsafe( \
typeof(*p) *local = (typeof(*p) *__force)(p); \
rcu_check_sparse(p, __rcu); \
((typeof(*p) __force __kernel *)(local)); \
-})
+)
/**
* unrcu_pointer - mark a pointer as not being RCU protected
* @p: pointer needing to lose its __rcu property
@@ -586,7 +597,7 @@ static inline bool lockdep_assert_rcu_helper(bool c)
* other macros that it invokes.
*/
#define rcu_assign_pointer(p, v) \
-do { \
+capability_unsafe( \
uintptr_t _r_a_p__v = (uintptr_t)(v); \
rcu_check_sparse(p, __rcu); \
\
@@ -594,7 +605,7 @@ do { \
WRITE_ONCE((p), (typeof(p))(_r_a_p__v)); \
else \
smp_store_release(&p, RCU_INITIALIZER((typeof(p))_r_a_p__v)); \
-} while (0)
+)

/**
* rcu_replace_pointer() - replace an RCU pointer, returning its old value
@@ -835,9 +846,10 @@ do { \
* only when acquiring spinlocks that are subject to priority inheritance.
*/
static __always_inline void rcu_read_lock(void)
+ __acquires_shared(RCU)
{
__rcu_read_lock();
- __acquire(RCU);
+ __acquire_shared(RCU);
rcu_lock_acquire(&rcu_lock_map);
RCU_LOCKDEP_WARN(!rcu_is_watching(),
"rcu_read_lock() used illegally while idle");
@@ -865,11 +877,12 @@ static __always_inline void rcu_read_lock(void)
* See rcu_read_lock() for more information.
*/
static inline void rcu_read_unlock(void)
+ __releases_shared(RCU)
{
RCU_LOCKDEP_WARN(!rcu_is_watching(),
"rcu_read_unlock() used illegally while idle");
rcu_lock_release(&rcu_lock_map); /* Keep acq info for rls diags. */
- __release(RCU);
+ __release_shared(RCU);
__rcu_read_unlock();
}

@@ -888,9 +901,11 @@ static inline void rcu_read_unlock(void)
* was invoked from some other task.
*/
static inline void rcu_read_lock_bh(void)
+ __acquires_shared(RCU) __acquires_shared(RCU_BH)
{
local_bh_disable();
- __acquire(RCU_BH);
+ __acquire_shared(RCU);
+ __acquire_shared(RCU_BH);
rcu_lock_acquire(&rcu_bh_lock_map);
RCU_LOCKDEP_WARN(!rcu_is_watching(),
"rcu_read_lock_bh() used illegally while idle");
@@ -902,11 +917,13 @@ static inline void rcu_read_lock_bh(void)
* See rcu_read_lock_bh() for more information.
*/
static inline void rcu_read_unlock_bh(void)
+ __releases_shared(RCU) __releases_shared(RCU_BH)
{
RCU_LOCKDEP_WARN(!rcu_is_watching(),
"rcu_read_unlock_bh() used illegally while idle");
rcu_lock_release(&rcu_bh_lock_map);
- __release(RCU_BH);
+ __release_shared(RCU_BH);
+ __release_shared(RCU);
local_bh_enable();
}

@@ -926,9 +943,11 @@ static inline void rcu_read_unlock_bh(void)
* rcu_read_lock_sched() was invoked from an NMI handler.
*/
static inline void rcu_read_lock_sched(void)
+ __acquires_shared(RCU) __acquires_shared(RCU_SCHED)
{
preempt_disable();
- __acquire(RCU_SCHED);
+ __acquire_shared(RCU);
+ __acquire_shared(RCU_SCHED);
rcu_lock_acquire(&rcu_sched_lock_map);
RCU_LOCKDEP_WARN(!rcu_is_watching(),
"rcu_read_lock_sched() used illegally while idle");
@@ -936,9 +955,11 @@ static inline void rcu_read_lock_sched(void)

/* Used by lockdep and tracing: cannot be traced, cannot call lockdep. */
static inline notrace void rcu_read_lock_sched_notrace(void)
+ __acquires_shared(RCU) __acquires_shared(RCU_SCHED)
{
preempt_disable_notrace();
- __acquire(RCU_SCHED);
+ __acquire_shared(RCU);
+ __acquire_shared(RCU_SCHED);
}

/**
@@ -947,18 +968,22 @@ static inline notrace void rcu_read_lock_sched_notrace(void)
* See rcu_read_lock_sched() for more information.
*/
static inline void rcu_read_unlock_sched(void)
+ __releases_shared(RCU) __releases_shared(RCU_SCHED)
{
RCU_LOCKDEP_WARN(!rcu_is_watching(),
"rcu_read_unlock_sched() used illegally while idle");
rcu_lock_release(&rcu_sched_lock_map);
- __release(RCU_SCHED);
+ __release_shared(RCU_SCHED);
+ __release_shared(RCU);
preempt_enable();
}

/* Used by lockdep and tracing: cannot be traced, cannot call lockdep. */
static inline notrace void rcu_read_unlock_sched_notrace(void)
+ __releases_shared(RCU) __releases_shared(RCU_SCHED)
{
- __release(RCU_SCHED);
+ __release_shared(RCU_SCHED);
+ __release_shared(RCU);
preempt_enable_notrace();
}

@@ -1001,10 +1026,10 @@ static inline notrace void rcu_read_unlock_sched_notrace(void)
* ordering guarantees for either the CPU or the compiler.
*/
#define RCU_INIT_POINTER(p, v) \
- do { \
+ capability_unsafe( \
rcu_check_sparse(p, __rcu); \
WRITE_ONCE(p, RCU_INITIALIZER(v)); \
- } while (0)
+ )

/**
* RCU_POINTER_INITIALIZER() - statically initialize an RCU protected pointer
@@ -1166,4 +1191,6 @@ DEFINE_LOCK_GUARD_0(rcu,
} while (0),
rcu_read_unlock())

+DECLARE_LOCK_GUARD_0_ATTRS(rcu, __acquires_shared(RCU), __releases_shared(RCU))
+
#endif /* __LINUX_RCUPDATE_H */
diff --git a/lib/test_capability-analysis.c b/lib/test_capability-analysis.c
index ad362d5a7916..31c9bc1e2405 100644
--- a/lib/test_capability-analysis.c
+++ b/lib/test_capability-analysis.c
@@ -7,6 +7,7 @@
#include <linux/bit_spinlock.h>
#include <linux/build_bug.h>
#include <linux/mutex.h>
+#include <linux/rcupdate.h>
#include <linux/seqlock.h>
#include <linux/spinlock.h>

@@ -277,3 +278,87 @@ static void __used test_bit_spin_lock(struct test_bit_spinlock_data *d)
bit_spin_unlock(3, &d->bits);
}
}
+
+/*
+ * Test that we can mark a variable guarded by RCU, and we can dereference and
+ * write to the pointer with RCU's primitives.
+ */
+struct test_rcu_data {
+ long __rcu_guarded *data;
+};
+
+static void __used test_rcu_guarded_reader(struct test_rcu_data *d)
+{
+ rcu_read_lock();
+ (void)rcu_dereference(d->data);
+ rcu_read_unlock();
+
+ rcu_read_lock_bh();
+ (void)rcu_dereference(d->data);
+ rcu_read_unlock_bh();
+
+ rcu_read_lock_sched();
+ (void)rcu_dereference(d->data);
+ rcu_read_unlock_sched();
+}
+
+static void __used test_rcu_guard(struct test_rcu_data *d)
+{
+ guard(rcu)();
+ (void)rcu_dereference(d->data);
+}
+
+static void __used test_rcu_guarded_updater(struct test_rcu_data *d)
+{
+ rcu_assign_pointer(d->data, NULL);
+ RCU_INIT_POINTER(d->data, NULL);
+ (void)unrcu_pointer(d->data);
+}
+
+static void wants_rcu_held(void) __must_hold_shared(RCU) { }
+static void wants_rcu_held_bh(void) __must_hold_shared(RCU_BH) { }
+static void wants_rcu_held_sched(void) __must_hold_shared(RCU_SCHED) { }
+
+static void __used test_rcu_lock_variants(void)
+{
+ rcu_read_lock();
+ wants_rcu_held();
+ rcu_read_unlock();
+
+ rcu_read_lock_bh();
+ wants_rcu_held_bh();
+ rcu_read_unlock_bh();
+
+ rcu_read_lock_sched();
+ wants_rcu_held_sched();
+ rcu_read_unlock_sched();
+}
+
+static void __used test_rcu_lock_reentrant(void)
+{
+ rcu_read_lock();
+ rcu_read_lock();
+ rcu_read_lock_bh();
+ rcu_read_lock_bh();
+ rcu_read_lock_sched();
+ rcu_read_lock_sched();
+
+ rcu_read_unlock_sched();
+ rcu_read_unlock_sched();
+ rcu_read_unlock_bh();
+ rcu_read_unlock_bh();
+ rcu_read_unlock();
+ rcu_read_unlock();
+}
+
+static void __used test_rcu_assert_variants(void)
+{
+ lockdep_assert_in_rcu_read_lock();
+ wants_rcu_held();
+
+ lockdep_assert_in_rcu_read_lock_bh();
+ wants_rcu_held_bh();
+
+ lockdep_assert_in_rcu_read_lock_sched();
+ wants_rcu_held_sched();
+}
--
2.51.0.384.g4c02a37b29-goog

Marco Elver

unread,
Sep 18, 2025, 10:06:11 AM (6 days ago) Sep 18
to el...@google.com, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
Add support for Clang's capability analysis for SRCU.

Signed-off-by: Marco Elver <el...@google.com>
---
v3:
* Switch to DECLARE_LOCK_GUARD_1_ATTRS() (suggested by Peter)
* Support SRCU being reentrant.
---
.../dev-tools/capability-analysis.rst | 2 +-
include/linux/srcu.h | 60 +++++++++++++------
include/linux/srcutiny.h | 4 ++
include/linux/srcutree.h | 6 +-
lib/test_capability-analysis.c | 24 ++++++++
5 files changed, 75 insertions(+), 21 deletions(-)

diff --git a/Documentation/dev-tools/capability-analysis.rst b/Documentation/dev-tools/capability-analysis.rst
index fdacc7f73da8..779ecb5ec17a 100644
--- a/Documentation/dev-tools/capability-analysis.rst
+++ b/Documentation/dev-tools/capability-analysis.rst
@@ -82,7 +82,7 @@ Supported Kernel Primitives

Currently the following synchronization primitives are supported:
`raw_spinlock_t`, `spinlock_t`, `rwlock_t`, `mutex`, `seqlock_t`,
-`bit_spinlock`, RCU.
+`bit_spinlock`, RCU, SRCU (`srcu_struct`).

For capabilities with an initialization function (e.g., `spin_lock_init()`),
calling this function on the capability instance before initializing any
diff --git a/include/linux/srcu.h b/include/linux/srcu.h
index f179700fecaf..6cafaf6dde71 100644
--- a/include/linux/srcu.h
+++ b/include/linux/srcu.h
@@ -21,7 +21,7 @@
#include <linux/workqueue.h>
#include <linux/rcu_segcblist.h>

-struct srcu_struct;
+struct_with_capability(srcu_struct, __reentrant_cap);

#ifdef CONFIG_DEBUG_LOCK_ALLOC

@@ -53,7 +53,7 @@ int init_srcu_struct(struct srcu_struct *ssp);
#define SRCU_READ_FLAVOR_SLOWGP SRCU_READ_FLAVOR_FAST
// Flavors requiring synchronize_rcu()
// instead of smp_mb().
-void __srcu_read_unlock(struct srcu_struct *ssp, int idx) __releases(ssp);
+void __srcu_read_unlock(struct srcu_struct *ssp, int idx) __releases_shared(ssp);

#ifdef CONFIG_TINY_SRCU
#include <linux/srcutiny.h>
@@ -107,14 +107,16 @@ static inline bool same_state_synchronize_srcu(unsigned long oldstate1, unsigned
}

#ifdef CONFIG_NEED_SRCU_NMI_SAFE
-int __srcu_read_lock_nmisafe(struct srcu_struct *ssp) __acquires(ssp);
-void __srcu_read_unlock_nmisafe(struct srcu_struct *ssp, int idx) __releases(ssp);
+int __srcu_read_lock_nmisafe(struct srcu_struct *ssp) __acquires_shared(ssp);
+void __srcu_read_unlock_nmisafe(struct srcu_struct *ssp, int idx) __releases_shared(ssp);
#else
static inline int __srcu_read_lock_nmisafe(struct srcu_struct *ssp)
+ __acquires_shared(ssp)
{
return __srcu_read_lock(ssp);
}
static inline void __srcu_read_unlock_nmisafe(struct srcu_struct *ssp, int idx)
+ __releases_shared(ssp)
{
__srcu_read_unlock(ssp, idx);
}
@@ -186,6 +188,14 @@ static inline int srcu_read_lock_held(const struct srcu_struct *ssp)

#endif /* #else #ifdef CONFIG_DEBUG_LOCK_ALLOC */

+/*
+ * No-op helper to denote that ssp must be held. Because SRCU-protected pointers
+ * should still be marked with __rcu_guarded, and we do not want to mark them
+ * with __guarded_by(ssp) as it would complicate annotations for writers, we
+ * choose the following strategy: srcu_dereference_check() calls this helper
+ * that checks that the passed ssp is held, and then fake-acquires 'RCU'.
+ */
+static inline void __srcu_read_lock_must_hold(const struct srcu_struct *ssp) __must_hold_shared(ssp) { }

/**
* srcu_dereference_check - fetch SRCU-protected pointer for later dereferencing
@@ -199,9 +209,15 @@ static inline int srcu_read_lock_held(const struct srcu_struct *ssp)
* to 1. The @c argument will normally be a logical expression containing
* lockdep_is_held() calls.
*/
-#define srcu_dereference_check(p, ssp, c) \
- __rcu_dereference_check((p), __UNIQUE_ID(rcu), \
- (c) || srcu_read_lock_held(ssp), __rcu)
+#define srcu_dereference_check(p, ssp, c) \
+({ \
+ __srcu_read_lock_must_hold(ssp); \
+ __acquire_shared_cap(RCU); \
+ __auto_type __v = __rcu_dereference_check((p), __UNIQUE_ID(rcu), \
+ (c) || srcu_read_lock_held(ssp), __rcu); \
+ __release_shared_cap(RCU); \
+ __v; \
+})

/**
* srcu_dereference - fetch SRCU-protected pointer for later dereferencing
@@ -244,7 +260,8 @@ static inline int srcu_read_lock_held(const struct srcu_struct *ssp)
* invoke srcu_read_unlock() from one task and the matching srcu_read_lock()
* from another.
*/
-static inline int srcu_read_lock(struct srcu_struct *ssp) __acquires(ssp)
+static inline int srcu_read_lock(struct srcu_struct *ssp)
+ __acquires_shared(ssp)
{
int retval;

@@ -271,7 +288,8 @@ static inline int srcu_read_lock(struct srcu_struct *ssp) __acquires(ssp)
* where RCU is watching, that is, from contexts where it would be legal
* to invoke rcu_read_lock(). Otherwise, lockdep will complain.
*/
-static inline struct srcu_ctr __percpu *srcu_read_lock_fast(struct srcu_struct *ssp) __acquires(ssp)
+static inline struct srcu_ctr __percpu *srcu_read_lock_fast(struct srcu_struct *ssp) __acquires_shared(ssp)
+ __acquires_shared(ssp)
{
struct srcu_ctr __percpu *retval;

@@ -292,7 +310,7 @@ static inline struct srcu_ctr __percpu *srcu_read_lock_fast(struct srcu_struct *
* The same srcu_struct may be used concurrently by srcu_down_read_fast()
* and srcu_read_lock_fast().
*/
-static inline struct srcu_ctr __percpu *srcu_down_read_fast(struct srcu_struct *ssp) __acquires(ssp)
+static inline struct srcu_ctr __percpu *srcu_down_read_fast(struct srcu_struct *ssp) __acquires_shared(ssp)
{
WARN_ON_ONCE(IS_ENABLED(CONFIG_PROVE_RCU) && in_nmi());
srcu_check_read_flavor_force(ssp, SRCU_READ_FLAVOR_FAST);
@@ -310,7 +328,8 @@ static inline struct srcu_ctr __percpu *srcu_down_read_fast(struct srcu_struct *
* then none of the other flavors may be used, whether before, during,
* or after.
*/
-static inline int srcu_read_lock_nmisafe(struct srcu_struct *ssp) __acquires(ssp)
+static inline int srcu_read_lock_nmisafe(struct srcu_struct *ssp)
+ __acquires_shared(ssp)
{
int retval;

@@ -322,7 +341,8 @@ static inline int srcu_read_lock_nmisafe(struct srcu_struct *ssp) __acquires(ssp

/* Used by tracing, cannot be traced and cannot invoke lockdep. */
static inline notrace int
-srcu_read_lock_notrace(struct srcu_struct *ssp) __acquires(ssp)
+srcu_read_lock_notrace(struct srcu_struct *ssp)
+ __acquires_shared(ssp)
{
int retval;

@@ -353,7 +373,8 @@ srcu_read_lock_notrace(struct srcu_struct *ssp) __acquires(ssp)
* which calls to down_read() may be nested. The same srcu_struct may be
* used concurrently by srcu_down_read() and srcu_read_lock().
*/
-static inline int srcu_down_read(struct srcu_struct *ssp) __acquires(ssp)
+static inline int srcu_down_read(struct srcu_struct *ssp)
+ __acquires_shared(ssp)
{
WARN_ON_ONCE(in_nmi());
srcu_check_read_flavor(ssp, SRCU_READ_FLAVOR_NORMAL);
@@ -368,7 +389,7 @@ static inline int srcu_down_read(struct srcu_struct *ssp) __acquires(ssp)
* Exit an SRCU read-side critical section.
*/
static inline void srcu_read_unlock(struct srcu_struct *ssp, int idx)
- __releases(ssp)
+ __releases_shared(ssp)
{
WARN_ON_ONCE(idx & ~0x1);
srcu_check_read_flavor(ssp, SRCU_READ_FLAVOR_NORMAL);
@@ -384,7 +405,7 @@ static inline void srcu_read_unlock(struct srcu_struct *ssp, int idx)
* Exit a light-weight SRCU read-side critical section.
*/
static inline void srcu_read_unlock_fast(struct srcu_struct *ssp, struct srcu_ctr __percpu *scp)
- __releases(ssp)
+ __releases_shared(ssp)
{
srcu_check_read_flavor(ssp, SRCU_READ_FLAVOR_FAST);
srcu_lock_release(&ssp->dep_map);
@@ -400,7 +421,7 @@ static inline void srcu_read_unlock_fast(struct srcu_struct *ssp, struct srcu_ct
* the same context as the maching srcu_down_read_fast().
*/
static inline void srcu_up_read_fast(struct srcu_struct *ssp, struct srcu_ctr __percpu *scp)
- __releases(ssp)
+ __releases_shared(ssp)
{
WARN_ON_ONCE(IS_ENABLED(CONFIG_PROVE_RCU) && in_nmi());
srcu_check_read_flavor(ssp, SRCU_READ_FLAVOR_FAST);
@@ -415,7 +436,7 @@ static inline void srcu_up_read_fast(struct srcu_struct *ssp, struct srcu_ctr __
* Exit an SRCU read-side critical section, but in an NMI-safe manner.
*/
static inline void srcu_read_unlock_nmisafe(struct srcu_struct *ssp, int idx)
- __releases(ssp)
+ __releases_shared(ssp)
{
WARN_ON_ONCE(idx & ~0x1);
srcu_check_read_flavor(ssp, SRCU_READ_FLAVOR_NMI);
@@ -425,7 +446,7 @@ static inline void srcu_read_unlock_nmisafe(struct srcu_struct *ssp, int idx)

/* Used by tracing, cannot be traced and cannot call lockdep. */
static inline notrace void
-srcu_read_unlock_notrace(struct srcu_struct *ssp, int idx) __releases(ssp)
+srcu_read_unlock_notrace(struct srcu_struct *ssp, int idx) __releases_shared(ssp)
{
srcu_check_read_flavor(ssp, SRCU_READ_FLAVOR_NORMAL);
__srcu_read_unlock(ssp, idx);
@@ -440,7 +461,7 @@ srcu_read_unlock_notrace(struct srcu_struct *ssp, int idx) __releases(ssp)
* the same context as the maching srcu_down_read().
*/
static inline void srcu_up_read(struct srcu_struct *ssp, int idx)
- __releases(ssp)
+ __releases_shared(ssp)
{
WARN_ON_ONCE(idx & ~0x1);
WARN_ON_ONCE(in_nmi());
@@ -480,6 +501,7 @@ DEFINE_LOCK_GUARD_1(srcu, struct srcu_struct,
_T->idx = srcu_read_lock(_T->lock),
srcu_read_unlock(_T->lock, _T->idx),
int idx)
+DECLARE_LOCK_GUARD_1_ATTRS(srcu, __assumes_cap(_T), /* */)

DEFINE_LOCK_GUARD_1(srcu_fast, struct srcu_struct,
_T->scp = srcu_read_lock_fast(_T->lock),
diff --git a/include/linux/srcutiny.h b/include/linux/srcutiny.h
index 51ce25f07930..c194b3c7c43b 100644
--- a/include/linux/srcutiny.h
+++ b/include/linux/srcutiny.h
@@ -61,6 +61,7 @@ void synchronize_srcu(struct srcu_struct *ssp);
* index that must be passed to the matching srcu_read_unlock().
*/
static inline int __srcu_read_lock(struct srcu_struct *ssp)
+ __acquires_shared(ssp)
{
int idx;

@@ -68,6 +69,7 @@ static inline int __srcu_read_lock(struct srcu_struct *ssp)
idx = ((READ_ONCE(ssp->srcu_idx) + 1) & 0x2) >> 1;
WRITE_ONCE(ssp->srcu_lock_nesting[idx], READ_ONCE(ssp->srcu_lock_nesting[idx]) + 1);
preempt_enable();
+ __acquire_shared(ssp);
return idx;
}

@@ -84,11 +86,13 @@ static inline struct srcu_ctr __percpu *__srcu_ctr_to_ptr(struct srcu_struct *ss
}

static inline struct srcu_ctr __percpu *__srcu_read_lock_fast(struct srcu_struct *ssp)
+ __acquires_shared(ssp)
{
return __srcu_ctr_to_ptr(ssp, __srcu_read_lock(ssp));
}

static inline void __srcu_read_unlock_fast(struct srcu_struct *ssp, struct srcu_ctr __percpu *scp)
+ __releases_shared(ssp)
{
__srcu_read_unlock(ssp, __srcu_ptr_to_ctr(ssp, scp));
}
diff --git a/include/linux/srcutree.h b/include/linux/srcutree.h
index bf44d8d1e69e..43754472e07a 100644
--- a/include/linux/srcutree.h
+++ b/include/linux/srcutree.h
@@ -207,7 +207,7 @@ struct srcu_struct {
#define DEFINE_SRCU(name) __DEFINE_SRCU(name, /* not static */)
#define DEFINE_STATIC_SRCU(name) __DEFINE_SRCU(name, static)

-int __srcu_read_lock(struct srcu_struct *ssp) __acquires(ssp);
+int __srcu_read_lock(struct srcu_struct *ssp) __acquires_shared(ssp);
void synchronize_srcu_expedited(struct srcu_struct *ssp);
void srcu_barrier(struct srcu_struct *ssp);
void srcu_torture_stats_print(struct srcu_struct *ssp, char *tt, char *tf);
@@ -241,6 +241,7 @@ static inline struct srcu_ctr __percpu *__srcu_ctr_to_ptr(struct srcu_struct *ss
* implementations of this_cpu_inc().
*/
static inline struct srcu_ctr __percpu *__srcu_read_lock_fast(struct srcu_struct *ssp)
+ __acquires_shared(ssp)
{
struct srcu_ctr __percpu *scp = READ_ONCE(ssp->srcu_ctrp);

@@ -250,6 +251,7 @@ static inline struct srcu_ctr __percpu *__srcu_read_lock_fast(struct srcu_struct
else
atomic_long_inc(raw_cpu_ptr(&scp->srcu_locks)); /* Z */
barrier(); /* Avoid leaking the critical section. */
+ __acquire_shared(ssp);
return scp;
}

@@ -269,7 +271,9 @@ static inline struct srcu_ctr __percpu *__srcu_read_lock_fast(struct srcu_struct
* implementations of this_cpu_inc().
*/
static inline void __srcu_read_unlock_fast(struct srcu_struct *ssp, struct srcu_ctr __percpu *scp)
+ __releases_shared(ssp)
{
+ __release_shared(ssp);
barrier(); /* Avoid leaking the critical section. */
if (!IS_ENABLED(CONFIG_NEED_SRCU_NMI_SAFE))
this_cpu_inc(scp->srcu_unlocks.counter); /* Z */
diff --git a/lib/test_capability-analysis.c b/lib/test_capability-analysis.c
index 31c9bc1e2405..5b17fd94f31e 100644
--- a/lib/test_capability-analysis.c
+++ b/lib/test_capability-analysis.c
@@ -10,6 +10,7 @@
#include <linux/rcupdate.h>
#include <linux/seqlock.h>
#include <linux/spinlock.h>
+#include <linux/srcu.h>

/*
* Test that helper macros work as expected.
@@ -362,3 +363,26 @@ static void __used test_rcu_assert_variants(void)
lockdep_assert_in_rcu_read_lock_sched();
wants_rcu_held_sched();
}
+
+struct test_srcu_data {
+ struct srcu_struct srcu;
+ long __rcu_guarded *data;
+};
+
+static void __used test_srcu(struct test_srcu_data *d)
+{
+ init_srcu_struct(&d->srcu);
+
+ int idx = srcu_read_lock(&d->srcu);
+ long *data = srcu_dereference(d->data, &d->srcu);
+ (void)data;
+ srcu_read_unlock(&d->srcu, idx);
+
+ rcu_assign_pointer(d->data, NULL);
+}
+
+static void __used test_srcu_guard(struct test_srcu_data *d)
+{
+ guard(srcu)(&d->srcu);
+ (void)srcu_dereference(d->data, &d->srcu);
+}
--
2.51.0.384.g4c02a37b29-goog

Marco Elver

unread,
Sep 18, 2025, 10:06:14 AM (6 days ago) Sep 18
to el...@google.com, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
Mark functions that conditionally acquire the passed lock.

Signed-off-by: Marco Elver <el...@google.com>
---
include/linux/kref.h | 2 ++
1 file changed, 2 insertions(+)

diff --git a/include/linux/kref.h b/include/linux/kref.h
index 88e82ab1367c..9bc6abe57572 100644
--- a/include/linux/kref.h
+++ b/include/linux/kref.h
@@ -81,6 +81,7 @@ static inline int kref_put(struct kref *kref, void (*release)(struct kref *kref)
static inline int kref_put_mutex(struct kref *kref,
void (*release)(struct kref *kref),
struct mutex *mutex)
+ __cond_acquires(true, mutex)
{
if (refcount_dec_and_mutex_lock(&kref->refcount, mutex)) {
release(kref);
@@ -102,6 +103,7 @@ static inline int kref_put_mutex(struct kref *kref,
static inline int kref_put_lock(struct kref *kref,
void (*release)(struct kref *kref),
spinlock_t *lock)
+ __cond_acquires(true, lock)
{
if (refcount_dec_and_lock(&kref->refcount, lock)) {
release(kref);
--
2.51.0.384.g4c02a37b29-goog

Marco Elver

unread,
Sep 18, 2025, 10:06:17 AM (6 days ago) Sep 18
to el...@google.com, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
Add support for Clang's capability analysis for rw_semaphore.

Signed-off-by: Marco Elver <el...@google.com>
---
v3:
* Switch to DECLARE_LOCK_GUARD_1_ATTRS() (suggested by Peter)
* __assert -> __assume rename
---
.../dev-tools/capability-analysis.rst | 2 +-
include/linux/rwsem.h | 66 ++++++++++++-------
lib/test_capability-analysis.c | 64 ++++++++++++++++++
3 files changed, 106 insertions(+), 26 deletions(-)

diff --git a/Documentation/dev-tools/capability-analysis.rst b/Documentation/dev-tools/capability-analysis.rst
index 779ecb5ec17a..7a4c2238c910 100644
--- a/Documentation/dev-tools/capability-analysis.rst
+++ b/Documentation/dev-tools/capability-analysis.rst
@@ -82,7 +82,7 @@ Supported Kernel Primitives

Currently the following synchronization primitives are supported:
`raw_spinlock_t`, `spinlock_t`, `rwlock_t`, `mutex`, `seqlock_t`,
-`bit_spinlock`, RCU, SRCU (`srcu_struct`).
+`bit_spinlock`, RCU, SRCU (`srcu_struct`), `rw_semaphore`.

For capabilities with an initialization function (e.g., `spin_lock_init()`),
calling this function on the capability instance before initializing any
diff --git a/include/linux/rwsem.h b/include/linux/rwsem.h
index f1aaf676a874..d2bce28be68b 100644
--- a/include/linux/rwsem.h
+++ b/include/linux/rwsem.h
@@ -45,7 +45,7 @@
* reduce the chance that they will share the same cacheline causing
* cacheline bouncing problem.
*/
-struct rw_semaphore {
+struct_with_capability(rw_semaphore) {
atomic_long_t count;
/*
* Write owner or one of the read owners as well flags regarding
@@ -76,11 +76,13 @@ static inline int rwsem_is_locked(struct rw_semaphore *sem)
}

static inline void rwsem_assert_held_nolockdep(const struct rw_semaphore *sem)
+ __assumes_cap(sem)
{
WARN_ON(atomic_long_read(&sem->count) == RWSEM_UNLOCKED_VALUE);
}

static inline void rwsem_assert_held_write_nolockdep(const struct rw_semaphore *sem)
+ __assumes_cap(sem)
{
WARN_ON(!(atomic_long_read(&sem->count) & RWSEM_WRITER_LOCKED));
}
@@ -119,6 +121,7 @@ do { \
static struct lock_class_key __key; \
\
__init_rwsem((sem), #sem, &__key); \
+ __assume_cap(sem); \
} while (0)

/*
@@ -148,7 +151,7 @@ extern bool is_rwsem_reader_owned(struct rw_semaphore *sem);

#include <linux/rwbase_rt.h>

-struct rw_semaphore {
+struct_with_capability(rw_semaphore) {
struct rwbase_rt rwbase;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
@@ -172,6 +175,7 @@ do { \
static struct lock_class_key __key; \
\
__init_rwsem((sem), #sem, &__key); \
+ __assume_cap(sem); \
} while (0)

static __always_inline int rwsem_is_locked(const struct rw_semaphore *sem)
@@ -180,11 +184,13 @@ static __always_inline int rwsem_is_locked(const struct rw_semaphore *sem)
}

static __always_inline void rwsem_assert_held_nolockdep(const struct rw_semaphore *sem)
+ __assumes_cap(sem)
{
WARN_ON(!rwsem_is_locked(sem));
}

static __always_inline void rwsem_assert_held_write_nolockdep(const struct rw_semaphore *sem)
+ __assumes_cap(sem)
{
WARN_ON(!rw_base_is_write_locked(&sem->rwbase));
}
@@ -202,6 +208,7 @@ static __always_inline int rwsem_is_contended(struct rw_semaphore *sem)
*/

static inline void rwsem_assert_held(const struct rw_semaphore *sem)
+ __assumes_cap(sem)
{
if (IS_ENABLED(CONFIG_LOCKDEP))
lockdep_assert_held(sem);
@@ -210,6 +217,7 @@ static inline void rwsem_assert_held(const struct rw_semaphore *sem)
}

static inline void rwsem_assert_held_write(const struct rw_semaphore *sem)
+ __assumes_cap(sem)
{
if (IS_ENABLED(CONFIG_LOCKDEP))
lockdep_assert_held_write(sem);
@@ -220,48 +228,56 @@ static inline void rwsem_assert_held_write(const struct rw_semaphore *sem)
/*
* lock for reading
*/
-extern void down_read(struct rw_semaphore *sem);
-extern int __must_check down_read_interruptible(struct rw_semaphore *sem);
-extern int __must_check down_read_killable(struct rw_semaphore *sem);
+extern void down_read(struct rw_semaphore *sem) __acquires_shared(sem);
+extern int __must_check down_read_interruptible(struct rw_semaphore *sem) __cond_acquires_shared(0, sem);
+extern int __must_check down_read_killable(struct rw_semaphore *sem) __cond_acquires_shared(0, sem);

/*
* trylock for reading -- returns 1 if successful, 0 if contention
*/
-extern int down_read_trylock(struct rw_semaphore *sem);
+extern int down_read_trylock(struct rw_semaphore *sem) __cond_acquires_shared(true, sem);

/*
* lock for writing
*/
-extern void down_write(struct rw_semaphore *sem);
-extern int __must_check down_write_killable(struct rw_semaphore *sem);
+extern void down_write(struct rw_semaphore *sem) __acquires(sem);
+extern int __must_check down_write_killable(struct rw_semaphore *sem) __cond_acquires(0, sem);

/*
* trylock for writing -- returns 1 if successful, 0 if contention
*/
-extern int down_write_trylock(struct rw_semaphore *sem);
+extern int down_write_trylock(struct rw_semaphore *sem) __cond_acquires(true, sem);

/*
* release a read lock
*/
-extern void up_read(struct rw_semaphore *sem);
+extern void up_read(struct rw_semaphore *sem) __releases_shared(sem);

/*
* release a write lock
*/
-extern void up_write(struct rw_semaphore *sem);
+extern void up_write(struct rw_semaphore *sem) __releases(sem);

-DEFINE_GUARD(rwsem_read, struct rw_semaphore *, down_read(_T), up_read(_T))
-DEFINE_GUARD_COND(rwsem_read, _try, down_read_trylock(_T))
-DEFINE_GUARD_COND(rwsem_read, _intr, down_read_interruptible(_T), _RET == 0)
+DEFINE_LOCK_GUARD_1(rwsem_read, struct rw_semaphore, down_read(_T->lock), up_read(_T->lock))
+DEFINE_LOCK_GUARD_1_COND(rwsem_read, _try, down_read_trylock(_T->lock))
+DEFINE_LOCK_GUARD_1_COND(rwsem_read, _intr, down_read_interruptible(_T->lock), _RET == 0)

-DEFINE_GUARD(rwsem_write, struct rw_semaphore *, down_write(_T), up_write(_T))
-DEFINE_GUARD_COND(rwsem_write, _try, down_write_trylock(_T))
-DEFINE_GUARD_COND(rwsem_write, _kill, down_write_killable(_T), _RET == 0)
+DECLARE_LOCK_GUARD_1_ATTRS(rwsem_read, __assumes_cap(_T), /* */)
+DECLARE_LOCK_GUARD_1_ATTRS(rwsem_read_try, __assumes_cap(_T), /* */)
+DECLARE_LOCK_GUARD_1_ATTRS(rwsem_read_intr, __assumes_cap(_T), /* */)
+
+DEFINE_LOCK_GUARD_1(rwsem_write, struct rw_semaphore, down_write(_T->lock), up_write(_T->lock))
+DEFINE_LOCK_GUARD_1_COND(rwsem_write, _try, down_write_trylock(_T->lock))
+DEFINE_LOCK_GUARD_1_COND(rwsem_write, _kill, down_write_killable(_T->lock), _RET == 0)
+
+DECLARE_LOCK_GUARD_1_ATTRS(rwsem_write, __assumes_cap(_T), /* */)
+DECLARE_LOCK_GUARD_1_ATTRS(rwsem_write_try, __assumes_cap(_T), /* */)
+DECLARE_LOCK_GUARD_1_ATTRS(rwsem_write_kill, __assumes_cap(_T), /* */)

/*
* downgrade write lock to read lock
*/
-extern void downgrade_write(struct rw_semaphore *sem);
+extern void downgrade_write(struct rw_semaphore *sem) __releases(sem) __acquires_shared(sem);

#ifdef CONFIG_DEBUG_LOCK_ALLOC
/*
@@ -277,11 +293,11 @@ extern void downgrade_write(struct rw_semaphore *sem);
* lockdep_set_class() at lock initialization time.
* See Documentation/locking/lockdep-design.rst for more details.)
*/
-extern void down_read_nested(struct rw_semaphore *sem, int subclass);
-extern int __must_check down_read_killable_nested(struct rw_semaphore *sem, int subclass);
-extern void down_write_nested(struct rw_semaphore *sem, int subclass);
-extern int down_write_killable_nested(struct rw_semaphore *sem, int subclass);
-extern void _down_write_nest_lock(struct rw_semaphore *sem, struct lockdep_map *nest_lock);
+extern void down_read_nested(struct rw_semaphore *sem, int subclass) __acquires_shared(sem);
+extern int __must_check down_read_killable_nested(struct rw_semaphore *sem, int subclass) __cond_acquires_shared(0, sem);
+extern void down_write_nested(struct rw_semaphore *sem, int subclass) __acquires(sem);
+extern int down_write_killable_nested(struct rw_semaphore *sem, int subclass) __cond_acquires(0, sem);
+extern void _down_write_nest_lock(struct rw_semaphore *sem, struct lockdep_map *nest_lock) __acquires(sem);

# define down_write_nest_lock(sem, nest_lock) \
do { \
@@ -295,8 +311,8 @@ do { \
* [ This API should be avoided as much as possible - the
* proper abstraction for this case is completions. ]
*/
-extern void down_read_non_owner(struct rw_semaphore *sem);
-extern void up_read_non_owner(struct rw_semaphore *sem);
+extern void down_read_non_owner(struct rw_semaphore *sem) __acquires_shared(sem);
+extern void up_read_non_owner(struct rw_semaphore *sem) __releases_shared(sem);
#else
# define down_read_nested(sem, subclass) down_read(sem)
# define down_read_killable_nested(sem, subclass) down_read_killable(sem)
diff --git a/lib/test_capability-analysis.c b/lib/test_capability-analysis.c
index 5b17fd94f31e..3c6dad0ba065 100644
--- a/lib/test_capability-analysis.c
+++ b/lib/test_capability-analysis.c
@@ -8,6 +8,7 @@
#include <linux/build_bug.h>
#include <linux/mutex.h>
#include <linux/rcupdate.h>
+#include <linux/rwsem.h>
#include <linux/seqlock.h>
#include <linux/spinlock.h>
#include <linux/srcu.h>
@@ -255,6 +256,69 @@ static void __used test_seqlock_writer(struct test_seqlock_data *d)
write_sequnlock_irqrestore(&d->sl, flags);
}

+struct test_rwsem_data {
+ struct rw_semaphore sem;
+ int counter __guarded_by(&sem);
+};
+
+static void __used test_rwsem_init(struct test_rwsem_data *d)
+{
+ init_rwsem(&d->sem);
+ d->counter = 0;
+}
+
+static void __used test_rwsem_reader(struct test_rwsem_data *d)
+{
+ down_read(&d->sem);
+ (void)d->counter;
+ up_read(&d->sem);
+
+ if (down_read_trylock(&d->sem)) {
+ (void)d->counter;
+ up_read(&d->sem);
+ }
+}
+
+static void __used test_rwsem_writer(struct test_rwsem_data *d)
+{
+ down_write(&d->sem);
+ d->counter++;
+ up_write(&d->sem);
+
+ down_write(&d->sem);
+ d->counter++;
+ downgrade_write(&d->sem);
+ (void)d->counter;
+ up_read(&d->sem);
+
+ if (down_write_trylock(&d->sem)) {
+ d->counter++;
+ up_write(&d->sem);
+ }
+}
+
+static void __used test_rwsem_assert(struct test_rwsem_data *d)
+{
+ rwsem_assert_held_nolockdep(&d->sem);
+ d->counter++;
+}
+
+static void __used test_rwsem_guard(struct test_rwsem_data *d)
+{
+ { guard(rwsem_read)(&d->sem); (void)d->counter; }
+ { guard(rwsem_write)(&d->sem); d->counter++; }
+}
+
+static void __used test_rwsem_cond_guard(struct test_rwsem_data *d)
+{
+ scoped_cond_guard(rwsem_read_try, return, &d->sem) {
+ (void)d->counter;
+ }
+ scoped_cond_guard(rwsem_write_try, return, &d->sem) {
+ d->counter++;
+ }
+}
+
struct test_bit_spinlock_data {
unsigned long bits;
int counter __guarded_by(__bitlock(3, &bits));
--
2.51.0.384.g4c02a37b29-goog

Marco Elver

unread,
Sep 18, 2025, 10:06:19 AM (6 days ago) Sep 18
to el...@google.com, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
Including <linux/local_lock.h> into an empty TU will result in the
compiler complaining:

./include/linux/local_lock.h: In function ‘class_local_lock_irqsave_constructor’:
./include/linux/local_lock_internal.h:95:17: error: implicit declaration of function ‘local_irq_save’; <...>
95 | local_irq_save(flags); \
| ^~~~~~~~~~~~~~

As well as (some architectures only, such as 'sh'):

./include/linux/local_lock_internal.h: In function ‘local_lock_acquire’:
./include/linux/local_lock_internal.h:33:20: error: ‘current’ undeclared (first use in this function)
33 | l->owner = current;

Include missing headers to allow including local_lock.h where the
required headers are not otherwise included.

Signed-off-by: Marco Elver <el...@google.com>
---
include/linux/local_lock_internal.h | 2 ++
1 file changed, 2 insertions(+)

diff --git a/include/linux/local_lock_internal.h b/include/linux/local_lock_internal.h
index d80b5306a2c0..4c0e117d2d08 100644
--- a/include/linux/local_lock_internal.h
+++ b/include/linux/local_lock_internal.h
@@ -4,7 +4,9 @@
#endif

#include <linux/percpu-defs.h>
+#include <linux/irqflags.h>
#include <linux/lockdep.h>
+#include <asm/current.h>

#ifndef CONFIG_PREEMPT_RT

--
2.51.0.384.g4c02a37b29-goog

Marco Elver

unread,
Sep 18, 2025, 10:06:22 AM (6 days ago) Sep 18
to el...@google.com, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
Add support for Clang's capability analysis for local_lock_t and
local_trylock_t.

Signed-off-by: Marco Elver <el...@google.com>
---
v3:
* Switch to DECLARE_LOCK_GUARD_1_ATTRS() (suggested by Peter)
* __assert -> __assume rename
* Rework __this_cpu_local_lock helper
* Support local_trylock_t
---
.../dev-tools/capability-analysis.rst | 2 +-
include/linux/local_lock.h | 45 +++++++-----
include/linux/local_lock_internal.h | 71 ++++++++++++++----
lib/test_capability-analysis.c | 73 +++++++++++++++++++
4 files changed, 156 insertions(+), 35 deletions(-)

diff --git a/Documentation/dev-tools/capability-analysis.rst b/Documentation/dev-tools/capability-analysis.rst
index 7a4c2238c910..9fb964e94920 100644
--- a/Documentation/dev-tools/capability-analysis.rst
+++ b/Documentation/dev-tools/capability-analysis.rst
@@ -82,7 +82,7 @@ Supported Kernel Primitives

Currently the following synchronization primitives are supported:
`raw_spinlock_t`, `spinlock_t`, `rwlock_t`, `mutex`, `seqlock_t`,
-`bit_spinlock`, RCU, SRCU (`srcu_struct`), `rw_semaphore`.
+`bit_spinlock`, RCU, SRCU (`srcu_struct`), `rw_semaphore`, `local_lock_t`.

For capabilities with an initialization function (e.g., `spin_lock_init()`),
calling this function on the capability instance before initializing any
diff --git a/include/linux/local_lock.h b/include/linux/local_lock.h
index 2ba846419524..cfdca5bee89e 100644
--- a/include/linux/local_lock.h
+++ b/include/linux/local_lock.h
@@ -13,13 +13,13 @@
* local_lock - Acquire a per CPU local lock
* @lock: The lock variable
*/
-#define local_lock(lock) __local_lock(this_cpu_ptr(lock))
+#define local_lock(lock) __local_lock(__this_cpu_local_lock(lock))

/**
* local_lock_irq - Acquire a per CPU local lock and disable interrupts
* @lock: The lock variable
*/
-#define local_lock_irq(lock) __local_lock_irq(this_cpu_ptr(lock))
+#define local_lock_irq(lock) __local_lock_irq(__this_cpu_local_lock(lock))

/**
* local_lock_irqsave - Acquire a per CPU local lock, save and disable
@@ -28,19 +28,19 @@
* @flags: Storage for interrupt flags
*/
#define local_lock_irqsave(lock, flags) \
- __local_lock_irqsave(this_cpu_ptr(lock), flags)
+ __local_lock_irqsave(__this_cpu_local_lock(lock), flags)

/**
* local_unlock - Release a per CPU local lock
* @lock: The lock variable
*/
-#define local_unlock(lock) __local_unlock(this_cpu_ptr(lock))
+#define local_unlock(lock) __local_unlock(__this_cpu_local_lock(lock))

/**
* local_unlock_irq - Release a per CPU local lock and enable interrupts
* @lock: The lock variable
*/
-#define local_unlock_irq(lock) __local_unlock_irq(this_cpu_ptr(lock))
+#define local_unlock_irq(lock) __local_unlock_irq(__this_cpu_local_lock(lock))

/**
* local_unlock_irqrestore - Release a per CPU local lock and restore
@@ -49,7 +49,7 @@
* @flags: Interrupt flags to restore
*/
#define local_unlock_irqrestore(lock, flags) \
- __local_unlock_irqrestore(this_cpu_ptr(lock), flags)
+ __local_unlock_irqrestore(__this_cpu_local_lock(lock), flags)

/**
* local_lock_init - Runtime initialize a lock instance
@@ -64,7 +64,7 @@
* locking constrains it will _always_ fail to acquire the lock in NMI or
* HARDIRQ context on PREEMPT_RT.
*/
-#define local_trylock(lock) __local_trylock(this_cpu_ptr(lock))
+#define local_trylock(lock) __local_trylock(__this_cpu_local_lock(lock))

/**
* local_trylock_irqsave - Try to acquire a per CPU local lock, save and disable
@@ -77,27 +77,32 @@
* HARDIRQ context on PREEMPT_RT.
*/
#define local_trylock_irqsave(lock, flags) \
- __local_trylock_irqsave(this_cpu_ptr(lock), flags)
-
-DEFINE_GUARD(local_lock, local_lock_t __percpu*,
- local_lock(_T),
- local_unlock(_T))
-DEFINE_GUARD(local_lock_irq, local_lock_t __percpu*,
- local_lock_irq(_T),
- local_unlock_irq(_T))
+ __local_trylock_irqsave(__this_cpu_local_lock(lock), flags)
+
+DEFINE_LOCK_GUARD_1(local_lock, local_lock_t __percpu,
+ local_lock(_T->lock),
+ local_unlock(_T->lock))
+DEFINE_LOCK_GUARD_1(local_lock_irq, local_lock_t __percpu,
+ local_lock_irq(_T->lock),
+ local_unlock_irq(_T->lock))
DEFINE_LOCK_GUARD_1(local_lock_irqsave, local_lock_t __percpu,
local_lock_irqsave(_T->lock, _T->flags),
local_unlock_irqrestore(_T->lock, _T->flags),
unsigned long flags)

#define local_lock_nested_bh(_lock) \
- __local_lock_nested_bh(this_cpu_ptr(_lock))
+ __local_lock_nested_bh(__this_cpu_local_lock(_lock))

#define local_unlock_nested_bh(_lock) \
- __local_unlock_nested_bh(this_cpu_ptr(_lock))
+ __local_unlock_nested_bh(__this_cpu_local_lock(_lock))

-DEFINE_GUARD(local_lock_nested_bh, local_lock_t __percpu*,
- local_lock_nested_bh(_T),
- local_unlock_nested_bh(_T))
+DEFINE_LOCK_GUARD_1(local_lock_nested_bh, local_lock_t __percpu,
+ local_lock_nested_bh(_T->lock),
+ local_unlock_nested_bh(_T->lock))
+
+DECLARE_LOCK_GUARD_1_ATTRS(local_lock, __assumes_cap(_T), /* */)
+DECLARE_LOCK_GUARD_1_ATTRS(local_lock_irq, __assumes_cap(_T), /* */)
+DECLARE_LOCK_GUARD_1_ATTRS(local_lock_irqsave, __assumes_cap(_T), /* */)
+DECLARE_LOCK_GUARD_1_ATTRS(local_lock_nested_bh, __assumes_cap(_T), /* */)

#endif
diff --git a/include/linux/local_lock_internal.h b/include/linux/local_lock_internal.h
index 4c0e117d2d08..22ffaf06d9eb 100644
--- a/include/linux/local_lock_internal.h
+++ b/include/linux/local_lock_internal.h
@@ -10,18 +10,20 @@

#ifndef CONFIG_PREEMPT_RT

-typedef struct {
+struct_with_capability(local_lock) {
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
struct task_struct *owner;
#endif
-} local_lock_t;
+};
+typedef struct local_lock local_lock_t;

/* local_trylock() and local_trylock_irqsave() only work with local_trylock_t */
-typedef struct {
+struct_with_capability(local_trylock) {
local_lock_t llock;
u8 acquired;
-} local_trylock_t;
+};
+typedef struct local_trylock local_trylock_t;

#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCAL_LOCK_DEBUG_INIT(lockname) \
@@ -81,9 +83,14 @@ do { \
0, LD_WAIT_CONFIG, LD_WAIT_INV, \
LD_LOCK_PERCPU); \
local_lock_debug_init(lock); \
+ __assume_cap(lock); \
} while (0)

-#define __local_trylock_init(lock) __local_lock_init(lock.llock)
+#define __local_trylock_init(lock) \
+do { \
+ __local_lock_init(lock.llock); \
+ __assume_cap(lock); \
+} while (0)

#define __spinlock_nested_bh_init(lock) \
do { \
@@ -94,6 +101,7 @@ do { \
0, LD_WAIT_CONFIG, LD_WAIT_INV, \
LD_LOCK_NORMAL); \
local_lock_debug_init(lock); \
+ __assume_cap(lock); \
} while (0)

#define __local_lock_acquire(lock) \
@@ -116,22 +124,25 @@ do { \
do { \
preempt_disable(); \
__local_lock_acquire(lock); \
+ __acquire(lock); \
} while (0)

#define __local_lock_irq(lock) \
do { \
local_irq_disable(); \
__local_lock_acquire(lock); \
+ __acquire(lock); \
} while (0)

#define __local_lock_irqsave(lock, flags) \
do { \
local_irq_save(flags); \
__local_lock_acquire(lock); \
+ __acquire(lock); \
} while (0)

#define __local_trylock(lock) \
- ({ \
+ __try_acquire_cap(lock, ({ \
local_trylock_t *tl; \
\
preempt_disable(); \
@@ -145,10 +156,10 @@ do { \
(local_lock_t *)tl); \
} \
!!tl; \
- })
+ }))

#define __local_trylock_irqsave(lock, flags) \
- ({ \
+ __try_acquire_cap(lock, ({ \
local_trylock_t *tl; \
\
local_irq_save(flags); \
@@ -162,7 +173,7 @@ do { \
(local_lock_t *)tl); \
} \
!!tl; \
- })
+ }))

#define __local_lock_release(lock) \
do { \
@@ -182,18 +193,21 @@ do { \

#define __local_unlock(lock) \
do { \
+ __release(lock); \
__local_lock_release(lock); \
preempt_enable(); \
} while (0)

#define __local_unlock_irq(lock) \
do { \
+ __release(lock); \
__local_lock_release(lock); \
local_irq_enable(); \
} while (0)

#define __local_unlock_irqrestore(lock, flags) \
do { \
+ __release(lock); \
__local_lock_release(lock); \
local_irq_restore(flags); \
} while (0)
@@ -202,13 +216,19 @@ do { \
do { \
lockdep_assert_in_softirq(); \
local_lock_acquire((lock)); \
+ __acquire(lock); \
} while (0)

#define __local_unlock_nested_bh(lock) \
- local_lock_release((lock))
+ do { \
+ __release(lock); \
+ local_lock_release((lock)); \
+ } while (0)

#else /* !CONFIG_PREEMPT_RT */

+#include <linux/spinlock.h>
+
/*
* On PREEMPT_RT local_lock maps to a per CPU spinlock, which protects the
* critical section while staying preemptible.
@@ -263,7 +283,7 @@ do { \
} while (0)

#define __local_trylock(lock) \
- ({ \
+ __try_acquire_cap(lock, capability_unsafe(({ \
int __locked; \
\
if (in_nmi() | in_hardirq()) { \
@@ -275,13 +295,36 @@ do { \
migrate_enable(); \
} \
__locked; \
- })
+ })))

#define __local_trylock_irqsave(lock, flags) \
- ({ \
+ __try_acquire_cap(lock, ({ \
typecheck(unsigned long, flags); \
flags = 0; \
__local_trylock(lock); \
- })
+ }))
+
+#endif /* CONFIG_PREEMPT_RT */

+#if defined(WARN_CAPABILITY_ANALYSIS)
+/*
+ * Because the compiler only knows about the base per-CPU variable, use this
+ * helper function to make the compiler think we lock/unlock the @base variable,
+ * and hide the fact we actually pass the per-CPU instance to lock/unlock
+ * functions.
+ */
+static __always_inline local_lock_t *__this_cpu_local_lock(local_lock_t __percpu *base)
+ __returns_cap(base) __attribute__((overloadable))
+{
+ return this_cpu_ptr(base);
+}
+#ifndef CONFIG_PREEMPT_RT
+static __always_inline local_trylock_t *__this_cpu_local_lock(local_trylock_t __percpu *base)
+ __returns_cap(base) __attribute__((overloadable))
+{
+ return this_cpu_ptr(base);
+}
#endif /* CONFIG_PREEMPT_RT */
+#else /* WARN_CAPABILITY_ANALYSIS */
+#define __this_cpu_local_lock(base) this_cpu_ptr(base)
+#endif /* WARN_CAPABILITY_ANALYSIS */
diff --git a/lib/test_capability-analysis.c b/lib/test_capability-analysis.c
index 3c6dad0ba065..e506dadb3933 100644
--- a/lib/test_capability-analysis.c
+++ b/lib/test_capability-analysis.c
@@ -6,7 +6,9 @@

#include <linux/bit_spinlock.h>
#include <linux/build_bug.h>
+#include <linux/local_lock.h>
#include <linux/mutex.h>
+#include <linux/percpu.h>
#include <linux/rcupdate.h>
#include <linux/rwsem.h>
#include <linux/seqlock.h>
@@ -450,3 +452,74 @@ static void __used test_srcu_guard(struct test_srcu_data *d)
guard(srcu)(&d->srcu);
(void)srcu_dereference(d->data, &d->srcu);
}
+
+struct test_local_lock_data {
+ local_lock_t lock;
+ int counter __guarded_by(&lock);
+};
+
+static DEFINE_PER_CPU(struct test_local_lock_data, test_local_lock_data) = {
+ .lock = INIT_LOCAL_LOCK(lock),
+};
+
+static void __used test_local_lock_init(struct test_local_lock_data *d)
+{
+ local_lock_init(&d->lock);
+ d->counter = 0;
+}
+
+static void __used test_local_lock(void)
+{
+ unsigned long flags;
+
+ local_lock(&test_local_lock_data.lock);
+ this_cpu_add(test_local_lock_data.counter, 1);
+ local_unlock(&test_local_lock_data.lock);
+
+ local_lock_irq(&test_local_lock_data.lock);
+ this_cpu_add(test_local_lock_data.counter, 1);
+ local_unlock_irq(&test_local_lock_data.lock);
+
+ local_lock_irqsave(&test_local_lock_data.lock, flags);
+ this_cpu_add(test_local_lock_data.counter, 1);
+ local_unlock_irqrestore(&test_local_lock_data.lock, flags);
+
+ local_lock_nested_bh(&test_local_lock_data.lock);
+ this_cpu_add(test_local_lock_data.counter, 1);
+ local_unlock_nested_bh(&test_local_lock_data.lock);
+}
+
+static void __used test_local_lock_guard(void)
+{
+ { guard(local_lock)(&test_local_lock_data.lock); this_cpu_add(test_local_lock_data.counter, 1); }
+ { guard(local_lock_irq)(&test_local_lock_data.lock); this_cpu_add(test_local_lock_data.counter, 1); }
+ { guard(local_lock_irqsave)(&test_local_lock_data.lock); this_cpu_add(test_local_lock_data.counter, 1); }
+ { guard(local_lock_nested_bh)(&test_local_lock_data.lock); this_cpu_add(test_local_lock_data.counter, 1); }
+}
+
+struct test_local_trylock_data {
+ local_trylock_t lock;
+ int counter __guarded_by(&lock);
+};
+
+static DEFINE_PER_CPU(struct test_local_trylock_data, test_local_trylock_data) = {
+ .lock = INIT_LOCAL_TRYLOCK(lock),
+};
+
+static void __used test_local_trylock_init(struct test_local_trylock_data *d)
+{
+ local_trylock_init(&d->lock);
+ d->counter = 0;
+}
+
+static void __used test_local_trylock(void)
+{
+ local_lock(&test_local_trylock_data.lock);
+ this_cpu_add(test_local_trylock_data.counter, 1);
+ local_unlock(&test_local_trylock_data.lock);
+
+ if (local_trylock(&test_local_trylock_data.lock)) {
+ this_cpu_add(test_local_trylock_data.counter, 1);
+ local_unlock(&test_local_trylock_data.lock);
+ }
+}
--
2.51.0.384.g4c02a37b29-goog

Marco Elver

unread,
Sep 18, 2025, 10:06:25 AM (6 days ago) Sep 18
to el...@google.com, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
Add support for Clang's capability analysis for ww_mutex.

The programming model for ww_mutex is subtly more complex than other
locking primitives when using ww_acquire_ctx. Encoding the respective
pre-conditions for ww_mutex lock/unlock based on ww_acquire_ctx state
using Clang's capability analysis makes incorrect use of the API harder.

Signed-off-by: Marco Elver <el...@google.com>
---
v3:
* __assert -> __assume rename

v2:
* New patch.
---
.../dev-tools/capability-analysis.rst | 3 +-
include/linux/ww_mutex.h | 22 ++++--
lib/test_capability-analysis.c | 69 +++++++++++++++++++
3 files changed, 87 insertions(+), 7 deletions(-)

diff --git a/Documentation/dev-tools/capability-analysis.rst b/Documentation/dev-tools/capability-analysis.rst
index 9fb964e94920..2b89d346723b 100644
--- a/Documentation/dev-tools/capability-analysis.rst
+++ b/Documentation/dev-tools/capability-analysis.rst
@@ -82,7 +82,8 @@ Supported Kernel Primitives

Currently the following synchronization primitives are supported:
`raw_spinlock_t`, `spinlock_t`, `rwlock_t`, `mutex`, `seqlock_t`,
-`bit_spinlock`, RCU, SRCU (`srcu_struct`), `rw_semaphore`, `local_lock_t`.
+`bit_spinlock`, RCU, SRCU (`srcu_struct`), `rw_semaphore`, `local_lock_t`,
+`ww_mutex`.

For capabilities with an initialization function (e.g., `spin_lock_init()`),
calling this function on the capability instance before initializing any
diff --git a/include/linux/ww_mutex.h b/include/linux/ww_mutex.h
index 45ff6f7a872b..549d75aee76a 100644
--- a/include/linux/ww_mutex.h
+++ b/include/linux/ww_mutex.h
@@ -44,7 +44,7 @@ struct ww_class {
unsigned int is_wait_die;
};

-struct ww_mutex {
+struct_with_capability(ww_mutex) {
struct WW_MUTEX_BASE base;
struct ww_acquire_ctx *ctx;
#ifdef DEBUG_WW_MUTEXES
@@ -52,7 +52,7 @@ struct ww_mutex {
#endif
};

-struct ww_acquire_ctx {
+struct_with_capability(ww_acquire_ctx) {
struct task_struct *task;
unsigned long stamp;
unsigned int acquired;
@@ -107,6 +107,7 @@ struct ww_acquire_ctx {
*/
static inline void ww_mutex_init(struct ww_mutex *lock,
struct ww_class *ww_class)
+ __assumes_cap(lock)
{
ww_mutex_base_init(&lock->base, ww_class->mutex_name, &ww_class->mutex_key);
lock->ctx = NULL;
@@ -141,6 +142,7 @@ static inline void ww_mutex_init(struct ww_mutex *lock,
*/
static inline void ww_acquire_init(struct ww_acquire_ctx *ctx,
struct ww_class *ww_class)
+ __acquires(ctx) __no_capability_analysis
{
ctx->task = current;
ctx->stamp = atomic_long_inc_return_relaxed(&ww_class->stamp);
@@ -179,6 +181,7 @@ static inline void ww_acquire_init(struct ww_acquire_ctx *ctx,
* data structures.
*/
static inline void ww_acquire_done(struct ww_acquire_ctx *ctx)
+ __releases(ctx) __acquires_shared(ctx) __no_capability_analysis
{
#ifdef DEBUG_WW_MUTEXES
lockdep_assert_held(ctx);
@@ -196,6 +199,7 @@ static inline void ww_acquire_done(struct ww_acquire_ctx *ctx)
* mutexes have been released with ww_mutex_unlock.
*/
static inline void ww_acquire_fini(struct ww_acquire_ctx *ctx)
+ __releases_shared(ctx) __no_capability_analysis
{
#ifdef CONFIG_DEBUG_LOCK_ALLOC
mutex_release(&ctx->first_lock_dep_map, _THIS_IP_);
@@ -245,7 +249,8 @@ static inline void ww_acquire_fini(struct ww_acquire_ctx *ctx)
*
* A mutex acquired with this function must be released with ww_mutex_unlock.
*/
-extern int /* __must_check */ ww_mutex_lock(struct ww_mutex *lock, struct ww_acquire_ctx *ctx);
+extern int /* __must_check */ ww_mutex_lock(struct ww_mutex *lock, struct ww_acquire_ctx *ctx)
+ __cond_acquires(0, lock) __must_hold(ctx);

/**
* ww_mutex_lock_interruptible - acquire the w/w mutex, interruptible
@@ -278,7 +283,8 @@ extern int /* __must_check */ ww_mutex_lock(struct ww_mutex *lock, struct ww_acq
* A mutex acquired with this function must be released with ww_mutex_unlock.
*/
extern int __must_check ww_mutex_lock_interruptible(struct ww_mutex *lock,
- struct ww_acquire_ctx *ctx);
+ struct ww_acquire_ctx *ctx)
+ __cond_acquires(0, lock) __must_hold(ctx);

/**
* ww_mutex_lock_slow - slowpath acquiring of the w/w mutex
@@ -305,6 +311,7 @@ extern int __must_check ww_mutex_lock_interruptible(struct ww_mutex *lock,
*/
static inline void
ww_mutex_lock_slow(struct ww_mutex *lock, struct ww_acquire_ctx *ctx)
+ __acquires(lock) __must_hold(ctx) __no_capability_analysis
{
int ret;
#ifdef DEBUG_WW_MUTEXES
@@ -342,6 +349,7 @@ ww_mutex_lock_slow(struct ww_mutex *lock, struct ww_acquire_ctx *ctx)
static inline int __must_check
ww_mutex_lock_slow_interruptible(struct ww_mutex *lock,
struct ww_acquire_ctx *ctx)
+ __cond_acquires(0, lock) __must_hold(ctx)
{
#ifdef DEBUG_WW_MUTEXES
DEBUG_LOCKS_WARN_ON(!ctx->contending_lock);
@@ -349,10 +357,11 @@ ww_mutex_lock_slow_interruptible(struct ww_mutex *lock,
return ww_mutex_lock_interruptible(lock, ctx);
}

-extern void ww_mutex_unlock(struct ww_mutex *lock);
+extern void ww_mutex_unlock(struct ww_mutex *lock) __releases(lock);

extern int __must_check ww_mutex_trylock(struct ww_mutex *lock,
- struct ww_acquire_ctx *ctx);
+ struct ww_acquire_ctx *ctx)
+ __cond_acquires(true, lock) __must_hold(ctx);

/***
* ww_mutex_destroy - mark a w/w mutex unusable
@@ -363,6 +372,7 @@ extern int __must_check ww_mutex_trylock(struct ww_mutex *lock,
* this function is called.
*/
static inline void ww_mutex_destroy(struct ww_mutex *lock)
+ __must_not_hold(lock)
{
#ifndef CONFIG_PREEMPT_RT
mutex_destroy(&lock->base);
diff --git a/lib/test_capability-analysis.c b/lib/test_capability-analysis.c
index e506dadb3933..12fd9716f0a4 100644
--- a/lib/test_capability-analysis.c
+++ b/lib/test_capability-analysis.c
@@ -14,6 +14,7 @@
#include <linux/seqlock.h>
#include <linux/spinlock.h>
#include <linux/srcu.h>
+#include <linux/ww_mutex.h>

/*
* Test that helper macros work as expected.
@@ -523,3 +524,71 @@ static void __used test_local_trylock(void)
local_unlock(&test_local_trylock_data.lock);
}
}
+
+static DEFINE_WD_CLASS(ww_class);
+
+struct test_ww_mutex_data {
+ struct ww_mutex mtx;
+ int counter __guarded_by(&mtx);
+};
+
+static void __used test_ww_mutex_init(struct test_ww_mutex_data *d)
+{
+ ww_mutex_init(&d->mtx, &ww_class);
+ d->counter = 0;
+}
+
+static void __used test_ww_mutex_lock_noctx(struct test_ww_mutex_data *d)
+{
+ if (!ww_mutex_lock(&d->mtx, NULL)) {
+ d->counter++;
+ ww_mutex_unlock(&d->mtx);
+ }
+
+ if (!ww_mutex_lock_interruptible(&d->mtx, NULL)) {
+ d->counter++;
+ ww_mutex_unlock(&d->mtx);
+ }
+
+ if (ww_mutex_trylock(&d->mtx, NULL)) {
+ d->counter++;
+ ww_mutex_unlock(&d->mtx);
+ }
+
+ ww_mutex_lock_slow(&d->mtx, NULL);
+ d->counter++;
+ ww_mutex_unlock(&d->mtx);
+
+ ww_mutex_destroy(&d->mtx);
+}
+
+static void __used test_ww_mutex_lock_ctx(struct test_ww_mutex_data *d)
+{
+ struct ww_acquire_ctx ctx;
+
+ ww_acquire_init(&ctx, &ww_class);
+
+ if (!ww_mutex_lock(&d->mtx, &ctx)) {
+ d->counter++;
+ ww_mutex_unlock(&d->mtx);
+ }
+
+ if (!ww_mutex_lock_interruptible(&d->mtx, &ctx)) {
+ d->counter++;
+ ww_mutex_unlock(&d->mtx);
+ }
+
+ if (ww_mutex_trylock(&d->mtx, &ctx)) {
+ d->counter++;
+ ww_mutex_unlock(&d->mtx);
+ }
+
+ ww_mutex_lock_slow(&d->mtx, &ctx);
+ d->counter++;
+ ww_mutex_unlock(&d->mtx);
+
+ ww_acquire_done(&ctx);
+ ww_acquire_fini(&ctx);
+
+ ww_mutex_destroy(&d->mtx);
+}
--
2.51.0.384.g4c02a37b29-goog

Marco Elver

unread,
Sep 18, 2025, 10:06:28 AM (6 days ago) Sep 18
to el...@google.com, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
When compiling include/linux/debugfs.h with CAPABILITY_ANALYSIS enabled,
we can see this error:

./include/linux/debugfs.h:239:17: error: use of undeclared identifier 'cancellation'
239 | void __acquires(cancellation)

Move the __acquires(..) attribute after the declaration, so that the
compiler can see the cancellation function argument, as well as making
struct debugfs_cancellation a real capability to benefit from Clang's
capability analysis.

Signed-off-by: Marco Elver <el...@google.com>
---
include/linux/debugfs.h | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)

diff --git a/include/linux/debugfs.h b/include/linux/debugfs.h
index 7cecda29447e..4ee838cf4678 100644
--- a/include/linux/debugfs.h
+++ b/include/linux/debugfs.h
@@ -239,18 +239,16 @@ ssize_t debugfs_read_file_str(struct file *file, char __user *user_buf,
* @cancel: callback to call
* @cancel_data: extra data for the callback to call
*/
-struct debugfs_cancellation {
+struct_with_capability(debugfs_cancellation) {
struct list_head list;
void (*cancel)(struct dentry *, void *);
void *cancel_data;
};

-void __acquires(cancellation)
-debugfs_enter_cancellation(struct file *file,
- struct debugfs_cancellation *cancellation);
-void __releases(cancellation)
-debugfs_leave_cancellation(struct file *file,
- struct debugfs_cancellation *cancellation);
+void debugfs_enter_cancellation(struct file *file,
+ struct debugfs_cancellation *cancellation) __acquires(cancellation);
+void debugfs_leave_cancellation(struct file *file,
+ struct debugfs_cancellation *cancellation) __releases(cancellation);

#else

--
2.51.0.384.g4c02a37b29-goog

Marco Elver

unread,
Sep 18, 2025, 10:06:30 AM (6 days ago) Sep 18
to el...@google.com, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
Remove Sparse support as discussed at [1].

The kernel codebase is still scattered with numerous places that try to
appease Sparse's context tracking ("annotation for sparse", "fake out
sparse", "work around sparse", etc.). Eventually, as more subsystems
enable Clang's capability analysis, these places will show up and need
adjustment or removal of the workarounds altogether.

Link: https://lore.kernel.org/all/2025020708...@noisy.programming.kicks-ass.net/ [1]
Link: https://lore.kernel.org/all/Z6XTKTo_...@elver.google.com/ [2]
Cc: "Luc Van Oostenryck" <luc.vano...@gmail.com>
Cc: Peter Zijlstra <pet...@infradead.org>
Signed-off-by: Marco Elver <el...@google.com>
---
v2:
* New patch.
---
Documentation/dev-tools/sparse.rst | 19 -----
include/linux/compiler-capability-analysis.h | 80 ++++++--------------
include/linux/rcupdate.h | 15 +---
3 files changed, 25 insertions(+), 89 deletions(-)

diff --git a/Documentation/dev-tools/sparse.rst b/Documentation/dev-tools/sparse.rst
index dc791c8d84d1..37b20170835d 100644
--- a/Documentation/dev-tools/sparse.rst
+++ b/Documentation/dev-tools/sparse.rst
@@ -53,25 +53,6 @@ sure that bitwise types don't get mixed up (little-endian vs big-endian
vs cpu-endian vs whatever), and there the constant "0" really _is_
special.

-Using sparse for lock checking
-------------------------------
-
-The following macros are undefined for gcc and defined during a sparse
-run to use the "context" tracking feature of sparse, applied to
-locking. These annotations tell sparse when a lock is held, with
-regard to the annotated function's entry and exit.
-
-__must_hold - The specified lock is held on function entry and exit.
-
-__acquires - The specified lock is held on function exit, but not entry.
-
-__releases - The specified lock is held on function entry, but not exit.
-
-If the function enters and exits without the lock held, acquiring and
-releasing the lock inside the function in a balanced way, no
-annotation is needed. The three annotations above are for cases where
-sparse would otherwise report a context imbalance.
-
Getting sparse
--------------

diff --git a/include/linux/compiler-capability-analysis.h b/include/linux/compiler-capability-analysis.h
index ccd312dbbf06..6046fca44f17 100644
--- a/include/linux/compiler-capability-analysis.h
+++ b/include/linux/compiler-capability-analysis.h
@@ -248,57 +248,32 @@ static inline void _capability_unsafe_alias(void **p) { }
extern const struct __capability_##cap *name

/*
- * Common keywords for static capability analysis. Both Clang's capability
- * analysis and Sparse's context tracking are currently supported.
- */
-#ifdef __CHECKER__
-
-/* Sparse context/lock checking support. */
-# define __must_hold(x) __attribute__((context(x,1,1)))
-# define __must_not_hold(x)
-# define __acquires(x) __attribute__((context(x,0,1)))
-# define __cond_acquires(ret, x) __attribute__((context(x,0,-1)))
-# define __releases(x) __attribute__((context(x,1,0)))
-# define __acquire(x) __context__(x,1)
-# define __release(x) __context__(x,-1)
-# define __cond_lock(x, c) ((c) ? ({ __acquire(x); 1; }) : 0)
-/* For Sparse, there's no distinction between exclusive and shared locks. */
-# define __must_hold_shared __must_hold
-# define __acquires_shared __acquires
-# define __cond_acquires_shared __cond_acquires
-# define __releases_shared __releases
-# define __acquire_shared __acquire
-# define __release_shared __release
-# define __cond_lock_shared __cond_acquire
-
-#else /* !__CHECKER__ */
+ * Common keywords for static capability analysis.
+ */

/**
* __must_hold() - function attribute, caller must hold exclusive capability
- * @x: capability instance pointer
*
* Function attribute declaring that the caller must hold the given capability
- * instance @x exclusively.
+ * instance(s) exclusively.
*/
-# define __must_hold(x) __requires_cap(x)
+#define __must_hold(...) __requires_cap(__VA_ARGS__)

/**
* __must_not_hold() - function attribute, caller must not hold capability
- * @x: capability instance pointer
*
* Function attribute declaring that the caller must not hold the given
- * capability instance @x.
+ * capability instance(s).
*/
-# define __must_not_hold(x) __excludes_cap(x)
+#define __must_not_hold(...) __excludes_cap(__VA_ARGS__)

/**
* __acquires() - function attribute, function acquires capability exclusively
- * @x: capability instance pointer
*
* Function attribute declaring that the function acquires the given
- * capability instance @x exclusively, but does not release it.
+ * capability instance(s) exclusively, but does not release them.
*/
-# define __acquires(x) __acquires_cap(x)
+#define __acquires(...) __acquires_cap(__VA_ARGS__)

/*
* Clang's analysis does not care precisely about the value, only that it is
@@ -325,16 +300,15 @@ static inline void _capability_unsafe_alias(void **p) { }
*
* @ret may be one of: true, false, nonzero, 0, nonnull, NULL.
*/
-# define __cond_acquires(ret, x) __cond_acquires_impl_##ret(x)
+#define __cond_acquires(ret, x) __cond_acquires_impl_##ret(x)

/**
* __releases() - function attribute, function releases a capability exclusively
- * @x: capability instance pointer
*
* Function attribute declaring that the function releases the given capability
- * instance @x exclusively. The capability must be held on entry.
+ * instance(s) exclusively. The capability must be held on entry.
*/
-# define __releases(x) __releases_cap(x)
+#define __releases(...) __releases_cap(__VA_ARGS__)

/**
* __acquire() - function to acquire capability exclusively
@@ -342,7 +316,7 @@ static inline void _capability_unsafe_alias(void **p) { }
*
* No-op function that acquires the given capability instance @x exclusively.
*/
-# define __acquire(x) __acquire_cap(x)
+#define __acquire(x) __acquire_cap(x)

/**
* __release() - function to release capability exclusively
@@ -350,7 +324,7 @@ static inline void _capability_unsafe_alias(void **p) { }
*
* No-op function that releases the given capability instance @x.
*/
-# define __release(x) __release_cap(x)
+#define __release(x) __release_cap(x)

/**
* __cond_lock() - function that conditionally acquires a capability
@@ -369,31 +343,28 @@ static inline void _capability_unsafe_alias(void **p) { }
*
* #define spin_trylock(l) __cond_lock(&lock, _spin_trylock(&lock))
*/
-# define __cond_lock(x, c) __try_acquire_cap(x, c)
+#define __cond_lock(x, c) __try_acquire_cap(x, c)

/**
* __must_hold_shared() - function attribute, caller must hold shared capability
- * @x: capability instance pointer
*
* Function attribute declaring that the caller must hold the given capability
- * instance @x with shared access.
+ * instance(s) with shared access.
*/
-# define __must_hold_shared(x) __requires_shared_cap(x)
+#define __must_hold_shared(...) __requires_shared_cap(__VA_ARGS__)

/**
* __acquires_shared() - function attribute, function acquires capability shared
- * @x: capability instance pointer
*
* Function attribute declaring that the function acquires the given
- * capability instance @x with shared access, but does not release it.
+ * capability instance(s) with shared access, but does not release them.
*/
-# define __acquires_shared(x) __acquires_shared_cap(x)
+#define __acquires_shared(...) __acquires_shared_cap(__VA_ARGS__)

/**
* __cond_acquires_shared() - function attribute, function conditionally
* acquires a capability shared
* @ret: abstract value returned by function if capability acquired
- * @x: capability instance pointer
*
* Function attribute declaring that the function conditionally acquires the
* given capability instance @x with shared access, but does not release it. The
@@ -401,17 +372,16 @@ static inline void _capability_unsafe_alias(void **p) { }
*
* @ret may be one of: true, false, nonzero, 0, nonnull, NULL.
*/
-# define __cond_acquires_shared(ret, x) __cond_acquires_impl_##ret(x, _shared)
+#define __cond_acquires_shared(ret, x) __cond_acquires_impl_##ret(x, _shared)

/**
* __releases_shared() - function attribute, function releases a
* capability shared
- * @x: capability instance pointer
*
* Function attribute declaring that the function releases the given capability
- * instance @x with shared access. The capability must be held on entry.
+ * instance(s) with shared access. The capability must be held on entry.
*/
-# define __releases_shared(x) __releases_shared_cap(x)
+#define __releases_shared(...) __releases_shared_cap(__VA_ARGS__)

/**
* __acquire_shared() - function to acquire capability shared
@@ -420,7 +390,7 @@ static inline void _capability_unsafe_alias(void **p) { }
* No-op function that acquires the given capability instance @x with shared
* access.
*/
-# define __acquire_shared(x) __acquire_shared_cap(x)
+#define __acquire_shared(x) __acquire_shared_cap(x)

/**
* __release_shared() - function to release capability shared
@@ -429,7 +399,7 @@ static inline void _capability_unsafe_alias(void **p) { }
* No-op function that releases the given capability instance @x with shared
* access.
*/
-# define __release_shared(x) __release_shared_cap(x)
+#define __release_shared(x) __release_shared_cap(x)

/**
* __cond_lock_shared() - function that conditionally acquires a capability
@@ -443,9 +413,7 @@ static inline void _capability_unsafe_alias(void **p) { }
* access, if the boolean expression @c is true. The result of @c is the return
* value, to be able to create a capability-enabled interface.
*/
-# define __cond_lock_shared(x, c) __try_acquire_shared_cap(x, c)
-
-#endif /* __CHECKER__ */
+#define __cond_lock_shared(x, c) __try_acquire_shared_cap(x, c)

/**
* __acquire_ret() - helper to acquire capability of return value
diff --git a/include/linux/rcupdate.h b/include/linux/rcupdate.h
index 8eeece72492c..aec28e98d3f2 100644
--- a/include/linux/rcupdate.h
+++ b/include/linux/rcupdate.h
@@ -1177,20 +1177,7 @@ rcu_head_after_call_rcu(struct rcu_head *rhp, rcu_callback_t f)
extern int rcu_expedited;
extern int rcu_normal;

-DEFINE_LOCK_GUARD_0(rcu,
- do {
- rcu_read_lock();
- /*
- * sparse doesn't call the cleanup function,
- * so just release immediately and don't track
- * the context. We don't need to anyway, since
- * the whole point of the guard is to not need
- * the explicit unlock.
- */
- __release(RCU);
- } while (0),
- rcu_read_unlock())
-
+DEFINE_LOCK_GUARD_0(rcu, rcu_read_lock(), rcu_read_unlock())
DECLARE_LOCK_GUARD_0_ATTRS(rcu, __acquires_shared(RCU), __releases_shared(RCU))

#endif /* __LINUX_RCUPDATE_H */
--
2.51.0.384.g4c02a37b29-goog

Marco Elver

unread,
Sep 18, 2025, 10:06:36 AM (6 days ago) Sep 18
to el...@google.com, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
While we can opt in individual subsystems which add the required
annotations, such subsystems inevitably include headers from other
subsystems which may not yet have the right annotations, which then
result in false positive warnings.

Making compatible by adding annotations across all common headers
currently requires an excessive number of __no_capability_analysis
annotations, or carefully analyzing non-trivial cases to add the correct
annotations. While this is desirable long-term, providing an incremental
path causes less churn and headaches for maintainers not yet interested
in dealing with such warnings.

Rather than clutter headers unnecessary and mandate all subsystem
maintainers to keep their headers working with capability analysis,
suppress all -Wthread-safety warnings in headers. Explicitly opt in
headers with capability-enabled primitives.

With this in place, we can start enabling the analysis on more complex
subsystems in subsequent changes.

Signed-off-by: Marco Elver <el...@google.com>
---
scripts/Makefile.capability-analysis | 4 +++
scripts/capability-analysis-suppression.txt | 32 +++++++++++++++++++++
2 files changed, 36 insertions(+)
create mode 100644 scripts/capability-analysis-suppression.txt

diff --git a/scripts/Makefile.capability-analysis b/scripts/Makefile.capability-analysis
index e137751a4c9a..76ef93ce2466 100644
--- a/scripts/Makefile.capability-analysis
+++ b/scripts/Makefile.capability-analysis
@@ -4,4 +4,8 @@ capability-analysis-cflags := -DWARN_CAPABILITY_ANALYSIS \
-fexperimental-late-parse-attributes -Wthread-safety \
-Wthread-safety-pointer -Wthread-safety-beta

+ifndef CONFIG_WARN_CAPABILITY_ANALYSIS_ALL
+capability-analysis-cflags += --warning-suppression-mappings=$(srctree)/scripts/capability-analysis-suppression.txt
+endif
+
export CFLAGS_CAPABILITY_ANALYSIS := $(capability-analysis-cflags)
diff --git a/scripts/capability-analysis-suppression.txt b/scripts/capability-analysis-suppression.txt
new file mode 100644
index 000000000000..95fb0b65a8e6
--- /dev/null
+++ b/scripts/capability-analysis-suppression.txt
@@ -0,0 +1,32 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# The suppressions file should only match common paths such as header files.
+# For individual subsytems use Makefile directive CAPABILITY_ANALYSIS := [yn].
+#
+# The suppressions are ignored when CONFIG_WARN_CAPABILITY_ANALYSIS_ALL is
+# selected.
+
+[thread-safety]
+src:*arch/*/include/*
+src:*include/acpi/*
+src:*include/asm-generic/*
+src:*include/linux/*
+src:*include/net/*
+
+# Opt-in headers:
+src:*include/linux/bit_spinlock.h=emit
+src:*include/linux/cleanup.h=emit
+src:*include/linux/kref.h=emit
+src:*include/linux/list*.h=emit
+src:*include/linux/local_lock*.h=emit
+src:*include/linux/lockdep.h=emit
+src:*include/linux/mutex*.h=emit
+src:*include/linux/rcupdate.h=emit
+src:*include/linux/refcount.h=emit
+src:*include/linux/rhashtable.h=emit
+src:*include/linux/rwlock*.h=emit
+src:*include/linux/rwsem.h=emit
+src:*include/linux/seqlock*.h=emit
+src:*include/linux/spinlock*.h=emit
+src:*include/linux/srcu*.h=emit
+src:*include/linux/ww_mutex.h=emit
--
2.51.0.384.g4c02a37b29-goog

Marco Elver

unread,
Sep 18, 2025, 10:06:38 AM (6 days ago) Sep 18
to el...@google.com, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
Many patterns that involve data-racy accesses often deliberately ignore
normal synchronization rules to avoid taking a lock.

If we have a lock-guarded variable on which we do a lock-less data-racy
access, rather than having to write capability_unsafe(data_race(..)),
simply make the data_race(..) macro imply capability-unsafety. The
data_race() macro already denotes the intent that something subtly
unsafe is about to happen, so it should be clear enough as-is.

Signed-off-by: Marco Elver <el...@google.com>
---
v2:
* New patch.
---
include/linux/compiler.h | 2 ++
lib/test_capability-analysis.c | 2 ++
2 files changed, 4 insertions(+)

diff --git a/include/linux/compiler.h b/include/linux/compiler.h
index 64ff73c533e5..eee60adb3645 100644
--- a/include/linux/compiler.h
+++ b/include/linux/compiler.h
@@ -186,7 +186,9 @@ void ftrace_likely_update(struct ftrace_likely_data *f, int val,
#define data_race(expr) \
({ \
__kcsan_disable_current(); \
+ disable_capability_analysis(); \
__auto_type __v = (expr); \
+ enable_capability_analysis(); \
__kcsan_enable_current(); \
__v; \
})
diff --git a/lib/test_capability-analysis.c b/lib/test_capability-analysis.c
index 12fd9716f0a4..513ad28ed06c 100644
--- a/lib/test_capability-analysis.c
+++ b/lib/test_capability-analysis.c
@@ -92,6 +92,8 @@ static void __used test_raw_spinlock_trylock_extra(struct test_raw_spinlock_data
{
unsigned long flags;

+ data_race(d->counter++); /* no warning */
+
if (raw_spin_trylock_irq(&d->lock)) {
d->counter++;
raw_spin_unlock_irq(&d->lock);
--
2.51.0.384.g4c02a37b29-goog

Marco Elver

unread,
Sep 18, 2025, 10:06:40 AM (6 days ago) Sep 18
to el...@google.com, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
Add entry for all new files added for Clang's capability analysis.

Signed-off-by: Marco Elver <el...@google.com>
Reviewed-by: Bart Van Assche <bvana...@acm.org>
---
MAINTAINERS | 11 +++++++++++
1 file changed, 11 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index cd7ff55b5d32..da4c8196c1b7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5951,6 +5951,17 @@ M: Nelson Escobar <nees...@cisco.com>
S: Supported
F: drivers/infiniband/hw/usnic/

+CLANG CAPABILITY ANALYSIS
+M: Marco Elver <el...@google.com>
+R: Bart Van Assche <bvana...@acm.org>
+L: ll...@lists.linux.dev
+S: Maintained
+F: Documentation/dev-tools/capability-analysis.rst
+F: include/linux/compiler-capability-analysis.h
+F: lib/test_capability-analysis.c
+F: scripts/Makefile.capability-analysis
+F: scripts/capability-analysis-suppression.txt
+
CLANG CONTROL FLOW INTEGRITY SUPPORT
M: Sami Tolvanen <samito...@google.com>
M: Kees Cook <ke...@kernel.org>
--
2.51.0.384.g4c02a37b29-goog

Marco Elver

unread,
Sep 18, 2025, 10:06:43 AM (6 days ago) Sep 18
to el...@google.com, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
Enable capability analysis for the KFENCE subsystem.

Notable, kfence_handle_page_fault() required minor restructure, which
also fixed a subtle race; arguably that function is more readable now.

Signed-off-by: Marco Elver <el...@google.com>
---
v2:
* Remove disable/enable_capability_analysis() around headers.
* Use __capability_unsafe() instead of __no_capability_analysis.
---
mm/kfence/Makefile | 2 ++
mm/kfence/core.c | 20 +++++++++++++-------
mm/kfence/kfence.h | 14 ++++++++------
mm/kfence/report.c | 4 ++--
4 files changed, 25 insertions(+), 15 deletions(-)

diff --git a/mm/kfence/Makefile b/mm/kfence/Makefile
index 2de2a58d11a1..b3640bdc3c69 100644
--- a/mm/kfence/Makefile
+++ b/mm/kfence/Makefile
@@ -1,5 +1,7 @@
# SPDX-License-Identifier: GPL-2.0

+CAPABILITY_ANALYSIS := y
+
obj-y := core.o report.o

CFLAGS_kfence_test.o := -fno-omit-frame-pointer -fno-optimize-sibling-calls
diff --git a/mm/kfence/core.c b/mm/kfence/core.c
index 0ed3be100963..53b81eb5f31a 100644
--- a/mm/kfence/core.c
+++ b/mm/kfence/core.c
@@ -132,8 +132,8 @@ struct kfence_metadata *kfence_metadata __read_mostly;
static struct kfence_metadata *kfence_metadata_init __read_mostly;

/* Freelist with available objects. */
-static struct list_head kfence_freelist = LIST_HEAD_INIT(kfence_freelist);
-static DEFINE_RAW_SPINLOCK(kfence_freelist_lock); /* Lock protecting freelist. */
+DEFINE_RAW_SPINLOCK(kfence_freelist_lock); /* Lock protecting freelist. */
+static struct list_head kfence_freelist __guarded_by(&kfence_freelist_lock) = LIST_HEAD_INIT(kfence_freelist);

/*
* The static key to set up a KFENCE allocation; or if static keys are not used
@@ -253,6 +253,7 @@ static bool kfence_unprotect(unsigned long addr)
}

static inline unsigned long metadata_to_pageaddr(const struct kfence_metadata *meta)
+ __must_hold(&meta->lock)
{
unsigned long offset = (meta - kfence_metadata + 1) * PAGE_SIZE * 2;
unsigned long pageaddr = (unsigned long)&__kfence_pool[offset];
@@ -288,6 +289,7 @@ static inline bool kfence_obj_allocated(const struct kfence_metadata *meta)
static noinline void
metadata_update_state(struct kfence_metadata *meta, enum kfence_object_state next,
unsigned long *stack_entries, size_t num_stack_entries)
+ __must_hold(&meta->lock)
{
struct kfence_track *track =
next == KFENCE_OBJECT_ALLOCATED ? &meta->alloc_track : &meta->free_track;
@@ -485,7 +487,7 @@ static void *kfence_guarded_alloc(struct kmem_cache *cache, size_t size, gfp_t g
alloc_covered_add(alloc_stack_hash, 1);

/* Set required slab fields. */
- slab = virt_to_slab((void *)meta->addr);
+ slab = virt_to_slab(addr);
slab->slab_cache = cache;
slab->objects = 1;

@@ -514,6 +516,7 @@ static void *kfence_guarded_alloc(struct kmem_cache *cache, size_t size, gfp_t g
static void kfence_guarded_free(void *addr, struct kfence_metadata *meta, bool zombie)
{
struct kcsan_scoped_access assert_page_exclusive;
+ u32 alloc_stack_hash;
unsigned long flags;
bool init;

@@ -546,9 +549,10 @@ static void kfence_guarded_free(void *addr, struct kfence_metadata *meta, bool z
/* Mark the object as freed. */
metadata_update_state(meta, KFENCE_OBJECT_FREED, NULL, 0);
init = slab_want_init_on_free(meta->cache);
+ alloc_stack_hash = meta->alloc_stack_hash;
raw_spin_unlock_irqrestore(&meta->lock, flags);

- alloc_covered_add(meta->alloc_stack_hash, -1);
+ alloc_covered_add(alloc_stack_hash, -1);

/* Check canary bytes for memory corruption. */
check_canary(meta);
@@ -593,6 +597,7 @@ static void rcu_guarded_free(struct rcu_head *h)
* which partial initialization succeeded.
*/
static unsigned long kfence_init_pool(void)
+ __capability_unsafe(/* constructor */)
{
unsigned long addr;
struct page *pages;
@@ -1192,6 +1197,7 @@ bool kfence_handle_page_fault(unsigned long addr, bool is_write, struct pt_regs
{
const int page_index = (addr - (unsigned long)__kfence_pool) / PAGE_SIZE;
struct kfence_metadata *to_report = NULL;
+ unsigned long unprotected_page = 0;
enum kfence_error_type error_type;
unsigned long flags;

@@ -1225,9 +1231,8 @@ bool kfence_handle_page_fault(unsigned long addr, bool is_write, struct pt_regs
if (!to_report)
goto out;

- raw_spin_lock_irqsave(&to_report->lock, flags);
- to_report->unprotected_page = addr;
error_type = KFENCE_ERROR_OOB;
+ unprotected_page = addr;

/*
* If the object was freed before we took the look we can still
@@ -1239,7 +1244,6 @@ bool kfence_handle_page_fault(unsigned long addr, bool is_write, struct pt_regs
if (!to_report)
goto out;

- raw_spin_lock_irqsave(&to_report->lock, flags);
error_type = KFENCE_ERROR_UAF;
/*
* We may race with __kfence_alloc(), and it is possible that a
@@ -1251,6 +1255,8 @@ bool kfence_handle_page_fault(unsigned long addr, bool is_write, struct pt_regs

out:
if (to_report) {
+ raw_spin_lock_irqsave(&to_report->lock, flags);
+ to_report->unprotected_page = unprotected_page;
kfence_report_error(addr, is_write, regs, to_report, error_type);
raw_spin_unlock_irqrestore(&to_report->lock, flags);
} else {
diff --git a/mm/kfence/kfence.h b/mm/kfence/kfence.h
index dfba5ea06b01..f9caea007246 100644
--- a/mm/kfence/kfence.h
+++ b/mm/kfence/kfence.h
@@ -34,6 +34,8 @@
/* Maximum stack depth for reports. */
#define KFENCE_STACK_DEPTH 64

+extern raw_spinlock_t kfence_freelist_lock;
+
/* KFENCE object states. */
enum kfence_object_state {
KFENCE_OBJECT_UNUSED, /* Object is unused. */
@@ -53,7 +55,7 @@ struct kfence_track {

/* KFENCE metadata per guarded allocation. */
struct kfence_metadata {
- struct list_head list; /* Freelist node; access under kfence_freelist_lock. */
+ struct list_head list __guarded_by(&kfence_freelist_lock); /* Freelist node. */
struct rcu_head rcu_head; /* For delayed freeing. */

/*
@@ -91,13 +93,13 @@ struct kfence_metadata {
* In case of an invalid access, the page that was unprotected; we
* optimistically only store one address.
*/
- unsigned long unprotected_page;
+ unsigned long unprotected_page __guarded_by(&lock);

/* Allocation and free stack information. */
- struct kfence_track alloc_track;
- struct kfence_track free_track;
+ struct kfence_track alloc_track __guarded_by(&lock);
+ struct kfence_track free_track __guarded_by(&lock);
/* For updating alloc_covered on frees. */
- u32 alloc_stack_hash;
+ u32 alloc_stack_hash __guarded_by(&lock);
#ifdef CONFIG_MEMCG
struct slabobj_ext obj_exts;
#endif
@@ -141,6 +143,6 @@ enum kfence_error_type {
void kfence_report_error(unsigned long address, bool is_write, struct pt_regs *regs,
const struct kfence_metadata *meta, enum kfence_error_type type);

-void kfence_print_object(struct seq_file *seq, const struct kfence_metadata *meta);
+void kfence_print_object(struct seq_file *seq, const struct kfence_metadata *meta) __must_hold(&meta->lock);

#endif /* MM_KFENCE_KFENCE_H */
diff --git a/mm/kfence/report.c b/mm/kfence/report.c
index 10e6802a2edf..787e87c26926 100644
--- a/mm/kfence/report.c
+++ b/mm/kfence/report.c
@@ -106,6 +106,7 @@ static int get_stack_skipnr(const unsigned long stack_entries[], int num_entries

static void kfence_print_stack(struct seq_file *seq, const struct kfence_metadata *meta,
bool show_alloc)
+ __must_hold(&meta->lock)
{
const struct kfence_track *track = show_alloc ? &meta->alloc_track : &meta->free_track;
u64 ts_sec = track->ts_nsec;
@@ -207,8 +208,6 @@ void kfence_report_error(unsigned long address, bool is_write, struct pt_regs *r
if (WARN_ON(type != KFENCE_ERROR_INVALID && !meta))
return;

- if (meta)
- lockdep_assert_held(&meta->lock);
/*
* Because we may generate reports in printk-unfriendly parts of the
* kernel, such as scheduler code, the use of printk() could deadlock.
@@ -263,6 +262,7 @@ void kfence_report_error(unsigned long address, bool is_write, struct pt_regs *r
stack_trace_print(stack_entries + skipnr, num_stack_entries - skipnr, 0);

if (meta) {
+ lockdep_assert_held(&meta->lock);
pr_err("\n");
kfence_print_object(NULL, meta);
}
--
2.51.0.384.g4c02a37b29-goog

Marco Elver

unread,
Sep 18, 2025, 10:06:46 AM (6 days ago) Sep 18
to el...@google.com, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
Enable capability analysis for the KCOV subsystem.

Signed-off-by: Marco Elver <el...@google.com>
---
v2:
* Remove disable/enable_capability_analysis() around headers.
---
kernel/Makefile | 2 ++
kernel/kcov.c | 36 +++++++++++++++++++++++++-----------
2 files changed, 27 insertions(+), 11 deletions(-)

diff --git a/kernel/Makefile b/kernel/Makefile
index c60623448235..2a2a10c6a197 100644
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -43,6 +43,8 @@ KASAN_SANITIZE_kcov.o := n
KCSAN_SANITIZE_kcov.o := n
UBSAN_SANITIZE_kcov.o := n
KMSAN_SANITIZE_kcov.o := n
+
+CAPABILITY_ANALYSIS_kcov.o := y
CFLAGS_kcov.o := $(call cc-option, -fno-conserve-stack) -fno-stack-protector

obj-y += sched/
diff --git a/kernel/kcov.c b/kernel/kcov.c
index 1d85597057e1..1897c8ca6209 100644
--- a/kernel/kcov.c
+++ b/kernel/kcov.c
@@ -55,13 +55,13 @@ struct kcov {
refcount_t refcount;
/* The lock protects mode, size, area and t. */
spinlock_t lock;
- enum kcov_mode mode;
+ enum kcov_mode mode __guarded_by(&lock);
/* Size of arena (in long's). */
- unsigned int size;
+ unsigned int size __guarded_by(&lock);
/* Coverage buffer shared with user space. */
- void *area;
+ void *area __guarded_by(&lock);
/* Task for which we collect coverage, or NULL. */
- struct task_struct *t;
+ struct task_struct *t __guarded_by(&lock);
/* Collecting coverage from remote (background) threads. */
bool remote;
/* Size of remote area (in long's). */
@@ -391,6 +391,7 @@ void kcov_task_init(struct task_struct *t)
}

static void kcov_reset(struct kcov *kcov)
+ __must_hold(&kcov->lock)
{
kcov->t = NULL;
kcov->mode = KCOV_MODE_INIT;
@@ -400,6 +401,7 @@ static void kcov_reset(struct kcov *kcov)
}

static void kcov_remote_reset(struct kcov *kcov)
+ __must_hold(&kcov->lock)
{
int bkt;
struct kcov_remote *remote;
@@ -419,6 +421,7 @@ static void kcov_remote_reset(struct kcov *kcov)
}

static void kcov_disable(struct task_struct *t, struct kcov *kcov)
+ __must_hold(&kcov->lock)
{
kcov_task_reset(t);
if (kcov->remote)
@@ -435,8 +438,11 @@ static void kcov_get(struct kcov *kcov)
static void kcov_put(struct kcov *kcov)
{
if (refcount_dec_and_test(&kcov->refcount)) {
- kcov_remote_reset(kcov);
- vfree(kcov->area);
+ /* Capability-safety: no references left, object being destroyed. */
+ capability_unsafe(
+ kcov_remote_reset(kcov);
+ vfree(kcov->area);
+ );
kfree(kcov);
}
}
@@ -491,6 +497,7 @@ static int kcov_mmap(struct file *filep, struct vm_area_struct *vma)
unsigned long size, off;
struct page *page;
unsigned long flags;
+ unsigned long *area;

spin_lock_irqsave(&kcov->lock, flags);
size = kcov->size * sizeof(unsigned long);
@@ -499,10 +506,11 @@ static int kcov_mmap(struct file *filep, struct vm_area_struct *vma)
res = -EINVAL;
goto exit;
}
+ area = kcov->area;
spin_unlock_irqrestore(&kcov->lock, flags);
vm_flags_set(vma, VM_DONTEXPAND);
for (off = 0; off < size; off += PAGE_SIZE) {
- page = vmalloc_to_page(kcov->area + off);
+ page = vmalloc_to_page(area + off);
res = vm_insert_page(vma, vma->vm_start + off, page);
if (res) {
pr_warn_once("kcov: vm_insert_page() failed\n");
@@ -522,10 +530,10 @@ static int kcov_open(struct inode *inode, struct file *filep)
kcov = kzalloc(sizeof(*kcov), GFP_KERNEL);
if (!kcov)
return -ENOMEM;
+ spin_lock_init(&kcov->lock);
kcov->mode = KCOV_MODE_DISABLED;
kcov->sequence = 1;
refcount_set(&kcov->refcount, 1);
- spin_lock_init(&kcov->lock);
filep->private_data = kcov;
return nonseekable_open(inode, filep);
}
@@ -556,6 +564,7 @@ static int kcov_get_mode(unsigned long arg)
* vmalloc fault handling path is instrumented.
*/
static void kcov_fault_in_area(struct kcov *kcov)
+ __must_hold(&kcov->lock)
{
unsigned long stride = PAGE_SIZE / sizeof(unsigned long);
unsigned long *area = kcov->area;
@@ -584,6 +593,7 @@ static inline bool kcov_check_handle(u64 handle, bool common_valid,

static int kcov_ioctl_locked(struct kcov *kcov, unsigned int cmd,
unsigned long arg)
+ __must_hold(&kcov->lock)
{
struct task_struct *t;
unsigned long flags, unused;
@@ -814,6 +824,7 @@ static inline bool kcov_mode_enabled(unsigned int mode)
}

static void kcov_remote_softirq_start(struct task_struct *t)
+ __must_hold(&kcov_percpu_data.lock)
{
struct kcov_percpu_data *data = this_cpu_ptr(&kcov_percpu_data);
unsigned int mode;
@@ -831,6 +842,7 @@ static void kcov_remote_softirq_start(struct task_struct *t)
}

static void kcov_remote_softirq_stop(struct task_struct *t)
+ __must_hold(&kcov_percpu_data.lock)
{
struct kcov_percpu_data *data = this_cpu_ptr(&kcov_percpu_data);

@@ -896,10 +908,12 @@ void kcov_remote_start(u64 handle)
/* Put in kcov_remote_stop(). */
kcov_get(kcov);
/*
- * Read kcov fields before unlock to prevent races with
- * KCOV_DISABLE / kcov_remote_reset().
+ * Read kcov fields before unlocking kcov_remote_lock to prevent races
+ * with KCOV_DISABLE and kcov_remote_reset(); cannot acquire kcov->lock
+ * here, because it might lead to deadlock given kcov_remote_lock is
+ * acquired _after_ kcov->lock elsewhere.
*/
- mode = kcov->mode;
+ mode = capability_unsafe(kcov->mode);
sequence = kcov->sequence;
if (in_task()) {
size = kcov->remote_size;
--
2.51.0.384.g4c02a37b29-goog

Marco Elver

unread,
Sep 18, 2025, 10:06:49 AM (6 days ago) Sep 18
to el...@google.com, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
Enable capability analysis for the KCSAN subsystem.

Signed-off-by: Marco Elver <el...@google.com>
---
v3:
* New patch.
---
kernel/kcsan/Makefile | 2 ++
kernel/kcsan/report.c | 11 ++++++++---
2 files changed, 10 insertions(+), 3 deletions(-)

diff --git a/kernel/kcsan/Makefile b/kernel/kcsan/Makefile
index a45f3dfc8d14..b088aa01409f 100644
--- a/kernel/kcsan/Makefile
+++ b/kernel/kcsan/Makefile
@@ -1,4 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
+CAPABILITY_ANALYSIS := y
+
KCSAN_SANITIZE := n
KCOV_INSTRUMENT := n
UBSAN_SANITIZE := n
diff --git a/kernel/kcsan/report.c b/kernel/kcsan/report.c
index e95ce7d7a76e..11a48b78f8d1 100644
--- a/kernel/kcsan/report.c
+++ b/kernel/kcsan/report.c
@@ -116,6 +116,7 @@ static DEFINE_RAW_SPINLOCK(report_lock);
* been reported since (now - KCSAN_REPORT_ONCE_IN_MS).
*/
static bool rate_limit_report(unsigned long frame1, unsigned long frame2)
+ __must_hold(&report_lock)
{
struct report_time *use_entry = &report_times[0];
unsigned long invalid_before;
@@ -366,6 +367,7 @@ static int sym_strcmp(void *addr1, void *addr2)

static void
print_stack_trace(unsigned long stack_entries[], int num_entries, unsigned long reordered_to)
+ __must_hold(&report_lock)
{
stack_trace_print(stack_entries, num_entries, 0);
if (reordered_to)
@@ -373,6 +375,7 @@ print_stack_trace(unsigned long stack_entries[], int num_entries, unsigned long
}

static void print_verbose_info(struct task_struct *task)
+ __must_hold(&report_lock)
{
if (!task)
return;
@@ -389,6 +392,7 @@ static void print_report(enum kcsan_value_change value_change,
const struct access_info *ai,
struct other_info *other_info,
u64 old, u64 new, u64 mask)
+ __must_hold(&report_lock)
{
unsigned long reordered_to = 0;
unsigned long stack_entries[NUM_STACK_ENTRIES] = { 0 };
@@ -496,6 +500,7 @@ static void print_report(enum kcsan_value_change value_change,
}

static void release_report(unsigned long *flags, struct other_info *other_info)
+ __releases(&report_lock)
{
/*
* Use size to denote valid/invalid, since KCSAN entirely ignores
@@ -507,13 +512,11 @@ static void release_report(unsigned long *flags, struct other_info *other_info)

/*
* Sets @other_info->task and awaits consumption of @other_info.
- *
- * Precondition: report_lock is held.
- * Postcondition: report_lock is held.
*/
static void set_other_info_task_blocking(unsigned long *flags,
const struct access_info *ai,
struct other_info *other_info)
+ __must_hold(&report_lock)
{
/*
* We may be instrumenting a code-path where current->state is already
@@ -572,6 +575,7 @@ static void set_other_info_task_blocking(unsigned long *flags,
static void prepare_report_producer(unsigned long *flags,
const struct access_info *ai,
struct other_info *other_info)
+ __must_not_hold(&report_lock)
{
raw_spin_lock_irqsave(&report_lock, *flags);

@@ -603,6 +607,7 @@ static void prepare_report_producer(unsigned long *flags,
static bool prepare_report_consumer(unsigned long *flags,
const struct access_info *ai,
struct other_info *other_info)
+ __cond_acquires(true, &report_lock)
{

raw_spin_lock_irqsave(&report_lock, *flags);
--
2.51.0.384.g4c02a37b29-goog

Marco Elver

unread,
Sep 18, 2025, 10:06:51 AM (6 days ago) Sep 18
to el...@google.com, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
Enable capability analysis for stackdepot.

Signed-off-by: Marco Elver <el...@google.com>
---
v2:
* Remove disable/enable_capability_analysis() around headers.
---
lib/Makefile | 1 +
lib/stackdepot.c | 20 ++++++++++++++------
2 files changed, 15 insertions(+), 6 deletions(-)

diff --git a/lib/Makefile b/lib/Makefile
index e677cb5cc777..43b965046c2c 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -251,6 +251,7 @@ obj-$(CONFIG_POLYNOMIAL) += polynomial.o
# Prevent the compiler from calling builtins like memcmp() or bcmp() from this
# file.
CFLAGS_stackdepot.o += -fno-builtin
+CAPABILITY_ANALYSIS_stackdepot.o := y
obj-$(CONFIG_STACKDEPOT) += stackdepot.o
KASAN_SANITIZE_stackdepot.o := n
# In particular, instrumenting stackdepot.c with KMSAN will result in infinite
diff --git a/lib/stackdepot.c b/lib/stackdepot.c
index de0b0025af2b..43122294f128 100644
--- a/lib/stackdepot.c
+++ b/lib/stackdepot.c
@@ -61,18 +61,18 @@ static unsigned int stack_bucket_number_order;
/* Hash mask for indexing the table. */
static unsigned int stack_hash_mask;

+/* The lock must be held when performing pool or freelist modifications. */
+static DEFINE_RAW_SPINLOCK(pool_lock);
/* Array of memory regions that store stack records. */
-static void **stack_pools;
+static void **stack_pools __pt_guarded_by(&pool_lock);
/* Newly allocated pool that is not yet added to stack_pools. */
static void *new_pool;
/* Number of pools in stack_pools. */
static int pools_num;
/* Offset to the unused space in the currently used pool. */
-static size_t pool_offset = DEPOT_POOL_SIZE;
+static size_t pool_offset __guarded_by(&pool_lock) = DEPOT_POOL_SIZE;
/* Freelist of stack records within stack_pools. */
-static LIST_HEAD(free_stacks);
-/* The lock must be held when performing pool or freelist modifications. */
-static DEFINE_RAW_SPINLOCK(pool_lock);
+static __guarded_by(&pool_lock) LIST_HEAD(free_stacks);

/* Statistics counters for debugfs. */
enum depot_counter_id {
@@ -291,6 +291,7 @@ EXPORT_SYMBOL_GPL(stack_depot_init);
* Initializes new stack pool, and updates the list of pools.
*/
static bool depot_init_pool(void **prealloc)
+ __must_hold(&pool_lock)
{
lockdep_assert_held(&pool_lock);

@@ -338,6 +339,7 @@ static bool depot_init_pool(void **prealloc)

/* Keeps the preallocated memory to be used for a new stack depot pool. */
static void depot_keep_new_pool(void **prealloc)
+ __must_hold(&pool_lock)
{
lockdep_assert_held(&pool_lock);

@@ -357,6 +359,7 @@ static void depot_keep_new_pool(void **prealloc)
* the current pre-allocation.
*/
static struct stack_record *depot_pop_free_pool(void **prealloc, size_t size)
+ __must_hold(&pool_lock)
{
struct stack_record *stack;
void *current_pool;
@@ -391,6 +394,7 @@ static struct stack_record *depot_pop_free_pool(void **prealloc, size_t size)

/* Try to find next free usable entry from the freelist. */
static struct stack_record *depot_pop_free(void)
+ __must_hold(&pool_lock)
{
struct stack_record *stack;

@@ -428,6 +432,7 @@ static inline size_t depot_stack_record_size(struct stack_record *s, unsigned in
/* Allocates a new stack in a stack depot pool. */
static struct stack_record *
depot_alloc_stack(unsigned long *entries, unsigned int nr_entries, u32 hash, depot_flags_t flags, void **prealloc)
+ __must_hold(&pool_lock)
{
struct stack_record *stack = NULL;
size_t record_size;
@@ -486,6 +491,7 @@ depot_alloc_stack(unsigned long *entries, unsigned int nr_entries, u32 hash, dep
}

static struct stack_record *depot_fetch_stack(depot_stack_handle_t handle)
+ __must_not_hold(&pool_lock)
{
const int pools_num_cached = READ_ONCE(pools_num);
union handle_parts parts = { .handle = handle };
@@ -502,7 +508,8 @@ static struct stack_record *depot_fetch_stack(depot_stack_handle_t handle)
return NULL;
}

- pool = stack_pools[pool_index];
+ /* @pool_index either valid, or user passed in corrupted value. */
+ pool = capability_unsafe(stack_pools[pool_index]);
if (WARN_ON(!pool))
return NULL;

@@ -515,6 +522,7 @@ static struct stack_record *depot_fetch_stack(depot_stack_handle_t handle)

/* Links stack into the freelist. */
static void depot_free_stack(struct stack_record *stack)
+ __must_not_hold(&pool_lock)
{
unsigned long flags;

--
2.51.0.384.g4c02a37b29-goog

Marco Elver

unread,
Sep 18, 2025, 10:06:54 AM (6 days ago) Sep 18
to el...@google.com, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
Enable capability analysis for rhashtable, which was used as an initial
test as it contains a combination of RCU, mutex, and bit_spinlock usage.

Users of rhashtable now also benefit from annotations on the API, which
will now warn if the RCU read lock is not held where required.

Signed-off-by: Marco Elver <el...@google.com>
Cc: Thomas Graf <tg...@suug.ch>
Cc: Herbert Xu <her...@gondor.apana.org.au>
Cc: linux-...@vger.kernel.org
---
v2:
* Remove disable/enable_capability_analysis() around headers.
---
include/linux/rhashtable.h | 14 +++++++++++---
lib/Makefile | 2 ++
lib/rhashtable.c | 5 +++--
3 files changed, 16 insertions(+), 5 deletions(-)

diff --git a/include/linux/rhashtable.h b/include/linux/rhashtable.h
index 6c85b28ea30b..347716b1ca37 100644
--- a/include/linux/rhashtable.h
+++ b/include/linux/rhashtable.h
@@ -245,16 +245,17 @@ void *rhashtable_insert_slow(struct rhashtable *ht, const void *key,
void rhashtable_walk_enter(struct rhashtable *ht,
struct rhashtable_iter *iter);
void rhashtable_walk_exit(struct rhashtable_iter *iter);
-int rhashtable_walk_start_check(struct rhashtable_iter *iter) __acquires(RCU);
+int rhashtable_walk_start_check(struct rhashtable_iter *iter) __acquires_shared(RCU);

static inline void rhashtable_walk_start(struct rhashtable_iter *iter)
+ __acquires_shared(RCU)
{
(void)rhashtable_walk_start_check(iter);
}

void *rhashtable_walk_next(struct rhashtable_iter *iter);
void *rhashtable_walk_peek(struct rhashtable_iter *iter);
-void rhashtable_walk_stop(struct rhashtable_iter *iter) __releases(RCU);
+void rhashtable_walk_stop(struct rhashtable_iter *iter) __releases_shared(RCU);

void rhashtable_free_and_destroy(struct rhashtable *ht,
void (*free_fn)(void *ptr, void *arg),
@@ -325,6 +326,7 @@ static inline struct rhash_lock_head __rcu **rht_bucket_insert(

static inline unsigned long rht_lock(struct bucket_table *tbl,
struct rhash_lock_head __rcu **bkt)
+ __acquires(__bitlock(0, bkt))
{
unsigned long flags;

@@ -337,6 +339,7 @@ static inline unsigned long rht_lock(struct bucket_table *tbl,
static inline unsigned long rht_lock_nested(struct bucket_table *tbl,
struct rhash_lock_head __rcu **bucket,
unsigned int subclass)
+ __acquires(__bitlock(0, bucket))
{
unsigned long flags;

@@ -349,6 +352,7 @@ static inline unsigned long rht_lock_nested(struct bucket_table *tbl,
static inline void rht_unlock(struct bucket_table *tbl,
struct rhash_lock_head __rcu **bkt,
unsigned long flags)
+ __releases(__bitlock(0, bkt))
{
lock_map_release(&tbl->dep_map);
bit_spin_unlock(0, (unsigned long *)bkt);
@@ -402,13 +406,14 @@ static inline void rht_assign_unlock(struct bucket_table *tbl,
struct rhash_lock_head __rcu **bkt,
struct rhash_head *obj,
unsigned long flags)
+ __releases(__bitlock(0, bkt))
{
if (rht_is_a_nulls(obj))
obj = NULL;
lock_map_release(&tbl->dep_map);
rcu_assign_pointer(*bkt, (void *)obj);
preempt_enable();
- __release(bitlock);
+ __release(__bitlock(0, bkt));
local_irq_restore(flags);
}

@@ -589,6 +594,7 @@ static inline int rhashtable_compare(struct rhashtable_compare_arg *arg,
static inline struct rhash_head *__rhashtable_lookup(
struct rhashtable *ht, const void *key,
const struct rhashtable_params params)
+ __must_hold_shared(RCU)
{
struct rhashtable_compare_arg arg = {
.ht = ht,
@@ -642,6 +648,7 @@ static inline struct rhash_head *__rhashtable_lookup(
static inline void *rhashtable_lookup(
struct rhashtable *ht, const void *key,
const struct rhashtable_params params)
+ __must_hold_shared(RCU)
{
struct rhash_head *he = __rhashtable_lookup(ht, key, params);

@@ -692,6 +699,7 @@ static inline void *rhashtable_lookup_fast(
static inline struct rhlist_head *rhltable_lookup(
struct rhltable *hlt, const void *key,
const struct rhashtable_params params)
+ __must_hold_shared(RCU)
{
struct rhash_head *he = __rhashtable_lookup(&hlt->ht, key, params);

diff --git a/lib/Makefile b/lib/Makefile
index 43b965046c2c..85a9144b008a 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -50,6 +50,8 @@ lib-$(CONFIG_MIN_HEAP) += min_heap.o
lib-y += kobject.o klist.o
obj-y += lockref.o

+CAPABILITY_ANALYSIS_rhashtable.o := y
+
obj-y += bcd.o sort.o parser.o debug_locks.o random32.o \
bust_spinlocks.o kasprintf.o bitmap.o scatterlist.o \
list_sort.o uuid.o iov_iter.o clz_ctz.o \
diff --git a/lib/rhashtable.c b/lib/rhashtable.c
index 3e555d012ed6..fe8dd776837c 100644
--- a/lib/rhashtable.c
+++ b/lib/rhashtable.c
@@ -358,6 +358,7 @@ static int rhashtable_rehash_table(struct rhashtable *ht)
static int rhashtable_rehash_alloc(struct rhashtable *ht,
struct bucket_table *old_tbl,
unsigned int size)
+ __must_hold(&ht->mutex)
{
struct bucket_table *new_tbl;
int err;
@@ -392,6 +393,7 @@ static int rhashtable_rehash_alloc(struct rhashtable *ht,
* bucket locks or concurrent RCU protected lookups and traversals.
*/
static int rhashtable_shrink(struct rhashtable *ht)
+ __must_hold(&ht->mutex)
{
struct bucket_table *old_tbl = rht_dereference(ht->tbl, ht);
unsigned int nelems = atomic_read(&ht->nelems);
@@ -724,7 +726,7 @@ EXPORT_SYMBOL_GPL(rhashtable_walk_exit);
* resize events and always continue.
*/
int rhashtable_walk_start_check(struct rhashtable_iter *iter)
- __acquires(RCU)
+ __acquires_shared(RCU)
{
struct rhashtable *ht = iter->ht;
bool rhlist = ht->rhlist;
@@ -940,7 +942,6 @@ EXPORT_SYMBOL_GPL(rhashtable_walk_peek);
* hash table.
*/
void rhashtable_walk_stop(struct rhashtable_iter *iter)
- __releases(RCU)
{
struct rhashtable *ht;
struct bucket_table *tbl = iter->walker.tbl;
--
2.51.0.384.g4c02a37b29-goog

Marco Elver

unread,
Sep 18, 2025, 10:06:58 AM (6 days ago) Sep 18
to el...@google.com, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
With Sparse support gone, Clang is a bit more strict and warns:

./include/linux/console.h:492:50: error: use of undeclared identifier 'console_mutex'
492 | extern void console_list_unlock(void) __releases(console_mutex);

Since it does not make sense to make console_mutex itself global, move
the annotation to printk.c. Capability analysis remains disabled for
printk.c.

This is needed to enable capability analysis for modules that include
<linux/console.h>.

Signed-off-by: Marco Elver <el...@google.com>
---
v2:
* New patch.
---
include/linux/console.h | 4 ++--
kernel/printk/printk.c | 2 ++
2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/include/linux/console.h b/include/linux/console.h
index 8f10d0a85bb4..6bc0a42e118c 100644
--- a/include/linux/console.h
+++ b/include/linux/console.h
@@ -488,8 +488,8 @@ static inline bool console_srcu_read_lock_is_held(void)
extern int console_srcu_read_lock(void);
extern void console_srcu_read_unlock(int cookie);

-extern void console_list_lock(void) __acquires(console_mutex);
-extern void console_list_unlock(void) __releases(console_mutex);
+extern void console_list_lock(void);
+extern void console_list_unlock(void);

extern struct hlist_head console_list;

diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c
index 0efbcdda9aab..e0bfc66152aa 100644
--- a/kernel/printk/printk.c
+++ b/kernel/printk/printk.c
@@ -244,6 +244,7 @@ int devkmsg_sysctl_set_loglvl(const struct ctl_table *table, int write,
* For console list or console->flags updates
*/
void console_list_lock(void)
+ __acquires(&console_mutex)
{
/*
* In unregister_console() and console_force_preferred_locked(),
@@ -268,6 +269,7 @@ EXPORT_SYMBOL(console_list_lock);
* Counterpart to console_list_lock()
*/
void console_list_unlock(void)
+ __releases(&console_mutex)
{
mutex_unlock(&console_mutex);
}
--
2.51.0.384.g4c02a37b29-goog

Marco Elver

unread,
Sep 18, 2025, 10:06:59 AM (6 days ago) Sep 18
to el...@google.com, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
Enable capability analysis for security/tomoyo.

This demonstrates a larger conversion to use Clang's capability
analysis. The benefit is additional static checking of locking rules,
along with better documentation.

Tomoyo makes use of several synchronization primitives, yet its clear
design made it relatively straightforward to enable capability analysis.

One notable finding was:

security/tomoyo/gc.c:664:20: error: reading variable 'write_buf' requires holding mutex '&tomoyo_io_buffer::io_sem'
664 | is_write = head->write_buf != NULL;

For which Tetsuo writes:

"Good catch. This should be data_race(), for tomoyo_write_control()
might concurrently update head->write_buf from non-NULL to non-NULL
with head->io_sem held."

Signed-off-by: Marco Elver <el...@google.com>
Cc: Kentaro Takeda <take...@nttdata.co.jp>
Cc: Tetsuo Handa <penguin...@I-love.SAKURA.ne.jp>
---
v2:
* New patch.
---
security/tomoyo/Makefile | 2 +
security/tomoyo/common.c | 52 ++++++++++++++++++++++++--
security/tomoyo/common.h | 77 ++++++++++++++++++++-------------------
security/tomoyo/domain.c | 1 +
security/tomoyo/environ.c | 1 +
security/tomoyo/file.c | 5 +++
security/tomoyo/gc.c | 28 ++++++++++----
security/tomoyo/mount.c | 2 +
security/tomoyo/network.c | 3 ++
9 files changed, 122 insertions(+), 49 deletions(-)

diff --git a/security/tomoyo/Makefile b/security/tomoyo/Makefile
index 55c67b9846a9..6b395ca4e3d2 100644
--- a/security/tomoyo/Makefile
+++ b/security/tomoyo/Makefile
@@ -1,4 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
+CAPABILITY_ANALYSIS := y
+
obj-y = audit.o common.o condition.o domain.o environ.o file.o gc.o group.o load_policy.o memory.o mount.o network.o realpath.o securityfs_if.o tomoyo.o util.o

targets += builtin-policy.h
diff --git a/security/tomoyo/common.c b/security/tomoyo/common.c
index 0f78898bce09..fa9fd134c9cc 100644
--- a/security/tomoyo/common.c
+++ b/security/tomoyo/common.c
@@ -268,6 +268,7 @@ static void tomoyo_io_printf(struct tomoyo_io_buffer *head, const char *fmt,
*/
static void tomoyo_io_printf(struct tomoyo_io_buffer *head, const char *fmt,
...)
+ __must_hold(&head->io_sem)
{
va_list args;
size_t len;
@@ -416,8 +417,9 @@ static void tomoyo_print_name_union_quoted(struct tomoyo_io_buffer *head,
*
* Returns nothing.
*/
-static void tomoyo_print_number_union_nospace
-(struct tomoyo_io_buffer *head, const struct tomoyo_number_union *ptr)
+static void
+tomoyo_print_number_union_nospace(struct tomoyo_io_buffer *head, const struct tomoyo_number_union *ptr)
+ __must_hold(&head->io_sem)
{
if (ptr->group) {
tomoyo_set_string(head, "@");
@@ -466,6 +468,7 @@ static void tomoyo_print_number_union_nospace
*/
static void tomoyo_print_number_union(struct tomoyo_io_buffer *head,
const struct tomoyo_number_union *ptr)
+ __must_hold(&head->io_sem)
{
tomoyo_set_space(head);
tomoyo_print_number_union_nospace(head, ptr);
@@ -664,6 +667,7 @@ static int tomoyo_set_mode(char *name, const char *value,
* Returns 0 on success, negative value otherwise.
*/
static int tomoyo_write_profile(struct tomoyo_io_buffer *head)
+ __must_hold(&head->io_sem)
{
char *data = head->write_buf;
unsigned int i;
@@ -719,6 +723,7 @@ static int tomoyo_write_profile(struct tomoyo_io_buffer *head)
* Caller prints functionality's name.
*/
static void tomoyo_print_config(struct tomoyo_io_buffer *head, const u8 config)
+ __must_hold(&head->io_sem)
{
tomoyo_io_printf(head, "={ mode=%s grant_log=%s reject_log=%s }\n",
tomoyo_mode[config & 3],
@@ -734,6 +739,7 @@ static void tomoyo_print_config(struct tomoyo_io_buffer *head, const u8 config)
* Returns nothing.
*/
static void tomoyo_read_profile(struct tomoyo_io_buffer *head)
+ __must_hold(&head->io_sem)
{
u8 index;
struct tomoyo_policy_namespace *ns =
@@ -852,6 +858,7 @@ static bool tomoyo_same_manager(const struct tomoyo_acl_head *a,
*/
static int tomoyo_update_manager_entry(const char *manager,
const bool is_delete)
+ __must_hold_shared(&tomoyo_ss)
{
struct tomoyo_manager e = { };
struct tomoyo_acl_param param = {
@@ -883,6 +890,8 @@ static int tomoyo_update_manager_entry(const char *manager,
* Caller holds tomoyo_read_lock().
*/
static int tomoyo_write_manager(struct tomoyo_io_buffer *head)
+ __must_hold_shared(&tomoyo_ss)
+ __must_hold(&head->io_sem)
{
char *data = head->write_buf;

@@ -901,6 +910,7 @@ static int tomoyo_write_manager(struct tomoyo_io_buffer *head)
* Caller holds tomoyo_read_lock().
*/
static void tomoyo_read_manager(struct tomoyo_io_buffer *head)
+ __must_hold_shared(&tomoyo_ss)
{
if (head->r.eof)
return;
@@ -927,6 +937,7 @@ static void tomoyo_read_manager(struct tomoyo_io_buffer *head)
* Caller holds tomoyo_read_lock().
*/
static bool tomoyo_manager(void)
+ __must_hold_shared(&tomoyo_ss)
{
struct tomoyo_manager *ptr;
const char *exe;
@@ -981,6 +992,8 @@ static struct tomoyo_domain_info *tomoyo_find_domain_by_qid
*/
static bool tomoyo_select_domain(struct tomoyo_io_buffer *head,
const char *data)
+ __must_hold_shared(&tomoyo_ss)
+ __must_hold(&head->io_sem)
{
unsigned int pid;
struct tomoyo_domain_info *domain = NULL;
@@ -1051,6 +1064,7 @@ static bool tomoyo_same_task_acl(const struct tomoyo_acl_info *a,
* Caller holds tomoyo_read_lock().
*/
static int tomoyo_write_task(struct tomoyo_acl_param *param)
+ __must_hold_shared(&tomoyo_ss)
{
int error = -EINVAL;

@@ -1079,6 +1093,7 @@ static int tomoyo_write_task(struct tomoyo_acl_param *param)
* Caller holds tomoyo_read_lock().
*/
static int tomoyo_delete_domain(char *domainname)
+ __must_hold_shared(&tomoyo_ss)
{
struct tomoyo_domain_info *domain;
struct tomoyo_path_info name;
@@ -1118,6 +1133,7 @@ static int tomoyo_delete_domain(char *domainname)
static int tomoyo_write_domain2(struct tomoyo_policy_namespace *ns,
struct list_head *list, char *data,
const bool is_delete)
+ __must_hold_shared(&tomoyo_ss)
{
struct tomoyo_acl_param param = {
.ns = ns,
@@ -1162,6 +1178,8 @@ const char * const tomoyo_dif[TOMOYO_MAX_DOMAIN_INFO_FLAGS] = {
* Caller holds tomoyo_read_lock().
*/
static int tomoyo_write_domain(struct tomoyo_io_buffer *head)
+ __must_hold_shared(&tomoyo_ss)
+ __must_hold(&head->io_sem)
{
char *data = head->write_buf;
struct tomoyo_policy_namespace *ns;
@@ -1223,6 +1241,7 @@ static int tomoyo_write_domain(struct tomoyo_io_buffer *head)
*/
static bool tomoyo_print_condition(struct tomoyo_io_buffer *head,
const struct tomoyo_condition *cond)
+ __must_hold(&head->io_sem)
{
switch (head->r.cond_step) {
case 0:
@@ -1364,6 +1383,7 @@ static bool tomoyo_print_condition(struct tomoyo_io_buffer *head,
*/
static void tomoyo_set_group(struct tomoyo_io_buffer *head,
const char *category)
+ __must_hold(&head->io_sem)
{
if (head->type == TOMOYO_EXCEPTIONPOLICY) {
tomoyo_print_namespace(head);
@@ -1383,6 +1403,7 @@ static void tomoyo_set_group(struct tomoyo_io_buffer *head,
*/
static bool tomoyo_print_entry(struct tomoyo_io_buffer *head,
struct tomoyo_acl_info *acl)
+ __must_hold(&head->io_sem)
{
const u8 acl_type = acl->type;
bool first = true;
@@ -1588,6 +1609,8 @@ static bool tomoyo_print_entry(struct tomoyo_io_buffer *head,
*/
static bool tomoyo_read_domain2(struct tomoyo_io_buffer *head,
struct list_head *list)
+ __must_hold_shared(&tomoyo_ss)
+ __must_hold(&head->io_sem)
{
list_for_each_cookie(head->r.acl, list) {
struct tomoyo_acl_info *ptr =
@@ -1608,6 +1631,8 @@ static bool tomoyo_read_domain2(struct tomoyo_io_buffer *head,
* Caller holds tomoyo_read_lock().
*/
static void tomoyo_read_domain(struct tomoyo_io_buffer *head)
+ __must_hold_shared(&tomoyo_ss)
+ __must_hold(&head->io_sem)
{
if (head->r.eof)
return;
@@ -1686,6 +1711,7 @@ static int tomoyo_write_pid(struct tomoyo_io_buffer *head)
* using read()/write() interface rather than sysctl() interface.
*/
static void tomoyo_read_pid(struct tomoyo_io_buffer *head)
+ __must_hold(&head->io_sem)
{
char *buf = head->write_buf;
bool global_pid = false;
@@ -1746,6 +1772,8 @@ static const char *tomoyo_group_name[TOMOYO_MAX_GROUP] = {
* Caller holds tomoyo_read_lock().
*/
static int tomoyo_write_exception(struct tomoyo_io_buffer *head)
+ __must_hold_shared(&tomoyo_ss)
+ __must_hold(&head->io_sem)
{
const bool is_delete = head->w.is_delete;
struct tomoyo_acl_param param = {
@@ -1787,6 +1815,8 @@ static int tomoyo_write_exception(struct tomoyo_io_buffer *head)
* Caller holds tomoyo_read_lock().
*/
static bool tomoyo_read_group(struct tomoyo_io_buffer *head, const int idx)
+ __must_hold_shared(&tomoyo_ss)
+ __must_hold(&head->io_sem)
{
struct tomoyo_policy_namespace *ns =
container_of(head->r.ns, typeof(*ns), namespace_list);
@@ -1846,6 +1876,7 @@ static bool tomoyo_read_group(struct tomoyo_io_buffer *head, const int idx)
* Caller holds tomoyo_read_lock().
*/
static bool tomoyo_read_policy(struct tomoyo_io_buffer *head, const int idx)
+ __must_hold_shared(&tomoyo_ss)
{
struct tomoyo_policy_namespace *ns =
container_of(head->r.ns, typeof(*ns), namespace_list);
@@ -1906,6 +1937,8 @@ static bool tomoyo_read_policy(struct tomoyo_io_buffer *head, const int idx)
* Caller holds tomoyo_read_lock().
*/
static void tomoyo_read_exception(struct tomoyo_io_buffer *head)
+ __must_hold_shared(&tomoyo_ss)
+ __must_hold(&head->io_sem)
{
struct tomoyo_policy_namespace *ns =
container_of(head->r.ns, typeof(*ns), namespace_list);
@@ -2097,6 +2130,7 @@ static void tomoyo_patternize_path(char *buffer, const int len, char *entry)
* Returns nothing.
*/
static void tomoyo_add_entry(struct tomoyo_domain_info *domain, char *header)
+ __must_hold_shared(&tomoyo_ss)
{
char *buffer;
char *realpath = NULL;
@@ -2301,6 +2335,7 @@ static __poll_t tomoyo_poll_query(struct file *file, poll_table *wait)
* @head: Pointer to "struct tomoyo_io_buffer".
*/
static void tomoyo_read_query(struct tomoyo_io_buffer *head)
+ __must_hold(&head->io_sem)
{
struct list_head *tmp;
unsigned int pos = 0;
@@ -2362,6 +2397,7 @@ static void tomoyo_read_query(struct tomoyo_io_buffer *head)
* Returns 0 on success, -EINVAL otherwise.
*/
static int tomoyo_write_answer(struct tomoyo_io_buffer *head)
+ __must_hold(&head->io_sem)
{
char *data = head->write_buf;
struct list_head *tmp;
@@ -2401,6 +2437,7 @@ static int tomoyo_write_answer(struct tomoyo_io_buffer *head)
* Returns version information.
*/
static void tomoyo_read_version(struct tomoyo_io_buffer *head)
+ __must_hold(&head->io_sem)
{
if (!head->r.eof) {
tomoyo_io_printf(head, "2.6.0");
@@ -2449,6 +2486,7 @@ void tomoyo_update_stat(const u8 index)
* Returns nothing.
*/
static void tomoyo_read_stat(struct tomoyo_io_buffer *head)
+ __must_hold(&head->io_sem)
{
u8 i;
unsigned int total = 0;
@@ -2493,6 +2531,7 @@ static void tomoyo_read_stat(struct tomoyo_io_buffer *head)
* Returns 0.
*/
static int tomoyo_write_stat(struct tomoyo_io_buffer *head)
+ __must_hold(&head->io_sem)
{
char *data = head->write_buf;
u8 i;
@@ -2717,6 +2756,8 @@ ssize_t tomoyo_read_control(struct tomoyo_io_buffer *head, char __user *buffer,
* Caller holds tomoyo_read_lock().
*/
static int tomoyo_parse_policy(struct tomoyo_io_buffer *head, char *line)
+ __must_hold_shared(&tomoyo_ss)
+ __must_hold(&head->io_sem)
{
/* Delete request? */
head->w.is_delete = !strncmp(line, "delete ", 7);
@@ -2969,8 +3010,11 @@ void __init tomoyo_load_builtin_policy(void)
break;
*end = '\0';
tomoyo_normalize_line(start);
- head.write_buf = start;
- tomoyo_parse_policy(&head, start);
+ /* head is stack-local and not shared. */
+ capability_unsafe(
+ head.write_buf = start;
+ tomoyo_parse_policy(&head, start);
+ );
start = end + 1;
}
}
diff --git a/security/tomoyo/common.h b/security/tomoyo/common.h
index 0e8e2e959aef..2ff05653743c 100644
--- a/security/tomoyo/common.h
+++ b/security/tomoyo/common.h
@@ -827,13 +827,13 @@ struct tomoyo_io_buffer {
bool is_delete;
} w;
/* Buffer for reading. */
- char *read_buf;
+ char *read_buf __guarded_by(&io_sem);
/* Size of read buffer. */
- size_t readbuf_size;
+ size_t readbuf_size __guarded_by(&io_sem);
/* Buffer for writing. */
- char *write_buf;
+ char *write_buf __guarded_by(&io_sem);
/* Size of write buffer. */
- size_t writebuf_size;
+ size_t writebuf_size __guarded_by(&io_sem);
/* Type of this interface. */
enum tomoyo_securityfs_interface_index type;
/* Users counter protected by tomoyo_io_buffer_list_lock. */
@@ -922,6 +922,35 @@ struct tomoyo_task {
struct tomoyo_domain_info *old_domain_info;
};

+/********** External variable definitions. **********/
+
+extern bool tomoyo_policy_loaded;
+extern int tomoyo_enabled;
+extern const char * const tomoyo_condition_keyword
+[TOMOYO_MAX_CONDITION_KEYWORD];
+extern const char * const tomoyo_dif[TOMOYO_MAX_DOMAIN_INFO_FLAGS];
+extern const char * const tomoyo_mac_keywords[TOMOYO_MAX_MAC_INDEX
+ + TOMOYO_MAX_MAC_CATEGORY_INDEX];
+extern const char * const tomoyo_mode[TOMOYO_CONFIG_MAX_MODE];
+extern const char * const tomoyo_path_keyword[TOMOYO_MAX_PATH_OPERATION];
+extern const char * const tomoyo_proto_keyword[TOMOYO_SOCK_MAX];
+extern const char * const tomoyo_socket_keyword[TOMOYO_MAX_NETWORK_OPERATION];
+extern const u8 tomoyo_index2category[TOMOYO_MAX_MAC_INDEX];
+extern const u8 tomoyo_pn2mac[TOMOYO_MAX_PATH_NUMBER_OPERATION];
+extern const u8 tomoyo_pnnn2mac[TOMOYO_MAX_MKDEV_OPERATION];
+extern const u8 tomoyo_pp2mac[TOMOYO_MAX_PATH2_OPERATION];
+extern struct list_head tomoyo_condition_list;
+extern struct list_head tomoyo_domain_list;
+extern struct list_head tomoyo_name_list[TOMOYO_MAX_HASH];
+extern struct list_head tomoyo_namespace_list;
+extern struct mutex tomoyo_policy_lock;
+extern struct srcu_struct tomoyo_ss;
+extern struct tomoyo_domain_info tomoyo_kernel_domain;
+extern struct tomoyo_policy_namespace tomoyo_kernel_namespace;
+extern unsigned int tomoyo_memory_quota[TOMOYO_MAX_MEMORY_STAT];
+extern unsigned int tomoyo_memory_used[TOMOYO_MAX_MEMORY_STAT];
+extern struct lsm_blob_sizes tomoyo_blob_sizes;
+
/********** Function prototypes. **********/

bool tomoyo_address_matches_group(const bool is_ipv6, const __be32 *address,
@@ -969,10 +998,10 @@ const struct tomoyo_path_info *tomoyo_path_matches_group
int tomoyo_check_open_permission(struct tomoyo_domain_info *domain,
const struct path *path, const int flag);
void tomoyo_close_control(struct tomoyo_io_buffer *head);
-int tomoyo_env_perm(struct tomoyo_request_info *r, const char *env);
+int tomoyo_env_perm(struct tomoyo_request_info *r, const char *env) __must_hold_shared(&tomoyo_ss);
int tomoyo_execute_permission(struct tomoyo_request_info *r,
- const struct tomoyo_path_info *filename);
-int tomoyo_find_next_domain(struct linux_binprm *bprm);
+ const struct tomoyo_path_info *filename) __must_hold_shared(&tomoyo_ss);
+int tomoyo_find_next_domain(struct linux_binprm *bprm) __must_hold_shared(&tomoyo_ss);
int tomoyo_get_mode(const struct tomoyo_policy_namespace *ns, const u8 profile,
const u8 index);
int tomoyo_init_request_info(struct tomoyo_request_info *r,
@@ -1000,6 +1029,7 @@ int tomoyo_socket_listen_permission(struct socket *sock);
int tomoyo_socket_sendmsg_permission(struct socket *sock, struct msghdr *msg,
int size);
int tomoyo_supervisor(struct tomoyo_request_info *r, const char *fmt, ...)
+ __must_hold_shared(&tomoyo_ss)
__printf(2, 3);
int tomoyo_update_domain(struct tomoyo_acl_info *new_entry, const int size,
struct tomoyo_acl_param *param,
@@ -1059,7 +1089,7 @@ void tomoyo_print_ulong(char *buffer, const int buffer_len,
const unsigned long value, const u8 type);
void tomoyo_put_name_union(struct tomoyo_name_union *ptr);
void tomoyo_put_number_union(struct tomoyo_number_union *ptr);
-void tomoyo_read_log(struct tomoyo_io_buffer *head);
+void tomoyo_read_log(struct tomoyo_io_buffer *head) __must_hold(&head->io_sem);
void tomoyo_update_stat(const u8 index);
void tomoyo_warn_oom(const char *function);
void tomoyo_write_log(struct tomoyo_request_info *r, const char *fmt, ...)
@@ -1067,35 +1097,6 @@ void tomoyo_write_log(struct tomoyo_request_info *r, const char *fmt, ...)
void tomoyo_write_log2(struct tomoyo_request_info *r, int len, const char *fmt,
va_list args) __printf(3, 0);

-/********** External variable definitions. **********/
-
-extern bool tomoyo_policy_loaded;
-extern int tomoyo_enabled;
-extern const char * const tomoyo_condition_keyword
-[TOMOYO_MAX_CONDITION_KEYWORD];
-extern const char * const tomoyo_dif[TOMOYO_MAX_DOMAIN_INFO_FLAGS];
-extern const char * const tomoyo_mac_keywords[TOMOYO_MAX_MAC_INDEX
- + TOMOYO_MAX_MAC_CATEGORY_INDEX];
-extern const char * const tomoyo_mode[TOMOYO_CONFIG_MAX_MODE];
-extern const char * const tomoyo_path_keyword[TOMOYO_MAX_PATH_OPERATION];
-extern const char * const tomoyo_proto_keyword[TOMOYO_SOCK_MAX];
-extern const char * const tomoyo_socket_keyword[TOMOYO_MAX_NETWORK_OPERATION];
-extern const u8 tomoyo_index2category[TOMOYO_MAX_MAC_INDEX];
-extern const u8 tomoyo_pn2mac[TOMOYO_MAX_PATH_NUMBER_OPERATION];
-extern const u8 tomoyo_pnnn2mac[TOMOYO_MAX_MKDEV_OPERATION];
-extern const u8 tomoyo_pp2mac[TOMOYO_MAX_PATH2_OPERATION];
-extern struct list_head tomoyo_condition_list;
-extern struct list_head tomoyo_domain_list;
-extern struct list_head tomoyo_name_list[TOMOYO_MAX_HASH];
-extern struct list_head tomoyo_namespace_list;
-extern struct mutex tomoyo_policy_lock;
-extern struct srcu_struct tomoyo_ss;
-extern struct tomoyo_domain_info tomoyo_kernel_domain;
-extern struct tomoyo_policy_namespace tomoyo_kernel_namespace;
-extern unsigned int tomoyo_memory_quota[TOMOYO_MAX_MEMORY_STAT];
-extern unsigned int tomoyo_memory_used[TOMOYO_MAX_MEMORY_STAT];
-extern struct lsm_blob_sizes tomoyo_blob_sizes;
-
/********** Inlined functions. **********/

/**
@@ -1104,6 +1105,7 @@ extern struct lsm_blob_sizes tomoyo_blob_sizes;
* Returns index number for tomoyo_read_unlock().
*/
static inline int tomoyo_read_lock(void)
+ __acquires_shared(&tomoyo_ss)
{
return srcu_read_lock(&tomoyo_ss);
}
@@ -1116,6 +1118,7 @@ static inline int tomoyo_read_lock(void)
* Returns nothing.
*/
static inline void tomoyo_read_unlock(int idx)
+ __releases_shared(&tomoyo_ss)
{
srcu_read_unlock(&tomoyo_ss, idx);
}
diff --git a/security/tomoyo/domain.c b/security/tomoyo/domain.c
index 5f9ccab26e9a..5b7989ad85bf 100644
--- a/security/tomoyo/domain.c
+++ b/security/tomoyo/domain.c
@@ -611,6 +611,7 @@ struct tomoyo_domain_info *tomoyo_assign_domain(const char *domainname,
* Returns 0 on success, negative value otherwise.
*/
static int tomoyo_environ(struct tomoyo_execve *ee)
+ __must_hold_shared(&tomoyo_ss)
{
struct tomoyo_request_info *r = &ee->r;
struct linux_binprm *bprm = ee->bprm;
diff --git a/security/tomoyo/environ.c b/security/tomoyo/environ.c
index 7f0a471f19b2..bcb05910facc 100644
--- a/security/tomoyo/environ.c
+++ b/security/tomoyo/environ.c
@@ -32,6 +32,7 @@ static bool tomoyo_check_env_acl(struct tomoyo_request_info *r,
* Returns 0 on success, negative value otherwise.
*/
static int tomoyo_audit_env_log(struct tomoyo_request_info *r)
+ __must_hold_shared(&tomoyo_ss)
{
return tomoyo_supervisor(r, "misc env %s\n",
r->param.environ.name->name);
diff --git a/security/tomoyo/file.c b/security/tomoyo/file.c
index 8f3b90b6e03d..e9b67dbb38e7 100644
--- a/security/tomoyo/file.c
+++ b/security/tomoyo/file.c
@@ -164,6 +164,7 @@ static bool tomoyo_get_realpath(struct tomoyo_path_info *buf, const struct path
* Returns 0 on success, negative value otherwise.
*/
static int tomoyo_audit_path_log(struct tomoyo_request_info *r)
+ __must_hold_shared(&tomoyo_ss)
{
return tomoyo_supervisor(r, "file %s %s\n", tomoyo_path_keyword
[r->param.path.operation],
@@ -178,6 +179,7 @@ static int tomoyo_audit_path_log(struct tomoyo_request_info *r)
* Returns 0 on success, negative value otherwise.
*/
static int tomoyo_audit_path2_log(struct tomoyo_request_info *r)
+ __must_hold_shared(&tomoyo_ss)
{
return tomoyo_supervisor(r, "file %s %s %s\n", tomoyo_mac_keywords
[tomoyo_pp2mac[r->param.path2.operation]],
@@ -193,6 +195,7 @@ static int tomoyo_audit_path2_log(struct tomoyo_request_info *r)
* Returns 0 on success, negative value otherwise.
*/
static int tomoyo_audit_mkdev_log(struct tomoyo_request_info *r)
+ __must_hold_shared(&tomoyo_ss)
{
return tomoyo_supervisor(r, "file %s %s 0%o %u %u\n",
tomoyo_mac_keywords
@@ -210,6 +213,7 @@ static int tomoyo_audit_mkdev_log(struct tomoyo_request_info *r)
* Returns 0 on success, negative value otherwise.
*/
static int tomoyo_audit_path_number_log(struct tomoyo_request_info *r)
+ __must_hold_shared(&tomoyo_ss)
{
const u8 type = r->param.path_number.operation;
u8 radix;
@@ -572,6 +576,7 @@ static int tomoyo_update_path2_acl(const u8 perm,
*/
static int tomoyo_path_permission(struct tomoyo_request_info *r, u8 operation,
const struct tomoyo_path_info *filename)
+ __must_hold_shared(&tomoyo_ss)
{
int error;

diff --git a/security/tomoyo/gc.c b/security/tomoyo/gc.c
index 026e29ea3796..34912f120854 100644
--- a/security/tomoyo/gc.c
+++ b/security/tomoyo/gc.c
@@ -23,11 +23,10 @@ static inline void tomoyo_memory_free(void *ptr)
tomoyo_memory_used[TOMOYO_MEMORY_POLICY] -= ksize(ptr);
kfree(ptr);
}
-
-/* The list for "struct tomoyo_io_buffer". */
-static LIST_HEAD(tomoyo_io_buffer_list);
/* Lock for protecting tomoyo_io_buffer_list. */
static DEFINE_SPINLOCK(tomoyo_io_buffer_list_lock);
+/* The list for "struct tomoyo_io_buffer". */
+static __guarded_by(&tomoyo_io_buffer_list_lock) LIST_HEAD(tomoyo_io_buffer_list);

/**
* tomoyo_struct_used_by_io_buffer - Check whether the list element is used by /sys/kernel/security/tomoyo/ users or not.
@@ -385,6 +384,7 @@ static inline void tomoyo_del_number_group(struct list_head *element)
*/
static void tomoyo_try_to_gc(const enum tomoyo_policy_id type,
struct list_head *element)
+ __must_hold(&tomoyo_policy_lock)
{
/*
* __list_del_entry() guarantees that the list element became no longer
@@ -484,6 +484,7 @@ static void tomoyo_try_to_gc(const enum tomoyo_policy_id type,
*/
static void tomoyo_collect_member(const enum tomoyo_policy_id id,
struct list_head *member_list)
+ __must_hold(&tomoyo_policy_lock)
{
struct tomoyo_acl_head *member;
struct tomoyo_acl_head *tmp;
@@ -504,6 +505,7 @@ static void tomoyo_collect_member(const enum tomoyo_policy_id id,
* Returns nothing.
*/
static void tomoyo_collect_acl(struct list_head *list)
+ __must_hold(&tomoyo_policy_lock)
{
struct tomoyo_acl_info *acl;
struct tomoyo_acl_info *tmp;
@@ -627,8 +629,11 @@ static int tomoyo_gc_thread(void *unused)
if (head->users)
continue;
list_del(&head->list);
- kfree(head->read_buf);
- kfree(head->write_buf);
+ /* Safe destruction because no users are left. */
+ capability_unsafe(
+ kfree(head->read_buf);
+ kfree(head->write_buf);
+ );
kfree(head);
}
spin_unlock(&tomoyo_io_buffer_list_lock);
@@ -656,11 +661,18 @@ void tomoyo_notify_gc(struct tomoyo_io_buffer *head, const bool is_register)
head->users = 1;
list_add(&head->list, &tomoyo_io_buffer_list);
} else {
- is_write = head->write_buf != NULL;
+ /*
+ * tomoyo_write_control() can concurrently update write_buf from
+ * a non-NULL to new non-NULL pointer with io_sem held.
+ */
+ is_write = data_race(head->write_buf != NULL);
if (!--head->users) {
list_del(&head->list);
- kfree(head->read_buf);
- kfree(head->write_buf);
+ /* Safe destruction because no users are left. */
+ capability_unsafe(
+ kfree(head->read_buf);
+ kfree(head->write_buf);
+ );
kfree(head);
}
}
diff --git a/security/tomoyo/mount.c b/security/tomoyo/mount.c
index 2755971f50df..322dfd188ada 100644
--- a/security/tomoyo/mount.c
+++ b/security/tomoyo/mount.c
@@ -28,6 +28,7 @@ static const char * const tomoyo_mounts[TOMOYO_MAX_SPECIAL_MOUNT] = {
* Returns 0 on success, negative value otherwise.
*/
static int tomoyo_audit_mount_log(struct tomoyo_request_info *r)
+ __must_hold_shared(&tomoyo_ss)
{
return tomoyo_supervisor(r, "file mount %s %s %s 0x%lX\n",
r->param.mount.dev->name,
@@ -78,6 +79,7 @@ static int tomoyo_mount_acl(struct tomoyo_request_info *r,
const char *dev_name,
const struct path *dir, const char *type,
unsigned long flags)
+ __must_hold_shared(&tomoyo_ss)
{
struct tomoyo_obj_info obj = { };
struct path path;
diff --git a/security/tomoyo/network.c b/security/tomoyo/network.c
index 8dc61335f65e..cfc2a019de1e 100644
--- a/security/tomoyo/network.c
+++ b/security/tomoyo/network.c
@@ -363,6 +363,7 @@ int tomoyo_write_unix_network(struct tomoyo_acl_param *param)
static int tomoyo_audit_net_log(struct tomoyo_request_info *r,
const char *family, const u8 protocol,
const u8 operation, const char *address)
+ __must_hold_shared(&tomoyo_ss)
{
return tomoyo_supervisor(r, "network %s %s %s %s\n", family,
tomoyo_proto_keyword[protocol],
@@ -377,6 +378,7 @@ static int tomoyo_audit_net_log(struct tomoyo_request_info *r,
* Returns 0 on success, negative value otherwise.
*/
static int tomoyo_audit_inet_log(struct tomoyo_request_info *r)
+ __must_hold_shared(&tomoyo_ss)
{
char buf[128];
int len;
@@ -402,6 +404,7 @@ static int tomoyo_audit_inet_log(struct tomoyo_request_info *r)
* Returns 0 on success, negative value otherwise.
*/
static int tomoyo_audit_unix_log(struct tomoyo_request_info *r)
+ __must_hold_shared(&tomoyo_ss)
{
return tomoyo_audit_net_log(r, "unix", r->param.unix_network.protocol,
r->param.unix_network.operation,
--
2.51.0.384.g4c02a37b29-goog

Marco Elver

unread,
Sep 18, 2025, 10:07:02 AM (6 days ago) Sep 18
to el...@google.com, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
Enable capability analysis for crypto subsystem.

This demonstrates a larger conversion to use Clang's capability
analysis. The benefit is additional static checking of locking rules,
along with better documentation.

Note the use of the __acquire_ret macro how to define an API where a
function returns a pointer to an object (struct scomp_scratch) with a
lock held. Additionally, the analysis only resolves aliases where the
analysis unambiguously sees that a variable was not reassigned after
initialization, requiring minor code changes.

Signed-off-by: Marco Elver <el...@google.com>
Cc: Herbert Xu <her...@gondor.apana.org.au>
Cc: "David S. Miller" <da...@davemloft.net>
Cc: linux-...@vger.kernel.org
---
v3:
* Rebase - make use of __acquire_ret macro for new functions.
* Initialize variables once where we want the analysis to recognize aliases.

v2:
* New patch.
---
crypto/Makefile | 2 ++
crypto/acompress.c | 6 +++---
crypto/algapi.c | 2 ++
crypto/api.c | 1 +
crypto/crypto_engine.c | 2 +-
crypto/drbg.c | 5 +++++
crypto/internal.h | 2 +-
crypto/proc.c | 3 +++
crypto/scompress.c | 24 ++++++++++++------------
include/crypto/internal/acompress.h | 7 ++++---
include/crypto/internal/engine.h | 2 +-
11 files changed, 35 insertions(+), 21 deletions(-)

diff --git a/crypto/Makefile b/crypto/Makefile
index 6c5d59369dac..e73c50a54119 100644
--- a/crypto/Makefile
+++ b/crypto/Makefile
@@ -3,6 +3,8 @@
# Cryptographic API
#

+CAPABILITY_ANALYSIS := y
+
obj-$(CONFIG_CRYPTO) += crypto.o
crypto-y := api.o cipher.o

diff --git a/crypto/acompress.c b/crypto/acompress.c
index be28cbfd22e3..25df368df098 100644
--- a/crypto/acompress.c
+++ b/crypto/acompress.c
@@ -449,8 +449,8 @@ int crypto_acomp_alloc_streams(struct crypto_acomp_streams *s)
}
EXPORT_SYMBOL_GPL(crypto_acomp_alloc_streams);

-struct crypto_acomp_stream *crypto_acomp_lock_stream_bh(
- struct crypto_acomp_streams *s) __acquires(stream)
+struct crypto_acomp_stream *_crypto_acomp_lock_stream_bh(
+ struct crypto_acomp_streams *s)
{
struct crypto_acomp_stream __percpu *streams = s->streams;
int cpu = raw_smp_processor_id();
@@ -469,7 +469,7 @@ struct crypto_acomp_stream *crypto_acomp_lock_stream_bh(
spin_lock(&ps->lock);
return ps;
}
-EXPORT_SYMBOL_GPL(crypto_acomp_lock_stream_bh);
+EXPORT_SYMBOL_GPL(_crypto_acomp_lock_stream_bh);

void acomp_walk_done_src(struct acomp_walk *walk, int used)
{
diff --git a/crypto/algapi.c b/crypto/algapi.c
index e604d0d8b7b4..abc9333327d4 100644
--- a/crypto/algapi.c
+++ b/crypto/algapi.c
@@ -244,6 +244,7 @@ EXPORT_SYMBOL_GPL(crypto_remove_spawns);

static void crypto_alg_finish_registration(struct crypto_alg *alg,
struct list_head *algs_to_put)
+ __must_hold(&crypto_alg_sem)
{
struct crypto_alg *q;

@@ -299,6 +300,7 @@ static struct crypto_larval *crypto_alloc_test_larval(struct crypto_alg *alg)

static struct crypto_larval *
__crypto_register_alg(struct crypto_alg *alg, struct list_head *algs_to_put)
+ __must_hold(&crypto_alg_sem)
{
struct crypto_alg *q;
struct crypto_larval *larval;
diff --git a/crypto/api.c b/crypto/api.c
index 5724d62e9d07..05629644a688 100644
--- a/crypto/api.c
+++ b/crypto/api.c
@@ -57,6 +57,7 @@ EXPORT_SYMBOL_GPL(crypto_mod_put);

static struct crypto_alg *__crypto_alg_lookup(const char *name, u32 type,
u32 mask)
+ __must_hold_shared(&crypto_alg_sem)
{
struct crypto_alg *q, *alg = NULL;
int best = -2;
diff --git a/crypto/crypto_engine.c b/crypto/crypto_engine.c
index 18e1689efe12..1653a4bf5b31 100644
--- a/crypto/crypto_engine.c
+++ b/crypto/crypto_engine.c
@@ -453,8 +453,8 @@ struct crypto_engine *crypto_engine_alloc_init_and_set(struct device *dev,
snprintf(engine->name, sizeof(engine->name),
"%s-engine", dev_name(dev));

- crypto_init_queue(&engine->queue, qlen);
spin_lock_init(&engine->queue_lock);
+ crypto_init_queue(&engine->queue, qlen);

engine->kworker = kthread_run_worker(0, "%s", engine->name);
if (IS_ERR(engine->kworker)) {
diff --git a/crypto/drbg.c b/crypto/drbg.c
index dbe4c8bb5ceb..9684d952fdfd 100644
--- a/crypto/drbg.c
+++ b/crypto/drbg.c
@@ -231,6 +231,7 @@ static inline unsigned short drbg_sec_strength(drbg_flag_t flags)
*/
static int drbg_fips_continuous_test(struct drbg_state *drbg,
const unsigned char *entropy)
+ __must_hold(&drbg->drbg_mutex)
{
unsigned short entropylen = drbg_sec_strength(drbg->core->flags);
int ret = 0;
@@ -1061,6 +1062,7 @@ static inline int __drbg_seed(struct drbg_state *drbg, struct list_head *seed,
static inline int drbg_get_random_bytes(struct drbg_state *drbg,
unsigned char *entropy,
unsigned int entropylen)
+ __must_hold(&drbg->drbg_mutex)
{
int ret;

@@ -1075,6 +1077,7 @@ static inline int drbg_get_random_bytes(struct drbg_state *drbg,
}

static int drbg_seed_from_random(struct drbg_state *drbg)
+ __must_hold(&drbg->drbg_mutex)
{
struct drbg_string data;
LIST_HEAD(seedlist);
@@ -1132,6 +1135,7 @@ static bool drbg_nopr_reseed_interval_elapsed(struct drbg_state *drbg)
*/
static int drbg_seed(struct drbg_state *drbg, struct drbg_string *pers,
bool reseed)
+ __must_hold(&drbg->drbg_mutex)
{
int ret;
unsigned char entropy[((32 + 16) * 2)];
@@ -1368,6 +1372,7 @@ static inline int drbg_alloc_state(struct drbg_state *drbg)
static int drbg_generate(struct drbg_state *drbg,
unsigned char *buf, unsigned int buflen,
struct drbg_string *addtl)
+ __must_hold(&drbg->drbg_mutex)
{
int len = 0;
LIST_HEAD(addtllist);
diff --git a/crypto/internal.h b/crypto/internal.h
index b9afd68767c1..8fbe0226d48e 100644
--- a/crypto/internal.h
+++ b/crypto/internal.h
@@ -61,8 +61,8 @@ enum {
/* Maximum number of (rtattr) parameters for each template. */
#define CRYPTO_MAX_ATTRS 32

-extern struct list_head crypto_alg_list;
extern struct rw_semaphore crypto_alg_sem;
+extern struct list_head crypto_alg_list __guarded_by(&crypto_alg_sem);
extern struct blocking_notifier_head crypto_chain;

int alg_test(const char *driver, const char *alg, u32 type, u32 mask);
diff --git a/crypto/proc.c b/crypto/proc.c
index 82f15b967e85..5fb9fe86d023 100644
--- a/crypto/proc.c
+++ b/crypto/proc.c
@@ -19,17 +19,20 @@
#include "internal.h"

static void *c_start(struct seq_file *m, loff_t *pos)
+ __acquires_shared(&crypto_alg_sem)
{
down_read(&crypto_alg_sem);
return seq_list_start(&crypto_alg_list, *pos);
}

static void *c_next(struct seq_file *m, void *p, loff_t *pos)
+ __must_hold_shared(&crypto_alg_sem)
{
return seq_list_next(p, &crypto_alg_list, pos);
}

static void c_stop(struct seq_file *m, void *p)
+ __releases_shared(&crypto_alg_sem)
{
up_read(&crypto_alg_sem);
}
diff --git a/crypto/scompress.c b/crypto/scompress.c
index c651e7f2197a..fb87f3716426 100644
--- a/crypto/scompress.c
+++ b/crypto/scompress.c
@@ -28,8 +28,8 @@
struct scomp_scratch {
spinlock_t lock;
union {
- void *src;
- unsigned long saddr;
+ void *src __guarded_by(&lock);
+ unsigned long saddr __guarded_by(&lock);
};
};

@@ -38,8 +38,8 @@ static DEFINE_PER_CPU(struct scomp_scratch, scomp_scratch) = {
};

static const struct crypto_type crypto_scomp_type;
-static int scomp_scratch_users;
static DEFINE_MUTEX(scomp_lock);
+static int scomp_scratch_users __guarded_by(&scomp_lock);

static cpumask_t scomp_scratch_want;
static void scomp_scratch_workfn(struct work_struct *work);
@@ -67,6 +67,7 @@ static void crypto_scomp_show(struct seq_file *m, struct crypto_alg *alg)
}

static void crypto_scomp_free_scratches(void)
+ __capability_unsafe(/* frees @scratch */)
{
struct scomp_scratch *scratch;
int i;
@@ -101,7 +102,7 @@ static void scomp_scratch_workfn(struct work_struct *work)
struct scomp_scratch *scratch;

scratch = per_cpu_ptr(&scomp_scratch, cpu);
- if (scratch->src)
+ if (capability_unsafe(scratch->src))
continue;
if (scomp_alloc_scratch(scratch, cpu))
break;
@@ -111,6 +112,7 @@ static void scomp_scratch_workfn(struct work_struct *work)
}

static int crypto_scomp_alloc_scratches(void)
+ __capability_unsafe(/* allocates @scratch */)
{
unsigned int i = cpumask_first(cpu_possible_mask);
struct scomp_scratch *scratch;
@@ -139,7 +141,8 @@ static int crypto_scomp_init_tfm(struct crypto_tfm *tfm)
return ret;
}

-static struct scomp_scratch *scomp_lock_scratch(void) __acquires(scratch)
+#define scomp_lock_scratch(...) __acquire_ret(_scomp_lock_scratch(__VA_ARGS__), &__ret->lock)
+static struct scomp_scratch *_scomp_lock_scratch(void) __acquires_ret
{
int cpu = raw_smp_processor_id();
struct scomp_scratch *scratch;
@@ -159,7 +162,7 @@ static struct scomp_scratch *scomp_lock_scratch(void) __acquires(scratch)
}

static inline void scomp_unlock_scratch(struct scomp_scratch *scratch)
- __releases(scratch)
+ __releases(&scratch->lock)
{
spin_unlock(&scratch->lock);
}
@@ -171,8 +174,6 @@ static int scomp_acomp_comp_decomp(struct acomp_req *req, int dir)
bool src_isvirt = acomp_request_src_isvirt(req);
bool dst_isvirt = acomp_request_dst_isvirt(req);
struct crypto_scomp *scomp = *tfm_ctx;
- struct crypto_acomp_stream *stream;
- struct scomp_scratch *scratch;
unsigned int slen = req->slen;
unsigned int dlen = req->dlen;
struct page *spage, *dpage;
@@ -232,13 +233,12 @@ static int scomp_acomp_comp_decomp(struct acomp_req *req, int dir)
} while (0);
}

- stream = crypto_acomp_lock_stream_bh(&crypto_scomp_alg(scomp)->streams);
+ struct crypto_acomp_stream *stream = crypto_acomp_lock_stream_bh(&crypto_scomp_alg(scomp)->streams);

if (!src_isvirt && !src) {
- const u8 *src;
+ struct scomp_scratch *scratch = scomp_lock_scratch();
+ const u8 *src = scratch->src;

- scratch = scomp_lock_scratch();
- src = scratch->src;
memcpy_from_sglist(scratch->src, req->src, 0, slen);

if (dir)
diff --git a/include/crypto/internal/acompress.h b/include/crypto/internal/acompress.h
index 2d97440028ff..9a3f28baa804 100644
--- a/include/crypto/internal/acompress.h
+++ b/include/crypto/internal/acompress.h
@@ -191,11 +191,12 @@ static inline bool crypto_acomp_req_virt(struct crypto_acomp *tfm)
void crypto_acomp_free_streams(struct crypto_acomp_streams *s);
int crypto_acomp_alloc_streams(struct crypto_acomp_streams *s);

-struct crypto_acomp_stream *crypto_acomp_lock_stream_bh(
- struct crypto_acomp_streams *s) __acquires(stream);
+#define crypto_acomp_lock_stream_bh(...) __acquire_ret(_crypto_acomp_lock_stream_bh(__VA_ARGS__), &__ret->lock);
+struct crypto_acomp_stream *_crypto_acomp_lock_stream_bh(
+ struct crypto_acomp_streams *s) __acquires_ret;

static inline void crypto_acomp_unlock_stream_bh(
- struct crypto_acomp_stream *stream) __releases(stream)
+ struct crypto_acomp_stream *stream) __releases(&stream->lock)
{
spin_unlock_bh(&stream->lock);
}
diff --git a/include/crypto/internal/engine.h b/include/crypto/internal/engine.h
index f19ef376833f..6a1d27880615 100644
--- a/include/crypto/internal/engine.h
+++ b/include/crypto/internal/engine.h
@@ -45,7 +45,7 @@ struct crypto_engine {

struct list_head list;
spinlock_t queue_lock;
- struct crypto_queue queue;
+ struct crypto_queue queue __guarded_by(&queue_lock);
struct device *dev;

struct kthread_worker *kworker;
--
2.51.0.384.g4c02a37b29-goog

Marco Elver

unread,
Sep 18, 2025, 10:07:05 AM (6 days ago) Sep 18
to el...@google.com, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org, Ingo Molnar
This demonstrates a larger conversion to use Clang's capability
analysis. The benefit is additional static checking of locking rules,
along with better documentation.

Notably, kernel/sched contains sufficiently complex synchronization
patterns, and application to core.c & fair.c demonstrates that the
latest Clang version has become powerful enough to start applying this
to more complex subsystems (with some modest annotations and changes).

Signed-off-by: Marco Elver <el...@google.com>
Cc: Peter Zijlstra <pet...@infradead.org>
Cc: Ingo Molnar <mi...@redhat.com>
---
v3:
* New patch.
---
include/linux/sched.h | 6 +-
include/linux/sched/signal.h | 4 +-
include/linux/sched/task.h | 5 +-
include/linux/sched/wake_q.h | 3 +
kernel/sched/Makefile | 3 +
kernel/sched/core.c | 89 +++++++++++-----
kernel/sched/fair.c | 9 +-
kernel/sched/sched.h | 110 +++++++++++++-------
scripts/capability-analysis-suppression.txt | 1 +
9 files changed, 162 insertions(+), 68 deletions(-)

diff --git a/include/linux/sched.h b/include/linux/sched.h
index f8188b833350..bc2be55a9f47 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -2102,9 +2102,9 @@ static inline int _cond_resched(void)
_cond_resched(); \
})

-extern int __cond_resched_lock(spinlock_t *lock);
-extern int __cond_resched_rwlock_read(rwlock_t *lock);
-extern int __cond_resched_rwlock_write(rwlock_t *lock);
+extern int __cond_resched_lock(spinlock_t *lock) __must_hold(lock);
+extern int __cond_resched_rwlock_read(rwlock_t *lock) __must_hold_shared(lock);
+extern int __cond_resched_rwlock_write(rwlock_t *lock) __must_hold(lock);

#define MIGHT_RESCHED_RCU_SHIFT 8
#define MIGHT_RESCHED_PREEMPT_MASK ((1U << MIGHT_RESCHED_RCU_SHIFT) - 1)
diff --git a/include/linux/sched/signal.h b/include/linux/sched/signal.h
index bc7f83b012fb..6f581a750e84 100644
--- a/include/linux/sched/signal.h
+++ b/include/linux/sched/signal.h
@@ -734,10 +734,12 @@ static inline int thread_group_empty(struct task_struct *p)
(thread_group_leader(p) && !thread_group_empty(p))

extern struct sighand_struct *lock_task_sighand(struct task_struct *task,
- unsigned long *flags);
+ unsigned long *flags)
+ __acquires(&task->sighand->siglock);

static inline void unlock_task_sighand(struct task_struct *task,
unsigned long *flags)
+ __releases(&task->sighand->siglock)
{
spin_unlock_irqrestore(&task->sighand->siglock, *flags);
}
diff --git a/include/linux/sched/task.h b/include/linux/sched/task.h
index ea41795a352b..4db5c9323989 100644
--- a/include/linux/sched/task.h
+++ b/include/linux/sched/task.h
@@ -215,15 +215,18 @@ static inline struct vm_struct *task_stack_vm_area(const struct task_struct *t)
* neither inside nor outside.
*/
static inline void task_lock(struct task_struct *p)
+ __acquires(&p->alloc_lock)
{
spin_lock(&p->alloc_lock);
}

static inline void task_unlock(struct task_struct *p)
+ __releases(&p->alloc_lock)
{
spin_unlock(&p->alloc_lock);
}

-DEFINE_GUARD(task_lock, struct task_struct *, task_lock(_T), task_unlock(_T))
+DEFINE_LOCK_GUARD_1(task_lock, struct task_struct, task_lock(_T->lock), task_unlock(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(task_lock, __assumes_cap(_T->alloc_lock), /* */)

#endif /* _LINUX_SCHED_TASK_H */
diff --git a/include/linux/sched/wake_q.h b/include/linux/sched/wake_q.h
index 0f28b4623ad4..765bbc3d54be 100644
--- a/include/linux/sched/wake_q.h
+++ b/include/linux/sched/wake_q.h
@@ -66,6 +66,7 @@ extern void wake_up_q(struct wake_q_head *head);
/* Spin unlock helpers to unlock and call wake_up_q with preempt disabled */
static inline
void raw_spin_unlock_wake(raw_spinlock_t *lock, struct wake_q_head *wake_q)
+ __releases(lock)
{
guard(preempt)();
raw_spin_unlock(lock);
@@ -77,6 +78,7 @@ void raw_spin_unlock_wake(raw_spinlock_t *lock, struct wake_q_head *wake_q)

static inline
void raw_spin_unlock_irq_wake(raw_spinlock_t *lock, struct wake_q_head *wake_q)
+ __releases(lock)
{
guard(preempt)();
raw_spin_unlock_irq(lock);
@@ -89,6 +91,7 @@ void raw_spin_unlock_irq_wake(raw_spinlock_t *lock, struct wake_q_head *wake_q)
static inline
void raw_spin_unlock_irqrestore_wake(raw_spinlock_t *lock, unsigned long flags,
struct wake_q_head *wake_q)
+ __releases(lock)
{
guard(preempt)();
raw_spin_unlock_irqrestore(lock, flags);
diff --git a/kernel/sched/Makefile b/kernel/sched/Makefile
index 8ae86371ddcd..8603987ce4c1 100644
--- a/kernel/sched/Makefile
+++ b/kernel/sched/Makefile
@@ -1,5 +1,8 @@
# SPDX-License-Identifier: GPL-2.0

+CAPABILITY_ANALYSIS_core.o := y
+CAPABILITY_ANALYSIS_fair.o := y
+
# The compilers are complaining about unused variables inside an if(0) scope
# block. This is daft, shut them up.
ccflags-y += $(call cc-disable-warning, unused-but-set-variable)
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index be00629f0ba4..233f774754b6 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -393,6 +393,8 @@ static atomic_t sched_core_count;
static struct cpumask sched_core_mask;

static void sched_core_lock(int cpu, unsigned long *flags)
+ __capability_unsafe(/* acquires multiple */)
+ __acquires(&runqueues.__lock) /* overapproximation */
{
const struct cpumask *smt_mask = cpu_smt_mask(cpu);
int t, i = 0;
@@ -403,6 +405,8 @@ static void sched_core_lock(int cpu, unsigned long *flags)
}

static void sched_core_unlock(int cpu, unsigned long *flags)
+ __capability_unsafe(/* releases multiple */)
+ __releases(&runqueues.__lock) /* overapproximation */
{
const struct cpumask *smt_mask = cpu_smt_mask(cpu);
int t;
@@ -627,6 +631,7 @@ EXPORT_SYMBOL(__trace_set_current_state);
*/

void raw_spin_rq_lock_nested(struct rq *rq, int subclass)
+ __capability_unsafe()
{
raw_spinlock_t *lock;

@@ -652,6 +657,7 @@ void raw_spin_rq_lock_nested(struct rq *rq, int subclass)
}

bool raw_spin_rq_trylock(struct rq *rq)
+ __capability_unsafe()
{
raw_spinlock_t *lock;
bool ret;
@@ -693,15 +699,16 @@ void double_rq_lock(struct rq *rq1, struct rq *rq2)
raw_spin_rq_lock(rq1);
if (__rq_lockp(rq1) != __rq_lockp(rq2))
raw_spin_rq_lock_nested(rq2, SINGLE_DEPTH_NESTING);
+ else
+ __acquire_cap(__rq_lockp(rq2)); /* fake acquire */

double_rq_clock_clear_update(rq1, rq2);
}

/*
- * __task_rq_lock - lock the rq @p resides on.
+ * ___task_rq_lock - lock the rq @p resides on.
*/
-struct rq *__task_rq_lock(struct task_struct *p, struct rq_flags *rf)
- __acquires(rq->lock)
+struct rq *___task_rq_lock(struct task_struct *p, struct rq_flags *rf)
{
struct rq *rq;

@@ -724,9 +731,7 @@ struct rq *__task_rq_lock(struct task_struct *p, struct rq_flags *rf)
/*
* task_rq_lock - lock p->pi_lock and lock the rq @p resides on.
*/
-struct rq *task_rq_lock(struct task_struct *p, struct rq_flags *rf)
- __acquires(p->pi_lock)
- __acquires(rq->lock)
+struct rq *_task_rq_lock(struct task_struct *p, struct rq_flags *rf)
{
struct rq *rq;

@@ -2498,6 +2503,7 @@ static inline bool is_cpu_allowed(struct task_struct *p, int cpu)
*/
static struct rq *move_queued_task(struct rq *rq, struct rq_flags *rf,
struct task_struct *p, int new_cpu)
+ __must_hold(__rq_lockp(rq))
{
lockdep_assert_rq_held(rq);

@@ -2544,6 +2550,7 @@ struct set_affinity_pending {
*/
static struct rq *__migrate_task(struct rq *rq, struct rq_flags *rf,
struct task_struct *p, int dest_cpu)
+ __must_hold(__rq_lockp(rq))
{
/* Affinity changed (again). */
if (!is_cpu_allowed(p, dest_cpu))
@@ -2580,6 +2587,12 @@ static int migration_cpu_stop(void *data)
*/
flush_smp_call_function_queue();

+ /*
+ * We may change the underlying rq, but the locks held will
+ * appropriately be "transferred" when switching.
+ */
+ capability_unsafe_alias(rq);
+
raw_spin_lock(&p->pi_lock);
rq_lock(rq, &rf);

@@ -2689,6 +2702,8 @@ int push_cpu_stop(void *arg)
if (!lowest_rq)
goto out_unlock;

+ lockdep_assert_rq_held(lowest_rq);
+
// XXX validate p is still the highest prio task
if (task_rq(p) == rq) {
move_queued_task_locked(rq, lowest_rq, p);
@@ -2934,8 +2949,7 @@ void release_user_cpus_ptr(struct task_struct *p)
*/
static int affine_move_task(struct rq *rq, struct task_struct *p, struct rq_flags *rf,
int dest_cpu, unsigned int flags)
- __releases(rq->lock)
- __releases(p->pi_lock)
+ __releases(__rq_lockp(rq), &p->pi_lock)
{
struct set_affinity_pending my_pending = { }, *pending = NULL;
bool stop_pending, complete = false;
@@ -3090,8 +3104,7 @@ static int __set_cpus_allowed_ptr_locked(struct task_struct *p,
struct affinity_context *ctx,
struct rq *rq,
struct rq_flags *rf)
- __releases(rq->lock)
- __releases(p->pi_lock)
+ __releases(__rq_lockp(rq), &p->pi_lock)
{
const struct cpumask *cpu_allowed_mask = task_cpu_possible_mask(p);
const struct cpumask *cpu_valid_mask = cpu_active_mask;
@@ -4383,29 +4396,30 @@ static bool __task_needs_rq_lock(struct task_struct *p)
*/
int task_call_func(struct task_struct *p, task_call_f func, void *arg)
{
- struct rq *rq = NULL;
struct rq_flags rf;
int ret;

raw_spin_lock_irqsave(&p->pi_lock, rf.flags);

- if (__task_needs_rq_lock(p))
- rq = __task_rq_lock(p, &rf);
+ if (__task_needs_rq_lock(p)) {
+ struct rq *rq = __task_rq_lock(p, &rf);

- /*
- * At this point the task is pinned; either:
- * - blocked and we're holding off wakeups (pi->lock)
- * - woken, and we're holding off enqueue (rq->lock)
- * - queued, and we're holding off schedule (rq->lock)
- * - running, and we're holding off de-schedule (rq->lock)
- *
- * The called function (@func) can use: task_curr(), p->on_rq and
- * p->__state to differentiate between these states.
- */
- ret = func(p, arg);
+ /*
+ * At this point the task is pinned; either:
+ * - blocked and we're holding off wakeups (pi->lock)
+ * - woken, and we're holding off enqueue (rq->lock)
+ * - queued, and we're holding off schedule (rq->lock)
+ * - running, and we're holding off de-schedule (rq->lock)
+ *
+ * The called function (@func) can use: task_curr(), p->on_rq and
+ * p->__state to differentiate between these states.
+ */
+ ret = func(p, arg);

- if (rq)
rq_unlock(rq, &rf);
+ } else {
+ ret = func(p, arg);
+ }

raw_spin_unlock_irqrestore(&p->pi_lock, rf.flags);
return ret;
@@ -5078,6 +5092,8 @@ void balance_callbacks(struct rq *rq, struct balance_callback *head)

static inline void
prepare_lock_switch(struct rq *rq, struct task_struct *next, struct rq_flags *rf)
+ __releases(__rq_lockp(rq))
+ __acquires(__rq_lockp(this_rq()))
{
/*
* Since the runqueue lock will be released by the next
@@ -5091,9 +5107,15 @@ prepare_lock_switch(struct rq *rq, struct task_struct *next, struct rq_flags *rf
/* this is a valid case when another task releases the spinlock */
rq_lockp(rq)->owner = next;
#endif
+ /*
+ * Model the rq reference switcheroo.
+ */
+ __release(__rq_lockp(rq));
+ __acquire(__rq_lockp(this_rq()));
}

static inline void finish_lock_switch(struct rq *rq)
+ __releases(__rq_lockp(rq))
{
/*
* If we are tracking spinlock dependencies then we have to
@@ -5149,6 +5171,7 @@ static inline void kmap_local_sched_in(void)
static inline void
prepare_task_switch(struct rq *rq, struct task_struct *prev,
struct task_struct *next)
+ __must_hold(__rq_lockp(rq))
{
kcov_prepare_switch(prev);
sched_info_switch(rq, prev, next);
@@ -5180,7 +5203,7 @@ prepare_task_switch(struct rq *rq, struct task_struct *prev,
* because prev may have moved to another CPU.
*/
static struct rq *finish_task_switch(struct task_struct *prev)
- __releases(rq->lock)
+ __releases(__rq_lockp(this_rq()))
{
struct rq *rq = this_rq();
struct mm_struct *mm = rq->prev_mm;
@@ -5268,7 +5291,7 @@ static struct rq *finish_task_switch(struct task_struct *prev)
* @prev: the thread we just switched away from.
*/
asmlinkage __visible void schedule_tail(struct task_struct *prev)
- __releases(rq->lock)
+ __releases(__rq_lockp(this_rq()))
{
/*
* New tasks start with FORK_PREEMPT_COUNT, see there and
@@ -5300,6 +5323,7 @@ asmlinkage __visible void schedule_tail(struct task_struct *prev)
static __always_inline struct rq *
context_switch(struct rq *rq, struct task_struct *prev,
struct task_struct *next, struct rq_flags *rf)
+ __releases(__rq_lockp(rq))
{
prepare_task_switch(rq, prev, next);

@@ -5980,6 +6004,7 @@ static void prev_balance(struct rq *rq, struct task_struct *prev,
*/
static inline struct task_struct *
__pick_next_task(struct rq *rq, struct task_struct *prev, struct rq_flags *rf)
+ __must_hold(__rq_lockp(rq))
{
const struct sched_class *class;
struct task_struct *p;
@@ -6072,6 +6097,7 @@ static void queue_core_balance(struct rq *rq);

static struct task_struct *
pick_next_task(struct rq *rq, struct task_struct *prev, struct rq_flags *rf)
+ __must_hold(__rq_lockp(rq))
{
struct task_struct *next, *p, *max = NULL;
const struct cpumask *smt_mask;
@@ -6371,6 +6397,7 @@ static bool steal_cookie_task(int cpu, struct sched_domain *sd)
}

static void sched_core_balance(struct rq *rq)
+ __must_hold(__rq_lockp(rq))
{
struct sched_domain *sd;
int cpu = cpu_of(rq);
@@ -6516,6 +6543,7 @@ static inline void sched_core_cpu_dying(unsigned int cpu) {}

static struct task_struct *
pick_next_task(struct rq *rq, struct task_struct *prev, struct rq_flags *rf)
+ __must_hold(__rq_lockp(rq))
{
return __pick_next_task(rq, prev, rf);
}
@@ -8173,6 +8201,12 @@ static int __balance_push_cpu_stop(void *arg)
struct rq_flags rf;
int cpu;

+ /*
+ * We may change the underlying rq, but the locks held will
+ * appropriately be "transferred" when switching.
+ */
+ capability_unsafe_alias(rq);
+
raw_spin_lock_irq(&p->pi_lock);
rq_lock(rq, &rf);

@@ -8200,6 +8234,7 @@ static DEFINE_PER_CPU(struct cpu_stop_work, push_work);
* effective when the hotplug motion is down.
*/
static void balance_push(struct rq *rq)
+ __must_hold(__rq_lockp(rq))
{
struct task_struct *push_task = rq->curr;

diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index b173a059315c..4c4663d99996 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -2876,6 +2876,7 @@ static int preferred_group_nid(struct task_struct *p, int nid)
}

static void task_numa_placement(struct task_struct *p)
+ __capability_unsafe(/* conditional locking */)
{
int seq, nid, max_nid = NUMA_NO_NODE;
unsigned long max_faults = 0;
@@ -4806,7 +4807,8 @@ static inline unsigned long cfs_rq_load_avg(struct cfs_rq *cfs_rq)
return cfs_rq->avg.load_avg;
}

-static int sched_balance_newidle(struct rq *this_rq, struct rq_flags *rf);
+static int sched_balance_newidle(struct rq *this_rq, struct rq_flags *rf)
+ __must_hold(__rq_lockp(this_rq));

static inline unsigned long task_util(struct task_struct *p)
{
@@ -6155,6 +6157,7 @@ static bool distribute_cfs_runtime(struct cfs_bandwidth *cfs_b)
* used to track this state.
*/
static int do_sched_cfs_period_timer(struct cfs_bandwidth *cfs_b, int overrun, unsigned long flags)
+ __must_hold(&cfs_b->lock)
{
int throttled;

@@ -8622,6 +8625,7 @@ static void set_cpus_allowed_fair(struct task_struct *p, struct affinity_context

static int
balance_fair(struct rq *rq, struct task_struct *prev, struct rq_flags *rf)
+ __must_hold(__rq_lockp(rq))
{
if (sched_fair_runnable(rq))
return 1;
@@ -8769,6 +8773,7 @@ static void set_next_task_fair(struct rq *rq, struct task_struct *p, bool first)

struct task_struct *
pick_next_task_fair(struct rq *rq, struct task_struct *prev, struct rq_flags *rf)
+ __must_hold(__rq_lockp(rq))
{
struct sched_entity *se;
struct task_struct *p;
@@ -8855,6 +8860,7 @@ pick_next_task_fair(struct rq *rq, struct task_struct *prev, struct rq_flags *rf
}

static struct task_struct *__pick_next_task_fair(struct rq *rq, struct task_struct *prev)
+ __must_hold(__rq_lockp(rq))
{
return pick_next_task_fair(rq, prev, NULL);
}
@@ -12691,6 +12697,7 @@ static inline void nohz_newidle_balance(struct rq *this_rq) { }
* > 0 - success, new (fair) tasks present
*/
static int sched_balance_newidle(struct rq *this_rq, struct rq_flags *rf)
+ __must_hold(__rq_lockp(this_rq))
{
unsigned long next_balance = jiffies + HZ;
int this_cpu = this_rq->cpu;
diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
index be9745d104f7..8fdbf2e3fb1e 100644
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -1322,8 +1322,13 @@ static inline bool is_migration_disabled(struct task_struct *p)

DECLARE_PER_CPU_SHARED_ALIGNED(struct rq, runqueues);

+static __always_inline struct rq *__this_rq(void)
+{
+ return this_cpu_ptr(&runqueues);
+}
+
#define cpu_rq(cpu) (&per_cpu(runqueues, (cpu)))
-#define this_rq() this_cpu_ptr(&runqueues)
+#define this_rq() __this_rq()
#define task_rq(p) cpu_rq(task_cpu(p))
#define cpu_curr(cpu) (cpu_rq(cpu)->curr)
#define raw_rq() raw_cpu_ptr(&runqueues)
@@ -1368,6 +1373,7 @@ static inline raw_spinlock_t *rq_lockp(struct rq *rq)
}

static inline raw_spinlock_t *__rq_lockp(struct rq *rq)
+ __returns_cap(rq_lockp(rq)) /* alias them */
{
if (rq->core_enabled)
return &rq->core->__lock;
@@ -1464,6 +1470,7 @@ static inline raw_spinlock_t *rq_lockp(struct rq *rq)
}

static inline raw_spinlock_t *__rq_lockp(struct rq *rq)
+ __returns_cap(rq_lockp(rq)) /* alias them */
{
return &rq->__lock;
}
@@ -1506,32 +1513,42 @@ static inline bool rt_group_sched_enabled(void)
#endif /* !CONFIG_RT_GROUP_SCHED */

static inline void lockdep_assert_rq_held(struct rq *rq)
+ __assumes_cap(__rq_lockp(rq))
{
lockdep_assert_held(__rq_lockp(rq));
}

-extern void raw_spin_rq_lock_nested(struct rq *rq, int subclass);
-extern bool raw_spin_rq_trylock(struct rq *rq);
-extern void raw_spin_rq_unlock(struct rq *rq);
+extern void raw_spin_rq_lock_nested(struct rq *rq, int subclass)
+ __acquires(__rq_lockp(rq));
+
+extern bool raw_spin_rq_trylock(struct rq *rq)
+ __cond_acquires(true, __rq_lockp(rq));
+
+extern void raw_spin_rq_unlock(struct rq *rq)
+ __releases(__rq_lockp(rq));

static inline void raw_spin_rq_lock(struct rq *rq)
+ __acquires(__rq_lockp(rq))
{
raw_spin_rq_lock_nested(rq, 0);
}

static inline void raw_spin_rq_lock_irq(struct rq *rq)
+ __acquires(__rq_lockp(rq))
{
local_irq_disable();
raw_spin_rq_lock(rq);
}

static inline void raw_spin_rq_unlock_irq(struct rq *rq)
+ __releases(__rq_lockp(rq))
{
raw_spin_rq_unlock(rq);
local_irq_enable();
}

static inline unsigned long _raw_spin_rq_lock_irqsave(struct rq *rq)
+ __acquires(__rq_lockp(rq))
{
unsigned long flags;

@@ -1542,6 +1559,7 @@ static inline unsigned long _raw_spin_rq_lock_irqsave(struct rq *rq)
}

static inline void raw_spin_rq_unlock_irqrestore(struct rq *rq, unsigned long flags)
+ __releases(__rq_lockp(rq))
{
raw_spin_rq_unlock(rq);
local_irq_restore(flags);
@@ -1790,17 +1808,15 @@ static inline void rq_repin_lock(struct rq *rq, struct rq_flags *rf)
rq->clock_update_flags |= rf->clock_update_flags;
}

-extern
-struct rq *__task_rq_lock(struct task_struct *p, struct rq_flags *rf)
- __acquires(rq->lock);
+#define __task_rq_lock(...) __acquire_ret(___task_rq_lock(__VA_ARGS__), __rq_lockp(__ret))
+extern struct rq *___task_rq_lock(struct task_struct *p, struct rq_flags *rf) __acquires_ret;

-extern
-struct rq *task_rq_lock(struct task_struct *p, struct rq_flags *rf)
- __acquires(p->pi_lock)
- __acquires(rq->lock);
+#define task_rq_lock(...) __acquire_ret(_task_rq_lock(__VA_ARGS__), __rq_lockp(__ret))
+extern struct rq *_task_rq_lock(struct task_struct *p, struct rq_flags *rf)
+ __acquires(&p->pi_lock) __acquires_ret;

static inline void __task_rq_unlock(struct rq *rq, struct rq_flags *rf)
- __releases(rq->lock)
+ __releases(__rq_lockp(rq))
{
rq_unpin_lock(rq, rf);
raw_spin_rq_unlock(rq);
@@ -1808,8 +1824,7 @@ static inline void __task_rq_unlock(struct rq *rq, struct rq_flags *rf)

static inline void
task_rq_unlock(struct rq *rq, struct task_struct *p, struct rq_flags *rf)
- __releases(rq->lock)
- __releases(p->pi_lock)
+ __releases(__rq_lockp(rq), &p->pi_lock)
{
rq_unpin_lock(rq, rf);
raw_spin_rq_unlock(rq);
@@ -1820,44 +1835,45 @@ DEFINE_LOCK_GUARD_1(task_rq_lock, struct task_struct,
_T->rq = task_rq_lock(_T->lock, &_T->rf),
task_rq_unlock(_T->rq, _T->lock, &_T->rf),
struct rq *rq; struct rq_flags rf)
+DECLARE_LOCK_GUARD_1_ATTRS(task_rq_lock, __assumes_cap(_T->pi_lock), /* */)

static inline void rq_lock_irqsave(struct rq *rq, struct rq_flags *rf)
- __acquires(rq->lock)
+ __acquires(__rq_lockp(rq))
{
raw_spin_rq_lock_irqsave(rq, rf->flags);
rq_pin_lock(rq, rf);
}

static inline void rq_lock_irq(struct rq *rq, struct rq_flags *rf)
- __acquires(rq->lock)
+ __acquires(__rq_lockp(rq))
{
raw_spin_rq_lock_irq(rq);
rq_pin_lock(rq, rf);
}

static inline void rq_lock(struct rq *rq, struct rq_flags *rf)
- __acquires(rq->lock)
+ __acquires(__rq_lockp(rq))
{
raw_spin_rq_lock(rq);
rq_pin_lock(rq, rf);
}

static inline void rq_unlock_irqrestore(struct rq *rq, struct rq_flags *rf)
- __releases(rq->lock)
+ __releases(__rq_lockp(rq))
{
rq_unpin_lock(rq, rf);
raw_spin_rq_unlock_irqrestore(rq, rf->flags);
}

static inline void rq_unlock_irq(struct rq *rq, struct rq_flags *rf)
- __releases(rq->lock)
+ __releases(__rq_lockp(rq))
{
rq_unpin_lock(rq, rf);
raw_spin_rq_unlock_irq(rq);
}

static inline void rq_unlock(struct rq *rq, struct rq_flags *rf)
- __releases(rq->lock)
+ __releases(__rq_lockp(rq))
{
rq_unpin_lock(rq, rf);
raw_spin_rq_unlock(rq);
@@ -1868,18 +1884,24 @@ DEFINE_LOCK_GUARD_1(rq_lock, struct rq,
rq_unlock(_T->lock, &_T->rf),
struct rq_flags rf)

+DECLARE_LOCK_GUARD_1_ATTRS(rq_lock, __assumes_cap(__rq_lockp(_T)), /* */);
+
DEFINE_LOCK_GUARD_1(rq_lock_irq, struct rq,
rq_lock_irq(_T->lock, &_T->rf),
rq_unlock_irq(_T->lock, &_T->rf),
struct rq_flags rf)

+DECLARE_LOCK_GUARD_1_ATTRS(rq_lock_irq, __assumes_cap(__rq_lockp(_T)), /* */);
+
DEFINE_LOCK_GUARD_1(rq_lock_irqsave, struct rq,
rq_lock_irqsave(_T->lock, &_T->rf),
rq_unlock_irqrestore(_T->lock, &_T->rf),
struct rq_flags rf)

-static inline struct rq *this_rq_lock_irq(struct rq_flags *rf)
- __acquires(rq->lock)
+DECLARE_LOCK_GUARD_1_ATTRS(rq_lock_irqsave, __assumes_cap(__rq_lockp(_T)), /* */);
+
+#define this_rq_lock_irq(...) __acquire_ret(_this_rq_lock_irq(__VA_ARGS__), __rq_lockp(__ret))
+static inline struct rq *_this_rq_lock_irq(struct rq_flags *rf) __acquires_ret
{
struct rq *rq;

@@ -2877,8 +2899,13 @@ static inline void double_rq_clock_clear_update(struct rq *rq1, struct rq *rq2)
#define DEFINE_LOCK_GUARD_2(name, type, _lock, _unlock, ...) \
__DEFINE_UNLOCK_GUARD(name, type, _unlock, type *lock2; __VA_ARGS__) \
static inline class_##name##_t class_##name##_constructor(type *lock, type *lock2) \
+ __no_capability_analysis \
{ class_##name##_t _t = { .lock = lock, .lock2 = lock2 }, *_T = &_t; \
_lock; return _t; }
+#define DECLARE_LOCK_GUARD_2_ATTRS(_name, _lock, _unlock) \
+static inline class_##_name##_t class_##_name##_constructor(lock_##_name##_t *_T1, \
+ lock_##_name##_t *_T2) _lock; \
+static inline void class_##_name##_destructor(class_##_name##_t *_T) _unlock

static inline bool rq_order_less(struct rq *rq1, struct rq *rq2)
{
@@ -2906,7 +2933,8 @@ static inline bool rq_order_less(struct rq *rq1, struct rq *rq2)
return rq1->cpu < rq2->cpu;
}

-extern void double_rq_lock(struct rq *rq1, struct rq *rq2);
+extern void double_rq_lock(struct rq *rq1, struct rq *rq2)
+ __acquires(__rq_lockp(rq1), __rq_lockp(rq2));

#ifdef CONFIG_PREEMPTION

@@ -2919,9 +2947,8 @@ extern void double_rq_lock(struct rq *rq1, struct rq *rq2);
* also adds more overhead and therefore may reduce throughput.
*/
static inline int _double_lock_balance(struct rq *this_rq, struct rq *busiest)
- __releases(this_rq->lock)
- __acquires(busiest->lock)
- __acquires(this_rq->lock)
+ __must_hold(__rq_lockp(this_rq))
+ __acquires(__rq_lockp(busiest))
{
raw_spin_rq_unlock(this_rq);
double_rq_lock(this_rq, busiest);
@@ -2938,12 +2965,16 @@ static inline int _double_lock_balance(struct rq *this_rq, struct rq *busiest)
* regardless of entry order into the function.
*/
static inline int _double_lock_balance(struct rq *this_rq, struct rq *busiest)
- __releases(this_rq->lock)
- __acquires(busiest->lock)
- __acquires(this_rq->lock)
+ __must_hold(__rq_lockp(this_rq))
+ __acquires(__rq_lockp(busiest))
{
- if (__rq_lockp(this_rq) == __rq_lockp(busiest) ||
- likely(raw_spin_rq_trylock(busiest))) {
+ if (__rq_lockp(this_rq) == __rq_lockp(busiest)) {
+ __acquire(__rq_lockp(busiest)); /* already held */
+ double_rq_clock_clear_update(this_rq, busiest);
+ return 0;
+ }
+
+ if (likely(raw_spin_rq_trylock(busiest))) {
double_rq_clock_clear_update(this_rq, busiest);
return 0;
}
@@ -2966,6 +2997,8 @@ static inline int _double_lock_balance(struct rq *this_rq, struct rq *busiest)
* double_lock_balance - lock the busiest runqueue, this_rq is locked already.
*/
static inline int double_lock_balance(struct rq *this_rq, struct rq *busiest)
+ __must_hold(__rq_lockp(this_rq))
+ __acquires(__rq_lockp(busiest))
{
lockdep_assert_irqs_disabled();

@@ -2973,14 +3006,17 @@ static inline int double_lock_balance(struct rq *this_rq, struct rq *busiest)
}

static inline void double_unlock_balance(struct rq *this_rq, struct rq *busiest)
- __releases(busiest->lock)
+ __releases(__rq_lockp(busiest))
{
if (__rq_lockp(this_rq) != __rq_lockp(busiest))
raw_spin_rq_unlock(busiest);
+ else
+ __release(__rq_lockp(busiest)); /* fake release */
lock_set_subclass(&__rq_lockp(this_rq)->dep_map, 0, _RET_IP_);
}

static inline void double_lock(spinlock_t *l1, spinlock_t *l2)
+ __acquires(l1, l2)
{
if (l1 > l2)
swap(l1, l2);
@@ -2990,6 +3026,7 @@ static inline void double_lock(spinlock_t *l1, spinlock_t *l2)
}

static inline void double_lock_irq(spinlock_t *l1, spinlock_t *l2)
+ __acquires(l1, l2)
{
if (l1 > l2)
swap(l1, l2);
@@ -2999,6 +3036,7 @@ static inline void double_lock_irq(spinlock_t *l1, spinlock_t *l2)
}

static inline void double_raw_lock(raw_spinlock_t *l1, raw_spinlock_t *l2)
+ __acquires(l1, l2)
{
if (l1 > l2)
swap(l1, l2);
@@ -3008,6 +3046,7 @@ static inline void double_raw_lock(raw_spinlock_t *l1, raw_spinlock_t *l2)
}

static inline void double_raw_unlock(raw_spinlock_t *l1, raw_spinlock_t *l2)
+ __releases(l1, l2)
{
raw_spin_unlock(l1);
raw_spin_unlock(l2);
@@ -3017,6 +3056,8 @@ DEFINE_LOCK_GUARD_2(double_raw_spinlock, raw_spinlock_t,
double_raw_lock(_T->lock, _T->lock2),
double_raw_unlock(_T->lock, _T->lock2))

+DECLARE_LOCK_GUARD_2_ATTRS(double_raw_spinlock, __assumes_cap(_T1) __assumes_cap(_T2), /* */);
+
/*
* double_rq_unlock - safely unlock two runqueues
*
@@ -3024,13 +3065,12 @@ DEFINE_LOCK_GUARD_2(double_raw_spinlock, raw_spinlock_t,
* you need to do so manually after calling.
*/
static inline void double_rq_unlock(struct rq *rq1, struct rq *rq2)
- __releases(rq1->lock)
- __releases(rq2->lock)
+ __releases(__rq_lockp(rq1), __rq_lockp(rq2))
{
if (__rq_lockp(rq1) != __rq_lockp(rq2))
raw_spin_rq_unlock(rq2);
else
- __release(rq2->lock);
+ __release(__rq_lockp(rq2)); /* fake release */
raw_spin_rq_unlock(rq1);
}

diff --git a/scripts/capability-analysis-suppression.txt b/scripts/capability-analysis-suppression.txt
index 95fb0b65a8e6..7ecd888ac522 100644
--- a/scripts/capability-analysis-suppression.txt
+++ b/scripts/capability-analysis-suppression.txt
@@ -26,6 +26,7 @@ src:*include/linux/refcount.h=emit
src:*include/linux/rhashtable.h=emit
src:*include/linux/rwlock*.h=emit
src:*include/linux/rwsem.h=emit
+src:*include/linux/sched*=emit
src:*include/linux/seqlock*.h=emit
src:*include/linux/spinlock*.h=emit
src:*include/linux/srcu*.h=emit
--
2.51.0.384.g4c02a37b29-goog

Marco Elver

unread,
Sep 18, 2025, 10:12:11 AM (6 days ago) Sep 18
to el...@google.com, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
As discussed in [1], removing __cond_lock() will improve the readability
of trylock code. Now that Sparse context tracking support has been
removed, we can also remove __cond_lock().

Change existing APIs to either drop __cond_lock() completely, or make
use of the __cond_acquires() function attribute instead.

In particular, spinlock and rwlock implementations required switching
over to inline helpers rather than statement-expressions for their
trylock_* variants.

Link: https://lore.kernel.org/all/2025020708...@noisy.programming.kicks-ass.net/ [1]
Suggested-by: Peter Zijlstra <pet...@infradead.org>
Signed-off-by: Marco Elver <el...@google.com>
---
v2:
* New patch.
---
.../dev-tools/capability-analysis.rst | 2 -
Documentation/mm/process_addrs.rst | 6 +-
.../net/wireless/intel/iwlwifi/iwl-trans.c | 4 +-
.../net/wireless/intel/iwlwifi/iwl-trans.h | 6 +-
.../intel/iwlwifi/pcie/gen1_2/internal.h | 5 +-
.../intel/iwlwifi/pcie/gen1_2/trans.c | 4 +-
include/linux/compiler-capability-analysis.h | 33 ----------
include/linux/mm.h | 33 ++--------
include/linux/rwlock.h | 11 +---
include/linux/rwlock_api_smp.h | 14 ++++-
include/linux/rwlock_rt.h | 21 ++++---
include/linux/sched/signal.h | 14 +----
include/linux/spinlock.h | 45 +++++---------
include/linux/spinlock_api_smp.h | 20 ++++++
include/linux/spinlock_api_up.h | 61 ++++++++++++++++---
include/linux/spinlock_rt.h | 26 ++++----
kernel/signal.c | 4 +-
kernel/time/posix-timers.c | 13 +---
lib/dec_and_lock.c | 8 +--
mm/memory.c | 4 +-
mm/pgtable-generic.c | 19 +++---
tools/include/linux/compiler_types.h | 2 -
22 files changed, 162 insertions(+), 193 deletions(-)

diff --git a/Documentation/dev-tools/capability-analysis.rst b/Documentation/dev-tools/capability-analysis.rst
index 2b89d346723b..3456132261c6 100644
--- a/Documentation/dev-tools/capability-analysis.rst
+++ b/Documentation/dev-tools/capability-analysis.rst
@@ -115,10 +115,8 @@ Keywords
__releases_shared
__acquire
__release
- __cond_lock
__acquire_shared
__release_shared
- __cond_lock_shared
capability_unsafe
__capability_unsafe
disable_capability_analysis enable_capability_analysis
diff --git a/Documentation/mm/process_addrs.rst b/Documentation/mm/process_addrs.rst
index be49e2a269e4..25d551a01f16 100644
--- a/Documentation/mm/process_addrs.rst
+++ b/Documentation/mm/process_addrs.rst
@@ -582,7 +582,7 @@ To access PTE-level page tables, a helper like :c:func:`!pte_offset_map_lock` or
:c:func:`!pte_offset_map` can be used depending on stability requirements.
These map the page table into kernel memory if required, take the RCU lock, and
depending on variant, may also look up or acquire the PTE lock.
-See the comment on :c:func:`!__pte_offset_map_lock`.
+See the comment on :c:func:`!pte_offset_map_lock`.

Atomicity
^^^^^^^^^
@@ -666,7 +666,7 @@ must be released via :c:func:`!pte_unmap_unlock`.
.. note:: There are some variants on this, such as
:c:func:`!pte_offset_map_rw_nolock` when we know we hold the PTE stable but
for brevity we do not explore this. See the comment for
- :c:func:`!__pte_offset_map_lock` for more details.
+ :c:func:`!pte_offset_map_lock` for more details.

When modifying data in ranges we typically only wish to allocate higher page
tables as necessary, using these locks to avoid races or overwriting anything,
@@ -685,7 +685,7 @@ At the leaf page table, that is the PTE, we can't entirely rely on this pattern
as we have separate PMD and PTE locks and a THP collapse for instance might have
eliminated the PMD entry as well as the PTE from under us.

-This is why :c:func:`!__pte_offset_map_lock` locklessly retrieves the PMD entry
+This is why :c:func:`!pte_offset_map_lock` locklessly retrieves the PMD entry
for the PTE, carefully checking it is as expected, before acquiring the
PTE-specific lock, and then *again* checking that the PMD entry is as expected.

diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-trans.c b/drivers/net/wireless/intel/iwlwifi/iwl-trans.c
index 3694b41d6621..5c32f0d95da4 100644
--- a/drivers/net/wireless/intel/iwlwifi/iwl-trans.c
+++ b/drivers/net/wireless/intel/iwlwifi/iwl-trans.c
@@ -566,11 +566,11 @@ int iwl_trans_read_config32(struct iwl_trans *trans, u32 ofs,
return iwl_trans_pcie_read_config32(trans, ofs, val);
}

-bool _iwl_trans_grab_nic_access(struct iwl_trans *trans)
+bool iwl_trans_grab_nic_access(struct iwl_trans *trans)
{
return iwl_trans_pcie_grab_nic_access(trans);
}
-IWL_EXPORT_SYMBOL(_iwl_trans_grab_nic_access);
+IWL_EXPORT_SYMBOL(iwl_trans_grab_nic_access);

void __releases(nic_access)
iwl_trans_release_nic_access(struct iwl_trans *trans)
diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-trans.h b/drivers/net/wireless/intel/iwlwifi/iwl-trans.h
index d0e658801c2e..d6b11893e6c7 100644
--- a/drivers/net/wireless/intel/iwlwifi/iwl-trans.h
+++ b/drivers/net/wireless/intel/iwlwifi/iwl-trans.h
@@ -1101,11 +1101,7 @@ int iwl_trans_sw_reset(struct iwl_trans *trans);
void iwl_trans_set_bits_mask(struct iwl_trans *trans, u32 reg,
u32 mask, u32 value);

-bool _iwl_trans_grab_nic_access(struct iwl_trans *trans);
-
-#define iwl_trans_grab_nic_access(trans) \
- __cond_lock(nic_access, \
- likely(_iwl_trans_grab_nic_access(trans)))
+bool iwl_trans_grab_nic_access(struct iwl_trans *trans);

void __releases(nic_access)
iwl_trans_release_nic_access(struct iwl_trans *trans);
diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/internal.h b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/internal.h
index f48aeebb151c..ccc891e99d8f 100644
--- a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/internal.h
+++ b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/internal.h
@@ -542,10 +542,7 @@ void iwl_trans_pcie_free(struct iwl_trans *trans);
void iwl_trans_pcie_free_pnvm_dram_regions(struct iwl_dram_regions *dram_regions,
struct device *dev);

-bool __iwl_trans_pcie_grab_nic_access(struct iwl_trans *trans, bool silent);
-#define _iwl_trans_pcie_grab_nic_access(trans, silent) \
- __cond_lock(nic_access_nobh, \
- likely(__iwl_trans_pcie_grab_nic_access(trans, silent)))
+bool _iwl_trans_pcie_grab_nic_access(struct iwl_trans *trans, bool silent);

void iwl_trans_pcie_check_product_reset_status(struct pci_dev *pdev);
void iwl_trans_pcie_check_product_reset_mode(struct pci_dev *pdev);
diff --git a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c
index 327366bf87de..c9ab1d124fc6 100644
--- a/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c
+++ b/drivers/net/wireless/intel/iwlwifi/pcie/gen1_2/trans.c
@@ -2304,7 +2304,7 @@ EXPORT_SYMBOL(iwl_trans_pcie_reset);
* This version doesn't disable BHs but rather assumes they're
* already disabled.
*/
-bool __iwl_trans_pcie_grab_nic_access(struct iwl_trans *trans, bool silent)
+bool _iwl_trans_pcie_grab_nic_access(struct iwl_trans *trans, bool silent)
{
int ret;
struct iwl_trans_pcie *trans_pcie = IWL_TRANS_GET_PCIE_TRANS(trans);
@@ -2392,7 +2392,7 @@ bool iwl_trans_pcie_grab_nic_access(struct iwl_trans *trans)
bool ret;

local_bh_disable();
- ret = __iwl_trans_pcie_grab_nic_access(trans, false);
+ ret = _iwl_trans_pcie_grab_nic_access(trans, false);
if (ret) {
/* keep BHs disabled until iwl_trans_pcie_release_nic_access */
return ret;
diff --git a/include/linux/compiler-capability-analysis.h b/include/linux/compiler-capability-analysis.h
index 6046fca44f17..f8a1da67589c 100644
--- a/include/linux/compiler-capability-analysis.h
+++ b/include/linux/compiler-capability-analysis.h
@@ -326,25 +326,6 @@ static inline void _capability_unsafe_alias(void **p) { }
*/
#define __release(x) __release_cap(x)

-/**
- * __cond_lock() - function that conditionally acquires a capability
- * exclusively
- * @x: capability instance pinter
- * @c: boolean expression
- *
- * Return: result of @c
- *
- * No-op function that conditionally acquires capability instance @x
- * exclusively, if the boolean expression @c is true. The result of @c is the
- * return value, to be able to create a capability-enabled interface; for
- * example:
- *
- * .. code-block:: c
- *
- * #define spin_trylock(l) __cond_lock(&lock, _spin_trylock(&lock))
- */
-#define __cond_lock(x, c) __try_acquire_cap(x, c)
-
/**
* __must_hold_shared() - function attribute, caller must hold shared capability
*
@@ -401,20 +382,6 @@ static inline void _capability_unsafe_alias(void **p) { }
*/
#define __release_shared(x) __release_shared_cap(x)

-/**
- * __cond_lock_shared() - function that conditionally acquires a capability
- * shared
- * @x: capability instance pinter
- * @c: boolean expression
- *
- * Return: result of @c
- *
- * No-op function that conditionally acquires capability instance @x with shared
- * access, if the boolean expression @c is true. The result of @c is the return
- * value, to be able to create a capability-enabled interface.
- */
-#define __cond_lock_shared(x, c) __try_acquire_shared_cap(x, c)
-
/**
* __acquire_ret() - helper to acquire capability of return value
* @call: call expression
diff --git a/include/linux/mm.h b/include/linux/mm.h
index 1ae97a0b8ec7..0ca9005378c5 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -2713,15 +2713,8 @@ static inline pud_t pud_mkspecial(pud_t pud)
}
#endif /* CONFIG_ARCH_SUPPORTS_PUD_PFNMAP */

-extern pte_t *__get_locked_pte(struct mm_struct *mm, unsigned long addr,
- spinlock_t **ptl);
-static inline pte_t *get_locked_pte(struct mm_struct *mm, unsigned long addr,
- spinlock_t **ptl)
-{
- pte_t *ptep;
- __cond_lock(*ptl, ptep = __get_locked_pte(mm, addr, ptl));
- return ptep;
-}
+extern pte_t *get_locked_pte(struct mm_struct *mm, unsigned long addr,
+ spinlock_t **ptl);

#ifdef __PAGETABLE_P4D_FOLDED
static inline int __p4d_alloc(struct mm_struct *mm, pgd_t *pgd,
@@ -3005,31 +2998,15 @@ static inline bool pagetable_pte_ctor(struct mm_struct *mm,
return true;
}

-pte_t *___pte_offset_map(pmd_t *pmd, unsigned long addr, pmd_t *pmdvalp);
-static inline pte_t *__pte_offset_map(pmd_t *pmd, unsigned long addr,
- pmd_t *pmdvalp)
-{
- pte_t *pte;
+pte_t *__pte_offset_map(pmd_t *pmd, unsigned long addr, pmd_t *pmdvalp);

- __cond_lock(RCU, pte = ___pte_offset_map(pmd, addr, pmdvalp));
- return pte;
-}
static inline pte_t *pte_offset_map(pmd_t *pmd, unsigned long addr)
{
return __pte_offset_map(pmd, addr, NULL);
}

-pte_t *__pte_offset_map_lock(struct mm_struct *mm, pmd_t *pmd,
- unsigned long addr, spinlock_t **ptlp);
-static inline pte_t *pte_offset_map_lock(struct mm_struct *mm, pmd_t *pmd,
- unsigned long addr, spinlock_t **ptlp)
-{
- pte_t *pte;
-
- __cond_lock(RCU, __cond_lock(*ptlp,
- pte = __pte_offset_map_lock(mm, pmd, addr, ptlp)));
- return pte;
-}
+pte_t *pte_offset_map_lock(struct mm_struct *mm, pmd_t *pmd,
+ unsigned long addr, spinlock_t **ptlp);

pte_t *pte_offset_map_ro_nolock(struct mm_struct *mm, pmd_t *pmd,
unsigned long addr, spinlock_t **ptlp);
diff --git a/include/linux/rwlock.h b/include/linux/rwlock.h
index 78e4d02ee2c6..827ea95c9e06 100644
--- a/include/linux/rwlock.h
+++ b/include/linux/rwlock.h
@@ -50,8 +50,8 @@ do { \
* regardless of whether CONFIG_SMP or CONFIG_PREEMPT are set. The various
* methods are defined as nops in the case they are not required.
*/
-#define read_trylock(lock) __cond_lock_shared(lock, _raw_read_trylock(lock))
-#define write_trylock(lock) __cond_lock(lock, _raw_write_trylock(lock))
+#define read_trylock(lock) _raw_read_trylock(lock)
+#define write_trylock(lock) _raw_write_trylock(lock)

#define write_lock(lock) _raw_write_lock(lock)
#define read_lock(lock) _raw_read_lock(lock)
@@ -113,12 +113,7 @@ do { \
} while (0)
#define write_unlock_bh(lock) _raw_write_unlock_bh(lock)

-#define write_trylock_irqsave(lock, flags) \
- __cond_lock(lock, ({ \
- local_irq_save(flags); \
- _raw_write_trylock(lock) ? \
- 1 : ({ local_irq_restore(flags); 0; }); \
- }))
+#define write_trylock_irqsave(lock, flags) _raw_write_trylock_irqsave(lock, &(flags))

#ifdef arch_rwlock_is_contended
#define rwlock_is_contended(lock) \
diff --git a/include/linux/rwlock_api_smp.h b/include/linux/rwlock_api_smp.h
index 3e975105a606..b289c3089ab7 100644
--- a/include/linux/rwlock_api_smp.h
+++ b/include/linux/rwlock_api_smp.h
@@ -26,8 +26,8 @@ unsigned long __lockfunc _raw_read_lock_irqsave(rwlock_t *lock)
__acquires(lock);
unsigned long __lockfunc _raw_write_lock_irqsave(rwlock_t *lock)
__acquires(lock);
-int __lockfunc _raw_read_trylock(rwlock_t *lock);
-int __lockfunc _raw_write_trylock(rwlock_t *lock);
+int __lockfunc _raw_read_trylock(rwlock_t *lock) __cond_acquires_shared(true, lock);
+int __lockfunc _raw_write_trylock(rwlock_t *lock) __cond_acquires(true, lock);
void __lockfunc _raw_read_unlock(rwlock_t *lock) __releases_shared(lock);
void __lockfunc _raw_write_unlock(rwlock_t *lock) __releases(lock);
void __lockfunc _raw_read_unlock_bh(rwlock_t *lock) __releases_shared(lock);
@@ -41,6 +41,16 @@ void __lockfunc
_raw_write_unlock_irqrestore(rwlock_t *lock, unsigned long flags)
__releases(lock);

+static inline bool _raw_write_trylock_irqsave(rwlock_t *lock, unsigned long *flags)
+ __cond_acquires(true, lock)
+{
+ local_irq_save(*flags);
+ if (_raw_write_trylock(lock))
+ return true;
+ local_irq_restore(*flags);
+ return false;
+}
+
#ifdef CONFIG_INLINE_READ_LOCK
#define _raw_read_lock(lock) __raw_read_lock(lock)
#endif
diff --git a/include/linux/rwlock_rt.h b/include/linux/rwlock_rt.h
index 52ef2dc63a96..6015e296914f 100644
--- a/include/linux/rwlock_rt.h
+++ b/include/linux/rwlock_rt.h
@@ -26,11 +26,11 @@ do { \
} while (0)

extern void rt_read_lock(rwlock_t *rwlock) __acquires_shared(rwlock);
-extern int rt_read_trylock(rwlock_t *rwlock);
+extern int rt_read_trylock(rwlock_t *rwlock) __cond_acquires_shared(true, rwlock);
extern void rt_read_unlock(rwlock_t *rwlock) __releases_shared(rwlock);
extern void rt_write_lock(rwlock_t *rwlock) __acquires(rwlock);
extern void rt_write_lock_nested(rwlock_t *rwlock, int subclass) __acquires(rwlock);
-extern int rt_write_trylock(rwlock_t *rwlock);
+extern int rt_write_trylock(rwlock_t *rwlock) __cond_acquires(true, rwlock);
extern void rt_write_unlock(rwlock_t *rwlock) __releases(rwlock);

static __always_inline void read_lock(rwlock_t *rwlock)
@@ -59,7 +59,7 @@ static __always_inline void read_lock_irq(rwlock_t *rwlock)
flags = 0; \
} while (0)

-#define read_trylock(lock) __cond_lock_shared(lock, rt_read_trylock(lock))
+#define read_trylock(lock) rt_read_trylock(lock)

static __always_inline void read_unlock(rwlock_t *rwlock)
__releases_shared(rwlock)
@@ -123,14 +123,15 @@ static __always_inline void write_lock_irq(rwlock_t *rwlock)
flags = 0; \
} while (0)

-#define write_trylock(lock) __cond_lock(lock, rt_write_trylock(lock))
+#define write_trylock(lock) rt_write_trylock(lock)

-#define write_trylock_irqsave(lock, flags) \
- __cond_lock(lock, ({ \
- typecheck(unsigned long, flags); \
- flags = 0; \
- rt_write_trylock(lock); \
- }))
+static __always_inline bool _write_trylock_irqsave(rwlock_t *rwlock, unsigned long *flags)
+ __cond_acquires(true, rwlock)
+{
+ *flags = 0;
+ return rt_write_trylock(rwlock);
+}
+#define write_trylock_irqsave(lock, flags) _write_trylock_irqsave(lock, &(flags))

static __always_inline void write_unlock(rwlock_t *rwlock)
__releases(rwlock)
diff --git a/include/linux/sched/signal.h b/include/linux/sched/signal.h
index 1ef1edbaaf79..bc7f83b012fb 100644
--- a/include/linux/sched/signal.h
+++ b/include/linux/sched/signal.h
@@ -733,18 +733,8 @@ static inline int thread_group_empty(struct task_struct *p)
#define delay_group_leader(p) \
(thread_group_leader(p) && !thread_group_empty(p))

-extern struct sighand_struct *__lock_task_sighand(struct task_struct *task,
- unsigned long *flags);
-
-static inline struct sighand_struct *lock_task_sighand(struct task_struct *task,
- unsigned long *flags)
-{
- struct sighand_struct *ret;
-
- ret = __lock_task_sighand(task, flags);
- (void)__cond_lock(&task->sighand->siglock, ret);
- return ret;
-}
+extern struct sighand_struct *lock_task_sighand(struct task_struct *task,
+ unsigned long *flags);

static inline void unlock_task_sighand(struct task_struct *task,
unsigned long *flags)
diff --git a/include/linux/spinlock.h b/include/linux/spinlock.h
index 22295a126c3a..d0cef13bfb33 100644
--- a/include/linux/spinlock.h
+++ b/include/linux/spinlock.h
@@ -213,7 +213,7 @@ static inline void do_raw_spin_unlock(raw_spinlock_t *lock) __releases(lock)
* various methods are defined as nops in the case they are not
* required.
*/
-#define raw_spin_trylock(lock) __cond_lock(lock, _raw_spin_trylock(lock))
+#define raw_spin_trylock(lock) _raw_spin_trylock(lock)

#define raw_spin_lock(lock) _raw_spin_lock(lock)

@@ -284,22 +284,11 @@ static inline void do_raw_spin_unlock(raw_spinlock_t *lock) __releases(lock)
} while (0)
#define raw_spin_unlock_bh(lock) _raw_spin_unlock_bh(lock)

-#define raw_spin_trylock_bh(lock) \
- __cond_lock(lock, _raw_spin_trylock_bh(lock))
+#define raw_spin_trylock_bh(lock) _raw_spin_trylock_bh(lock)

-#define raw_spin_trylock_irq(lock) \
- __cond_lock(lock, ({ \
- local_irq_disable(); \
- _raw_spin_trylock(lock) ? \
- 1 : ({ local_irq_enable(); 0; }); \
- }))
+#define raw_spin_trylock_irq(lock) _raw_spin_trylock_irq(lock)

-#define raw_spin_trylock_irqsave(lock, flags) \
- __cond_lock(lock, ({ \
- local_irq_save(flags); \
- _raw_spin_trylock(lock) ? \
- 1 : ({ local_irq_restore(flags); 0; }); \
- }))
+#define raw_spin_trylock_irqsave(lock, flags) _raw_spin_trylock_irqsave(lock, &(flags))

#ifndef CONFIG_PREEMPT_RT
/* Include rwlock functions for !RT */
@@ -433,8 +422,12 @@ static __always_inline int spin_trylock_irq(spinlock_t *lock)
return raw_spin_trylock_irq(&lock->rlock);
}

-#define spin_trylock_irqsave(lock, flags) \
- __cond_lock(lock, raw_spin_trylock_irqsave(spinlock_check(lock), flags))
+static __always_inline bool _spin_trylock_irqsave(spinlock_t *lock, unsigned long *flags)
+ __cond_acquires(true, lock) __no_capability_analysis
+{
+ return raw_spin_trylock_irqsave(spinlock_check(lock), *flags);
+}
+#define spin_trylock_irqsave(lock, flags) _spin_trylock_irqsave(lock, &(flags))

/**
* spin_is_locked() - Check whether a spinlock is locked.
@@ -512,23 +505,17 @@ static inline int rwlock_needbreak(rwlock_t *lock)
* Decrements @atomic by 1. If the result is 0, returns true and locks
* @lock. Returns false for all other cases.
*/
-extern int _atomic_dec_and_lock(atomic_t *atomic, spinlock_t *lock);
-#define atomic_dec_and_lock(atomic, lock) \
- __cond_lock(lock, _atomic_dec_and_lock(atomic, lock))
+extern int atomic_dec_and_lock(atomic_t *atomic, spinlock_t *lock) __cond_acquires(true, lock);

extern int _atomic_dec_and_lock_irqsave(atomic_t *atomic, spinlock_t *lock,
- unsigned long *flags);
-#define atomic_dec_and_lock_irqsave(atomic, lock, flags) \
- __cond_lock(lock, _atomic_dec_and_lock_irqsave(atomic, lock, &(flags)))
+ unsigned long *flags) __cond_acquires(true, lock);
+#define atomic_dec_and_lock_irqsave(atomic, lock, flags) _atomic_dec_and_lock_irqsave(atomic, lock, &(flags))

-extern int _atomic_dec_and_raw_lock(atomic_t *atomic, raw_spinlock_t *lock);
-#define atomic_dec_and_raw_lock(atomic, lock) \
- __cond_lock(lock, _atomic_dec_and_raw_lock(atomic, lock))
+extern int atomic_dec_and_raw_lock(atomic_t *atomic, raw_spinlock_t *lock) __cond_acquires(true, lock);

extern int _atomic_dec_and_raw_lock_irqsave(atomic_t *atomic, raw_spinlock_t *lock,
- unsigned long *flags);
-#define atomic_dec_and_raw_lock_irqsave(atomic, lock, flags) \
- __cond_lock(lock, _atomic_dec_and_raw_lock_irqsave(atomic, lock, &(flags)))
+ unsigned long *flags) __cond_acquires(true, lock);
+#define atomic_dec_and_raw_lock_irqsave(atomic, lock, flags) _atomic_dec_and_raw_lock_irqsave(atomic, lock, &(flags))

int __alloc_bucket_spinlocks(spinlock_t **locks, unsigned int *lock_mask,
size_t max_size, unsigned int cpu_mult,
diff --git a/include/linux/spinlock_api_smp.h b/include/linux/spinlock_api_smp.h
index a77b76003ebb..1b1896595cbc 100644
--- a/include/linux/spinlock_api_smp.h
+++ b/include/linux/spinlock_api_smp.h
@@ -95,6 +95,26 @@ static inline int __raw_spin_trylock(raw_spinlock_t *lock)
return 0;
}

+static __always_inline bool _raw_spin_trylock_irq(raw_spinlock_t *lock)
+ __cond_acquires(true, lock)
+{
+ local_irq_disable();
+ if (_raw_spin_trylock(lock))
+ return true;
+ local_irq_enable();
+ return false;
+}
+
+static __always_inline bool _raw_spin_trylock_irqsave(raw_spinlock_t *lock, unsigned long *flags)
+ __cond_acquires(true, lock)
+{
+ local_irq_save(*flags);
+ if (_raw_spin_trylock(lock))
+ return true;
+ local_irq_restore(*flags);
+ return false;
+}
+
/*
* If lockdep is enabled then we use the non-preemption spin-ops
* even on CONFIG_PREEMPTION, because lockdep assumes that interrupts are
diff --git a/include/linux/spinlock_api_up.h b/include/linux/spinlock_api_up.h
index 018f5aabc1be..a9d5c7c66e03 100644
--- a/include/linux/spinlock_api_up.h
+++ b/include/linux/spinlock_api_up.h
@@ -24,14 +24,11 @@
* flags straight, to suppress compiler warnings of unused lock
* variables, and to add the proper checker annotations:
*/
-#define ___LOCK_void(lock) \
- do { (void)(lock); } while (0)
-
#define ___LOCK_(lock) \
- do { __acquire(lock); ___LOCK_void(lock); } while (0)
+ do { __acquire(lock); (void)(lock); } while (0)

#define ___LOCK_shared(lock) \
- do { __acquire_shared(lock); ___LOCK_void(lock); } while (0)
+ do { __acquire_shared(lock); (void)(lock); } while (0)

#define __LOCK(lock, ...) \
do { preempt_disable(); ___LOCK_##__VA_ARGS__(lock); } while (0)
@@ -78,10 +75,56 @@
#define _raw_spin_lock_irqsave(lock, flags) __LOCK_IRQSAVE(lock, flags)
#define _raw_read_lock_irqsave(lock, flags) __LOCK_IRQSAVE(lock, flags, shared)
#define _raw_write_lock_irqsave(lock, flags) __LOCK_IRQSAVE(lock, flags)
-#define _raw_spin_trylock(lock) ({ __LOCK(lock, void); 1; })
-#define _raw_read_trylock(lock) ({ __LOCK(lock, void); 1; })
-#define _raw_write_trylock(lock) ({ __LOCK(lock, void); 1; })
-#define _raw_spin_trylock_bh(lock) ({ __LOCK_BH(lock, void); 1; })
+
+static __always_inline int _raw_spin_trylock(raw_spinlock_t *lock)
+ __cond_acquires(true, lock)
+{
+ __LOCK(lock);
+ return 1;
+}
+
+static __always_inline int _raw_spin_trylock_bh(raw_spinlock_t *lock)
+ __cond_acquires(true, lock)
+{
+ __LOCK_BH(lock);
+ return 1;
+}
+
+static __always_inline int _raw_spin_trylock_irq(raw_spinlock_t *lock)
+ __cond_acquires(true, lock)
+{
+ __LOCK_IRQ(lock);
+ return 1;
+}
+
+static __always_inline int _raw_spin_trylock_irqsave(raw_spinlock_t *lock, unsigned long *flags)
+ __cond_acquires(true, lock)
+{
+ __LOCK_IRQSAVE(lock, *(flags));
+ return 1;
+}
+
+static __always_inline int _raw_read_trylock(rwlock_t *lock)
+ __cond_acquires_shared(true, lock)
+{
+ __LOCK(lock, shared);
+ return 1;
+}
+
+static __always_inline int _raw_write_trylock(rwlock_t *lock)
+ __cond_acquires(true, lock)
+{
+ __LOCK(lock);
+ return 1;
+}
+
+static __always_inline int _raw_write_trylock_irqsave(rwlock_t *lock, unsigned long *flags)
+ __cond_acquires(true, lock)
+{
+ __LOCK_IRQSAVE(lock, *(flags));
+ return 1;
+}
+
#define _raw_spin_unlock(lock) __UNLOCK(lock)
#define _raw_read_unlock(lock) __UNLOCK(lock, shared)
#define _raw_write_unlock(lock) __UNLOCK(lock)
diff --git a/include/linux/spinlock_rt.h b/include/linux/spinlock_rt.h
index 9688675b7536..23760f0e35e2 100644
--- a/include/linux/spinlock_rt.h
+++ b/include/linux/spinlock_rt.h
@@ -37,8 +37,8 @@ extern void rt_spin_lock_nested(spinlock_t *lock, int subclass) __acquires(lock)
extern void rt_spin_lock_nest_lock(spinlock_t *lock, struct lockdep_map *nest_lock) __acquires(lock);
extern void rt_spin_unlock(spinlock_t *lock) __releases(lock);
extern void rt_spin_lock_unlock(spinlock_t *lock);
-extern int rt_spin_trylock_bh(spinlock_t *lock);
-extern int rt_spin_trylock(spinlock_t *lock);
+extern int rt_spin_trylock_bh(spinlock_t *lock) __cond_acquires(true, lock);
+extern int rt_spin_trylock(spinlock_t *lock) __cond_acquires(true, lock);

static __always_inline void spin_lock(spinlock_t *lock)
__acquires(lock)
@@ -130,21 +130,19 @@ static __always_inline void spin_unlock_irqrestore(spinlock_t *lock,
rt_spin_unlock(lock);
}

-#define spin_trylock(lock) \
- __cond_lock(lock, rt_spin_trylock(lock))
+#define spin_trylock(lock) rt_spin_trylock(lock)

-#define spin_trylock_bh(lock) \
- __cond_lock(lock, rt_spin_trylock_bh(lock))
+#define spin_trylock_bh(lock) rt_spin_trylock_bh(lock)

-#define spin_trylock_irq(lock) \
- __cond_lock(lock, rt_spin_trylock(lock))
+#define spin_trylock_irq(lock) rt_spin_trylock(lock)

-#define spin_trylock_irqsave(lock, flags) \
- __cond_lock(lock, ({ \
- typecheck(unsigned long, flags); \
- flags = 0; \
- rt_spin_trylock(lock); \
- }))
+static __always_inline bool _spin_trylock_irqsave(spinlock_t *lock, unsigned long *flags)
+ __cond_acquires(true, lock)
+{
+ *flags = 0;
+ return rt_spin_trylock(lock);
+}
+#define spin_trylock_irqsave(lock, flags) _spin_trylock_irqsave(lock, &(flags))

#define spin_is_contended(lock) (((void)(lock), 0))

diff --git a/kernel/signal.c b/kernel/signal.c
index fe9190d84f28..9ff96a341e42 100644
--- a/kernel/signal.c
+++ b/kernel/signal.c
@@ -1355,8 +1355,8 @@ int zap_other_threads(struct task_struct *p)
return count;
}

-struct sighand_struct *__lock_task_sighand(struct task_struct *tsk,
- unsigned long *flags)
+struct sighand_struct *lock_task_sighand(struct task_struct *tsk,
+ unsigned long *flags)
{
struct sighand_struct *sighand;

diff --git a/kernel/time/posix-timers.c b/kernel/time/posix-timers.c
index 8b582174b1f9..3c043330aa21 100644
--- a/kernel/time/posix-timers.c
+++ b/kernel/time/posix-timers.c
@@ -66,14 +66,7 @@ static const struct k_clock clock_realtime, clock_monotonic;
#error "SIGEV_THREAD_ID must not share bit with other SIGEV values!"
#endif

-static struct k_itimer *__lock_timer(timer_t timer_id);
-
-#define lock_timer(tid) \
-({ struct k_itimer *__timr; \
- __cond_lock(&__timr->it_lock, __timr = __lock_timer(tid)); \
- __timr; \
-})
-
+static struct k_itimer *lock_timer(timer_t timer_id);
static inline void unlock_timer(struct k_itimer *timr)
{
if (likely((timr)))
@@ -85,7 +78,7 @@ static inline void unlock_timer(struct k_itimer *timr)

#define scoped_timer (scope)

-DEFINE_CLASS(lock_timer, struct k_itimer *, unlock_timer(_T), __lock_timer(id), timer_t id);
+DEFINE_CLASS(lock_timer, struct k_itimer *, unlock_timer(_T), lock_timer(id), timer_t id);
DEFINE_CLASS_IS_COND_GUARD(lock_timer);

static struct timer_hash_bucket *hash_bucket(struct signal_struct *sig, unsigned int nr)
@@ -601,7 +594,7 @@ COMPAT_SYSCALL_DEFINE3(timer_create, clockid_t, which_clock,
}
#endif

-static struct k_itimer *__lock_timer(timer_t timer_id)
+static struct k_itimer *lock_timer(timer_t timer_id)
{
struct k_itimer *timr;

diff --git a/lib/dec_and_lock.c b/lib/dec_and_lock.c
index 1dcca8f2e194..8c7c398fd770 100644
--- a/lib/dec_and_lock.c
+++ b/lib/dec_and_lock.c
@@ -18,7 +18,7 @@
* because the spin-lock and the decrement must be
* "atomic".
*/
-int _atomic_dec_and_lock(atomic_t *atomic, spinlock_t *lock)
+int atomic_dec_and_lock(atomic_t *atomic, spinlock_t *lock)
{
/* Subtract 1 from counter unless that drops it to 0 (ie. it was 1) */
if (atomic_add_unless(atomic, -1, 1))
@@ -32,7 +32,7 @@ int _atomic_dec_and_lock(atomic_t *atomic, spinlock_t *lock)
return 0;
}

-EXPORT_SYMBOL(_atomic_dec_and_lock);
+EXPORT_SYMBOL(atomic_dec_and_lock);

int _atomic_dec_and_lock_irqsave(atomic_t *atomic, spinlock_t *lock,
unsigned long *flags)
@@ -50,7 +50,7 @@ int _atomic_dec_and_lock_irqsave(atomic_t *atomic, spinlock_t *lock,
}
EXPORT_SYMBOL(_atomic_dec_and_lock_irqsave);

-int _atomic_dec_and_raw_lock(atomic_t *atomic, raw_spinlock_t *lock)
+int atomic_dec_and_raw_lock(atomic_t *atomic, raw_spinlock_t *lock)
{
/* Subtract 1 from counter unless that drops it to 0 (ie. it was 1) */
if (atomic_add_unless(atomic, -1, 1))
@@ -63,7 +63,7 @@ int _atomic_dec_and_raw_lock(atomic_t *atomic, raw_spinlock_t *lock)
raw_spin_unlock(lock);
return 0;
}
-EXPORT_SYMBOL(_atomic_dec_and_raw_lock);
+EXPORT_SYMBOL(atomic_dec_and_raw_lock);

int _atomic_dec_and_raw_lock_irqsave(atomic_t *atomic, raw_spinlock_t *lock,
unsigned long *flags)
diff --git a/mm/memory.c b/mm/memory.c
index 0ba4f6b71847..454baefb4989 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -2086,8 +2086,8 @@ static pmd_t *walk_to_pmd(struct mm_struct *mm, unsigned long addr)
return pmd;
}

-pte_t *__get_locked_pte(struct mm_struct *mm, unsigned long addr,
- spinlock_t **ptl)
+pte_t *get_locked_pte(struct mm_struct *mm, unsigned long addr,
+ spinlock_t **ptl)
{
pmd_t *pmd = walk_to_pmd(mm, addr);

diff --git a/mm/pgtable-generic.c b/mm/pgtable-generic.c
index 567e2d084071..808f18d68279 100644
--- a/mm/pgtable-generic.c
+++ b/mm/pgtable-generic.c
@@ -278,7 +278,7 @@ static unsigned long pmdp_get_lockless_start(void) { return 0; }
static void pmdp_get_lockless_end(unsigned long irqflags) { }
#endif

-pte_t *___pte_offset_map(pmd_t *pmd, unsigned long addr, pmd_t *pmdvalp)
+pte_t *__pte_offset_map(pmd_t *pmd, unsigned long addr, pmd_t *pmdvalp)
{
unsigned long irqflags;
pmd_t pmdval;
@@ -330,13 +330,12 @@ pte_t *pte_offset_map_rw_nolock(struct mm_struct *mm, pmd_t *pmd,
}

/*
- * pte_offset_map_lock(mm, pmd, addr, ptlp), and its internal implementation
- * __pte_offset_map_lock() below, is usually called with the pmd pointer for
- * addr, reached by walking down the mm's pgd, p4d, pud for addr: either while
- * holding mmap_lock or vma lock for read or for write; or in truncate or rmap
- * context, while holding file's i_mmap_lock or anon_vma lock for read (or for
- * write). In a few cases, it may be used with pmd pointing to a pmd_t already
- * copied to or constructed on the stack.
+ * pte_offset_map_lock(mm, pmd, addr, ptlp) is usually called with the pmd
+ * pointer for addr, reached by walking down the mm's pgd, p4d, pud for addr:
+ * either while holding mmap_lock or vma lock for read or for write; or in
+ * truncate or rmap context, while holding file's i_mmap_lock or anon_vma lock
+ * for read (or for write). In a few cases, it may be used with pmd pointing to
+ * a pmd_t already copied to or constructed on the stack.
*
* When successful, it returns the pte pointer for addr, with its page table
* kmapped if necessary (when CONFIG_HIGHPTE), and locked against concurrent
@@ -387,8 +386,8 @@ pte_t *pte_offset_map_rw_nolock(struct mm_struct *mm, pmd_t *pmd,
* table, and may not use RCU at all: "outsiders" like khugepaged should avoid
* pte_offset_map() and co once the vma is detached from mm or mm_users is zero.
*/
-pte_t *__pte_offset_map_lock(struct mm_struct *mm, pmd_t *pmd,
- unsigned long addr, spinlock_t **ptlp)
+pte_t *pte_offset_map_lock(struct mm_struct *mm, pmd_t *pmd,
+ unsigned long addr, spinlock_t **ptlp)
{
spinlock_t *ptl;
pmd_t pmdval;
diff --git a/tools/include/linux/compiler_types.h b/tools/include/linux/compiler_types.h
index d09f9dc172a4..067a5b4e0f7b 100644
--- a/tools/include/linux/compiler_types.h
+++ b/tools/include/linux/compiler_types.h
@@ -20,7 +20,6 @@
# define __releases(x) __attribute__((context(x,1,0)))
# define __acquire(x) __context__(x,1)
# define __release(x) __context__(x,-1)
-# define __cond_lock(x,c) ((c) ? ({ __acquire(x); 1; }) : 0)
#else /* __CHECKER__ */
/* context/locking */
# define __must_hold(x)
@@ -28,7 +27,6 @@
# define __releases(x)
# define __acquire(x) (void)0
# define __release(x) (void)0
-# define __cond_lock(x,c) (c)
#endif /* __CHECKER__ */

/* Compiler specific macros. */
--
2.51.0.384.g4c02a37b29-goog

Christoph Hellwig

unread,
Sep 18, 2025, 10:15:18 AM (6 days ago) Sep 18
to Marco Elver, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
On Thu, Sep 18, 2025 at 03:59:11PM +0200, Marco Elver wrote:
> A Clang version that supports `-Wthread-safety-pointer` and the new
> alias-analysis of capability pointers is required (from this version
> onwards):
>
> https://github.com/llvm/llvm-project/commit/b4c98fcbe1504841203e610c351a3227f36c92a4 [3]

There's no chance to make say x86 pre-built binaries for that available?

Marco Elver

unread,
Sep 18, 2025, 10:31:35 AM (6 days ago) Sep 18
to Christoph Hellwig, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
Not officially, but I can try to build something to share if you prefer.
Or a script that automatically pulls and builds clang for you - I have
this old script I just updated to the above commit:
https://gist.github.com/melver/fe8a5fd9e43e21fab569ee24fc9c6072
Does that help?

Christoph Hellwig

unread,
Sep 18, 2025, 10:38:13 AM (6 days ago) Sep 18
to Marco Elver, Christoph Hellwig, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
On Thu, Sep 18, 2025 at 04:30:55PM +0200, Marco Elver wrote:
> Not officially, but I can try to build something to share if you prefer.
> Or a script that automatically pulls and builds clang for you - I have
> this old script I just updated to the above commit:
> https://gist.github.com/melver/fe8a5fd9e43e21fab569ee24fc9c6072
> Does that help?

Just kicked it off, I'll see how long this will take on my laptop.
At least the error checking that tells me about dependencies and work
that needs to be done before starting the build is nice so that I
hopefully don't have to restart the build too often.

Linus Torvalds

unread,
Sep 18, 2025, 11:50:09 AM (6 days ago) Sep 18
to Marco Elver, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
On Thu, 18 Sept 2025 at 07:05, Marco Elver <el...@google.com> wrote:
>
> Capability analysis is a C language extension, which enables statically
> checking that user-definable "capabilities" are acquired and released where
> required. An obvious application is lock-safety checking for the kernel's
> various synchronization primitives (each of which represents a "capability"),
> and checking that locking rules are not violated.
>
> Clang originally called the feature "Thread Safety Analysis" [1],

So this looks really interesting, but I absolutely *hate* the new
"capability" name.

We have existing and traditional - and very very different - meaning
of "capabilities" in the kernel, and having this thing called
"capability" is just wrong. Particularly as it then talks about
"acquiring capabilities" - which is *EXACTLY* what our lon-existing
capabilities are all about, but are something entirely and totally
different.

So please - call it something else. Even if clang then calls it
'capability analysis", within the context of a kernel, please ignore
that, and call it something that makes more sense (I don't think
"capabilities" make sense even in the context of clang, but hey,
that's _their_ choice - but we should not then take that bad choice
and run with it).

Sparse called it "context analysis", and while the "analysis" part is
debatable - sparse never did much anything clever enough to merit
calling it analysis - at least the "context" part of the name is I
think somewhat sane.

Because it's about making decisions based on the context the code runs in.

But I'm certainly not married to the "context" name either. I'd still
claim it makes more sense than "capability", but the real problem with
"capability" isn't that it doesn't make sense, it's that we already
*HAVE* that as a concept, and old and traditional use is important.

But we do use the word "context" in this context quite widely even
outside of the sparse usage, ie that's what we say when we talk about
things like locking and RCU (ie we talk about running in "process
context", or about "interrupt context" etc). That's obviously where
the sparse naming comes from - it's not like sparse made that up.

So I'm really happy to see compilers start exposing these kinds of
interfaces, and the patches look sane apart from the absolutely
horrible and unacceptable name. Really - there is no way in hell we
can call this "capability" in a kernel context.

I'd suggest just doing a search-and-replace of 's/capability/context/'
and it would already make things a ton better. But maybe there are
better names for this still?

I mean, even apart from the fact that we have an existing meaning for
"capability", just look at the documentation patch, and read the first
sentence:

Capability analysis is a C language extension, which enables statically
checking that user-definable "capabilities" are acquired and released where
required.

and just from a plain English language standpoint, the word
"capability" makes zero sense. I think you even realized that, in that
you put that word in quotes, because it's _so_ nonsensical.

And if not "context", maybe some other word? But really, absolutely
*not* "capability". Because that's just crazy talk.

Please? Because other than this naming issue, I think this really is a
good idea.

Linus

Ian Rogers

unread,
Sep 18, 2025, 11:58:38 AM (6 days ago) Sep 18
to Marco Elver, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
On Thu, Sep 18, 2025 at 7:05 AM Marco Elver <el...@google.com> wrote:
>
> Capability analysis is a C language extension, which enables statically
> checking that user-definable "capabilities" are acquired and released where
> required. An obvious application is lock-safety checking for the kernel's
> various synchronization primitives (each of which represents a "capability"),
> and checking that locking rules are not violated.
>
> Clang originally called the feature "Thread Safety Analysis" [1], with
> some terminology still using the thread-safety-analysis-only names. This
> was later changed and the feature became more flexible, gaining the
> ability to define custom "capabilities". Its foundations can be found in
> "capability systems", used to specify the permissibility of operations
> to depend on some capability being held (or not held).
>
> [1] https://clang.llvm.org/docs/ThreadSafetyAnalysis.html
> [2] https://www.cs.cornell.edu/talc/papers/capabilities.pdf
>
> Because the feature is not just able to express capabilities related to
> synchronization primitives, the naming chosen for the kernel departs
> from Clang's initial "Thread Safety" nomenclature and refers to the
> feature as "Capability Analysis" to avoid confusion. The implementation
> still makes references to the older terminology in some places, such as
> `-Wthread-safety` being the warning enabled option that also still
> appears in diagnostic messages.
>
> See more details in the kernel-doc documentation added in this and the
> subsequent changes.
>
> Clang version 22+ is required.
>
> Signed-off-by: Marco Elver <el...@google.com>
> ---
> v3:
> * Require Clang 22 or later (reentrant capabilities, basic alias analysis).
> * Rename __assert_cap/__asserts_cap -> __assume_cap/__assumes_cap (suggested by Peter).
> * Add __acquire_ret and __acquire_shared_ret helper macros - can be used
> to define function-like macros that return objects which contains a
> held capabilities. Works now because of capability alias analysis.
> * Add capability_unsafe_alias() helper, where the analysis rightfully
> points out we're doing strange things with aliases but we don't care.
> * Support multi-argument attributes.
>
> v2:
> * New -Wthread-safety feature rename to -Wthread-safety-pointer (was
> -Wthread-safety-addressof).
> * Introduce __capability_unsafe() function attribute.
> * Rename __var_guarded_by to simply __guarded_by. The initial idea was
> to be explicit if the variable or pointed-to data is guarded by, but
> having a shorter attribute name is likely better long-term.
> * Rename __ref_guarded_by to __pt_guarded_by (pointed-to guarded by).
> ---
> Makefile | 1 +
> include/linux/compiler-capability-analysis.h | 449 ++++++++++++++++++-
> lib/Kconfig.debug | 31 ++
> scripts/Makefile.capability-analysis | 7 +
> scripts/Makefile.lib | 10 +
> 5 files changed, 491 insertions(+), 7 deletions(-)
> create mode 100644 scripts/Makefile.capability-analysis
>
> diff --git a/Makefile b/Makefile
> index cf37b9407821..2c91730e513b 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -1096,6 +1096,7 @@ include-$(CONFIG_RANDSTRUCT) += scripts/Makefile.randstruct
> include-$(CONFIG_KSTACK_ERASE) += scripts/Makefile.kstack_erase
> include-$(CONFIG_AUTOFDO_CLANG) += scripts/Makefile.autofdo
> include-$(CONFIG_PROPELLER_CLANG) += scripts/Makefile.propeller
> +include-$(CONFIG_WARN_CAPABILITY_ANALYSIS) += scripts/Makefile.capability-analysis
> include-$(CONFIG_GCC_PLUGINS) += scripts/Makefile.gcc-plugins
>
> include $(addprefix $(srctree)/, $(include-y))
> diff --git a/include/linux/compiler-capability-analysis.h b/include/linux/compiler-capability-analysis.h
> index 7546ddb83f86..6f3f185478bc 100644
> --- a/include/linux/compiler-capability-analysis.h
> +++ b/include/linux/compiler-capability-analysis.h
> @@ -6,27 +6,462 @@
> #ifndef _LINUX_COMPILER_CAPABILITY_ANALYSIS_H
> #define _LINUX_COMPILER_CAPABILITY_ANALYSIS_H
>
> +#if defined(WARN_CAPABILITY_ANALYSIS)
> +
> +/*
> + * The below attributes are used to define new capability types. Internal only.
> + */
> +# define __cap_type(name) __attribute__((capability(#name)))
> +# define __reentrant_cap __attribute__((reentrant_capability))
> +# define __acquires_cap(...) __attribute__((acquire_capability(__VA_ARGS__)))
> +# define __acquires_shared_cap(...) __attribute__((acquire_shared_capability(__VA_ARGS__)))
> +# define __try_acquires_cap(ret, var) __attribute__((try_acquire_capability(ret, var)))
> +# define __try_acquires_shared_cap(ret, var) __attribute__((try_acquire_shared_capability(ret, var)))
> +# define __releases_cap(...) __attribute__((release_capability(__VA_ARGS__)))
> +# define __releases_shared_cap(...) __attribute__((release_shared_capability(__VA_ARGS__)))
> +# define __assumes_cap(...) __attribute__((assert_capability(__VA_ARGS__)))
> +# define __assumes_shared_cap(...) __attribute__((assert_shared_capability(__VA_ARGS__)))
> +# define __returns_cap(var) __attribute__((lock_returned(var)))
> +
> +/*
> + * The below are used to annotate code being checked. Internal only.
> + */
> +# define __excludes_cap(...) __attribute__((locks_excluded(__VA_ARGS__)))
> +# define __requires_cap(...) __attribute__((requires_capability(__VA_ARGS__)))
> +# define __requires_shared_cap(...) __attribute__((requires_shared_capability(__VA_ARGS__)))
> +
> +/**
> + * __guarded_by - struct member and globals attribute, declares variable
> + * protected by capability
> + *
> + * Declares that the struct member or global variable must be guarded by the
> + * given capabilities. Read operations on the data require shared access,
> + * while write operations require exclusive access.
> + *
> + * .. code-block:: c
> + *
> + * struct some_state {
> + * spinlock_t lock;
> + * long counter __guarded_by(&lock);
> + * };
> + */
> +# define __guarded_by(...) __attribute__((guarded_by(__VA_ARGS__)))
> +
> +/**
> + * __pt_guarded_by - struct member and globals attribute, declares pointed-to
> + * data is protected by capability
> + *
> + * Declares that the data pointed to by the struct member pointer or global
> + * pointer must be guarded by the given capabilities. Read operations on the
> + * data require shared access, while write operations require exclusive access.
> + *
> + * .. code-block:: c
> + *
> + * struct some_state {
> + * spinlock_t lock;
> + * long *counter __pt_guarded_by(&lock);
> + * };
> + */
> +# define __pt_guarded_by(...) __attribute__((pt_guarded_by(__VA_ARGS__)))
> +
> +/**
> + * struct_with_capability() - declare or define a capability struct
> + * @name: struct name
> + *
> + * Helper to declare or define a struct type with capability of the same name.
> + *
> + * .. code-block:: c
> + *
> + * struct_with_capability(my_handle) {
> + * int foo;
> + * long bar;
> + * };
> + *
> + * struct some_state {
> + * ...
> + * };
> + * // ... declared elsewhere ...
> + * struct_with_capability(some_state);
> + *
> + * Note: The implementation defines several helper functions that can acquire,
> + * release, and assert the capability.
> + */
> +# define struct_with_capability(name, ...) \
> + struct __cap_type(name) __VA_ARGS__ name; \
> + static __always_inline void __acquire_cap(const struct name *var) \
> + __attribute__((overloadable)) __no_capability_analysis __acquires_cap(var) { } \
> + static __always_inline void __acquire_shared_cap(const struct name *var) \
> + __attribute__((overloadable)) __no_capability_analysis __acquires_shared_cap(var) { } \
> + static __always_inline bool __try_acquire_cap(const struct name *var, bool ret) \
> + __attribute__((overloadable)) __no_capability_analysis __try_acquires_cap(1, var) \
> + { return ret; } \
> + static __always_inline bool __try_acquire_shared_cap(const struct name *var, bool ret) \
> + __attribute__((overloadable)) __no_capability_analysis __try_acquires_shared_cap(1, var) \
> + { return ret; } \
> + static __always_inline void __release_cap(const struct name *var) \
> + __attribute__((overloadable)) __no_capability_analysis __releases_cap(var) { } \
> + static __always_inline void __release_shared_cap(const struct name *var) \
> + __attribute__((overloadable)) __no_capability_analysis __releases_shared_cap(var) { } \
> + static __always_inline void __assume_cap(const struct name *var) \
> + __attribute__((overloadable)) __assumes_cap(var) { } \
> + static __always_inline void __assume_shared_cap(const struct name *var) \
> + __attribute__((overloadable)) __assumes_shared_cap(var) { } \
> + struct name
> +
> +/**
> + * disable_capability_analysis() - disables capability analysis
> + *
> + * Disables capability analysis. Must be paired with a later
> + * enable_capability_analysis().
> + */
> +# define disable_capability_analysis() \
> + __diag_push(); \
> + __diag_ignore_all("-Wunknown-warning-option", "") \
> + __diag_ignore_all("-Wthread-safety", "") \
> + __diag_ignore_all("-Wthread-safety-pointer", "")
> +
> +/**
> + * enable_capability_analysis() - re-enables capability analysis
> + *
> + * Re-enables capability analysis. Must be paired with a prior
> + * disable_capability_analysis().
> + */
> +# define enable_capability_analysis() __diag_pop()
> +
> +/**
> + * __no_capability_analysis - function attribute, disables capability analysis
> + *
> + * Function attribute denoting that capability analysis is disabled for the
> + * whole function. Prefer use of `capability_unsafe()` where possible.
> + */
> +# define __no_capability_analysis __attribute__((no_thread_safety_analysis))
> +
> +#else /* !WARN_CAPABILITY_ANALYSIS */
> +
> +# define __cap_type(name)
> +# define __reentrant_cap
> +# define __acquires_cap(...)
> +# define __acquires_shared_cap(...)
> +# define __try_acquires_cap(ret, var)
> +# define __try_acquires_shared_cap(ret, var)
> +# define __releases_cap(...)
> +# define __releases_shared_cap(...)
> +# define __assumes_cap(...)
> +# define __assumes_shared_cap(...)
> +# define __returns_cap(var)
> +# define __guarded_by(...)
> +# define __pt_guarded_by(...)
> +# define __excludes_cap(...)
> +# define __requires_cap(...)
> +# define __requires_shared_cap(...)
> +# define __acquire_cap(var) do { } while (0)
> +# define __acquire_shared_cap(var) do { } while (0)
> +# define __try_acquire_cap(var, ret) (ret)
> +# define __try_acquire_shared_cap(var, ret) (ret)
> +# define __release_cap(var) do { } while (0)
> +# define __release_shared_cap(var) do { } while (0)
> +# define __assume_cap(var) do { (void)(var); } while (0)
> +# define __assume_shared_cap(var) do { (void)(var); } while (0)
> +# define struct_with_capability(name, ...) struct __VA_ARGS__ name
> +# define disable_capability_analysis()
> +# define enable_capability_analysis()
> +# define __no_capability_analysis
> +
> +#endif /* WARN_CAPABILITY_ANALYSIS */
> +
> +/**
> + * capability_unsafe() - disable capability checking for contained code
> + *
> + * Disables capability checking for contained statements or expression.
> + *
> + * .. code-block:: c
> + *
> + * struct some_data {
> + * spinlock_t lock;
> + * int counter __guarded_by(&lock);
> + * };
> + *
> + * int foo(struct some_data *d)
> + * {
> + * // ...
> + * // other code that is still checked ...
> + * // ...
> + * return capability_unsafe(d->counter);
> + * }
> + */
> +#define capability_unsafe(...) \
> +({ \
> + disable_capability_analysis(); \
> + __VA_ARGS__; \
> + enable_capability_analysis() \
> +})
> +
> +/**
> + * __capability_unsafe() - function attribute, disable capability checking
> + * @comment: comment explaining why opt-out is safe
> + *
> + * Function attribute denoting that capability analysis is disabled for the
> + * whole function. Forces adding an inline comment as argument.
> + */
> +#define __capability_unsafe(comment) __no_capability_analysis
> +
> +/**
> + * capability_unsafe_alias() - helper to insert a capability "alias barrier"
> + * @p: pointer aliasing a capability or object containing capabilities
> + *
> + * No-op function that acts as a "capability alias barrier", where the analysis
> + * rightfully detects that we're switching aliases, but the switch is considered
> + * safe but beyond the analysis reasoning abilities.
> + *
> + * This should be inserted before the first use of such an alias.
> + *
> + * Implementation Note: The compiler ignores aliases that may be reassigned but
> + * their value cannot be determined (e.g. when passing a non-const pointer to an
> + * alias as a function argument).
> + */
> +#define capability_unsafe_alias(p) _capability_unsafe_alias((void **)&(p))
> +static inline void _capability_unsafe_alias(void **p) { }
> +
> +/**
> + * token_capability() - declare an abstract global capability instance
> + * @name: token capability name
> + *
> + * Helper that declares an abstract global capability instance @name that can be
> + * used as a token capability, but not backed by a real data structure (linker
> + * error if accidentally referenced). The type name is `__capability_@name`.
> + */
> +#define token_capability(name, ...) \
> + struct_with_capability(__capability_##name, ##__VA_ARGS__) {}; \
> + extern const struct __capability_##name *name
> +
> +/**
> + * token_capability_instance() - declare another instance of a global capability
> + * @cap: token capability previously declared with token_capability()
> + * @name: name of additional global capability instance
> + *
> + * Helper that declares an additional instance @name of the same token
> + * capability class @name. This is helpful where multiple related token
> + * capabilities are declared, as it also allows using the same underlying type
> + * (`__capability_@cap`) as function arguments.
> + */
> +#define token_capability_instance(cap, name) \
> + extern const struct __capability_##cap *name
> +
> +/*
> + * Common keywords for static capability analysis. Both Clang's capability
> + * analysis and Sparse's context tracking are currently supported.
> + */
> #ifdef __CHECKER__
>
> /* Sparse context/lock checking support. */
> # define __must_hold(x) __attribute__((context(x,1,1)))
> +# define __must_not_hold(x)
> # define __acquires(x) __attribute__((context(x,0,1)))
> # define __cond_acquires(x) __attribute__((context(x,0,-1)))
> # define __releases(x) __attribute__((context(x,1,0)))
> # define __acquire(x) __context__(x,1)
> # define __release(x) __context__(x,-1)
> # define __cond_lock(x, c) ((c) ? ({ __acquire(x); 1; }) : 0)
> +/* For Sparse, there's no distinction between exclusive and shared locks. */
> +# define __must_hold_shared __must_hold
> +# define __acquires_shared __acquires
> +# define __cond_acquires_shared __cond_acquires
> +# define __releases_shared __releases
> +# define __acquire_shared __acquire
> +# define __release_shared __release
> +# define __cond_lock_shared __cond_acquire
>
> #else /* !__CHECKER__ */
>
> -# define __must_hold(x)
> -# define __acquires(x)
> -# define __cond_acquires(x)
> -# define __releases(x)
> -# define __acquire(x) (void)0
> -# define __release(x) (void)0
> -# define __cond_lock(x, c) (c)
> +/**
> + * __must_hold() - function attribute, caller must hold exclusive capability
> + * @x: capability instance pointer
> + *
> + * Function attribute declaring that the caller must hold the given capability
> + * instance @x exclusively.
> + */
> +# define __must_hold(x) __requires_cap(x)
> +
> +/**
> + * __must_not_hold() - function attribute, caller must not hold capability
> + * @x: capability instance pointer
> + *
> + * Function attribute declaring that the caller must not hold the given
> + * capability instance @x.
> + */
> +# define __must_not_hold(x) __excludes_cap(x)
> +
> +/**
> + * __acquires() - function attribute, function acquires capability exclusively
> + * @x: capability instance pointer
> + *
> + * Function attribute declaring that the function acquires the given
> + * capability instance @x exclusively, but does not release it.
> + */
> +# define __acquires(x) __acquires_cap(x)
> +
> +/**
> + * __cond_acquires() - function attribute, function conditionally
> + * acquires a capability exclusively
> + * @x: capability instance pointer
> + *
> + * Function attribute declaring that the function conditionally acquires the
> + * given capability instance @x exclusively, but does not release it.
> + */
> +# define __cond_acquires(x) __try_acquires_cap(1, x)
> +
> +/**
> + * __releases() - function attribute, function releases a capability exclusively
> + * @x: capability instance pointer
> + *
> + * Function attribute declaring that the function releases the given capability
> + * instance @x exclusively. The capability must be held on entry.
> + */
> +# define __releases(x) __releases_cap(x)
> +
> +/**
> + * __acquire() - function to acquire capability exclusively
> + * @x: capability instance pointer
> + *
> + * No-op function that acquires the given capability instance @x exclusively.
> + */
> +# define __acquire(x) __acquire_cap(x)
> +
> +/**
> + * __release() - function to release capability exclusively
> + * @x: capability instance pointer
> + *
> + * No-op function that releases the given capability instance @x.
> + */
> +# define __release(x) __release_cap(x)
> +
> +/**
> + * __cond_lock() - function that conditionally acquires a capability
> + * exclusively
> + * @x: capability instance pinter
> + * @c: boolean expression
> + *
> + * Return: result of @c
> + *
> + * No-op function that conditionally acquires capability instance @x
> + * exclusively, if the boolean expression @c is true. The result of @c is the
> + * return value, to be able to create a capability-enabled interface; for
> + * example:
> + *
> + * .. code-block:: c
> + *
> + * #define spin_trylock(l) __cond_lock(&lock, _spin_trylock(&lock))
> + */
> +# define __cond_lock(x, c) __try_acquire_cap(x, c)
> +
> +/**
> + * __must_hold_shared() - function attribute, caller must hold shared capability
> + * @x: capability instance pointer
> + *
> + * Function attribute declaring that the caller must hold the given capability
> + * instance @x with shared access.
> + */
> +# define __must_hold_shared(x) __requires_shared_cap(x)
> +
> +/**
> + * __acquires_shared() - function attribute, function acquires capability shared
> + * @x: capability instance pointer
> + *
> + * Function attribute declaring that the function acquires the given
> + * capability instance @x with shared access, but does not release it.
> + */
> +# define __acquires_shared(x) __acquires_shared_cap(x)
> +
> +/**
> + * __cond_acquires_shared() - function attribute, function conditionally
> + * acquires a capability shared
> + * @x: capability instance pointer
> + *
> + * Function attribute declaring that the function conditionally acquires the
> + * given capability instance @x with shared access, but does not release it.
> + */
> +# define __cond_acquires_shared(x) __try_acquires_shared_cap(1, x)
> +
> +/**
> + * __releases_shared() - function attribute, function releases a
> + * capability shared
> + * @x: capability instance pointer
> + *
> + * Function attribute declaring that the function releases the given capability
> + * instance @x with shared access. The capability must be held on entry.
> + */
> +# define __releases_shared(x) __releases_shared_cap(x)
> +
> +/**
> + * __acquire_shared() - function to acquire capability shared
> + * @x: capability instance pointer
> + *
> + * No-op function that acquires the given capability instance @x with shared
> + * access.
> + */
> +# define __acquire_shared(x) __acquire_shared_cap(x)
> +
> +/**
> + * __release_shared() - function to release capability shared
> + * @x: capability instance pointer
> + *
> + * No-op function that releases the given capability instance @x with shared
> + * access.
> + */
> +# define __release_shared(x) __release_shared_cap(x)
> +
> +/**
> + * __cond_lock_shared() - function that conditionally acquires a capability
> + * shared
> + * @x: capability instance pinter
> + * @c: boolean expression
> + *
> + * Return: result of @c
> + *
> + * No-op function that conditionally acquires capability instance @x with shared
> + * access, if the boolean expression @c is true. The result of @c is the return
> + * value, to be able to create a capability-enabled interface.
> + */
> +# define __cond_lock_shared(x, c) __try_acquire_shared_cap(x, c)
>
> #endif /* __CHECKER__ */
>
> +/**
> + * __acquire_ret() - helper to acquire capability of return value
> + * @call: call expression
> + * @ret_expr: acquire expression that uses __ret
> + */
> +#define __acquire_ret(call, ret_expr) \
> + ({ \
> + __auto_type __ret = call; \
> + __acquire(ret_expr); \
> + __ret; \
> + })
> +
> +/**
> + * __acquire_shared_ret() - helper to acquire capability shared of return value
> + * @call: call expression
> + * @ret_expr: acquire shared expression that uses __ret
> + */
> +#define __acquire_shared_ret(call, ret_expr) \
> + ({ \
> + __auto_type __ret = call; \
> + __acquire_shared(ret_expr); \
> + __ret; \
> + })
> +
> +/*
> + * Attributes to mark functions returning acquired capabilities. This is purely
> + * cosmetic to help readability, and should be used with the above macros as
> + * follows:
> + *
> + * struct foo { spinlock_t lock; ... };
> + * ...
> + * #define myfunc(...) __acquire_ret(_myfunc(__VA_ARGS__), &__ret->lock)
> + * struct foo *_myfunc(int bar) __acquires_ret;
> + * ...
> + */
> +#define __acquires_ret __no_capability_analysis
> +#define __acquires_shared_ret __no_capability_analysis
> +
> #endif /* _LINUX_COMPILER_CAPABILITY_ANALYSIS_H */
> diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
> index dc0e0c6ed075..57e09615f88d 100644
> --- a/lib/Kconfig.debug
> +++ b/lib/Kconfig.debug
> @@ -613,6 +613,37 @@ config DEBUG_FORCE_WEAK_PER_CPU
> To ensure that generic code follows the above rules, this
> option forces all percpu variables to be defined as weak.
>
> +config WARN_CAPABILITY_ANALYSIS
> + bool "Compiler capability-analysis warnings"
> + depends on CC_IS_CLANG && CLANG_VERSION >= 220000
> + # Branch profiling re-defines "if", which messes with the compiler's
> + # ability to analyze __cond_acquires(..), resulting in false positives.
> + depends on !TRACE_BRANCH_PROFILING

Err, wow! What and huh, and why? Crikes. I'm amazed you found such an
option exists. I must be very naive to have never heard of it and now
I wonder if it is needed and load bearing?

Ian


> + default y
> + help
> + Capability analysis is a C language extension, which enables
> + statically checking that user-definable "capabilities" are acquired
> + and released where required.
> +
> + Clang's name of the feature ("Thread Safety Analysis") refers to
> + the original name of the feature; it was later expanded to be a
> + generic "Capability Analysis" framework.
> +
> + Requires Clang 22 or later.
> +
> + Produces warnings by default. Select CONFIG_WERROR if you wish to
> + turn these warnings into errors.
> +
> +config WARN_CAPABILITY_ANALYSIS_ALL
> + bool "Enable capability analysis for all source files"
> + depends on WARN_CAPABILITY_ANALYSIS
> + depends on EXPERT && !COMPILE_TEST
> + help
> + Enable tree-wide capability analysis. This is likely to produce a
> + large number of false positives - enable at your own risk.
> +
> + If unsure, say N.
> +
> endmenu # "Compiler options"
>
> menu "Generic Kernel Debugging Instruments"
> diff --git a/scripts/Makefile.capability-analysis b/scripts/Makefile.capability-analysis
> new file mode 100644
> index 000000000000..e137751a4c9a
> --- /dev/null
> +++ b/scripts/Makefile.capability-analysis
> @@ -0,0 +1,7 @@
> +# SPDX-License-Identifier: GPL-2.0
> +
> +capability-analysis-cflags := -DWARN_CAPABILITY_ANALYSIS \
> + -fexperimental-late-parse-attributes -Wthread-safety \
> + -Wthread-safety-pointer -Wthread-safety-beta
> +
> +export CFLAGS_CAPABILITY_ANALYSIS := $(capability-analysis-cflags)
> diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib
> index 1d581ba5df66..e0ac273bf9eb 100644
> --- a/scripts/Makefile.lib
> +++ b/scripts/Makefile.lib
> @@ -105,6 +105,16 @@ _c_flags += $(if $(patsubst n%,, \
> -D__KCSAN_INSTRUMENT_BARRIERS__)
> endif
>
> +#
> +# Enable capability analysis flags only where explicitly opted in.
> +# (depends on variables CAPABILITY_ANALYSIS_obj.o, CAPABILITY_ANALYSIS)
> +#
> +ifeq ($(CONFIG_WARN_CAPABILITY_ANALYSIS),y)
> +_c_flags += $(if $(patsubst n%,, \
> + $(CAPABILITY_ANALYSIS_$(target-stem).o)$(CAPABILITY_ANALYSIS)$(if $(is-kernel-object),$(CONFIG_WARN_CAPABILITY_ANALYSIS_ALL))), \
> + $(CFLAGS_CAPABILITY_ANALYSIS))
> +endif
> +
> #
> # Enable AutoFDO build flags except some files or directories we don't want to
> # enable (depends on variables AUTOFDO_PROFILE_obj.o and AUTOFDO_PROFILE).
> --
> 2.51.0.384.g4c02a37b29-goog
>

Bart Van Assche

unread,
Sep 18, 2025, 12:04:10 PM (6 days ago) Sep 18
to Ian Rogers, Marco Elver, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
(+Steven)

This is an old option. I think this commit introduced it:

commit 52f232cb720a7babb752849cbc2cab2d24021209
Author: Steven Rostedt <ros...@goodmis.org>
Date: Wed Nov 12 00:14:40 2008 -0500

tracing: likely/unlikely branch annotation tracer

Bart.

Ian Rogers

unread,
Sep 18, 2025, 12:22:08 PM (6 days ago) Sep 18
to Marco Elver, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
On Thu, Sep 18, 2025 at 7:05 AM Marco Elver <el...@google.com> wrote:
>
> Capability analysis is a C language extension, which enables statically
> checking that user-definable "capabilities" are acquired and released where
> required. An obvious application is lock-safety checking for the kernel's
> various synchronization primitives (each of which represents a "capability"),
> and checking that locking rules are not violated.
>
> Clang originally called the feature "Thread Safety Analysis" [1], with
> some terminology still using the thread-safety-analysis-only names. This
> was later changed and the feature became more flexible, gaining the
> ability to define custom "capabilities". Its foundations can be found in
> "capability systems" [2], used to specify the permissibility of
> operations to depend on some capability being held (or not held).
>
> Because the feature is not just able to express capabilities related to
> synchronization primitives, the naming chosen for the kernel departs
> from Clang's initial "Thread Safety" nomenclature and refers to the
> feature as "Capability Analysis" to avoid confusion. The implementation
> still makes references to the older terminology in some places, such as
> `-Wthread-safety` being the warning enabled option that also still
> appears in diagnostic messages.
>
> Enabling capability analysis can be seen as enabling a dialect of Linux
> C with a Capability System.
>
> Additional details can be found in the added kernel-doc documentation.
> An LWN article covered v2 of the series: https://lwn.net/Articles/1012990/
> === Development Approach ===
>
> Prior art exists in the form of Sparse's context tracking. Locking
> annotations on functions exist, so the concept of analyzing locking rules
> is not foreign to the kernel's codebase.
>
> However, Clang's analysis is more complete vs. Sparse's, with the
> typical trade-offs in static analysis: improved completeness is
> sacrificed for more possible false positives or additional annotations
> required by the programmer. Numerous options exist to disable or opt out
> certain code from analysis.
>
> This series initially aimed to retain compatibility with Sparse, which
> can provide tree-wide analysis of a subset of the capability analysis
> introduced, but it was later decided to drop Sparse compatibility. For
> the most part, the new (and old) keywords used for annotations remain
> the same, and many of the pre-existing annotations remain valid.
>
> One big question is how to enable this feature, given we end up with a
> new dialect of C -- 2 approaches have been considered:
>
> A. Tree-wide all-or-nothing approach. This approach requires tree-wide
> changes, adding annotations or selective opt-outs. Making additional
> primitives capability-enabled increases churn, esp. where maintainers
> are unaware of the feature's existence and how to use it.
>
> Because we can't change the programming language (even if from one C
> dialect to another) of the kernel overnight, a different approach might
> cause less friction.
>
> B. A selective, incremental, and much less intrusive approach.
> Maintainers of subsystems opt in their modules or directories into
> "capability analysis" (via Makefile):
>
> CAPABILITY_ANALYSIS_foo.o := y # foo.o only
> CAPABILITY_ANALYSIS := y # all TUs
>
> Most (eventually all) synchronization primitives and more
> capabilities (including ones that could track "irq disabled",
> "preemption" disabled, etc.) could be supported.
>
> The approach taken by this series is B. This ensures that only
> subsystems where maintainers are willing to deal with any warnings are
> opted-in. Introducing the feature can be done incrementally, without
> large tree-wide changes and adding numerous opt-outs and annotations to
> the majority of code.
>
> Note: Bart Van Assche concurrently worked on enabling -Wthread-safety:
> https://lore.kernel.org/all/20250206175114.19...@acm.org/
> Bart's work has shown what it might take to go with approach A
> (tree-wide, restricted to 'mutex' usage). This has shown that the
> analysis finds real issues when applied to enough subsystems! We hope
> this serves as motivation to eventually enable the analysis in as many
> subsystems as possible, particularly subsystems that are not as easily
> tested by CI systems and test robots.
>
> === Initial Uses ===
>
> With this initial series, the following synchronization primitives are
> supported: `raw_spinlock_t`, `spinlock_t`, `rwlock_t`, `mutex`,
> `seqlock_t`, `bit_spinlock`, RCU, SRCU (`srcu_struct`), `rw_semaphore`,
> `local_lock_t`, `ww_mutex`.
>
> To demonstrate use of the feature on real kernel code, the series also
> enables capability analysis for the following subsystems:
>
> * kernel/kcov
> * kernel/kcsan
> * kernel/sched/
> * lib/rhashtable
> * lib/stackdepot
> * mm/kfence
> * security/tomoyo
> * crypto/
>
> The initial benefits are static detection of violations of locking
> rules. As more capabilities are added, we would see more static checking
> beyond what regular C can provide, all while remaining easy (read quick)
> to use via the Clang compiler.
>
> Note: The kernel already provides dynamic analysis tools Lockdep and
> KCSAN for lock-safety checking and data-race detection respectively.
> Unlike those, Clang's capability analysis is a compile-time static
> analysis with no runtime impact. The static analysis complements
> existing dynamic analysis tools, as it may catch some issues before
> even getting into a running kernel, but is *not* a replacement for
> whole-kernel testing with the dynamic analysis tools enabled!
>
> === Appendix ===
>
> A Clang version that supports `-Wthread-safety-pointer` and the new
> alias-analysis of capability pointers is required (from this version
> onwards):
>
> https://github.com/llvm/llvm-project/commit/b4c98fcbe1504841203e610c351a3227f36c92a4 [3]
>
> This series is also available at this Git tree:
>
> https://git.kernel.org/pub/scm/linux/kernel/git/melver/linux.git/log/?h=cap-analysis/dev
>
> === Changelog ===
>
> v3:
>
> - Bump min. Clang version to 22+ (unreleased), which now supports:
>
> * re-entrancy via __attribute__((reentrant_capability));
> * basic form of capability alias analysis [3] - which is the
> biggest improvement since v2.
>
> This was the result of conclusions from this discussion:
> https://lore.kernel.org/all/CANpmjNPquO=W1JAh1FNQb8pMQjgeZAKCPQUAd7qUg=5pjJ6x=Q...@mail.gmail.com/
>
> - Rename __asserts_cap/__assert_cap to __assumes_cap/__assume_cap.
>
> - Switch to DECLARE_LOCK_GUARD_1_ATTRS().
>
> - Add __acquire_ret and __acquire_shared_ret helper macros - can be
> used to define function-like macros that return objects which
> contains a held capabilities. Works now because of capability alias
> analysis.
>
> - Add capability_unsafe_alias() helper, where the analysis rightfully
> points out we're doing strange things with aliases but we don't
> care.
>
> - Support multi-argument attributes.
>
> - Enable for kernel/sched/{core,fair}.c, kernel/kcsan.
> - Drop drivers/tty changes (revisit later).
>
> v2: https://lore.kernel.org/all/20250304092417....@google.com/
>
> - Remove Sparse context tracking support - after the introduction of
> Clang support, so that backports can skip removal of Sparse support.
>
> - Remove __cond_lock() function-like helper.
>
> - ww_mutex support.
>
> - -Wthread-safety-addressof was reworked and committed in upstream
> Clang as -Wthread-safety-pointer.
>
> - Make __cond_acquires() and __cond_acquires_shared() take abstract
> value, since compiler only cares about zero and non-zero.
>
> - Rename __var_guarded_by to simply __guarded_by. Initially the idea
> was to be explicit about if the variable itself or the pointed-to
> data is guarded, but in the long-term, making this shorter might be
> better.
>
> - Likewise rename __ref_guarded_by to __pt_guarded_by.
>
> - Introduce common header warning suppressions - this is a better
> solution than guarding header inclusions with disable_ +
> enable_capability_analysis(). Header suppressions are disabled when
> selecting CONFIG_WARN_CAPABILITY_ANALYSIS_ALL=y. This bumps the
> minimum Clang version required to 20+.
>
> - Make the data_race() macro imply disabled capability analysis.
> Writing capability_unsafe(data_race(..)) is unnecessarily verbose
> and data_race() on its own already indicates something subtly unsafe
> is happening. This change was made after analysis of a finding in
> security/tomoyo.
>
> - Enable analysis in the following subsystems as additional examples
> of larger subsystem. Where it was obvious, the __guarded_by
> attribute was added to lock-guarded variables to improve coverage.
>
> * drivers/tty
> * security/tomoyo
> * crypto/
>
> RFC v1: https://lore.kernel.org/lkml/20250206181711....@google.com

Thanks for this and lgtm. Fwiw, there is already thread safety
analysis in tools/perf:
https://git.kernel.org/pub/scm/linux/kernel/git/perf/perf-tools-next.git/tree/tools/perf/util/mutex.h?h=perf-tools-next#n43
and we should migrate that code to use this code.

Something that I've wondered about capabilities is to use them for
detecting missing reference count "puts", which feel similar to missed
unlocks. In my experience the sanitizers are weak in this area as in
C++ you can trivially use RAII, however, frustratingly clang's
capability analysis is disabled in C++'s constructors and destructors
(not an issue here :-) and based on my rusty memory). To solve this
for perf (and fix many many bugs) we did a form of runtime RAII:
https://perfwiki.github.io/main/reference-count-checking/
There is likely something better than can be done with the nearly RAII
that is/are cleanups. Trying to make that sane for a data-structure
like an rbtree is hard and maybe rust is just the only solution there.
Anyway, it is great to see thread safety analysis pushed forward.

Thanks,
Ian

> Marco Elver (35):
> compiler_types: Move lock checking attributes to
> compiler-capability-analysis.h
> compiler-capability-analysis: Add infrastructure for Clang's
> capability analysis
> compiler-capability-analysis: Add test stub
> Documentation: Add documentation for Compiler-Based Capability
> Analysis
> checkpatch: Warn about capability_unsafe() without comment
> cleanup: Basic compatibility with capability analysis
> lockdep: Annotate lockdep assertions for capability analysis
> locking/rwlock, spinlock: Support Clang's capability analysis
> compiler-capability-analysis: Change __cond_acquires to take return
> value
> locking/mutex: Support Clang's capability analysis
> locking/seqlock: Support Clang's capability analysis
> bit_spinlock: Include missing <asm/processor.h>
> bit_spinlock: Support Clang's capability analysis
> rcu: Support Clang's capability analysis
> srcu: Support Clang's capability analysis
> kref: Add capability-analysis annotations
> locking/rwsem: Support Clang's capability analysis
> locking/local_lock: Include missing headers
> locking/local_lock: Support Clang's capability analysis
> locking/ww_mutex: Support Clang's capability analysis
> debugfs: Make debugfs_cancellation a capability struct
> compiler-capability-analysis: Remove Sparse support
> compiler-capability-analysis: Remove __cond_lock() function-like
> helper
> compiler-capability-analysis: Introduce header suppressions
> compiler: Let data_race() imply disabled capability analysis
> MAINTAINERS: Add entry for Capability Analysis
> kfence: Enable capability analysis
> kcov: Enable capability analysis
> kcsan: Enable capability analysis
> stackdepot: Enable capability analysis
> rhashtable: Enable capability analysis
> printk: Move locking annotation to printk.c
> security/tomoyo: Enable capability analysis
> crypto: Enable capability analysis
> sched: Enable capability analysis for core.c and fair.c
>
> .../dev-tools/capability-analysis.rst | 148 +++++
> Documentation/dev-tools/index.rst | 1 +
> Documentation/dev-tools/sparse.rst | 19 -
> Documentation/mm/process_addrs.rst | 6 +-
> MAINTAINERS | 11 +
> Makefile | 1 +
> crypto/Makefile | 2 +
> crypto/acompress.c | 6 +-
> crypto/algapi.c | 2 +
> crypto/api.c | 1 +
> crypto/crypto_engine.c | 2 +-
> crypto/drbg.c | 5 +
> crypto/internal.h | 2 +-
> crypto/proc.c | 3 +
> crypto/scompress.c | 24 +-
> .../net/wireless/intel/iwlwifi/iwl-trans.c | 4 +-
> .../net/wireless/intel/iwlwifi/iwl-trans.h | 6 +-
> .../intel/iwlwifi/pcie/gen1_2/internal.h | 5 +-
> .../intel/iwlwifi/pcie/gen1_2/trans.c | 4 +-
> fs/dlm/lock.c | 2 +-
> include/crypto/internal/acompress.h | 7 +-
> include/crypto/internal/engine.h | 2 +-
> include/linux/bit_spinlock.h | 24 +-
> include/linux/cleanup.h | 17 +
> include/linux/compiler-capability-analysis.h | 423 +++++++++++++
> include/linux/compiler.h | 2 +
> include/linux/compiler_types.h | 18 +-
> include/linux/console.h | 4 +-
> include/linux/debugfs.h | 12 +-
> include/linux/kref.h | 2 +
> include/linux/list_bl.h | 2 +
> include/linux/local_lock.h | 45 +-
> include/linux/local_lock_internal.h | 73 ++-
> include/linux/lockdep.h | 12 +-
> include/linux/mm.h | 33 +-
> include/linux/mutex.h | 35 +-
> include/linux/mutex_types.h | 4 +-
> include/linux/rcupdate.h | 86 +--
> include/linux/refcount.h | 6 +-
> include/linux/rhashtable.h | 14 +-
> include/linux/rwlock.h | 22 +-
> include/linux/rwlock_api_smp.h | 43 +-
> include/linux/rwlock_rt.h | 44 +-
> include/linux/rwlock_types.h | 10 +-
> include/linux/rwsem.h | 66 +-
> include/linux/sched.h | 6 +-
> include/linux/sched/signal.h | 16 +-
> include/linux/sched/task.h | 5 +-
> include/linux/sched/wake_q.h | 3 +
> include/linux/seqlock.h | 24 +
> include/linux/seqlock_types.h | 5 +-
> include/linux/spinlock.h | 89 ++-
> include/linux/spinlock_api_smp.h | 34 +-
> include/linux/spinlock_api_up.h | 112 +++-
> include/linux/spinlock_rt.h | 37 +-
> include/linux/spinlock_types.h | 10 +-
> include/linux/spinlock_types_raw.h | 5 +-
> include/linux/srcu.h | 60 +-
> include/linux/srcutiny.h | 4 +
> include/linux/srcutree.h | 6 +-
> include/linux/ww_mutex.h | 22 +-
> kernel/Makefile | 2 +
> kernel/kcov.c | 36 +-
> kernel/kcsan/Makefile | 2 +
> kernel/kcsan/report.c | 11 +-
> kernel/printk/printk.c | 2 +
> kernel/sched/Makefile | 3 +
> kernel/sched/core.c | 89 ++-
> kernel/sched/fair.c | 9 +-
> kernel/sched/sched.h | 110 +++-
> kernel/signal.c | 4 +-
> kernel/time/posix-timers.c | 13 +-
> lib/Kconfig.debug | 45 ++
> lib/Makefile | 6 +
> lib/dec_and_lock.c | 8 +-
> lib/rhashtable.c | 5 +-
> lib/stackdepot.c | 20 +-
> lib/test_capability-analysis.c | 596 ++++++++++++++++++
> mm/kfence/Makefile | 2 +
> mm/kfence/core.c | 20 +-
> mm/kfence/kfence.h | 14 +-
> mm/kfence/report.c | 4 +-
> mm/memory.c | 4 +-
> mm/pgtable-generic.c | 19 +-
> net/ipv4/tcp_sigpool.c | 2 +-
> scripts/Makefile.capability-analysis | 11 +
> scripts/Makefile.lib | 10 +
> scripts/capability-analysis-suppression.txt | 33 +
> scripts/checkpatch.pl | 8 +
> security/tomoyo/Makefile | 2 +
> security/tomoyo/common.c | 52 +-
> security/tomoyo/common.h | 77 +--
> security/tomoyo/domain.c | 1 +
> security/tomoyo/environ.c | 1 +
> security/tomoyo/file.c | 5 +
> security/tomoyo/gc.c | 28 +-
> security/tomoyo/mount.c | 2 +
> security/tomoyo/network.c | 3 +
> tools/include/linux/compiler_types.h | 2 -
> 99 files changed, 2370 insertions(+), 589 deletions(-)
> create mode 100644 Documentation/dev-tools/capability-analysis.rst
> create mode 100644 include/linux/compiler-capability-analysis.h
> create mode 100644 lib/test_capability-analysis.c
> create mode 100644 scripts/Makefile.capability-analysis
> create mode 100644 scripts/capability-analysis-suppression.txt
>
> --
> 2.51.0.384.g4c02a37b29-goog
>

Nathan Chancellor

unread,
Sep 18, 2025, 1:46:11 PM (6 days ago) Sep 18
to Christoph Hellwig, Marco Elver, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
I can use my existing kernel.org LLVM [1] build infrastructure to
generate prebuilt x86 binaries. Just give me a bit to build and upload
them. You may not be the only developer or maintainer who may want to
play with this.

[1]: https://kernel.org/pub/tools/llvm/

Cheers,
Nathan

Nathan Chancellor

unread,
Sep 18, 2025, 3:40:40 PM (6 days ago) Sep 18
to Christoph Hellwig, Marco Elver, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
This should include Marco's change, let me know if there are any issues.

https://kernel.org/pub/tools/llvm/files/prerelease/llvm-22.0.0-e19fa930ca838715028c00c234874d1db4f93154-20250918-184558-x86_64.tar.xz

Cheers,
Nathan

syzbot ci

unread,
Sep 18, 2025, 3:41:28 PM (6 days ago) Sep 18
to ar...@arndb.de, boqun...@gmail.com, bvana...@acm.org, cor...@lwn.net, da...@davemloft.net, dvy...@google.com, edum...@google.com, el...@google.com, fred...@kernel.org, gli...@google.com, gre...@linuxfoundation.org, h...@lst.de, her...@gondor.apana.org.au, iro...@google.com, ja...@google.com, joela...@nvidia.com, jo...@joshtriplett.org, justi...@google.com, kasa...@googlegroups.com, ke...@kernel.org, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, lon...@redhat.com, luc.vano...@gmail.com, lukas....@gmail.com, mark.r...@arm.com, mathieu....@efficios.com, mi...@kernel.org, mi...@redhat.com, mo...@google.com, nat...@kernel.org, neeraj....@kernel.org, nick.des...@gmail.com, oj...@kernel.org, pau...@kernel.org, penguin...@i-love.sakura.ne.jp, pet...@infradead.org, r...@vger.kernel.org, ros...@goodmis.org, take...@nttdata.co.jp, tg...@linutronix.de, tg...@suug.ch, ure...@gmail.com, wi...@kernel.org, syz...@lists.linux.dev, syzkall...@googlegroups.com
syzbot ci has tested the following series

[v3] Compiler-Based Capability- and Locking-Analysis
https://lore.kernel.org/all/20250918140451....@google.com
* [PATCH v3 01/35] compiler_types: Move lock checking attributes to compiler-capability-analysis.h
* [PATCH v3 02/35] compiler-capability-analysis: Add infrastructure for Clang's capability analysis
* [PATCH v3 03/35] compiler-capability-analysis: Add test stub
* [PATCH v3 04/35] Documentation: Add documentation for Compiler-Based Capability Analysis
* [PATCH v3 05/35] checkpatch: Warn about capability_unsafe() without comment
* [PATCH v3 06/35] cleanup: Basic compatibility with capability analysis
* [PATCH v3 07/35] lockdep: Annotate lockdep assertions for capability analysis
* [PATCH v3 08/35] locking/rwlock, spinlock: Support Clang's capability analysis
* [PATCH v3 09/35] compiler-capability-analysis: Change __cond_acquires to take return value
* [PATCH v3 10/35] locking/mutex: Support Clang's capability analysis
* [PATCH v3 11/35] locking/seqlock: Support Clang's capability analysis
* [PATCH v3 12/35] bit_spinlock: Include missing <asm/processor.h>
* [PATCH v3 13/35] bit_spinlock: Support Clang's capability analysis
* [PATCH v3 14/35] rcu: Support Clang's capability analysis
* [PATCH v3 15/35] srcu: Support Clang's capability analysis
* [PATCH v3 16/35] kref: Add capability-analysis annotations
* [PATCH v3 17/35] locking/rwsem: Support Clang's capability analysis
* [PATCH v3 18/35] locking/local_lock: Include missing headers
* [PATCH v3 19/35] locking/local_lock: Support Clang's capability analysis
* [PATCH v3 20/35] locking/ww_mutex: Support Clang's capability analysis
* [PATCH v3 21/35] debugfs: Make debugfs_cancellation a capability struct
* [PATCH v3 22/35] compiler-capability-analysis: Remove Sparse support
* [PATCH v3 23/35] compiler-capability-analysis: Remove __cond_lock() function-like helper
* [PATCH v3 24/35] compiler-capability-analysis: Introduce header suppressions
* [PATCH v3 25/35] compiler: Let data_race() imply disabled capability analysis
* [PATCH v3 26/35] MAINTAINERS: Add entry for Capability Analysis
* [PATCH v3 27/35] kfence: Enable capability analysis
* [PATCH v3 28/35] kcov: Enable capability analysis
* [PATCH v3 29/35] kcsan: Enable capability analysis
* [PATCH v3 30/35] stackdepot: Enable capability analysis
* [PATCH v3 31/35] rhashtable: Enable capability analysis
* [PATCH v3 32/35] printk: Move locking annotation to printk.c
* [PATCH v3 33/35] security/tomoyo: Enable capability analysis
* [PATCH v3 34/35] crypto: Enable capability analysis
* [PATCH v3 35/35] sched: Enable capability analysis for core.c and fair.c

and found the following issue:
general protection fault in validate_page_before_insert

Full report is available here:
https://ci.syzbot.org/series/81182522-74c0-4494-bcf8-976133df7dc7

***

general protection fault in validate_page_before_insert

tree: torvalds
URL: https://kernel.googlesource.com/pub/scm/linux/kernel/git/torvalds/linux
base: f83ec76bf285bea5727f478a68b894f5543ca76e
arch: amd64
compiler: Debian clang version 20.1.8 (++20250708063551+0c9f909b7976-1~exp1~20250708183702.136), Debian LLD 20.1.8
config: https://ci.syzbot.org/builds/8f7ff868-4cf7-40da-b62b-45ebfec4e994/config

cgroup: Unknown subsys name 'net'
cgroup: Unknown subsys name 'cpuset'
cgroup: Unknown subsys name 'rlimit'
Oops: general protection fault, probably for non-canonical address 0xdffffc0000000001: 0000 [#1] SMP KASAN PTI
KASAN: null-ptr-deref in range [0x0000000000000008-0x000000000000000f]
CPU: 0 UID: 0 PID: 5775 Comm: syz-executor Not tainted syzkaller #0 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-debian-1.16.2-1 04/01/2014
RIP: 0010:validate_page_before_insert+0x2a/0x300
Code: 55 41 57 41 56 41 55 41 54 53 48 89 f3 49 89 fe 49 bd 00 00 00 00 00 fc ff df e8 f1 3f b3 ff 4c 8d 7b 08 4c 89 f8 48 c1 e8 03 <42> 80 3c 28 00 74 08 4c 89 ff e8 17 b3 16 00 4d 8b 3f 4c 89 fe 48
RSP: 0018:ffffc90002a5f608 EFLAGS: 00010202
RAX: 0000000000000001 RBX: 0000000000000000 RCX: ffff888022891cc0
RDX: 0000000000000000 RSI: 0000000000000000 RDI: ffff888028c71200
RBP: ffffc90002a5f720 R08: 0000000000000000 R09: 1ffff11021cf81e0
R10: dffffc0000000000 R11: ffffed1021cf81e1 R12: dffffc0000000000
R13: dffffc0000000000 R14: ffff888028c71200 R15: 0000000000000008
FS: 00005555815ad500(0000) GS:ffff8880b8615000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007f1788fd20b8 CR3: 000000010d8a4000 CR4: 00000000000006f0
Call Trace:
<TASK>
insert_page+0x90/0x2c0
kcov_mmap+0xc3/0x130
mmap_region+0x18ae/0x20c0
do_mmap+0xc45/0x10d0
vm_mmap_pgoff+0x2a6/0x4d0
ksys_mmap_pgoff+0x51f/0x760
do_syscall_64+0xfa/0x3b0
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f1788d8ebe3
Code: f7 d8 64 89 01 48 83 c8 ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 00 41 89 ca 41 f7 c1 ff 0f 00 00 75 14 b8 09 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 25 c3 0f 1f 40 00 48 c7 c0 a8 ff ff ff 64 c7
RSP: 002b:00007ffc8a37e638 EFLAGS: 00000246 ORIG_RAX: 0000000000000009
RAX: ffffffffffffffda RBX: 00007ffc8a37e670 RCX: 00007f1788d8ebe3
RDX: 0000000000000003 RSI: 0000000000400000 RDI: 00007f17867ff000
RBP: 00007ffc8a37e940 R08: 00000000000000d8 R09: 0000000000000000
R10: 0000000000000011 R11: 0000000000000246 R12: 0000000000000003
R13: 0000000000000000 R14: 00007f1788fa11c0 R15: 00007f1788e2e478
</TASK>
Modules linked in:
---[ end trace 0000000000000000 ]---
RIP: 0010:validate_page_before_insert+0x2a/0x300
Code: 55 41 57 41 56 41 55 41 54 53 48 89 f3 49 89 fe 49 bd 00 00 00 00 00 fc ff df e8 f1 3f b3 ff 4c 8d 7b 08 4c 89 f8 48 c1 e8 03 <42> 80 3c 28 00 74 08 4c 89 ff e8 17 b3 16 00 4d 8b 3f 4c 89 fe 48
RSP: 0018:ffffc90002a5f608 EFLAGS: 00010202
RAX: 0000000000000001 RBX: 0000000000000000 RCX: ffff888022891cc0
RDX: 0000000000000000 RSI: 0000000000000000 RDI: ffff888028c71200
RBP: ffffc90002a5f720 R08: 0000000000000000 R09: 1ffff11021cf81e0
R10: dffffc0000000000 R11: ffffed1021cf81e1 R12: dffffc0000000000
R13: dffffc0000000000 R14: ffff888028c71200 R15: 0000000000000008
FS: 00005555815ad500(0000) GS:ffff8880b8615000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007f1788fd20b8 CR3: 000000010d8a4000 CR4: 00000000000006f0


***

If these findings have caused you to resend the series or submit a
separate fix, please add the following tag to your commit message:
Tested-by: syz...@syzkaller.appspotmail.com

---
This report is generated by a bot. It may contain errors.
syzbot ci engineers can be reached at syzk...@googlegroups.com.

Joe Perches

unread,
Sep 18, 2025, 4:37:07 PM (6 days ago) Sep 18
to Marco Elver, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
On Thu, 2025-09-18 at 15:59 +0200, Marco Elver wrote:
> Warn about applications of capability_unsafe() without a comment, to
> encourage documenting the reasoning behind why it was deemed safe.
[]
> diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl
[]
> @@ -6717,6 +6717,14 @@ sub process {
> }
> }
>
> +# check for capability_unsafe without a comment.
> + if ($line =~ /\bcapability_unsafe\b/) {
> + if (!ctx_has_comment($first_line, $linenr)) {
> + WARN("CAPABILITY_UNSAFE",
> + "capability_unsafe without comment\n" . $herecurr);

while most of these are using the same multi-line style
I'd prefer combining and reducing indentation

if ($line =~ /\bcapability_unsafe\b/ &&
!ctx_has_comment($first_line, $linenr)) {
WARN(etc...

Marco Elver

unread,
Sep 18, 2025, 5:26:29 PM (6 days ago) Sep 18
to Linus Torvalds, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
On Thu, Sep 18, 2025 at 08:49AM -0700, Linus Torvalds wrote:

> I'd suggest just doing a search-and-replace of 's/capability/context/'
> and it would already make things a ton better. But maybe there are
> better names for this still?

Fair points. "Context Analysis" makes sense, but it makes the thing
(e.g. lock) used to establish that context a little awkward to refer to
-- see half-baked attempt at reworking the documentation below.

Maybe this:

Instance that must be acquired to enter context = "Context Guard"?

We can then still call it "Context Analysis". And I need to be mindful
of calling the objects themselves "Context Guard" throughout that
search-and-replace. E.g. the macro to create a context-guard-enabled
struct would be "context_guard_struct(spinlock) { ..".

I also thought about "Guard Analysis", but that sounds wrong, too.
Because we also have overloaded "guard(..)" (<linux/cleanup.h>).

Preferences?

[...]
> And if not "context", maybe some other word? But really, absolutely
> *not* "capability". Because that's just crazy talk.
>
> Please? Because other than this naming issue, I think this really is a
> good idea.

Thanks,
-- Marco

------ >8 ------

diff --git a/Documentation/dev-tools/capability-analysis.rst b/Documentation/dev-tools/capability-analysis.rst
index 3456132261c6..b0c0961d6af5 100644
--- a/Documentation/dev-tools/capability-analysis.rst
+++ b/Documentation/dev-tools/capability-analysis.rst
@@ -1,80 +1,79 @@
.. SPDX-License-Identifier: GPL-2.0
.. Copyright (C) 2025, Google LLC.

-.. _capability-analysis:
+.. _context-analysis:

-Compiler-Based Capability Analysis
-==================================
+Compiler-Based Context Analysis
+===============================

-Capability analysis is a C language extension, which enables statically
-checking that user-definable "capabilities" are acquired and released where
-required. An obvious application is lock-safety checking for the kernel's
-various synchronization primitives (each of which represents a "capability"),
-and checking that locking rules are not violated.
+Context analysis is a C language extension, which enables statically checking
+that user-definable contexts are acquired and released where required. An
+obvious application is lock-safety checking for the kernel's various
+synchronization primitives (each of which represents a context if held), and
+checking that locking rules are not violated.

-The Clang compiler currently supports the full set of capability analysis
+The Clang compiler currently supports the full set of context analysis
features. To enable for Clang, configure the kernel with::

- CONFIG_WARN_CAPABILITY_ANALYSIS=y
+ CONFIG_WARN_CONTEXT_ANALYSIS=y

The feature requires Clang 22 or later.

The analysis is *opt-in by default*, and requires declaring which modules and
subsystems should be analyzed in the respective `Makefile`::

- CAPABILITY_ANALYSIS_mymodule.o := y
+ CONTEXT_ANALYSIS_mymodule.o := y

Or for all translation units in the directory::

- CAPABILITY_ANALYSIS := y
+ CONTEXT_ANALYSIS := y

It is possible to enable the analysis tree-wide, however, which will result in
numerous false positive warnings currently and is *not* generally recommended::

- CONFIG_WARN_CAPABILITY_ANALYSIS_ALL=y
+ CONFIG_WARN_CONTEXT_ANALYSIS_ALL=y

Programming Model
-----------------

-The below describes the programming model around using capability-enabled
-types.
+The below describes the programming model around using context-enabled types.

.. note::
- Enabling capability analysis can be seen as enabling a dialect of Linux C with
- a Capability System. Some valid patterns involving complex control-flow are
+ Enabling context analysis can be seen as enabling a dialect of Linux C with
+ a Context System. Some valid patterns involving complex control-flow are
constrained (such as conditional acquisition and later conditional release
- in the same function, or returning pointers to capabilities from functions.
+ in the same function).

-Capability analysis is a way to specify permissibility of operations to depend
-on capabilities being held (or not held). Typically we are interested in
-protecting data and code by requiring some capability to be held, for example a
-specific lock. The analysis ensures that the caller cannot perform the
-operation without holding the appropriate capability.
+Context analysis is a way to specify permissibility of operations to depend on
+contexts being held (or not held). Typically we are interested in protecting
+data and code in a critical section by requiring a specific context to be held,
+for example a specific lock. The analysis ensures that the caller cannot
+perform the operation without holding the appropriate context.

-Capabilities are associated with named structs, along with functions that
-operate on capability-enabled struct instances to acquire and release the
-associated capability.
+Contexts are associated with named structs, along with functions that operate
+on context-enabled struct instances to acquire and release the associated
+context.

-Capabilities can be held either exclusively or shared. This mechanism allows
-assign more precise privileges when holding a capability, typically to
+Contexts can be held either exclusively or shared. This mechanism allows
+assigning more precise privileges when holding a context, typically to
distinguish where a thread may only read (shared) or also write (exclusive) to
guarded data.

-The set of capabilities that are actually held by a given thread at a given
-point in program execution is a run-time concept. The static analysis works by
-calculating an approximation of that set, called the capability environment.
-The capability environment is calculated for every program point, and describes
-the set of capabilities that are statically known to be held, or not held, at
-that particular point. This environment is a conservative approximation of the
-full set of capabilities that will actually held by a thread at run-time.
+The set of contexts that are actually held by a given thread at a given point
+in program execution is a run-time concept. The static analysis works by
+calculating an approximation of that set, called the context environment. The
+context environment is calculated for every program point, and describes the
+set of contexts that are statically known to be held, or not held, at that
+particular point. This environment is a conservative approximation of the full
+set of contexts that will actually held by a thread at run-time.

More details are also documented `here
<https://clang.llvm.org/docs/ThreadSafetyAnalysis.html>`_.

.. note::
- Clang's analysis explicitly does not infer capabilities acquired or released
+ Clang's analysis explicitly does not infer contexts acquired or released
by inline functions. It requires explicit annotations to (a) assert that
- it's not a bug if a capability is released or acquired, and (b) to retain
+ it's not a bug if a context is released or acquired, and (b) to retain
consistency between inline and non-inline function declarations.

Supported Kernel Primitives
@@ -85,13 +84,13 @@ Currently the following synchronization primitives are supported:
`bit_spinlock`, RCU, SRCU (`srcu_struct`), `rw_semaphore`, `local_lock_t`,
`ww_mutex`.

-For capabilities with an initialization function (e.g., `spin_lock_init()`),
-calling this function on the capability instance before initializing any
-guarded members or globals prevents the compiler from issuing warnings about
-unguarded initialization.
+For contexts with an initialization function (e.g., `spin_lock_init()`),
+calling this function on the context instance before initializing any guarded
+members or globals prevents the compiler from issuing warnings about unguarded
+initialization.

Lockdep assertions, such as `lockdep_assert_held()`, inform the compiler's
-capability analysis that the associated synchronization primitive is held after
+context analysis that the associated synchronization primitive is held after
the assertion. This avoids false positives in complex control-flow scenarios
and encourages the use of Lockdep where static analysis is limited. For
example, this is useful when a function doesn't *always* require a lock, making
@@ -100,9 +99,9 @@ example, this is useful when a function doesn't *always* require a lock, making
Keywords
~~~~~~~~

-.. kernel-doc:: include/linux/compiler-capability-analysis.h
- :identifiers: struct_with_capability
- token_capability token_capability_instance
+.. kernel-doc:: include/linux/compiler-context-analysis.h
+ :identifiers: struct_with_context
+ token_context token_context_instance
__guarded_by __pt_guarded_by
__must_hold
__must_not_hold
@@ -117,13 +116,13 @@ Keywords
__release
__acquire_shared
__release_shared
- capability_unsafe
- __capability_unsafe
- disable_capability_analysis enable_capability_analysis
+ context_unsafe
+ __context_unsafe
+ disable_context_analysis enable_context_analysis

.. note::
- The function attribute `__no_capability_analysis` is reserved for internal
- implementation of capability-enabled primitives, and should be avoided in
+ The function attribute `__no_context_analysis` is reserved for internal
+ implementation of context-enabled primitives, and should be avoided in
normal code.

Background
@@ -140,9 +139,10 @@ Indeed, its foundations can be found in `capability systems
the permissibility of operations to depend on some capability being held (or
not held).

-Because the feature is not just able to express capabilities related to
-synchronization primitives, the naming chosen for the kernel departs from
-Clang's initial "Thread Safety" nomenclature and refers to the feature as
-"Capability Analysis" to avoid confusion. The implementation still makes
-references to the older terminology in some places, such as `-Wthread-safety`
-being the warning option that also still appears in diagnostic messages.
+Because the feature is not just able to express contexts related to
+synchronization primitives, and "capability" is already overloaded in the
+kernel, the naming chosen for the kernel departs from Clang's initial "Thread
+Safety" and "Capability" nomenclature and refers to the feature as "Context
+Analysis" to avoid confusion. The internal implementation still makes
+references to Clang's terminology, such as `-Wthread-safety` being the warning
+option that also still appears in diagnostic messages.
diff --git a/include/linux/compiler-capability-analysis.h b/include/linux/compiler-capability-analysis.h
index f8a1da67589c..7882684a8308 100644
--- a/include/linux/compiler-capability-analysis.h
+++ b/include/linux/compiler-capability-analysis.h
@@ -1,42 +1,43 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
- * Macros and attributes for compiler-based static capability analysis.
+ * Macros and attributes for compiler-based static context analysis.
*/

-#ifndef _LINUX_COMPILER_CAPABILITY_ANALYSIS_H
-#define _LINUX_COMPILER_CAPABILITY_ANALYSIS_H
+#ifndef _LINUX_COMPILER_CONTEXT_ANALYSIS_H
+#define _LINUX_COMPILER_CONTEXT_ANALYSIS_H

-#if defined(WARN_CAPABILITY_ANALYSIS)
+#if defined(WARN_CONTEXT_ANALYSIS)

/*
- * The below attributes are used to define new capability types. Internal only.
- */
-# define __cap_type(name) __attribute__((capability(#name)))
-# define __reentrant_cap __attribute__((reentrant_capability))
-# define __acquires_cap(...) __attribute__((acquire_capability(__VA_ARGS__)))
-# define __acquires_shared_cap(...) __attribute__((acquire_shared_capability(__VA_ARGS__)))
-# define __try_acquires_cap(ret, var) __attribute__((try_acquire_capability(ret, var)))
-# define __try_acquires_shared_cap(ret, var) __attribute__((try_acquire_shared_capability(ret, var)))
-# define __releases_cap(...) __attribute__((release_capability(__VA_ARGS__)))
-# define __releases_shared_cap(...) __attribute__((release_shared_capability(__VA_ARGS__)))
-# define __assumes_cap(...) __attribute__((assert_capability(__VA_ARGS__)))
-# define __assumes_shared_cap(...) __attribute__((assert_shared_capability(__VA_ARGS__)))
-# define __returns_cap(var) __attribute__((lock_returned(var)))
+ * The below attributes are used to define new context (Clang: capability) types.
+ * Internal only.
+ */
+# define __ctx_type(name) __attribute__((capability(#name)))
+# define __reentrant_ctx __attribute__((reentrant_capability))
+# define __acquires_ctx(...) __attribute__((acquire_capability(__VA_ARGS__)))
+# define __acquires_shared_ctx(...) __attribute__((acquire_shared_capability(__VA_ARGS__)))
+# define __try_acquires_ctx(ret, var) __attribute__((try_acquire_capability(ret, var)))
+# define __try_acquires_shared_ctx(ret, var) __attribute__((try_acquire_shared_capability(ret, var)))
+# define __releases_ctx(...) __attribute__((release_capability(__VA_ARGS__)))
+# define __releases_shared_ctx(...) __attribute__((release_shared_capability(__VA_ARGS__)))
+# define __assumes_ctx(...) __attribute__((assert_capability(__VA_ARGS__)))
+# define __assumes_shared_ctx(...) __attribute__((assert_shared_capability(__VA_ARGS__)))
+# define __returns_ctx(var) __attribute__((lock_returned(var)))

/*
* The below are used to annotate code being checked. Internal only.
*/
-# define __excludes_cap(...) __attribute__((locks_excluded(__VA_ARGS__)))
-# define __requires_cap(...) __attribute__((requires_capability(__VA_ARGS__)))
-# define __requires_shared_cap(...) __attribute__((requires_shared_capability(__VA_ARGS__)))
+# define __excludes_ctx(...) __attribute__((locks_excluded(__VA_ARGS__)))
+# define __requires_ctx(...) __attribute__((requires_capability(__VA_ARGS__)))
+# define __requires_shared_ctx(...) __attribute__((requires_shared_capability(__VA_ARGS__)))

/**
* __guarded_by - struct member and globals attribute, declares variable
- * protected by capability
+ * protected by context
*
* Declares that the struct member or global variable must be guarded by the
- * given capabilities. Read operations on the data require shared access,
- * while write operations require exclusive access.
+ * given context. Read operations on the data require shared access, while write
+ * operations require exclusive access.
*
* .. code-block:: c
*
@@ -49,11 +50,11 @@

/**
* __pt_guarded_by - struct member and globals attribute, declares pointed-to
- * data is protected by capability
+ * data is protected by context
*
* Declares that the data pointed to by the struct member pointer or global
- * pointer must be guarded by the given capabilities. Read operations on the
- * data require shared access, while write operations require exclusive access.
+ * pointer must be guarded by the given contexts. Read operations on the data
+ * require shared access, while write operations require exclusive access.
*
* .. code-block:: c
*
@@ -65,14 +66,14 @@
# define __pt_guarded_by(...) __attribute__((pt_guarded_by(__VA_ARGS__)))

/**
- * struct_with_capability() - declare or define a capability struct
+ * struct_with_context() - declare or define a context struct
* @name: struct name
*
- * Helper to declare or define a struct type with capability of the same name.
+ * Helper to declare or define a struct type with context of the same name.
*
* .. code-block:: c
*
- * struct_with_capability(my_handle) {
+ * struct_with_context(my_handle) {
* int foo;
* long bar;
* };
@@ -81,98 +82,98 @@
* ...
* };
* // ... declared elsewhere ...
- * struct_with_capability(some_state);
+ * struct_with_context(some_state);
*
* Note: The implementation defines several helper functions that can acquire,
- * release, and assert the capability.
- */
-# define struct_with_capability(name, ...) \
- struct __cap_type(name) __VA_ARGS__ name; \
- static __always_inline void __acquire_cap(const struct name *var) \
- __attribute__((overloadable)) __no_capability_analysis __acquires_cap(var) { } \
- static __always_inline void __acquire_shared_cap(const struct name *var) \
- __attribute__((overloadable)) __no_capability_analysis __acquires_shared_cap(var) { } \
- static __always_inline bool __try_acquire_cap(const struct name *var, bool ret) \
- __attribute__((overloadable)) __no_capability_analysis __try_acquires_cap(1, var) \
+ * release, and assert the context is held.
+ */
+# define struct_with_context(name, ...) \
+ struct __ctx_type(name) __VA_ARGS__ name; \
+ static __always_inline void __acquire_ctx(const struct name *var) \
+ __attribute__((overloadable)) __no_context_analysis __acquires_ctx(var) { } \
+ static __always_inline void __acquire_shared_ctx(const struct name *var) \
+ __attribute__((overloadable)) __no_context_analysis __acquires_shared_ctx(var) { } \
+ static __always_inline bool __try_acquire_ctx(const struct name *var, bool ret) \
+ __attribute__((overloadable)) __no_context_analysis __try_acquires_ctx(1, var) \
{ return ret; } \
- static __always_inline bool __try_acquire_shared_cap(const struct name *var, bool ret) \
- __attribute__((overloadable)) __no_capability_analysis __try_acquires_shared_cap(1, var) \
+ static __always_inline bool __try_acquire_shared_ctx(const struct name *var, bool ret) \
+ __attribute__((overloadable)) __no_context_analysis __try_acquires_shared_ctx(1, var) \
{ return ret; } \
- static __always_inline void __release_cap(const struct name *var) \
- __attribute__((overloadable)) __no_capability_analysis __releases_cap(var) { } \
- static __always_inline void __release_shared_cap(const struct name *var) \
- __attribute__((overloadable)) __no_capability_analysis __releases_shared_cap(var) { } \
- static __always_inline void __assume_cap(const struct name *var) \
- __attribute__((overloadable)) __assumes_cap(var) { } \
- static __always_inline void __assume_shared_cap(const struct name *var) \
- __attribute__((overloadable)) __assumes_shared_cap(var) { } \
+ static __always_inline void __release_ctx(const struct name *var) \
+ __attribute__((overloadable)) __no_context_analysis __releases_ctx(var) { } \
+ static __always_inline void __release_shared_ctx(const struct name *var) \
+ __attribute__((overloadable)) __no_context_analysis __releases_shared_ctx(var) { } \
+ static __always_inline void __assume_ctx(const struct name *var) \
+ __attribute__((overloadable)) __assumes_ctx(var) { } \
+ static __always_inline void __assume_shared_ctx(const struct name *var) \
+ __attribute__((overloadable)) __assumes_shared_ctx(var) { } \
struct name

/**
- * disable_capability_analysis() - disables capability analysis
+ * disable_context_analysis() - disables context analysis
*
- * Disables capability analysis. Must be paired with a later
- * enable_capability_analysis().
+ * Disables context analysis. Must be paired with a later
+ * enable_context_analysis().
*/
-# define disable_capability_analysis() \
+# define disable_context_analysis() \
__diag_push(); \
__diag_ignore_all("-Wunknown-warning-option", "") \
__diag_ignore_all("-Wthread-safety", "") \
__diag_ignore_all("-Wthread-safety-pointer", "")

/**
- * enable_capability_analysis() - re-enables capability analysis
+ * enable_context_analysis() - re-enables context analysis
*
- * Re-enables capability analysis. Must be paired with a prior
- * disable_capability_analysis().
+ * Re-enables context analysis. Must be paired with a prior
+ * disable_context_analysis().
*/
-# define enable_capability_analysis() __diag_pop()
+# define enable_context_analysis() __diag_pop()

/**
- * __no_capability_analysis - function attribute, disables capability analysis
- *
- * Function attribute denoting that capability analysis is disabled for the
- * whole function. Prefer use of `capability_unsafe()` where possible.
- */
-# define __no_capability_analysis __attribute__((no_thread_safety_analysis))
-
-#else /* !WARN_CAPABILITY_ANALYSIS */
-
-# define __cap_type(name)
-# define __reentrant_cap
-# define __acquires_cap(...)
-# define __acquires_shared_cap(...)
-# define __try_acquires_cap(ret, var)
-# define __try_acquires_shared_cap(ret, var)
-# define __releases_cap(...)
-# define __releases_shared_cap(...)
-# define __assumes_cap(...)
-# define __assumes_shared_cap(...)
-# define __returns_cap(var)
+ * __no_context_analysis - function attribute, disables context analysis
+ *
+ * Function attribute denoting that context analysis is disabled for the
+ * whole function. Prefer use of `context_unsafe()` where possible.
+ */
+# define __no_context_analysis __attribute__((no_thread_safety_analysis))
+
+#else /* !WARN_CONTEXT_ANALYSIS */
+
+# define __ctx_type(name)
+# define __reentrant_ctx
+# define __acquires_ctx(...)
+# define __acquires_shared_ctx(...)
+# define __try_acquires_ctx(ret, var)
+# define __try_acquires_shared_ctx(ret, var)
+# define __releases_ctx(...)
+# define __releases_shared_ctx(...)
+# define __assumes_ctx(...)
+# define __assumes_shared_ctx(...)
+# define __returns_ctx(var)
# define __guarded_by(...)
# define __pt_guarded_by(...)
-# define __excludes_cap(...)
-# define __requires_cap(...)
-# define __requires_shared_cap(...)
-# define __acquire_cap(var) do { } while (0)
-# define __acquire_shared_cap(var) do { } while (0)
-# define __try_acquire_cap(var, ret) (ret)
-# define __try_acquire_shared_cap(var, ret) (ret)
-# define __release_cap(var) do { } while (0)
-# define __release_shared_cap(var) do { } while (0)
-# define __assume_cap(var) do { (void)(var); } while (0)
-# define __assume_shared_cap(var) do { (void)(var); } while (0)
-# define struct_with_capability(name, ...) struct __VA_ARGS__ name
-# define disable_capability_analysis()
-# define enable_capability_analysis()
-# define __no_capability_analysis
-
-#endif /* WARN_CAPABILITY_ANALYSIS */
+# define __excludes_ctx(...)
+# define __requires_ctx(...)
+# define __requires_shared_ctx(...)
+# define __acquire_ctx(var) do { } while (0)
+# define __acquire_shared_ctx(var) do { } while (0)
+# define __try_acquire_ctx(var, ret) (ret)
+# define __try_acquire_shared_ctx(var, ret) (ret)
+# define __release_ctx(var) do { } while (0)
+# define __release_shared_ctx(var) do { } while (0)
+# define __assume_ctx(var) do { (void)(var); } while (0)
+# define __assume_shared_ctx(var) do { (void)(var); } while (0)
+# define struct_with_context(name, ...) struct __VA_ARGS__ name
+# define disable_context_analysis()
+# define enable_context_analysis()
+# define __no_context_analysis
+
+#endif /* WARN_CONTEXT_ANALYSIS */

/**
- * capability_unsafe() - disable capability checking for contained code
+ * context_unsafe() - disable context checking for contained code
*
- * Disables capability checking for contained statements or expression.
+ * Disables context checking for contained statements or expression.
*
* .. code-block:: c
*
@@ -186,30 +187,30 @@
* // ...
* // other code that is still checked ...
* // ...
- * return capability_unsafe(d->counter);
+ * return context_unsafe(d->counter);
* }
*/
-#define capability_unsafe(...) \
+#define context_unsafe(...) \
({ \
- disable_capability_analysis(); \
+ disable_context_analysis(); \
__VA_ARGS__; \
- enable_capability_analysis() \
+ enable_context_analysis() \
})

/**
- * __capability_unsafe() - function attribute, disable capability checking
+ * __context_unsafe() - function attribute, disable context checking
* @comment: comment explaining why opt-out is safe
*
- * Function attribute denoting that capability analysis is disabled for the
+ * Function attribute denoting that context analysis is disabled for the
* whole function. Forces adding an inline comment as argument.
*/
-#define __capability_unsafe(comment) __no_capability_analysis
+#define __context_unsafe(comment) __no_context_analysis

/**
- * capability_unsafe_alias() - helper to insert a capability "alias barrier"
- * @p: pointer aliasing a capability or object containing capabilities
+ * context_unsafe_alias() - helper to insert a context "alias barrier"
+ * @p: pointer aliasing a context or object containing context pointers
*
- * No-op function that acts as a "capability alias barrier", where the analysis
+ * No-op function that acts as a "context alias barrier", where the analysis
* rightfully detects that we're switching aliases, but the switch is considered
* safe but beyond the analysis reasoning abilities.
*
@@ -219,61 +220,61 @@
* their value cannot be determined (e.g. when passing a non-const pointer to an
* alias as a function argument).
*/
-#define capability_unsafe_alias(p) _capability_unsafe_alias((void **)&(p))
-static inline void _capability_unsafe_alias(void **p) { }
+#define context_unsafe_alias(p) _context_unsafe_alias((void **)&(p))
+static inline void _context_unsafe_alias(void **p) { }

/**
- * token_capability() - declare an abstract global capability instance
- * @name: token capability name
+ * token_context() - declare an abstract global context instance
+ * @name: token context name
*
- * Helper that declares an abstract global capability instance @name that can be
- * used as a token capability, but not backed by a real data structure (linker
- * error if accidentally referenced). The type name is `__capability_@name`.
+ * Helper that declares an abstract global context instance @name that can be
+ * used as a token context, but not backed by a real data structure (linker
+ * error if accidentally referenced). The type name is `__context_@name`.
*/
-#define token_capability(name, ...) \
- struct_with_capability(__capability_##name, ##__VA_ARGS__) {}; \
- extern const struct __capability_##name *name
+#define token_context(name, ...) \
+ struct_with_context(__context_##name, ##__VA_ARGS__) {}; \
+ extern const struct __context_##name *name

/**
- * token_capability_instance() - declare another instance of a global capability
- * @cap: token capability previously declared with token_capability()
- * @name: name of additional global capability instance
+ * token_context_instance() - declare another instance of a global context
+ * @ctx: token context previously declared with token_context()
+ * @name: name of additional global context instance
*
* Helper that declares an additional instance @name of the same token
- * capability class @name. This is helpful where multiple related token
- * capabilities are declared, as it also allows using the same underlying type
- * (`__capability_@cap`) as function arguments.
+ * context class @name. This is helpful where multiple related token
+ * contexts are declared, as it also allows using the same underlying type
+ * (`__context_@ctx`) as function arguments.
*/
-#define token_capability_instance(cap, name) \
- extern const struct __capability_##cap *name
+#define token_context_instance(ctx, name) \
+ extern const struct __context_##ctx *name

/*
- * Common keywords for static capability analysis.
+ * Common keywords for static context analysis.
*/

/**
- * __must_hold() - function attribute, caller must hold exclusive capability
+ * __must_hold() - function attribute, caller must hold exclusive context
*
- * Function attribute declaring that the caller must hold the given capability
+ * Function attribute declaring that the caller must hold the given context
* instance(s) exclusively.
*/
-#define __must_hold(...) __requires_cap(__VA_ARGS__)
+#define __must_hold(...) __requires_ctx(__VA_ARGS__)

/**
- * __must_not_hold() - function attribute, caller must not hold capability
+ * __must_not_hold() - function attribute, caller must not hold context
*
* Function attribute declaring that the caller must not hold the given
- * capability instance(s).
+ * context instance(s).
*/
-#define __must_not_hold(...) __excludes_cap(__VA_ARGS__)
+#define __must_not_hold(...) __excludes_ctx(__VA_ARGS__)

/**
- * __acquires() - function attribute, function acquires capability exclusively
+ * __acquires() - function attribute, function acquires context exclusively
*
* Function attribute declaring that the function acquires the given
- * capability instance(s) exclusively, but does not release them.
+ * context instance(s) exclusively, but does not release them.
*/
-#define __acquires(...) __acquires_cap(__VA_ARGS__)
+#define __acquires(...) __acquires_ctx(__VA_ARGS__)

/*
* Clang's analysis does not care precisely about the value, only that it is
@@ -281,75 +282,75 @@ static inline void _capability_unsafe_alias(void **p) { }
* misleading if we say that @ret is the value returned if acquired. Instead,
* provide symbolic variants which we translate.
*/
-#define __cond_acquires_impl_true(x, ...) __try_acquires##__VA_ARGS__##_cap(1, x)
-#define __cond_acquires_impl_false(x, ...) __try_acquires##__VA_ARGS__##_cap(0, x)
-#define __cond_acquires_impl_nonzero(x, ...) __try_acquires##__VA_ARGS__##_cap(1, x)
-#define __cond_acquires_impl_0(x, ...) __try_acquires##__VA_ARGS__##_cap(0, x)
-#define __cond_acquires_impl_nonnull(x, ...) __try_acquires##__VA_ARGS__##_cap(1, x)
-#define __cond_acquires_impl_NULL(x, ...) __try_acquires##__VA_ARGS__##_cap(0, x)
+#define __cond_acquires_impl_true(x, ...) __try_acquires##__VA_ARGS__##_ctx(1, x)
+#define __cond_acquires_impl_false(x, ...) __try_acquires##__VA_ARGS__##_ctx(0, x)
+#define __cond_acquires_impl_nonzero(x, ...) __try_acquires##__VA_ARGS__##_ctx(1, x)
+#define __cond_acquires_impl_0(x, ...) __try_acquires##__VA_ARGS__##_ctx(0, x)
+#define __cond_acquires_impl_nonnull(x, ...) __try_acquires##__VA_ARGS__##_ctx(1, x)
+#define __cond_acquires_impl_NULL(x, ...) __try_acquires##__VA_ARGS__##_ctx(0, x)

/**
* __cond_acquires() - function attribute, function conditionally
- * acquires a capability exclusively
- * @ret: abstract value returned by function if capability acquired
- * @x: capability instance pointer
+ * acquires a context exclusively
+ * @ret: abstract value returned by function if context acquired
+ * @x: context instance pointer
*
* Function attribute declaring that the function conditionally acquires the
- * given capability instance @x exclusively, but does not release it. The
- * function return value @ret denotes when the capability is acquired.
+ * given context instance @x exclusively, but does not release it. The
+ * function return value @ret denotes when the context is acquired.
*
* @ret may be one of: true, false, nonzero, 0, nonnull, NULL.
*/
#define __cond_acquires(ret, x) __cond_acquires_impl_##ret(x)

/**
- * __releases() - function attribute, function releases a capability exclusively
+ * __releases() - function attribute, function releases a context exclusively
*
- * Function attribute declaring that the function releases the given capability
- * instance(s) exclusively. The capability must be held on entry.
+ * Function attribute declaring that the function releases the given context
+ * instance(s) exclusively. The context must be held on entry.
*/
-#define __releases(...) __releases_cap(__VA_ARGS__)
+#define __releases(...) __releases_ctx(__VA_ARGS__)

/**
- * __acquire() - function to acquire capability exclusively
- * @x: capability instance pointer
+ * __acquire() - function to acquire context exclusively
+ * @x: context instance pointer
*
- * No-op function that acquires the given capability instance @x exclusively.
+ * No-op function that acquires the given context instance @x exclusively.
*/
-#define __acquire(x) __acquire_cap(x)
+#define __acquire(x) __acquire_ctx(x)

/**
- * __release() - function to release capability exclusively
- * @x: capability instance pointer
+ * __release() - function to release context exclusively
+ * @x: context instance pointer
*
- * No-op function that releases the given capability instance @x.
+ * No-op function that releases the given context instance @x.
*/
-#define __release(x) __release_cap(x)
+#define __release(x) __release_ctx(x)

/**
- * __must_hold_shared() - function attribute, caller must hold shared capability
+ * __must_hold_shared() - function attribute, caller must hold shared context
*
- * Function attribute declaring that the caller must hold the given capability
+ * Function attribute declaring that the caller must hold the given context
* instance(s) with shared access.
*/
-#define __must_hold_shared(...) __requires_shared_cap(__VA_ARGS__)
+#define __must_hold_shared(...) __requires_shared_ctx(__VA_ARGS__)

/**
- * __acquires_shared() - function attribute, function acquires capability shared
+ * __acquires_shared() - function attribute, function acquires context shared
*
* Function attribute declaring that the function acquires the given
- * capability instance(s) with shared access, but does not release them.
+ * context instance(s) with shared access, but does not release them.
*/
-#define __acquires_shared(...) __acquires_shared_cap(__VA_ARGS__)
+#define __acquires_shared(...) __acquires_shared_ctx(__VA_ARGS__)

/**
* __cond_acquires_shared() - function attribute, function conditionally
- * acquires a capability shared
- * @ret: abstract value returned by function if capability acquired
+ * acquires a context shared
+ * @ret: abstract value returned by function if context acquired
*
* Function attribute declaring that the function conditionally acquires the
- * given capability instance @x with shared access, but does not release it. The
- * function return value @ret denotes when the capability is acquired.
+ * given context instance @x with shared access, but does not release it. The
+ * function return value @ret denotes when the context is acquired.
*
* @ret may be one of: true, false, nonzero, 0, nonnull, NULL.
*/
@@ -357,33 +358,33 @@ static inline void _capability_unsafe_alias(void **p) { }

/**
* __releases_shared() - function attribute, function releases a
- * capability shared
+ * context shared
*
- * Function attribute declaring that the function releases the given capability
- * instance(s) with shared access. The capability must be held on entry.
+ * Function attribute declaring that the function releases the given context
+ * instance(s) with shared access. The context must be held on entry.
*/
-#define __releases_shared(...) __releases_shared_cap(__VA_ARGS__)
+#define __releases_shared(...) __releases_shared_ctx(__VA_ARGS__)

/**
- * __acquire_shared() - function to acquire capability shared
- * @x: capability instance pointer
+ * __acquire_shared() - function to acquire context shared
+ * @x: context instance pointer
*
- * No-op function that acquires the given capability instance @x with shared
+ * No-op function that acquires the given context instance @x with shared
* access.
*/
-#define __acquire_shared(x) __acquire_shared_cap(x)
+#define __acquire_shared(x) __acquire_shared_ctx(x)

/**
- * __release_shared() - function to release capability shared
- * @x: capability instance pointer
+ * __release_shared() - function to release context shared
+ * @x: context instance pointer
*
- * No-op function that releases the given capability instance @x with shared
+ * No-op function that releases the given context instance @x with shared
* access.
*/
-#define __release_shared(x) __release_shared_cap(x)
+#define __release_shared(x) __release_shared_ctx(x)

/**
- * __acquire_ret() - helper to acquire capability of return value
+ * __acquire_ret() - helper to acquire context of return value
* @call: call expression
* @ret_expr: acquire expression that uses __ret
*/
@@ -395,7 +396,7 @@ static inline void _capability_unsafe_alias(void **p) { }
})

/**
- * __acquire_shared_ret() - helper to acquire capability shared of return value
+ * __acquire_shared_ret() - helper to acquire context shared of return value
* @call: call expression
* @ret_expr: acquire shared expression that uses __ret
*/
@@ -407,7 +408,7 @@ static inline void _capability_unsafe_alias(void **p) { }
})

/*
- * Attributes to mark functions returning acquired capabilities. This is purely
+ * Attributes to mark functions returning acquired contexts. This is purely
* cosmetic to help readability, and should be used with the above macros as
* follows:
*
@@ -417,7 +418,7 @@ static inline void _capability_unsafe_alias(void **p) { }
* struct foo *_myfunc(int bar) __acquires_ret;
* ...
*/
-#define __acquires_ret __no_capability_analysis
-#define __acquires_shared_ret __no_capability_analysis
+#define __acquires_ret __no_context_analysis
+#define __acquires_shared_ret __no_context_analysis

-#endif /* _LINUX_COMPILER_CAPABILITY_ANALYSIS_H */
+#endif /* _LINUX_COMPILER_CONTEXT_ANALYSIS_H */

Linus Torvalds

unread,
Sep 18, 2025, 5:54:34 PM (6 days ago) Sep 18
to Marco Elver, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
On Thu, 18 Sept 2025 at 14:26, Marco Elver <el...@google.com> wrote:
>
> Fair points. "Context Analysis" makes sense, but it makes the thing
> (e.g. lock) used to establish that context a little awkward to refer to
> -- see half-baked attempt at reworking the documentation below.

Yeah, I agree that some of that reads more than a bit oddly.

I wonder if we could talk about "context analysis", but then when
discussing what is *held* for a particular context, call that a
"context token" or something like that?

But I don't mind your "Context guard" notion either. I'm not loving
it, but it's not offensive to me either.

Then the language would be feel fairly straightforward,

Eg:

> +Context analysis is a way to specify permissibility of operations to depend on
> +contexts being held (or not held).

That "contexts being held" sounds odd, but talking about "context
markers", or "context tokens" would seem natural.

An alternative would be to not talk about markers / tokens / guards at
all, but simply about a context being *active*.

IOW, instead of wording it like this:

> +The set of contexts that are actually held by a given thread at a given point
> +in program execution is a run-time concept.

that talks about "being held", you could just state it in the sense of
the "set of contexts being active", and that immediately reads fairly
naturally, doesn't it?

Because a context is a *state* you are in, it's not something you hold on to.

The tokens - or whatever - would be only some internal implementation
detail of how the compiler keeps track of which state is active, not
the conceptual idea itself.

So you name states, and you have functions to mark those context
states as being entered or exited, but you don't really even have to
talk about "holding" anything.

No?

Linus

Marco Elver

unread,
Sep 19, 2025, 3:06:09 AM (5 days ago) Sep 19
to syzbot ci, ar...@arndb.de, boqun...@gmail.com, bvana...@acm.org, cor...@lwn.net, da...@davemloft.net, dvy...@google.com, edum...@google.com, fred...@kernel.org, gli...@google.com, gre...@linuxfoundation.org, h...@lst.de, her...@gondor.apana.org.au, iro...@google.com, ja...@google.com, joela...@nvidia.com, jo...@joshtriplett.org, justi...@google.com, kasa...@googlegroups.com, ke...@kernel.org, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, lon...@redhat.com, luc.vano...@gmail.com, lukas....@gmail.com, mark.r...@arm.com, mathieu....@efficios.com, mi...@kernel.org, mi...@redhat.com, mo...@google.com, nat...@kernel.org, neeraj....@kernel.org, nick.des...@gmail.com, oj...@kernel.org, pau...@kernel.org, penguin...@i-love.sakura.ne.jp, pet...@infradead.org, r...@vger.kernel.org, ros...@goodmis.org, take...@nttdata.co.jp, tg...@linutronix.de, tg...@suug.ch, ure...@gmail.com, wi...@kernel.org, syz...@lists.linux.dev, syzkall...@googlegroups.com
On Thu, Sep 18, 2025 at 12:41PM -0700, syzbot ci wrote:
> syzbot ci has tested the following series
>
> [v3] Compiler-Based Capability- and Locking-Analysis
[...]
> and found the following issue:
> general protection fault in validate_page_before_insert
>
> Full report is available here:
> https://ci.syzbot.org/series/81182522-74c0-4494-bcf8-976133df7dc7
>
> ***
>
> general protection fault in validate_page_before_insert

Thanks, syzbot ci!

I messed up the type when moving kcov->area access inside the critical
section. This is the fix:


fixup! kcov: Enable capability analysis

diff --git a/kernel/kcov.c b/kernel/kcov.c
index 1897c8ca6209..e81e3c0d01c6 100644
--- a/kernel/kcov.c
+++ b/kernel/kcov.c
@@ -497,7 +497,7 @@ static int kcov_mmap(struct file *filep, struct vm_area_struct *vma)
unsigned long size, off;
struct page *page;
unsigned long flags;
- unsigned long *area;
+ void *area;

Marco Elver

unread,
Sep 19, 2025, 5:10:40 AM (5 days ago) Sep 19
to Linus Torvalds, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Christoph Hellwig, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Nathan Chancellor, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
On Thu, Sep 18, 2025 at 02:47PM -0700, Linus Torvalds wrote:
[...]
> But I don't mind your "Context guard" notion either. I'm not loving
> it, but it's not offensive to me either.
>
> Then the language would be feel fairly straightforward,
>
> Eg:
>
> > +Context analysis is a way to specify permissibility of operations to depend on
> > +contexts being held (or not held).
>
> That "contexts being held" sounds odd, but talking about "context
> markers", or "context tokens" would seem natural.
>
> An alternative would be to not talk about markers / tokens / guards at
> all, but simply about a context being *active*.

That works for high-level descriptions, but we need something for the
API, too, which specifically operates and refers to the objects which
are acquired/released to enter/exit a context.

> IOW, instead of wording it like this:
>
> > +The set of contexts that are actually held by a given thread at a given point
> > +in program execution is a run-time concept.
>
> that talks about "being held", you could just state it in the sense of
> the "set of contexts being active", and that immediately reads fairly
> naturally, doesn't it?
>
> Because a context is a *state* you are in, it's not something you hold on to.
>
> The tokens - or whatever - would be only some internal implementation
> detail of how the compiler keeps track of which state is active, not
> the conceptual idea itself.
>
> So you name states, and you have functions to mark those context
> states as being entered or exited, but you don't really even have to
> talk about "holding" anything.

It's a tough one -- because fundamentally we operate on objects, which
when acquired/released we enter/exit some context. I tried to balance
not venturing off too far from common terminology, while keeping it
general enough to allow eventual uses for "IRQ enable/disable", "preempt
enable/disable", or anything else where we might need to enter/exit some
context to access a resource (incl. perhaps Ian's suggestion of figuring
out if we can design a refcount_t API that uses context tracking).

I went with "context guard" to refer to the objects themselves, as that
doesn't look too odd. It does match the concept of "guard" in
<linux/cleanup.h>.

See second attempt below.

Preferences?

Thanks,
-- Marco

------ >8 ------

diff --git a/Documentation/dev-tools/capability-analysis.rst b/Documentation/dev-tools/capability-analysis.rst
index 3456132261c6..87125ec2db11 100644
--- a/Documentation/dev-tools/capability-analysis.rst
+++ b/Documentation/dev-tools/capability-analysis.rst
@@ -1,81 +1,80 @@
.. SPDX-License-Identifier: GPL-2.0
.. Copyright (C) 2025, Google LLC.

-.. _capability-analysis:
+.. _context-analysis:

-Compiler-Based Capability Analysis
-==================================
+Compiler-Based Context Analysis
+===============================

-Capability analysis is a C language extension, which enables statically
-checking that user-definable "capabilities" are acquired and released where
-required. An obvious application is lock-safety checking for the kernel's
-various synchronization primitives (each of which represents a "capability"),
-and checking that locking rules are not violated.
+Context analysis is a C language extension, which enables statically checking
+that user-definable context guards are acquired and released where required. An
+obvious application is lock-safety checking for the kernel's various
+synchronization primitives (each of which represents a context guard), and
+checking that locking rules are not violated.

-The Clang compiler currently supports the full set of capability analysis
+The Clang compiler currently supports the full set of context analysis
features. To enable for Clang, configure the kernel with::

- CONFIG_WARN_CAPABILITY_ANALYSIS=y
+ CONFIG_WARN_CONTEXT_ANALYSIS=y

The feature requires Clang 22 or later.

The analysis is *opt-in by default*, and requires declaring which modules and
subsystems should be analyzed in the respective `Makefile`::

- CAPABILITY_ANALYSIS_mymodule.o := y
+ CONTEXT_ANALYSIS_mymodule.o := y

Or for all translation units in the directory::

- CAPABILITY_ANALYSIS := y
+ CONTEXT_ANALYSIS := y

It is possible to enable the analysis tree-wide, however, which will result in
numerous false positive warnings currently and is *not* generally recommended::

- CONFIG_WARN_CAPABILITY_ANALYSIS_ALL=y
+ CONFIG_WARN_CONTEXT_ANALYSIS_ALL=y

Programming Model
-----------------

-The below describes the programming model around using capability-enabled
-types.
+The below describes the programming model around using context guard types.

.. note::
- Enabling capability analysis can be seen as enabling a dialect of Linux C with
- a Capability System. Some valid patterns involving complex control-flow are
+ Enabling context analysis can be seen as enabling a dialect of Linux C with
+ a Context System. Some valid patterns involving complex control-flow are
constrained (such as conditional acquisition and later conditional release
- in the same function, or returning pointers to capabilities from functions.
+ in the same function).

-Capability analysis is a way to specify permissibility of operations to depend
-on capabilities being held (or not held). Typically we are interested in
-protecting data and code by requiring some capability to be held, for example a
-specific lock. The analysis ensures that the caller cannot perform the
-operation without holding the appropriate capability.
+Context analysis is a way to specify permissibility of operations to depend on
+context guards being held (or not held). Typically we are interested in
+protecting data and code in a critical section by requiring a specific context
+to be active, for example by holding a specific lock. The analysis ensures that
+callers cannot perform an operation without the required context being active.

-Capabilities are associated with named structs, along with functions that
-operate on capability-enabled struct instances to acquire and release the
-associated capability.
+Context guards are associated with named structs, along with functions that
+operate on struct instances to acquire and release the associated context
+guard.

-Capabilities can be held either exclusively or shared. This mechanism allows
-assign more precise privileges when holding a capability, typically to
+Context guards can be held either exclusively or shared. This mechanism allows
+assigning more precise privileges when a context is active, typically to
distinguish where a thread may only read (shared) or also write (exclusive) to
-guarded data.
+data guarded within a context.

-The set of capabilities that are actually held by a given thread at a given
-point in program execution is a run-time concept. The static analysis works by
-calculating an approximation of that set, called the capability environment.
-The capability environment is calculated for every program point, and describes
-the set of capabilities that are statically known to be held, or not held, at
-that particular point. This environment is a conservative approximation of the
-full set of capabilities that will actually held by a thread at run-time.
+The set of contexts that are actually active in a given thread at a given point
+in program execution is a run-time concept. The static analysis works by
+calculating an approximation of that set, called the context environment. The
+context environment is calculated for every program point, and describes the
+set of contexts that are statically known to be active, or inactive, at that
+particular point. This environment is a conservative approximation of the full
+set of contexts that will actually be active in a thread at run-time.

More details are also documented `here
<https://clang.llvm.org/docs/ThreadSafetyAnalysis.html>`_.

.. note::
- Clang's analysis explicitly does not infer capabilities acquired or released
- by inline functions. It requires explicit annotations to (a) assert that
- it's not a bug if a capability is released or acquired, and (b) to retain
- consistency between inline and non-inline function declarations.
+ Clang's analysis explicitly does not infer context guards acquired or
+ released by inline functions. It requires explicit annotations to (a) assert
+ that it's not a bug if a context guard is released or acquired, and (b) to
+ retain consistency between inline and non-inline function declarations.

Supported Kernel Primitives
~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -85,13 +84,12 @@ Currently the following synchronization primitives are supported:
`bit_spinlock`, RCU, SRCU (`srcu_struct`), `rw_semaphore`, `local_lock_t`,
`ww_mutex`.

-For capabilities with an initialization function (e.g., `spin_lock_init()`),
-calling this function on the capability instance before initializing any
-guarded members or globals prevents the compiler from issuing warnings about
-unguarded initialization.
+For context guards with an initialization function (e.g., `spin_lock_init()`),
+calling this function before initializing any guarded members or globals
+prevents the compiler from issuing warnings about unguarded initialization.

Lockdep assertions, such as `lockdep_assert_held()`, inform the compiler's
-capability analysis that the associated synchronization primitive is held after
+context analysis that the associated synchronization primitive is held after
the assertion. This avoids false positives in complex control-flow scenarios
and encourages the use of Lockdep where static analysis is limited. For
example, this is useful when a function doesn't *always* require a lock, making
@@ -100,9 +98,9 @@ example, this is useful when a function doesn't *always* require a lock, making
Keywords
~~~~~~~~

-.. kernel-doc:: include/linux/compiler-capability-analysis.h
- :identifiers: struct_with_capability
- token_capability token_capability_instance
+.. kernel-doc:: include/linux/compiler-context-analysis.h
+ :identifiers: context_guard_struct
+ token_context_guard token_context_guard_instance
__guarded_by __pt_guarded_by
__must_hold
__must_not_hold
@@ -117,32 +115,32 @@ Keywords
__release
__acquire_shared
__release_shared
- capability_unsafe
- __capability_unsafe
- disable_capability_analysis enable_capability_analysis
+ __acquire_ret
+ __acquire_shared_ret
+ context_unsafe
+ __context_unsafe
+ disable_context_analysis enable_context_analysis

.. note::
- The function attribute `__no_capability_analysis` is reserved for internal
- implementation of capability-enabled primitives, and should be avoided in
- normal code.
+ The function attribute `__no_context_analysis` is reserved for internal
+ implementation of context guard types, and should be avoided in normal code.

Background
----------

Clang originally called the feature `Thread Safety Analysis
-<https://clang.llvm.org/docs/ThreadSafetyAnalysis.html>`_, with some
-terminology still using the thread-safety-analysis-only names. This was later
-changed and the feature became more flexible, gaining the ability to define
-custom "capabilities".
-
-Indeed, its foundations can be found in `capability systems
-<https://www.cs.cornell.edu/talc/papers/capabilities.pdf>`_, used to specify
-the permissibility of operations to depend on some capability being held (or
-not held).
+<https://clang.llvm.org/docs/ThreadSafetyAnalysis.html>`_, with some keywords
+and documentation still using the thread-safety-analysis-only terminology. This
+was later changed and the feature became more flexible, gaining the ability to
+define custom "capabilities". Its foundations can be found in `Capability
+Systems <https://www.cs.cornell.edu/talc/papers/capabilities.pdf>`_, used to
+specify the permissibility of operations to depend on some "capability" being
+held (or not held).

Because the feature is not just able to express capabilities related to
-synchronization primitives, the naming chosen for the kernel departs from
-Clang's initial "Thread Safety" nomenclature and refers to the feature as
-"Capability Analysis" to avoid confusion. The implementation still makes
-references to the older terminology in some places, such as `-Wthread-safety`
+synchronization primitives, and "capability" is already overloaded in the
+kernel, the naming chosen for the kernel departs from Clang's initial "Thread
+Safety" and "capability" nomenclature; we refer to the feature as "Context
+Analysis" to avoid confusion. The internal implementation still makes
+references to Clang's terminology in a few places, such as `-Wthread-safety`
being the warning option that also still appears in diagnostic messages.
diff --git a/include/linux/compiler-capability-analysis.h b/include/linux/compiler-capability-analysis.h
index f8a1da67589c..b3804c5ac40d 100644
+ * These attributes define new context guard (Clang: capability) types.
+ * Internal only.
+ */
+# define __ctx_guard_type(name) __attribute__((capability(#name)))
+# define __reentrant_ctx_guard __attribute__((reentrant_capability))
+# define __acquires_ctx_guard(...) __attribute__((acquire_capability(__VA_ARGS__)))
+# define __acquires_shared_ctx_guard(...) __attribute__((acquire_shared_capability(__VA_ARGS__)))
+# define __try_acquires_ctx_guard(ret, var) __attribute__((try_acquire_capability(ret, var)))
+# define __try_acquires_shared_ctx_guard(ret, var) __attribute__((try_acquire_shared_capability(ret, var)))
+# define __releases_ctx_guard(...) __attribute__((release_capability(__VA_ARGS__)))
+# define __releases_shared_ctx_guard(...) __attribute__((release_shared_capability(__VA_ARGS__)))
+# define __assumes_ctx_guard(...) __attribute__((assert_capability(__VA_ARGS__)))
+# define __assumes_shared_ctx_guard(...) __attribute__((assert_shared_capability(__VA_ARGS__)))
+# define __returns_ctx_guard(var) __attribute__((lock_returned(var)))

/*
* The below are used to annotate code being checked. Internal only.
*/
-# define __excludes_cap(...) __attribute__((locks_excluded(__VA_ARGS__)))
-# define __requires_cap(...) __attribute__((requires_capability(__VA_ARGS__)))
-# define __requires_shared_cap(...) __attribute__((requires_shared_capability(__VA_ARGS__)))
+# define __excludes_ctx_guard(...) __attribute__((locks_excluded(__VA_ARGS__)))
+# define __requires_ctx_guard(...) __attribute__((requires_capability(__VA_ARGS__)))
+# define __requires_shared_ctx_guard(...) __attribute__((requires_shared_capability(__VA_ARGS__)))

/**
* __guarded_by - struct member and globals attribute, declares variable
- * protected by capability
+ * only accessible within active context
*
- * Declares that the struct member or global variable must be guarded by the
- * given capabilities. Read operations on the data require shared access,
- * while write operations require exclusive access.
+ * Declares that the struct member or global variable is only accessible within
+ * the context entered by the given context guard. Read operations on the data
+ * require shared access, while write operations require exclusive access.
*
* .. code-block:: c
*
@@ -49,11 +50,12 @@

/**
* __pt_guarded_by - struct member and globals attribute, declares pointed-to
- * data is protected by capability
+ * data only accessible within active context
*
* Declares that the data pointed to by the struct member pointer or global
- * pointer must be guarded by the given capabilities. Read operations on the
- * data require shared access, while write operations require exclusive access.
+ * pointer is only accessible within the context entered by the given context
+ * guard. Read operations on the data require shared access, while write
+ * operations require exclusive access.
*
* .. code-block:: c
*
@@ -65,14 +67,14 @@
# define __pt_guarded_by(...) __attribute__((pt_guarded_by(__VA_ARGS__)))

/**
- * struct_with_capability() - declare or define a capability struct
+ * context_guard_struct() - declare or define a context guard struct
* @name: struct name
*
- * Helper to declare or define a struct type with capability of the same name.
+ * Helper to declare or define a struct type that is also a context guard.
*
* .. code-block:: c
*
- * struct_with_capability(my_handle) {
+ * context_guard_struct(my_handle) {
* int foo;
* long bar;
* };
@@ -81,98 +83,98 @@
* ...
* };
* // ... declared elsewhere ...
- * struct_with_capability(some_state);
- *
- * Note: The implementation defines several helper functions that can acquire,
- * release, and assert the capability.
- */
-# define struct_with_capability(name, ...) \
- struct __cap_type(name) __VA_ARGS__ name; \
- static __always_inline void __acquire_cap(const struct name *var) \
- __attribute__((overloadable)) __no_capability_analysis __acquires_cap(var) { } \
- static __always_inline void __acquire_shared_cap(const struct name *var) \
- __attribute__((overloadable)) __no_capability_analysis __acquires_shared_cap(var) { } \
- static __always_inline bool __try_acquire_cap(const struct name *var, bool ret) \
- __attribute__((overloadable)) __no_capability_analysis __try_acquires_cap(1, var) \
+ * context_guard_struct(some_state);
+ *
+ * Note: The implementation defines several helper functions that can acquire
+ * and release the context guard.
+ */
+# define context_guard_struct(name, ...) \
+ struct __ctx_guard_type(name) __VA_ARGS__ name; \
+ static __always_inline void __acquire_ctx_guard(const struct name *var) \
+ __attribute__((overloadable)) __no_context_analysis __acquires_ctx_guard(var) { } \
+ static __always_inline void __acquire_shared_ctx_guard(const struct name *var) \
+ __attribute__((overloadable)) __no_context_analysis __acquires_shared_ctx_guard(var) { } \
+ static __always_inline bool __try_acquire_ctx_guard(const struct name *var, bool ret) \
+ __attribute__((overloadable)) __no_context_analysis __try_acquires_ctx_guard(1, var) \
{ return ret; } \
- static __always_inline bool __try_acquire_shared_cap(const struct name *var, bool ret) \
- __attribute__((overloadable)) __no_capability_analysis __try_acquires_shared_cap(1, var) \
+ static __always_inline bool __try_acquire_shared_ctx_guard(const struct name *var, bool ret) \
+ __attribute__((overloadable)) __no_context_analysis __try_acquires_shared_ctx_guard(1, var) \
{ return ret; } \
- static __always_inline void __release_cap(const struct name *var) \
- __attribute__((overloadable)) __no_capability_analysis __releases_cap(var) { } \
- static __always_inline void __release_shared_cap(const struct name *var) \
- __attribute__((overloadable)) __no_capability_analysis __releases_shared_cap(var) { } \
- static __always_inline void __assume_cap(const struct name *var) \
- __attribute__((overloadable)) __assumes_cap(var) { } \
- static __always_inline void __assume_shared_cap(const struct name *var) \
- __attribute__((overloadable)) __assumes_shared_cap(var) { } \
+ static __always_inline void __release_ctx_guard(const struct name *var) \
+ __attribute__((overloadable)) __no_context_analysis __releases_ctx_guard(var) { } \
+ static __always_inline void __release_shared_ctx_guard(const struct name *var) \
+ __attribute__((overloadable)) __no_context_analysis __releases_shared_ctx_guard(var) { } \
+ static __always_inline void __assume_ctx_guard(const struct name *var) \
+ __attribute__((overloadable)) __assumes_ctx_guard(var) { } \
+ static __always_inline void __assume_shared_ctx_guard(const struct name *var) \
+ __attribute__((overloadable)) __assumes_shared_ctx_guard(var) { } \
+# define __ctx_guard_type(name)
+# define __reentrant_ctx_guard
+# define __acquires_ctx_guard(...)
+# define __acquires_shared_ctx_guard(...)
+# define __try_acquires_ctx_guard(ret, var)
+# define __try_acquires_shared_ctx_guard(ret, var)
+# define __releases_ctx_guard(...)
+# define __releases_shared_ctx_guard(...)
+# define __assumes_ctx_guard(...)
+# define __assumes_shared_ctx_guard(...)
+# define __returns_ctx_guard(var)
# define __guarded_by(...)
# define __pt_guarded_by(...)
-# define __excludes_cap(...)
-# define __requires_cap(...)
-# define __requires_shared_cap(...)
-# define __acquire_cap(var) do { } while (0)
-# define __acquire_shared_cap(var) do { } while (0)
-# define __try_acquire_cap(var, ret) (ret)
-# define __try_acquire_shared_cap(var, ret) (ret)
-# define __release_cap(var) do { } while (0)
-# define __release_shared_cap(var) do { } while (0)
-# define __assume_cap(var) do { (void)(var); } while (0)
-# define __assume_shared_cap(var) do { (void)(var); } while (0)
-# define struct_with_capability(name, ...) struct __VA_ARGS__ name
-# define disable_capability_analysis()
-# define enable_capability_analysis()
-# define __no_capability_analysis
-
-#endif /* WARN_CAPABILITY_ANALYSIS */
+# define __excludes_ctx_guard(...)
+# define __requires_ctx_guard(...)
+# define __requires_shared_ctx_guard(...)
+# define __acquire_ctx_guard(var) do { } while (0)
+# define __acquire_shared_ctx_guard(var) do { } while (0)
+# define __try_acquire_ctx_guard(var, ret) (ret)
+# define __try_acquire_shared_ctx_guard(var, ret) (ret)
+# define __release_ctx_guard(var) do { } while (0)
+# define __release_shared_ctx_guard(var) do { } while (0)
+# define __assume_ctx_guard(var) do { (void)(var); } while (0)
+# define __assume_shared_ctx_guard(var) do { (void)(var); } while (0)
+# define context_guard_struct(name, ...) struct __VA_ARGS__ name
+# define disable_context_analysis()
+# define enable_context_analysis()
+# define __no_context_analysis
+
+#endif /* WARN_CONTEXT_ANALYSIS */

/**
- * capability_unsafe() - disable capability checking for contained code
+ * context_unsafe() - disable context checking for contained code
*
- * Disables capability checking for contained statements or expression.
+ * Disables context checking for contained statements or expression.
*
* .. code-block:: c
*
@@ -186,32 +188,32 @@
+ * context_unsafe_alias() - helper to insert a context guard "alias barrier"
+ * @p: pointer aliasing a context guard or object containing context guards
*
- * No-op function that acts as a "capability alias barrier", where the analysis
- * rightfully detects that we're switching aliases, but the switch is considered
- * safe but beyond the analysis reasoning abilities.
+ * No-op function that acts as a "context guard alias barrier", where the
+ * analysis rightfully detects that we're switching aliases, but the switch is
+ * considered safe but beyond the analysis reasoning abilities.
*
* This should be inserted before the first use of such an alias.
*
@@ -219,61 +221,61 @@
* their value cannot be determined (e.g. when passing a non-const pointer to an
* alias as a function argument).
*/
-#define capability_unsafe_alias(p) _capability_unsafe_alias((void **)&(p))
-static inline void _capability_unsafe_alias(void **p) { }
+#define context_unsafe_alias(p) _context_unsafe_alias((void **)&(p))
+static inline void _context_unsafe_alias(void **p) { }

/**
- * token_capability() - declare an abstract global capability instance
- * @name: token capability name
+ * token_context_guard() - declare an abstract global context guard instance
+ * @name: token context guard name
*
- * Helper that declares an abstract global capability instance @name that can be
- * used as a token capability, but not backed by a real data structure (linker
- * error if accidentally referenced). The type name is `__capability_@name`.
+ * Helper that declares an abstract global context guard instance @name, but not
+ * backed by a real data structure (linker error if accidentally referenced).
+ * The type name is `__context_@name`.
*/
-#define token_capability(name, ...) \
- struct_with_capability(__capability_##name, ##__VA_ARGS__) {}; \
- extern const struct __capability_##name *name
+#define token_context_guard(name, ...) \
+ context_guard_struct(__context_##name, ##__VA_ARGS__) {}; \
+ extern const struct __context_##name *name

/**
- * token_capability_instance() - declare another instance of a global capability
- * @cap: token capability previously declared with token_capability()
- * @name: name of additional global capability instance
+ * token_context_guard_instance() - declare another instance of a global context guard
+ * @ctx: token context guard previously declared with token_context_guard()
+ * @name: name of additional global context guard instance
*
- * Helper that declares an additional instance @name of the same token
- * capability class @name. This is helpful where multiple related token
- * capabilities are declared, as it also allows using the same underlying type
- * (`__capability_@cap`) as function arguments.
+ * Helper that declares an additional instance @name of the same token context
+ * guard class @ctx. This is helpful where multiple related token contexts are
+ * declared, as it also allows using the same underlying type (`__context_@ctx`)
+ * as function arguments.
*/
-#define token_capability_instance(cap, name) \
- extern const struct __capability_##cap *name
+#define token_context_guard_instance(ctx, name) \
+ extern const struct __context_##ctx *name

/*
- * Common keywords for static capability analysis.
+ * Common keywords for static context analysis.
*/

/**
- * __must_hold() - function attribute, caller must hold exclusive capability
+ * __must_hold() - function attribute, caller must hold exclusive context guard
*
- * Function attribute declaring that the caller must hold the given capability
- * instance(s) exclusively.
+ * Function attribute declaring that the caller must hold the given context
+ * guard instance(s) exclusively.
*/
-#define __must_hold(...) __requires_cap(__VA_ARGS__)
+#define __must_hold(...) __requires_ctx_guard(__VA_ARGS__)

/**
- * __must_not_hold() - function attribute, caller must not hold capability
+ * __must_not_hold() - function attribute, caller must not hold context guard
*
* Function attribute declaring that the caller must not hold the given
- * capability instance(s).
+ * context guard instance(s).
*/
-#define __must_not_hold(...) __excludes_cap(__VA_ARGS__)
+#define __must_not_hold(...) __excludes_ctx_guard(__VA_ARGS__)

/**
- * __acquires() - function attribute, function acquires capability exclusively
+ * __acquires() - function attribute, function acquires context guard exclusively
*
* Function attribute declaring that the function acquires the given
- * capability instance(s) exclusively, but does not release them.
+ * context guard instance(s) exclusively, but does not release them.
*/
-#define __acquires(...) __acquires_cap(__VA_ARGS__)
+#define __acquires(...) __acquires_ctx_guard(__VA_ARGS__)

/*
* Clang's analysis does not care precisely about the value, only that it is
@@ -281,75 +283,76 @@ static inline void _capability_unsafe_alias(void **p) { }
* misleading if we say that @ret is the value returned if acquired. Instead,
* provide symbolic variants which we translate.
*/
-#define __cond_acquires_impl_true(x, ...) __try_acquires##__VA_ARGS__##_cap(1, x)
-#define __cond_acquires_impl_false(x, ...) __try_acquires##__VA_ARGS__##_cap(0, x)
-#define __cond_acquires_impl_nonzero(x, ...) __try_acquires##__VA_ARGS__##_cap(1, x)
-#define __cond_acquires_impl_0(x, ...) __try_acquires##__VA_ARGS__##_cap(0, x)
-#define __cond_acquires_impl_nonnull(x, ...) __try_acquires##__VA_ARGS__##_cap(1, x)
-#define __cond_acquires_impl_NULL(x, ...) __try_acquires##__VA_ARGS__##_cap(0, x)
+#define __cond_acquires_impl_true(x, ...) __try_acquires##__VA_ARGS__##_ctx_guard(1, x)
+#define __cond_acquires_impl_false(x, ...) __try_acquires##__VA_ARGS__##_ctx_guard(0, x)
+#define __cond_acquires_impl_nonzero(x, ...) __try_acquires##__VA_ARGS__##_ctx_guard(1, x)
+#define __cond_acquires_impl_0(x, ...) __try_acquires##__VA_ARGS__##_ctx_guard(0, x)
+#define __cond_acquires_impl_nonnull(x, ...) __try_acquires##__VA_ARGS__##_ctx_guard(1, x)
+#define __cond_acquires_impl_NULL(x, ...) __try_acquires##__VA_ARGS__##_ctx_guard(0, x)

/**
* __cond_acquires() - function attribute, function conditionally
- * acquires a capability exclusively
- * @ret: abstract value returned by function if capability acquired
- * @x: capability instance pointer
+ * acquires a context guard exclusively
+ * @ret: abstract value returned by function if context guard acquired
+ * @x: context guard instance pointer
*
* Function attribute declaring that the function conditionally acquires the
- * given capability instance @x exclusively, but does not release it. The
- * function return value @ret denotes when the capability is acquired.
+ * given context guard instance @x exclusively, but does not release it. The
+ * function return value @ret denotes when the context guard is acquired.
*
* @ret may be one of: true, false, nonzero, 0, nonnull, NULL.
*/
#define __cond_acquires(ret, x) __cond_acquires_impl_##ret(x)

/**
- * __releases() - function attribute, function releases a capability exclusively
+ * __releases() - function attribute, function releases a context guard exclusively
*
- * Function attribute declaring that the function releases the given capability
- * instance(s) exclusively. The capability must be held on entry.
+ * Function attribute declaring that the function releases the given context
+ * guard instance(s) exclusively. The associated context must be active on
+ * entry.
*/
-#define __releases(...) __releases_cap(__VA_ARGS__)
+#define __releases(...) __releases_ctx_guard(__VA_ARGS__)

/**
- * __acquire() - function to acquire capability exclusively
- * @x: capability instance pointer
+ * __acquire() - function to acquire context guard exclusively
+ * @x: context guard instance pointer
*
- * No-op function that acquires the given capability instance @x exclusively.
+ * No-op function that acquires the given context guard instance @x exclusively.
*/
-#define __acquire(x) __acquire_cap(x)
+#define __acquire(x) __acquire_ctx_guard(x)

/**
- * __release() - function to release capability exclusively
- * @x: capability instance pointer
+ * __release() - function to release context guard exclusively
+ * @x: context guard instance pointer
*
- * No-op function that releases the given capability instance @x.
+ * No-op function that releases the given context guard instance @x.
*/
-#define __release(x) __release_cap(x)
+#define __release(x) __release_ctx_guard(x)

/**
- * __must_hold_shared() - function attribute, caller must hold shared capability
+ * __must_hold_shared() - function attribute, caller must hold shared context guard
*
- * Function attribute declaring that the caller must hold the given capability
- * instance(s) with shared access.
+ * Function attribute declaring that the caller must hold the given context
+ * guard instance(s) with shared access.
*/
-#define __must_hold_shared(...) __requires_shared_cap(__VA_ARGS__)
+#define __must_hold_shared(...) __requires_shared_ctx_guard(__VA_ARGS__)

/**
- * __acquires_shared() - function attribute, function acquires capability shared
+ * __acquires_shared() - function attribute, function acquires context guard shared
*
* Function attribute declaring that the function acquires the given
- * capability instance(s) with shared access, but does not release them.
+ * context guard instance(s) with shared access, but does not release them.
*/
-#define __acquires_shared(...) __acquires_shared_cap(__VA_ARGS__)
+#define __acquires_shared(...) __acquires_shared_ctx_guard(__VA_ARGS__)

/**
* __cond_acquires_shared() - function attribute, function conditionally
- * acquires a capability shared
- * @ret: abstract value returned by function if capability acquired
+ * acquires a context guard shared
+ * @ret: abstract value returned by function if context guard acquired
*
* Function attribute declaring that the function conditionally acquires the
- * given capability instance @x with shared access, but does not release it. The
- * function return value @ret denotes when the capability is acquired.
+ * given context guard instance @x with shared access, but does not release it.
+ * The function return value @ret denotes when the context guard is acquired.
*
* @ret may be one of: true, false, nonzero, 0, nonnull, NULL.
*/
@@ -357,33 +360,34 @@ static inline void _capability_unsafe_alias(void **p) { }

/**
* __releases_shared() - function attribute, function releases a
- * capability shared
+ * context guard shared
*
- * Function attribute declaring that the function releases the given capability
- * instance(s) with shared access. The capability must be held on entry.
+ * Function attribute declaring that the function releases the given context
+ * guard instance(s) with shared access. The associated context must be active
+ * on entry.
*/
-#define __releases_shared(...) __releases_shared_cap(__VA_ARGS__)
+#define __releases_shared(...) __releases_shared_ctx_guard(__VA_ARGS__)

/**
- * __acquire_shared() - function to acquire capability shared
- * @x: capability instance pointer
+ * __acquire_shared() - function to acquire context guard shared
+ * @x: context guard instance pointer
*
- * No-op function that acquires the given capability instance @x with shared
+ * No-op function that acquires the given context guard instance @x with shared
* access.
*/
-#define __acquire_shared(x) __acquire_shared_cap(x)
+#define __acquire_shared(x) __acquire_shared_ctx_guard(x)

/**
- * __release_shared() - function to release capability shared
- * @x: capability instance pointer
+ * __release_shared() - function to release context guard shared
+ * @x: context guard instance pointer
*
- * No-op function that releases the given capability instance @x with shared
+ * No-op function that releases the given context guard instance @x with shared
* access.
*/
-#define __release_shared(x) __release_shared_cap(x)
+#define __release_shared(x) __release_shared_ctx_guard(x)

/**
- * __acquire_ret() - helper to acquire capability of return value
+ * __acquire_ret() - helper to acquire context guard of return value
* @call: call expression
* @ret_expr: acquire expression that uses __ret
*/
@@ -395,7 +399,7 @@ static inline void _capability_unsafe_alias(void **p) { }
})

/**
- * __acquire_shared_ret() - helper to acquire capability shared of return value
+ * __acquire_shared_ret() - helper to acquire context guard shared of return value
* @call: call expression
* @ret_expr: acquire shared expression that uses __ret
*/
@@ -407,9 +411,9 @@ static inline void _capability_unsafe_alias(void **p) { }
})

/*
- * Attributes to mark functions returning acquired capabilities. This is purely
- * cosmetic to help readability, and should be used with the above macros as
- * follows:
+ * Attributes to mark functions returning acquired context guards. This is
+ * purely cosmetic to help readability, and should be used with the above macros
+ * as follows:
*
* struct foo { spinlock_t lock; ... };
* ...
@@ -417,7 +421,7 @@ static inline void _capability_unsafe_alias(void **p) { }

Christoph Hellwig

unread,
Sep 19, 2025, 10:08:12 AM (5 days ago) Sep 19
to Nathan Chancellor, Christoph Hellwig, Marco Elver, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
That did work, thanks.

I started to play around with that. For the nvme code adding the
annotations was very simply, and I also started adding trivial
__guarded_by which instantly found issues.

For XFS it was a lot more work and I still see tons of compiler
warnings, which I'm not entirely sure how to address. Right now I
see three major classes:

1) locks held over loop iterations like:

fs/xfs/xfs_extent_busy.c:573:26: warning: expecting spinlock 'xfs_group_hold(busyp->group)..xg_busy_extents->eb_lock' to be held at start of each loop [-Wthread-safety-analysis]
573 | struct xfs_group *xg = xfs_group_hold(busyp->group);
| ^
fs/xfs/xfs_extent_busy.c:577:3: note: spinlock acquired here
577 | spin_lock(&eb->eb_lock);
| ^

This is perfectly find code and needs some annotations, but I can't find
any good example.

2) Locks on returned objects, which can be NULL. I.e., something
like crossover of __acquire_ret and __cond_acquires

3) Wrappers that take multiple locks conditionally

We have helpers that take different locks in the same object based on the
arguments like xfs_ilock() or those that take the same lock and a variable
number of objects like xfs_dqlockn based on input and sorting. The
first are just historic and we might want to kill them, but the
sorting of objects to acquire locks in order thing is a pattern in
various places including the VFS, so we'll need some way to annotate it.

Christoph Hellwig

unread,
Sep 19, 2025, 10:10:04 AM (5 days ago) Sep 19
to Nathan Chancellor, Christoph Hellwig, Marco Elver, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
On Fri, Sep 19, 2025 at 04:08:03PM +0200, Christoph Hellwig wrote:
> I started to play around with that. For the nvme code adding the
> annotations was very simply, and I also started adding trivial
> __guarded_by which instantly found issues.
>
> For XFS it was a lot more work and I still see tons of compiler
> warnings, which I'm not entirely sure how to address. Right now I
> see three major classes:

And in case anyone cares, here are my patches for that:

https://git.infradead.org/?p=users/hch/misc.git;a=shortlog;h=refs/heads/cap-analysis

git://git.infradead.org/users/hch/misc.git cap-analysis

Bart Van Assche

unread,
Sep 19, 2025, 1:21:28 PM (5 days ago) Sep 19
to Christoph Hellwig, Nathan Chancellor, Marco Elver, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bill Wendling, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
On 9/19/25 7:08 AM, Christoph Hellwig wrote:
> 3) Wrappers that take multiple locks conditionally
>
> We have helpers that take different locks in the same object based on the
> arguments like xfs_ilock() or those that take the same lock and a variable
> number of objects like xfs_dqlockn based on input and sorting. The
> first are just historic and we might want to kill them, but the
> sorting of objects to acquire locks in order thing is a pattern in
> various places including the VFS, so we'll need some way to annotate it.

Hi Christoph,

As you probably remember some time ago I took a look myself at adding
locking annotations to kernel code. I ended up annotating multiple XFS
functions with NO_THREAD_SAFETY_ANALYSIS. Maybe the locking patterns in
XFS are too complex for compile-time analysis? See also the XFS changes
in
https://lore.kernel.org/lkml/20250206175114.197...@acm.org/.

Thanks,

Bart.

Marco Elver

unread,
Sep 20, 2025, 6:24:03 AM (4 days ago) Sep 20
to Christoph Hellwig, Nathan Chancellor, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
This is an interesting one, and might be a bug in the alias analysis I
recently implemented in Clang. I'll try to figure out a fix.

Marco Elver

unread,
Sep 20, 2025, 8:45:31 AM (4 days ago) Sep 20
to Christoph Hellwig, Nathan Chancellor, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
This fixes the problem: https://github.com/llvm/llvm-project/pull/159921

I guess I have to update the base Clang commit hash for v4 again. :-)

And thanks for testing!

Marco Elver

unread,
Sep 22, 2025, 5:33:36 AM (2 days ago) Sep 22
to Christoph Hellwig, Nathan Chancellor, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
I gave this a try, and with the below patch and the Clang fix [1],
fs/xfs compiles cleanly. I think the fundamental limitation are the
conditional locking wrappers. I suspect it's possible to do better than
disabling the analysis here, by overapproximating the lock set taken
(like you did elsewhere), so that at least the callers are checked, but
when I tried it showed lots of callers need annotating as well, so I
gave up at that point. Still, it might be better than no checking at
all.

[1] https://github.com/llvm/llvm-project/pull/159921

Thanks,
-- Marco


diff --git a/fs/xfs/xfs_inode.c b/fs/xfs/xfs_inode.c
index 9c39251961a3..f371a08e5d44 100644
--- a/fs/xfs/xfs_inode.c
+++ b/fs/xfs/xfs_inode.c
@@ -140,6 +140,7 @@ void
xfs_ilock(
xfs_inode_t *ip,
uint lock_flags)
+ __capability_unsafe(/* conditional locking */)
{
trace_xfs_ilock(ip, lock_flags, _RET_IP_);

@@ -183,6 +184,7 @@ int
xfs_ilock_nowait(
xfs_inode_t *ip,
uint lock_flags)
+ __capability_unsafe(/* conditional locking */)
{
trace_xfs_ilock_nowait(ip, lock_flags, _RET_IP_);

@@ -243,6 +245,7 @@ void
xfs_iunlock(
xfs_inode_t *ip,
uint lock_flags)
+ __capability_unsafe(/* conditional locking */)
{
xfs_lock_flags_assert(lock_flags);

@@ -272,6 +275,7 @@ void
xfs_ilock_demote(
xfs_inode_t *ip,
uint lock_flags)
+ __capability_unsafe(/* conditional locking */)
{
ASSERT(lock_flags & (XFS_IOLOCK_EXCL|XFS_MMAPLOCK_EXCL|XFS_ILOCK_EXCL));
ASSERT((lock_flags &
diff --git a/fs/xfs/xfs_log.c b/fs/xfs/xfs_log.c
index d9ac9521c203..9c4ec3aa8bf9 100644
--- a/fs/xfs/xfs_log.c
+++ b/fs/xfs/xfs_log.c
@@ -472,6 +472,7 @@ xfs_log_reserve(
static void
xlog_state_shutdown_callbacks(
struct xlog *log)
+ __must_hold(&log->l_icloglock)
{
struct xlog_in_core *iclog;
LIST_HEAD(cb_list);

Christoph Hellwig

unread,
Sep 22, 2025, 1:11:45 PM (2 days ago) Sep 22
to Marco Elver, Christoph Hellwig, Nathan Chancellor, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
On Mon, Sep 22, 2025 at 11:33:23AM +0200, Marco Elver wrote:
> I gave this a try, and with the below patch and the Clang fix [1],
> fs/xfs compiles cleanly. I think the fundamental limitation are the
> conditional locking wrappers. I suspect it's possible to do better than
> disabling the analysis here, by overapproximating the lock set taken
> (like you did elsewhere), so that at least the callers are checked, but
> when I tried it showed lots of callers need annotating as well, so I
> gave up at that point. Still, it might be better than no checking at
> all.

I guess this at least allows us to work with the analysis, even if it
drops coverage for one of the most important locks. I guess you also
have CONFIG_XFS_QUOTA disabled as that would lead to similar warnings,
and also currently has the lock the object on return if it's not a
NULL return case? I now have a local series to remove that instance,
but I've seen that pattern elsewhere in the kernel code.

Besides the conditional locking these two also do another thing that
is nasty to the analysis, the locked state can be attached to a
transaction and unlocked at transaction commit. Not sure how to best
model that.

> [1] https://github.com/llvm/llvm-project/pull/159921

Thanks for all the work!

Christoph Hellwig

unread,
Sep 22, 2025, 1:12:33 PM (2 days ago) Sep 22
to Bart Van Assche, Christoph Hellwig, Nathan Chancellor, Marco Elver, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bill Wendling, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
On Fri, Sep 19, 2025 at 10:20:37AM -0700, Bart Van Assche wrote:
> locking annotations to kernel code. I ended up annotating multiple XFS
> functions with NO_THREAD_SAFETY_ANALYSIS. Maybe the locking patterns in
> XFS are too complex for compile-time analysis?

If our locking patterns are too complex for analysis, either the code or
the analysis has problems that need addressing. Potentially both.

Nathan Chancellor

unread,
Sep 23, 2025, 3:49:29 PM (14 hours ago) Sep 23
to Marco Elver, Christoph Hellwig, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon, David S. Miller, Luc Van Oostenryck, Paul E. McKenney, Alexander Potapenko, Arnd Bergmann, Bart Van Assche, Bill Wendling, Dmitry Vyukov, Eric Dumazet, Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers, Jann Horn, Joel Fernandes, Jonathan Corbet, Josh Triplett, Justin Stitt, Kees Cook, Kentaro Takeda, Lukas Bulwahn, Mark Rutland, Mathieu Desnoyers, Miguel Ojeda, Neeraj Upadhyay, Nick Desaulniers, Steven Rostedt, Tetsuo Handa, Thomas Gleixner, Thomas Graf, Uladzislau Rezki, Waiman Long, kasa...@googlegroups.com, linux-...@vger.kernel.org, linu...@vger.kernel.org, linux-...@vger.kernel.org, linux-...@vger.kernel.org, linu...@kvack.org, linux-secu...@vger.kernel.org, linux-...@vger.kernel.org, ll...@lists.linux.dev, r...@vger.kernel.org
On Mon, Sep 22, 2025 at 11:33:23AM +0200, Marco Elver wrote:
> [1] https://github.com/llvm/llvm-project/pull/159921

Now that this is merged, I have pushed an updated snapshot for x86_64:

https://mirrors.edge.kernel.org/pub/tools/llvm/files/prerelease/llvm-22.0.0-ca2e8fc928ad103f46ca9f827e147c43db3a5c47-20250923-185804-x86_64.tar.xz

Cheers,
Nathan
Reply all
Reply to author
Forward
0 new messages