* [PATCH v4 11/35] locking/seqlock: Support Clang's context analysis
From: Marco Elver @ 2025-11-20 15:09 UTC (permalink / raw)
To: elver, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon
Cc: David S. Miller, Luc Van Oostenryck, Chris Li, Paul E. McKenney,
Alexander Potapenko, Arnd Bergmann, Bart Van Assche,
Christoph Hellwig, Dmitry Vyukov, Eric Dumazet,
Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers,
Jann Horn, Joel Fernandes, Johannes Berg, 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, kasan-dev, linux-crypto, linux-doc,
linux-kbuild, linux-kernel, linux-mm, linux-security-module,
linux-sparse, linux-wireless, llvm, rcu
In-Reply-To: <20251120151033.3840508-7-elver@google.com>
Add support for Clang's context analysis for seqlock_t.
Signed-off-by: Marco Elver <elver@google.com>
---
v3:
* __assert -> __assume rename
---
Documentation/dev-tools/context-analysis.rst | 2 +-
include/linux/seqlock.h | 24 +++++++++++
include/linux/seqlock_types.h | 5 ++-
lib/test_context-analysis.c | 43 ++++++++++++++++++++
4 files changed, 71 insertions(+), 3 deletions(-)
diff --git a/Documentation/dev-tools/context-analysis.rst b/Documentation/dev-tools/context-analysis.rst
index 1f5d7c758219..598962f6cb40 100644
--- a/Documentation/dev-tools/context-analysis.rst
+++ b/Documentation/dev-tools/context-analysis.rst
@@ -80,7 +80,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 context guards with an initialization function (e.g., `spin_lock_init()`),
calling this function before initializing any guarded members or globals
diff --git a/include/linux/seqlock.h b/include/linux/seqlock.h
index 5ce48eab7a2a..c79210d369ef 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_ctx_guard(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_context_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_context_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_context_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_context_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_context_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_context_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_context_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_context_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_context_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_context_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_context_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_context_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_context_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_context_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_context_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_context_analysis
{
spin_unlock_irq(&sl->lock);
}
static inline unsigned long __read_seqlock_excl_irqsave(seqlock_t *sl)
+ __acquires_shared(sl) __no_context_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_context_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_context_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_context_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_context_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_context_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_context_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..7b195368e654 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 {
+context_guard_struct(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_context-analysis.c b/lib/test_context-analysis.c
index 2b28d20c5f51..59c6642c582e 100644
--- a/lib/test_context-analysis.c
+++ b/lib/test_context-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.52.0.rc1.455.g30608eb744-goog
^ permalink raw reply related
* [PATCH v4 10/35] locking/mutex: Support Clang's context analysis
From: Marco Elver @ 2025-11-20 15:09 UTC (permalink / raw)
To: elver, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon
Cc: David S. Miller, Luc Van Oostenryck, Chris Li, Paul E. McKenney,
Alexander Potapenko, Arnd Bergmann, Bart Van Assche,
Christoph Hellwig, Dmitry Vyukov, Eric Dumazet,
Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers,
Jann Horn, Joel Fernandes, Johannes Berg, 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, kasan-dev, linux-crypto, linux-doc,
linux-kbuild, linux-kernel, linux-mm, linux-security-module,
linux-sparse, linux-wireless, llvm, rcu
In-Reply-To: <20251120151033.3840508-7-elver@google.com>
Add support for Clang's context analysis for mutex.
Signed-off-by: Marco Elver <elver@google.com>
---
v4:
* Rename capability -> context analysis.
v3:
* Switch to DECLARE_LOCK_GUARD_1_ATTRS() (suggested by Peter)
* __assert -> __assume rename
---
Documentation/dev-tools/context-analysis.rst | 2 +-
include/linux/mutex.h | 35 ++++++-----
include/linux/mutex_types.h | 4 +-
lib/test_context-analysis.c | 64 ++++++++++++++++++++
4 files changed, 87 insertions(+), 18 deletions(-)
diff --git a/Documentation/dev-tools/context-analysis.rst b/Documentation/dev-tools/context-analysis.rst
index 50b57a1228ea..1f5d7c758219 100644
--- a/Documentation/dev-tools/context-analysis.rst
+++ b/Documentation/dev-tools/context-analysis.rst
@@ -80,7 +80,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 context guards with an initialization function (e.g., `spin_lock_init()`),
calling this function before initializing any guarded members or globals
diff --git a/include/linux/mutex.h b/include/linux/mutex.h
index 847b81ca6436..be91f991a846 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_ctx_guard(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_ctx_guard(_T), /* */)
+DECLARE_LOCK_GUARD_1_ATTRS(mutex_try, __assumes_ctx_guard(_T), /* */)
+DECLARE_LOCK_GUARD_1_ATTRS(mutex_intr, __assumes_ctx_guard(_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..3a5efaa2da2d 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 {
+context_guard_struct(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 {
+context_guard_struct(mutex) {
struct rt_mutex_base rtmutex;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
diff --git a/lib/test_context-analysis.c b/lib/test_context-analysis.c
index 273fa9d34657..2b28d20c5f51 100644
--- a/lib/test_context-analysis.c
+++ b/lib/test_context-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.52.0.rc1.455.g30608eb744-goog
^ permalink raw reply related
* [PATCH v4 09/35] compiler-context-analysis: Change __cond_acquires to take return value
From: Marco Elver @ 2025-11-20 15:09 UTC (permalink / raw)
To: elver, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon
Cc: David S. Miller, Luc Van Oostenryck, Chris Li, Paul E. McKenney,
Alexander Potapenko, Arnd Bergmann, Bart Van Assche,
Christoph Hellwig, Dmitry Vyukov, Eric Dumazet,
Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers,
Jann Horn, Joel Fernandes, Johannes Berg, 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, kasan-dev, linux-crypto, linux-doc,
linux-kbuild, linux-kernel, linux-mm, linux-security-module,
linux-sparse, linux-wireless, llvm, rcu
In-Reply-To: <20251120151033.3840508-7-elver@google.com>
While Sparse is oblivious to the return value of conditional acquire
functions, Clang's context 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 ==> context guard acquired in
branch taken if condition non-zero, and 0 ==> context guard 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 <elver@google.com>
---
v4:
* Rename capability -> context analysis.
v2:
* Use symbolic values for __cond_acquires() and __cond_acquires_shared()
(suggested by Bart).
---
fs/dlm/lock.c | 2 +-
include/linux/compiler-context-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 be938fdf17d9..0ce04be0d3de 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-context-analysis.h b/include/linux/compiler-context-analysis.h
index 8c75e1d0034a..935e59089d75 100644
--- a/include/linux/compiler-context-analysis.h
+++ b/include/linux/compiler-context-analysis.h
@@ -259,7 +259,7 @@ static inline void _context_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)
@@ -302,15 +302,32 @@ static inline void _context_unsafe_alias(void **p) { }
*/
# define __acquires(x) __acquires_ctx_guard(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__##_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 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 context guard instance @x exclusively, but does not release it.
+ * 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(x) __try_acquires_ctx_guard(1, x)
+# define __cond_acquires(ret, x) __cond_acquires_impl_##ret(x)
/**
* __releases() - function attribute, function releases a context guard exclusively
@@ -377,12 +394,16 @@ static inline void _context_unsafe_alias(void **p) { }
/**
* __cond_acquires_shared() - function attribute, function conditionally
* acquires a context guard shared
+ * @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 context guard instance @x with shared access, but does not release it.
+ * 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.
*/
-# define __cond_acquires_shared(x) __try_acquires_shared_ctx_guard(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 2bcb3f0bf00e..274d866a0be3 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_context_analysis
+ __cond_acquires(true, lock) __no_context_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_context_analysis
+ __cond_acquires(true, lock) __no_context_analysis
{
return raw_spin_trylock_bh(&lock->rlock);
}
static __always_inline int spin_trylock_irq(spinlock_t *lock)
- __cond_acquires(lock) __no_context_analysis
+ __cond_acquires(true, lock) __no_context_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 d19327e04df9..7e7d7d373213 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.52.0.rc1.455.g30608eb744-goog
^ permalink raw reply related
* [PATCH v4 08/35] locking/rwlock, spinlock: Support Clang's context analysis
From: Marco Elver @ 2025-11-20 15:09 UTC (permalink / raw)
To: elver, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon
Cc: David S. Miller, Luc Van Oostenryck, Chris Li, Paul E. McKenney,
Alexander Potapenko, Arnd Bergmann, Bart Van Assche,
Christoph Hellwig, Dmitry Vyukov, Eric Dumazet,
Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers,
Jann Horn, Joel Fernandes, Johannes Berg, 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, kasan-dev, linux-crypto, linux-doc,
linux-kbuild, linux-kernel, linux-mm, linux-security-module,
linux-sparse, linux-wireless, llvm, rcu
In-Reply-To: <20251120151033.3840508-7-elver@google.com>
Add support for Clang's context 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
lock as acquired when initialized before guarded variables.
The test verifies that common patterns do not generate false positives.
Signed-off-by: Marco Elver <elver@google.com>
---
v4:
* Rename capability -> context analysis.
v3:
* Switch to DECLARE_LOCK_GUARD_1_ATTRS() (suggested by Peter)
* __assert -> __assume rename
---
Documentation/dev-tools/context-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_context-analysis.c | 128 +++++++++++++++++++
12 files changed, 324 insertions(+), 97 deletions(-)
diff --git a/Documentation/dev-tools/context-analysis.rst b/Documentation/dev-tools/context-analysis.rst
index a15436e288fd..50b57a1228ea 100644
--- a/Documentation/dev-tools/context-analysis.rst
+++ b/Documentation/dev-tools/context-analysis.rst
@@ -79,7 +79,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 context guards with an initialization function (e.g., `spin_lock_init()`),
calling this function before initializing any guarded members or globals
diff --git a/include/linux/rwlock.h b/include/linux/rwlock.h
index 5b87c6f4a243..a2f85a0356c4 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_ctx_guard(lock); \
} while (0)
#else
# define rwlock_init(lock) \
- do { *(lock) = __RW_LOCK_UNLOCKED(lock); } while (0)
+ do { *(lock) = __RW_LOCK_UNLOCKED(lock); __assume_ctx_guard(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..6d5cc0b7be1f 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_context_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_context_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_context_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_context_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_context_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_context_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_context_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_context_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_context_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..2723abbe0e7a 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_ctx_guard(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..7df534791589 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 {
+context_guard_struct(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 {
+context_guard_struct(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..2bcb3f0bf00e 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_ctx_guard(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_ctx_guard(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_ctx_guard(lock); \
} while (0)
#else
@@ -342,21 +344,25 @@ do { \
do { \
spinlock_check(_lock); \
*(_lock) = __SPIN_LOCK_UNLOCKED(_lock); \
+ __assume_ctx_guard(_lock); \
} while (0)
#endif
static __always_inline void spin_lock(spinlock_t *lock)
+ __acquires(lock) __no_context_analysis
{
raw_spin_lock(&lock->rlock);
}
static __always_inline void spin_lock_bh(spinlock_t *lock)
+ __acquires(lock) __no_context_analysis
{
raw_spin_lock_bh(&lock->rlock);
}
static __always_inline int spin_trylock(spinlock_t *lock)
+ __cond_acquires(lock) __no_context_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_context_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_context_analysis
{
raw_spin_unlock(&lock->rlock);
}
static __always_inline void spin_unlock_bh(spinlock_t *lock)
+ __releases(lock) __no_context_analysis
{
raw_spin_unlock_bh(&lock->rlock);
}
static __always_inline void spin_unlock_irq(spinlock_t *lock)
+ __releases(lock) __no_context_analysis
{
raw_spin_unlock_irq(&lock->rlock);
}
static __always_inline void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)
+ __releases(lock) __no_context_analysis
{
raw_spin_unlock_irqrestore(&lock->rlock, flags);
}
static __always_inline int spin_trylock_bh(spinlock_t *lock)
+ __cond_acquires(lock) __no_context_analysis
{
return raw_spin_trylock_bh(&lock->rlock);
}
static __always_inline int spin_trylock_irq(spinlock_t *lock)
+ __cond_acquires(lock) __no_context_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_ctx_guard(_T), /* */)
DEFINE_LOCK_GUARD_1_COND(raw_spinlock, _try, raw_spin_trylock(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(raw_spinlock_try, __assumes_ctx_guard(_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_ctx_guard(_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_ctx_guard(_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_ctx_guard(_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_ctx_guard(_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_ctx_guard(_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_ctx_guard(_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_ctx_guard(_T), /* */)
DEFINE_LOCK_GUARD_1(spinlock, spinlock_t,
spin_lock(_T->lock),
spin_unlock(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(spinlock, __assumes_ctx_guard(_T), /* */)
DEFINE_LOCK_GUARD_1_COND(spinlock, _try, spin_trylock(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(spinlock_try, __assumes_ctx_guard(_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_ctx_guard(_T), /* */)
DEFINE_LOCK_GUARD_1_COND(spinlock_irq, _try,
spin_trylock_irq(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(spinlock_irq_try, __assumes_ctx_guard(_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_ctx_guard(_T), /* */)
DEFINE_LOCK_GUARD_1_COND(spinlock_bh, _try,
spin_trylock_bh(_T->lock))
+DECLARE_LOCK_GUARD_1_ATTRS(spinlock_bh_try, __assumes_ctx_guard(_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_ctx_guard(_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_ctx_guard(_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_ctx_guard(_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_ctx_guard(_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_ctx_guard(_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_ctx_guard(_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_ctx_guard(_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_ctx_guard(_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..d19327e04df9 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_context_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_context_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_context_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_context_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..817a1e331cd1 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_ctx_guard(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..dc3d338f58e8 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 {
+context_guard_struct(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 {
+context_guard_struct(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..84ee4d9f099f 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 {
+context_guard_struct(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_context-analysis.c b/lib/test_context-analysis.c
index 68f075dec0e0..273fa9d34657 100644
--- a/lib/test_context-analysis.c
+++ b/lib/test_context-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(context_unsafe((void)2, 3) != 3); /* does not swallow commas */
context_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.52.0.rc1.455.g30608eb744-goog
^ permalink raw reply related
* [PATCH v4 07/35] lockdep: Annotate lockdep assertions for context analysis
From: Marco Elver @ 2025-11-20 15:09 UTC (permalink / raw)
To: elver, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon
Cc: David S. Miller, Luc Van Oostenryck, Chris Li, Paul E. McKenney,
Alexander Potapenko, Arnd Bergmann, Bart Van Assche,
Christoph Hellwig, Dmitry Vyukov, Eric Dumazet,
Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers,
Jann Horn, Joel Fernandes, Johannes Berg, 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, kasan-dev, linux-crypto, linux-doc,
linux-kbuild, linux-kernel, linux-mm, linux-security-module,
linux-sparse, linux-wireless, llvm, rcu
In-Reply-To: <20251120151033.3840508-7-elver@google.com>
Clang's context analysis can be made aware of functions that assert that
locks are held.
Presence of these annotations causes the analysis to assume the context
guard 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 <elver@google.com>
---
v4:
* Rename capability -> context analysis.
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..2c99a6823161 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_ctx_guard(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_ctx_guard(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_ctx_guard(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_ctx_guard(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_ctx_guard(l)
+#define lockdep_assert_held_read(l) __assume_shared_ctx_guard(l)
#define lockdep_assert_held_once(l) do { (void)(l); } while (0)
#define lockdep_assert_none_held_once() do { } while (0)
--
2.52.0.rc1.455.g30608eb744-goog
^ permalink raw reply related
* [PATCH v4 06/35] cleanup: Basic compatibility with context analysis
From: Marco Elver @ 2025-11-20 15:09 UTC (permalink / raw)
To: elver, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon
Cc: David S. Miller, Luc Van Oostenryck, Chris Li, Paul E. McKenney,
Alexander Potapenko, Arnd Bergmann, Bart Van Assche,
Christoph Hellwig, Dmitry Vyukov, Eric Dumazet,
Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers,
Jann Horn, Joel Fernandes, Johannes Berg, 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, kasan-dev, linux-crypto, linux-doc,
linux-kbuild, linux-kernel, linux-mm, linux-security-module,
linux-sparse, linux-wireless, llvm, rcu
In-Reply-To: <20251120145835.3833031-2-elver@google.com>
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_ctx_guard().
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-context
Signed-off-by: Marco Elver <elver@google.com>
---
v4:
* Rename capability -> context analysis.
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..4f5e9ea02f54 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_context_analysis \
{ _type _T = *p; _exit; } \
static inline _type class_##_name##_constructor(_init_args) \
+ __no_context_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_context_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_context_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_context_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_context_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.52.0.rc1.455.g30608eb744-goog
^ permalink raw reply related
* [PATCH v4 05/35] checkpatch: Warn about context_unsafe() without comment
From: Marco Elver @ 2025-11-20 14:49 UTC (permalink / raw)
To: elver, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon
Cc: David S. Miller, Luc Van Oostenryck, Chris Li, Paul E. McKenney,
Alexander Potapenko, Arnd Bergmann, Bart Van Assche,
Christoph Hellwig, Dmitry Vyukov, Eric Dumazet,
Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers,
Jann Horn, Joel Fernandes, Johannes Berg, 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, kasan-dev, linux-crypto, linux-doc,
linux-kbuild, linux-kernel, linux-mm, linux-security-module,
linux-sparse, linux-wireless, llvm, rcu
In-Reply-To: <20251120145835.3833031-2-elver@google.com>
Warn about applications of context_unsafe() without a comment, to
encourage documenting the reasoning behind why it was deemed safe.
Signed-off-by: Marco Elver <elver@google.com>
---
v4:
* Rename capability -> context analysis.
* Avoid nested if.
---
scripts/checkpatch.pl | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl
index 92669904eecc..a5db6b583b88 100755
--- a/scripts/checkpatch.pl
+++ b/scripts/checkpatch.pl
@@ -6722,6 +6722,13 @@ sub process {
}
}
+# check for context_unsafe without a comment.
+ if ($line =~ /\bcontext_unsafe\b/ &&
+ !ctx_has_comment($first_line, $linenr)) {
+ WARN("CONTEXT_UNSAFE",
+ "context_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.52.0.rc1.455.g30608eb744-goog
^ permalink raw reply related
* [PATCH v4 04/35] Documentation: Add documentation for Compiler-Based Context Analysis
From: Marco Elver @ 2025-11-20 14:49 UTC (permalink / raw)
To: elver, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon
Cc: David S. Miller, Luc Van Oostenryck, Chris Li, Paul E. McKenney,
Alexander Potapenko, Arnd Bergmann, Bart Van Assche,
Christoph Hellwig, Dmitry Vyukov, Eric Dumazet,
Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers,
Jann Horn, Joel Fernandes, Johannes Berg, 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, kasan-dev, linux-crypto, linux-doc,
linux-kbuild, linux-kernel, linux-mm, linux-security-module,
linux-sparse, linux-wireless, llvm, rcu
In-Reply-To: <20251120145835.3833031-2-elver@google.com>
Adds documentation in Documentation/dev-tools/context-analysis.rst, and
adds it to the index.
Signed-off-by: Marco Elver <elver@google.com>
---
v4:
* Rename capability -> context analysis.
v2:
* Remove cross-reference to Sparse, since we plan to remove Sparse
support anyway.
* Mention __no_context_analysis should be avoided.
---
Documentation/dev-tools/context-analysis.rst | 145 +++++++++++++++++++
Documentation/dev-tools/index.rst | 1 +
2 files changed, 146 insertions(+)
create mode 100644 Documentation/dev-tools/context-analysis.rst
diff --git a/Documentation/dev-tools/context-analysis.rst b/Documentation/dev-tools/context-analysis.rst
new file mode 100644
index 000000000000..a15436e288fd
--- /dev/null
+++ b/Documentation/dev-tools/context-analysis.rst
@@ -0,0 +1,145 @@
+.. SPDX-License-Identifier: GPL-2.0
+.. Copyright (C) 2025, Google LLC.
+
+.. _context-analysis:
+
+Compiler-Based Context Analysis
+===============================
+
+Context Analysis is a language extension, which enables statically checking
+that required contexts are active (or inactive) by acquiring and releasing
+user-definable "context guards". 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 context analysis
+features. To enable for Clang, configure the kernel with::
+
+ 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`::
+
+ CONTEXT_ANALYSIS_mymodule.o := y
+
+Or for all translation units in the directory::
+
+ 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_CONTEXT_ANALYSIS_ALL=y
+
+Programming Model
+-----------------
+
+The below describes the programming model around using context guard types.
+
+.. note::
+ 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).
+
+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.
+
+Context guards are associated with named structs, along with functions that
+operate on struct instances to acquire and release the associated context
+guard.
+
+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
+data guarded within a context.
+
+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 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
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. Currently the following synchronization primitives are supported:
+
+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
+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
+`__must_hold()` inappropriate.
+
+Keywords
+~~~~~~~~
+
+.. 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
+ __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
+ __acquire_ret
+ __acquire_shared_ret
+ context_unsafe
+ __context_unsafe
+ disable_context_analysis enable_context_analysis
+
+.. note::
+ 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 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, 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/Documentation/dev-tools/index.rst b/Documentation/dev-tools/index.rst
index 4b8425e348ab..d864b3da4cc7 100644
--- a/Documentation/dev-tools/index.rst
+++ b/Documentation/dev-tools/index.rst
@@ -21,6 +21,7 @@ Documentation/process/debugging/index.rst
checkpatch
clang-format
coccinelle
+ context-analysis
sparse
kcov
gcov
--
2.52.0.rc1.455.g30608eb744-goog
^ permalink raw reply related
* [PATCH v4 03/35] compiler-context-analysis: Add test stub
From: Marco Elver @ 2025-11-20 14:49 UTC (permalink / raw)
To: elver, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon
Cc: David S. Miller, Luc Van Oostenryck, Chris Li, Paul E. McKenney,
Alexander Potapenko, Arnd Bergmann, Bart Van Assche,
Christoph Hellwig, Dmitry Vyukov, Eric Dumazet,
Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers,
Jann Horn, Joel Fernandes, Johannes Berg, 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, kasan-dev, linux-crypto, linux-doc,
linux-kbuild, linux-kernel, linux-mm, linux-security-module,
linux-sparse, linux-wireless, llvm, rcu
In-Reply-To: <20251120145835.3833031-2-elver@google.com>
Add a simple test stub where we will add common supported patterns that
should not generate false positive of each new supported context guard.
Signed-off-by: Marco Elver <elver@google.com>
---
v4:
* Rename capability -> context analysis.
---
lib/Kconfig.debug | 14 ++++++++++++++
lib/Makefile | 3 +++
lib/test_context-analysis.c | 18 ++++++++++++++++++
3 files changed, 35 insertions(+)
create mode 100644 lib/test_context-analysis.c
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 696e2a148a15..0c499d22407c 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -2833,6 +2833,20 @@ config LINEAR_RANGES_TEST
If unsure, say N.
+config CONTEXT_ANALYSIS_TEST
+ bool "Compiler context-analysis warnings test"
+ depends on EXPERT
+ help
+ This builds the test for compiler-based context 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 context guards, 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 1ab2c4be3b66..59ed5f881bcb 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -329,4 +329,7 @@ obj-$(CONFIG_GENERIC_LIB_DEVMEM_IS_ALLOWED) += devmem_is_allowed.o
obj-$(CONFIG_FIRMWARE_TABLE) += fw_table.o
+CONTEXT_ANALYSIS_test_context-analysis.o := y
+obj-$(CONFIG_CONTEXT_ANALYSIS_TEST) += test_context-analysis.o
+
subdir-$(CONFIG_FORTIFY_SOURCE) += test_fortify
diff --git a/lib/test_context-analysis.c b/lib/test_context-analysis.c
new file mode 100644
index 000000000000..68f075dec0e0
--- /dev/null
+++ b/lib/test_context-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 context analysis.
+ */
+
+#include <linux/build_bug.h>
+
+/*
+ * Test that helper macros work as expected.
+ */
+static void __used test_common_helpers(void)
+{
+ BUILD_BUG_ON(context_unsafe(3) != 3); /* plain expression */
+ BUILD_BUG_ON(context_unsafe((void)2; 3) != 3); /* does not swallow semi-colon */
+ BUILD_BUG_ON(context_unsafe((void)2, 3) != 3); /* does not swallow commas */
+ context_unsafe(do { } while (0)); /* works with void statements */
+}
--
2.52.0.rc1.455.g30608eb744-goog
^ permalink raw reply related
* [PATCH v4 02/35] compiler-context-analysis: Add infrastructure for Context Analysis with Clang
From: Marco Elver @ 2025-11-20 14:49 UTC (permalink / raw)
To: elver, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon
Cc: David S. Miller, Luc Van Oostenryck, Chris Li, Paul E. McKenney,
Alexander Potapenko, Arnd Bergmann, Bart Van Assche,
Christoph Hellwig, Dmitry Vyukov, Eric Dumazet,
Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers,
Jann Horn, Joel Fernandes, Johannes Berg, 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, kasan-dev, linux-crypto, linux-doc,
linux-kbuild, linux-kernel, linux-mm, linux-security-module,
linux-sparse, linux-wireless, llvm, rcu
In-Reply-To: <20251120145835.3833031-2-elver@google.com>
Context Analysis is a language extension, which enables statically
checking that required contexts are active (or inactive), by acquiring
and releasing user-definable "context guards". 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.
Clang originally called the feature "Thread Safety Analysis" [1]. 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, and "capability" is already overloaded in
the kernel, the naming chosen for the kernel departs from Clang's
"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.
[1] https://clang.llvm.org/docs/ThreadSafetyAnalysis.html
[2] https://www.cs.cornell.edu/talc/papers/capabilities.pdf
See more details in the kernel-doc documentation added in this and
subsequent changes.
Clang version 22+ is required.
Signed-off-by: Marco Elver <elver@google.com>
---
v4:
* Rename capability -> context analysis.
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-context-analysis.h | 452 +++++++++++++++++++++-
lib/Kconfig.debug | 30 ++
scripts/Makefile.context-analysis | 7 +
scripts/Makefile.lib | 10 +
5 files changed, 493 insertions(+), 7 deletions(-)
create mode 100644 scripts/Makefile.context-analysis
diff --git a/Makefile b/Makefile
index d763c2c75cdb..0cad6e76f801 100644
--- a/Makefile
+++ b/Makefile
@@ -1093,6 +1093,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_CONTEXT_ANALYSIS) += scripts/Makefile.context-analysis
include-$(CONFIG_GCC_PLUGINS) += scripts/Makefile.gcc-plugins
include $(addprefix $(srctree)/, $(include-y))
diff --git a/include/linux/compiler-context-analysis.h b/include/linux/compiler-context-analysis.h
index f8af63045281..8c75e1d0034a 100644
--- a/include/linux/compiler-context-analysis.h
+++ b/include/linux/compiler-context-analysis.h
@@ -6,27 +6,465 @@
#ifndef _LINUX_COMPILER_CONTEXT_ANALYSIS_H
#define _LINUX_COMPILER_CONTEXT_ANALYSIS_H
+#if defined(WARN_CONTEXT_ANALYSIS)
+
+/*
+ * 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_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
+ * only accessible within active context
+ *
+ * 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
+ *
+ * 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 only accessible within active context
+ *
+ * Declares that the data pointed to by the struct member pointer or global
+ * 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
+ *
+ * struct some_state {
+ * spinlock_t lock;
+ * long *counter __pt_guarded_by(&lock);
+ * };
+ */
+# define __pt_guarded_by(...) __attribute__((pt_guarded_by(__VA_ARGS__)))
+
+/**
+ * context_guard_struct() - declare or define a context guard struct
+ * @name: struct name
+ *
+ * Helper to declare or define a struct type that is also a context guard.
+ *
+ * .. code-block:: c
+ *
+ * context_guard_struct(my_handle) {
+ * int foo;
+ * long bar;
+ * };
+ *
+ * struct some_state {
+ * ...
+ * };
+ * // ... declared elsewhere ...
+ * 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_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_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) { } \
+ struct name
+
+/**
+ * disable_context_analysis() - disables context analysis
+ *
+ * Disables context analysis. Must be paired with a later
+ * enable_context_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_context_analysis() - re-enables context analysis
+ *
+ * Re-enables context analysis. Must be paired with a prior
+ * disable_context_analysis().
+ */
+# define enable_context_analysis() __diag_pop()
+
+/**
+ * __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_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_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 */
+
+/**
+ * context_unsafe() - disable context checking for contained code
+ *
+ * Disables context 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 context_unsafe(d->counter);
+ * }
+ */
+#define context_unsafe(...) \
+({ \
+ disable_context_analysis(); \
+ __VA_ARGS__; \
+ enable_context_analysis() \
+})
+
+/**
+ * __context_unsafe() - function attribute, disable context checking
+ * @comment: comment explaining why opt-out is safe
+ *
+ * Function attribute denoting that context analysis is disabled for the
+ * whole function. Forces adding an inline comment as argument.
+ */
+#define __context_unsafe(comment) __no_context_analysis
+
+/**
+ * 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 "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.
+ *
+ * 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 context_unsafe_alias(p) _context_unsafe_alias((void **)&(p))
+static inline void _context_unsafe_alias(void **p) { }
+
+/**
+ * token_context_guard() - declare an abstract global context guard instance
+ * @name: token context guard 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 `__ctx_guard_@name`.
+ */
+#define token_context_guard(name, ...) \
+ context_guard_struct(__ctx_guard_##name, ##__VA_ARGS__) {}; \
+ extern const struct __ctx_guard_##name *name
+
+/**
+ * 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 context
+ * guard class @ctx. This is helpful where multiple related token contexts are
+ * declared, to allow using the same underlying type (`__ctx_guard_@ctx`) as
+ * function arguments.
+ */
+#define token_context_guard_instance(ctx, name) \
+ extern const struct __ctx_guard_##ctx *name
+
+/*
+ * Common keywords for static context 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 context guard
+ * @x: context guard instance pointer
+ *
+ * Function attribute declaring that the caller must hold the given context
+ * guard instance @x exclusively.
+ */
+# define __must_hold(x) __requires_ctx_guard(x)
+
+/**
+ * __must_not_hold() - function attribute, caller must not hold context guard
+ * @x: context guard instance pointer
+ *
+ * Function attribute declaring that the caller must not hold the given context
+ * guard instance @x.
+ */
+# define __must_not_hold(x) __excludes_ctx_guard(x)
+
+/**
+ * __acquires() - function attribute, function acquires context guard exclusively
+ * @x: context guard instance pointer
+ *
+ * Function attribute declaring that the function acquires the given context
+ * guard instance @x exclusively, but does not release it.
+ */
+# define __acquires(x) __acquires_ctx_guard(x)
+
+/**
+ * __cond_acquires() - function attribute, function conditionally
+ * acquires a context guard exclusively
+ * @x: context guard instance pointer
+ *
+ * Function attribute declaring that the function conditionally acquires the
+ * given context guard instance @x exclusively, but does not release it.
+ */
+# define __cond_acquires(x) __try_acquires_ctx_guard(1, x)
+
+/**
+ * __releases() - function attribute, function releases a context guard exclusively
+ * @x: context guard instance pointer
+ *
+ * Function attribute declaring that the function releases the given context
+ * guard instance @x exclusively. The associated context must be active on
+ * entry.
+ */
+# define __releases(x) __releases_ctx_guard(x)
+
+/**
+ * __acquire() - function to acquire context guard exclusively
+ * @x: context guard instance pointer
+ *
+ * No-op function that acquires the given context guard instance @x exclusively.
+ */
+# define __acquire(x) __acquire_ctx_guard(x)
+
+/**
+ * __release() - function to release context guard exclusively
+ * @x: context guard instance pointer
+ *
+ * No-op function that releases the given context guard instance @x.
+ */
+# define __release(x) __release_ctx_guard(x)
+
+/**
+ * __cond_lock() - function that conditionally acquires a context guard
+ * exclusively
+ * @x: context guard instance pinter
+ * @c: boolean expression
+ *
+ * Return: result of @c
+ *
+ * No-op function that conditionally acquires context guard instance @x
+ * exclusively, if the boolean expression @c is true. The result of @c is the
+ * return value; for example:
+ *
+ * .. code-block:: c
+ *
+ * #define spin_trylock(l) __cond_lock(&lock, _spin_trylock(&lock))
+ */
+# define __cond_lock(x, c) __try_acquire_ctx_guard(x, c)
+
+/**
+ * __must_hold_shared() - function attribute, caller must hold shared context guard
+ * @x: context guard instance pointer
+ *
+ * Function attribute declaring that the caller must hold the given context
+ * guard instance @x with shared access.
+ */
+# define __must_hold_shared(x) __requires_shared_ctx_guard(x)
+
+/**
+ * __acquires_shared() - function attribute, function acquires context guard shared
+ * @x: context guard instance pointer
+ *
+ * Function attribute declaring that the function acquires the given
+ * context guard instance @x with shared access, but does not release it.
+ */
+# define __acquires_shared(x) __acquires_shared_ctx_guard(x)
+
+/**
+ * __cond_acquires_shared() - function attribute, function conditionally
+ * acquires a context guard shared
+ * @x: context guard instance pointer
+ *
+ * Function attribute declaring that the function conditionally acquires the
+ * given context guard instance @x with shared access, but does not release it.
+ */
+# define __cond_acquires_shared(x) __try_acquires_shared_ctx_guard(1, x)
+
+/**
+ * __releases_shared() - function attribute, function releases a
+ * context guard shared
+ * @x: context guard instance pointer
+ *
+ * Function attribute declaring that the function releases the given context
+ * guard instance @x with shared access. The associated context must be active
+ * on entry.
+ */
+# define __releases_shared(x) __releases_shared_ctx_guard(x)
+
+/**
+ * __acquire_shared() - function to acquire context guard shared
+ * @x: context guard instance pointer
+ *
+ * No-op function that acquires the given context guard instance @x with shared
+ * access.
+ */
+# define __acquire_shared(x) __acquire_shared_ctx_guard(x)
+
+/**
+ * __release_shared() - function to release context guard shared
+ * @x: context guard instance pointer
+ *
+ * No-op function that releases the given context guard instance @x with shared
+ * access.
+ */
+# define __release_shared(x) __release_shared_ctx_guard(x)
+
+/**
+ * __cond_lock_shared() - function that conditionally acquires a context guard shared
+ * @x: context guard instance pinter
+ * @c: boolean expression
+ *
+ * Return: result of @c
+ *
+ * No-op function that conditionally acquires context guard instance @x with
+ * shared access, if the boolean expression @c is true. The result of @c is the
+ * return value.
+ */
+# define __cond_lock_shared(x, c) __try_acquire_shared_ctx_guard(x, c)
#endif /* __CHECKER__ */
+/**
+ * __acquire_ret() - helper to acquire context guard 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 context guard 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 context guards.
+ *
+ * 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_context_analysis
+#define __acquires_shared_ret __no_context_analysis
+
#endif /* _LINUX_COMPILER_CONTEXT_ANALYSIS_H */
diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index 3034e294d50d..696e2a148a15 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -613,6 +613,36 @@ 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_CONTEXT_ANALYSIS
+ bool "Compiler context-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
+ Context Analysis is a language extension, which enables statically
+ checking that required contexts are active (or inactive) by acquiring
+ and releasing user-definable "context guards".
+
+ Clang's name of the feature is "Thread Safety Analysis". Requires
+ Clang 22 or later.
+
+ Produces warnings by default. Select CONFIG_WERROR if you wish to
+ turn these warnings into errors.
+
+ For more details, see Documentation/dev-tools/context-analysis.rst.
+
+config WARN_CONTEXT_ANALYSIS_ALL
+ bool "Enable context analysis for all source files"
+ depends on WARN_CONTEXT_ANALYSIS
+ depends on EXPERT && !COMPILE_TEST
+ help
+ Enable tree-wide context 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.context-analysis b/scripts/Makefile.context-analysis
new file mode 100644
index 000000000000..70549f7fae1a
--- /dev/null
+++ b/scripts/Makefile.context-analysis
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0
+
+context-analysis-cflags := -DWARN_CONTEXT_ANALYSIS \
+ -fexperimental-late-parse-attributes -Wthread-safety \
+ -Wthread-safety-pointer -Wthread-safety-beta
+
+export CFLAGS_CONTEXT_ANALYSIS := $(context-analysis-cflags)
diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib
index 1d581ba5df66..aa45b3273f7c 100644
--- a/scripts/Makefile.lib
+++ b/scripts/Makefile.lib
@@ -105,6 +105,16 @@ _c_flags += $(if $(patsubst n%,, \
-D__KCSAN_INSTRUMENT_BARRIERS__)
endif
+#
+# Enable context analysis flags only where explicitly opted in.
+# (depends on variables CONTEXT_ANALYSIS_obj.o, CONTEXT_ANALYSIS)
+#
+ifeq ($(CONFIG_WARN_CONTEXT_ANALYSIS),y)
+_c_flags += $(if $(patsubst n%,, \
+ $(CONTEXT_ANALYSIS_$(target-stem).o)$(CONTEXT_ANALYSIS)$(if $(is-kernel-object),$(CONFIG_WARN_CONTEXT_ANALYSIS_ALL))), \
+ $(CFLAGS_CONTEXT_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.52.0.rc1.455.g30608eb744-goog
^ permalink raw reply related
* [PATCH v4 01/35] compiler_types: Move lock checking attributes to compiler-context-analysis.h
From: Marco Elver @ 2025-11-20 14:49 UTC (permalink / raw)
To: elver, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon
Cc: David S. Miller, Luc Van Oostenryck, Chris Li, Paul E. McKenney,
Alexander Potapenko, Arnd Bergmann, Bart Van Assche,
Christoph Hellwig, Dmitry Vyukov, Eric Dumazet,
Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers,
Jann Horn, Joel Fernandes, Johannes Berg, 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, kasan-dev, linux-crypto, linux-doc,
linux-kbuild, linux-kernel, linux-mm, linux-security-module,
linux-sparse, linux-wireless, llvm, rcu
In-Reply-To: <20251120145835.3833031-2-elver@google.com>
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 "context analysis".
No functional change intended.
Signed-off-by: Marco Elver <elver@google.com>
Reviewed-by: Bart Van Assche <bvanassche@acm.org>
---
v4:
* Rename capability -> context analysis.
---
include/linux/compiler-context-analysis.h | 32 +++++++++++++++++++++++
include/linux/compiler_types.h | 18 ++-----------
2 files changed, 34 insertions(+), 16 deletions(-)
create mode 100644 include/linux/compiler-context-analysis.h
diff --git a/include/linux/compiler-context-analysis.h b/include/linux/compiler-context-analysis.h
new file mode 100644
index 000000000000..f8af63045281
--- /dev/null
+++ b/include/linux/compiler-context-analysis.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Macros and attributes for compiler-based static context analysis.
+ */
+
+#ifndef _LINUX_COMPILER_CONTEXT_ANALYSIS_H
+#define _LINUX_COMPILER_CONTEXT_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_CONTEXT_ANALYSIS_H */
diff --git a/include/linux/compiler_types.h b/include/linux/compiler_types.h
index 0a1b9598940d..7eb8d8db6c28 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-context-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.52.0.rc1.455.g30608eb744-goog
^ permalink raw reply related
* [PATCH v4 00/35] Compiler-Based Context- and Locking-Analysis
From: Marco Elver @ 2025-11-20 14:49 UTC (permalink / raw)
To: elver, Peter Zijlstra, Boqun Feng, Ingo Molnar, Will Deacon,
Linus Torvalds
Cc: David S. Miller, Luc Van Oostenryck, Chris Li, Paul E. McKenney,
Alexander Potapenko, Arnd Bergmann, Bart Van Assche,
Christoph Hellwig, Dmitry Vyukov, Eric Dumazet,
Frederic Weisbecker, Greg Kroah-Hartman, Herbert Xu, Ian Rogers,
Jann Horn, Joel Fernandes, Johannes Berg, 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, kasan-dev, linux-crypto, linux-doc,
linux-kbuild, linux-kernel, linux-mm, linux-security-module,
linux-sparse, linux-wireless, llvm, rcu
Context Analysis is a language extension, which enables statically
checking that required contexts are active (or inactive) by acquiring
and releasing user-definable "context guards". 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 feature requires Clang 22 (unreleased) or later. Clang originally
called the feature "Thread Safety Analysis" [1]. 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, and "capability" is already overloaded in
the kernel, the naming chosen for the kernel departs from Clang's
"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.
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 already exist sparsely, 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 context 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; two approaches have been considered:
A. Tree-wide all-or-nothing approach. This approach requires tree-wide
changes, adding annotations or selective opt-outs. Making more
primitives context-analysis aware increases churn where maintainers
are unfamiliar with the feature and the analysis is unable to deal
with complex code patterns as-is.
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
context analysis (via Makefile):
CONTEXT_ANALYSIS_foo.o := y # foo.o only
CONTEXT_ANALYSIS := y # all TUs
Most (eventually all) synchronization primitives, and more
context guards including ones that 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.1974171-1-bvanassche@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 context 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 context guards are supported, we would see more static
checking beyond what regular C can provide, all while remaining easy
(and 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 context 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 context-guard pointers is required (from this version
onwards):
https://github.com/llvm/llvm-project/commit/7ccb5c08f0685d4787f12c3224a72f0650c5865e
The minimum required release version will be Clang 22.
This series is also available at this Git tree:
https://git.kernel.org/pub/scm/linux/kernel/git/melver/linux.git/log/?h=ctx-analysis/dev
=== Changelog ===
v4:
- Rename capability -> context analysis, per Linus's suggestion:
https://lore.kernel.org/all/CAHk-=wgd-Wcp0GpYaQnU7S9ci+FvFmaNw1gm75mzf0ZWdNLxvw@mail.gmail.com/
- Minor fixes.
v3: https://lore.kernel.org/all/20250918140451.1289454-1-elver@google.com/
- Bump min. Clang version to 22+ (unreleased), which now supports:
* re-entrancy via __attribute__((reentrant_capability));
* basic form of capability alias analysis - 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.2873893-1-elver@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.1902989-1-elver@google.com
Marco Elver (35):
compiler_types: Move lock checking attributes to
compiler-context-analysis.h
compiler-context-analysis: Add infrastructure for Context Analysis
with Clang
compiler-context-analysis: Add test stub
Documentation: Add documentation for Compiler-Based Context Analysis
checkpatch: Warn about context_unsafe() without comment
cleanup: Basic compatibility with context analysis
lockdep: Annotate lockdep assertions for context analysis
locking/rwlock, spinlock: Support Clang's context analysis
compiler-context-analysis: Change __cond_acquires to take return value
locking/mutex: Support Clang's context analysis
locking/seqlock: Support Clang's context analysis
bit_spinlock: Include missing <asm/processor.h>
bit_spinlock: Support Clang's context analysis
rcu: Support Clang's context analysis
srcu: Support Clang's context analysis
kref: Add context-analysis annotations
locking/rwsem: Support Clang's context analysis
locking/local_lock: Include missing headers
locking/local_lock: Support Clang's context analysis
locking/ww_mutex: Support Clang's context analysis
debugfs: Make debugfs_cancellation a context guard struct
compiler-context-analysis: Remove Sparse support
compiler-context-analysis: Remove __cond_lock() function-like helper
compiler-context-analysis: Introduce header suppressions
compiler: Let data_race() imply disabled context analysis
MAINTAINERS: Add entry for Context Analysis
kfence: Enable context analysis
kcov: Enable context analysis
kcsan: Enable context analysis
stackdepot: Enable context analysis
rhashtable: Enable context analysis
printk: Move locking annotation to printk.c
security/tomoyo: Enable context analysis
crypto: Enable context analysis
sched: Enable context analysis for core.c and fair.c
Documentation/dev-tools/context-analysis.rst | 146 +++++
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-context-analysis.h | 429 +++++++++++++
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 | 90 +--
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 | 64 +-
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 | 44 ++
lib/Makefile | 6 +
lib/dec_and_lock.c | 8 +-
lib/rhashtable.c | 5 +-
lib/stackdepot.c | 20 +-
lib/test_context-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.context-analysis | 11 +
scripts/Makefile.lib | 10 +
scripts/checkpatch.pl | 7 +
scripts/context-analysis-suppression.txt | 33 +
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, 2377 insertions(+), 592 deletions(-)
create mode 100644 Documentation/dev-tools/context-analysis.rst
create mode 100644 include/linux/compiler-context-analysis.h
create mode 100644 lib/test_context-analysis.c
create mode 100644 scripts/Makefile.context-analysis
create mode 100644 scripts/context-analysis-suppression.txt
--
2.52.0.rc1.455.g30608eb744-goog
^ permalink raw reply
* Re: [PATCH v2] lockdown: Only log restrictions once
From: Xiujianfeng @ 2025-11-20 13:35 UTC (permalink / raw)
To: Daniel Tang, Nicolas Bouchinet, Xiu Jianfeng, Paul Moore
Cc: linux-security-module, linux-kernel, Nathan Lynch,
Matthew Garrett, Kees Cook, David Howells, James Morris
In-Reply-To: <7645139.4DdEvYhyI6@daniel-desktop3>
Hi Daniel,
On 11/20/2025 5:26 PM, Daniel Tang wrote:
> On Thursday, 20 November 2025, 02:37:56 EST Xiujianfeng <xiujianfeng@huaweicloud.com> wrote:
>> Is it possible to adjust the printk_ratelimit & printk_ratelimit_burst
>> in /proc/sys/kernel/ to reduce the logs in your scenario?
>
> It's not working. Watching the console after setting the sysctl and
> repeatedly clicking org.freedesktop.login1.Manager.CanSuspend in
> qdbusviewer (simulating what the lockscreen does), I see:
>
> ```console
> root@daniel-desktop3:~# uname -a
> Linux daniel-desktop3 6.17.0-6-generic #6-Ubuntu SMP PREEMPT_DYNAMIC Tue Oct 7 13:34:17 UTC 2025 x86_64 GNU/Linux
> root@daniel-desktop3:~# sysctl kernel.printk_ratelimit_burst=1
> kernel.printk_ratelimit_burst = 1
> root@daniel-desktop3:~# sysctl kernel.printk_ratelimit=999999
> kernel.printk_ratelimit = 999999
> root@daniel-desktop3:~# dmesg -W
> [14385.334698] lockdown_is_locked_down: 3 callbacks suppressed
> [14385.334701] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
> [14385.614738] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
> [14385.878857] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
> [14386.166744] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
> [14386.454771] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
> [14386.750900] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
> [14387.038795] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
> [14387.334770] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
> [14387.622696] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
> [14387.926763] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
> [14390.366582] lockdown_is_locked_down: 7 callbacks suppressed
> [14390.366585] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
> [14390.798744] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
> [14391.118802] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
> [14391.422728] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
> [14391.742754] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
> [14392.046735] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
> [14392.350745] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
> [14392.654992] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
> [14392.974797] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
> [14393.270741] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
> ```
>
> At my desk, I lock my screen every 5 hours. In public, I might lock my
> screen every 1 minute, 5 minute, or 15 *minutes*. printk_ratelimit
> seems to be targeted towards things that happen every N *seconds*.
Sorry for misleading.
After reading the code, I found that the macro of printk_ratelimited is
#define printk_ratelimited(fmt, ...) \
({ \
static DEFINE_RATELIMIT_STATE(_rs, \
DEFAULT_RATELIMIT_INTERVAL, \
DEFAULT_RATELIMIT_BURST); \
\
if (__ratelimit(&_rs)) \
printk(fmt, ##__VA_ARGS__); \
})
It seems that the rate is fixed and can not be modified via sysctl.
While another interface with a modifiable rate, which is
printk_ratelimit(), is marked as "don't use".
>
>> logs here serve a purpose similar to auditing. Based on this, I think
>> this change will meaningfully degrade the quality of the logs, making it
>> hard for users to find out what happens when lockdown is active,
>> especially after a long time running.
>
> For v3 in December, I'm thinking of adding a code path to special-case
> *reads* from /sys/power/state. What do you think?
Sorry, I am not familiar with hibernation, maybe you can CC hibernation
maintainers.
>
^ permalink raw reply
* Re: [RFC v1 0/1] Implement IMA Event Log Trimming
From: Roberto Sassu @ 2025-11-20 11:02 UTC (permalink / raw)
To: Anirudh Venkataramanan, linux-integrity
Cc: Mimi Zohar, Roberto Sassu, Dmitry Kasatkin, Eric Snowberg,
Paul Moore, James Morris, Serge E . Hallyn, linux-security-module,
Steven Chen, Gregory Lumen, Lakshmi Ramasubramanian,
Sush Shringarputale
In-Reply-To: <20251119213359.39397-1-anirudhve@linux.microsoft.com>
On Wed, 2025-11-19 at 13:33 -0800, Anirudh Venkataramanan wrote:
> ==========================================================================
> > A. Introduction |
> ==========================================================================
>
> IMA events are kept in kernel memory and preserved across kexec soft
> reboots. This can lead to increased kernel memory usage over time,
> especially with aggressive IMA policies that measure everything. To reduce
> memory pressure, it becomes necessary to discard IMA events but given that
> IMA events are extended into PCRs in the TPM, just discarding events will
> break the PCR extension chain, making future verification of the IMA event
> log impossible.
>
> This patch series proposes a method to discard IMA events while keeping the
> log verifiable. While reducing memory pressure is the primary objective,
> the second order benefit of trimming the IMA log is that IMA log verifiers
> (local userspace daemon or a remote cloud service) can process smaller IMA
> logs on a rolling basis, thus avoiding re-verification of previously
> verified events.
Hi Anirudh
I will rephrase this paragraph, to be sure that I understood it
correctly.
You are proposing a method to trim the measurement list and, at the
same time, to keep the measurement list verifiable after trimming. The
way you would like to achieve that is to keep the verification state in
the kernel in the form of PCR values.
Those values mean what verifiers have already verified. Thus, for the
next verification attempt, verifiers take the current PCR values as
starting values, replay the truncated IMA measurement list, and again
you match with the current PCR values and you trim until that point.
So the benefit of this proposal is that you keep the verification of
the IMA measurement list self-contained by using the last verification
state (PCR starting value) and the truncated IMA measurement list as
the inputs of your verification.
Let me reiterate on the trusted computing principles IMA relies on for
providing the evidence about a system's integrity.
Unless you are at the beginning of the measurement chain, where the
Root of Trust for Measurement (RTM) is trusted by assumption, the
measurements done by a component can be trusted because that component
was already measured by the previous component in the boot chain,
before it had any chance to corrupt the system.
In the context of IMA, IMA can be trusted to make new measurements
because it measures every file before those files could cause any harm
to the system. So, potentially IMA and the kernel can be corrupted by
any file.
What you are proposing would not work, because you are placing trust in
an input (the PCR starting value) that can be manipulated at any time
by a corrupted kernel, before you had the chance to detect such
corruption.
Let me describe a scenario where I could take advantage of such
weakness. After the first measurement list trim, I perform an attack on
the system such that it corrupts the kernel. IMA added a new entry in
the measurement list, which would reveal the attack.
But, since I have control of the kernel, I conveniently update the PCR
starting value to replay the new measurement entry, and remove the
measurement entry from the measurement list.
Now, from the perspective of the user space verifiers everything is
fine, the truncated IMA measurement list is clean, no attack, and the
current PCR values match by replaying the new PCR starting value with
the remaining of the IMA measurement list.
So, in my opinion the kernel should just offer the ability to trim the
measurement list, and a remote verifier should be responsible to verify
the measurement list, without relying on anything from the system being
evaluated.
Sure, the remote verifier can verify just the trimmed IMA measurement
list, but the remote verifier must solely rely on state information
maintained internally.
Roberto
> The method has other advantages too:
>
> 1. It provides a userspace interface that can be used to precisely control
> trim point, allowing for trim points to be optionally aligned with
> userspace IMA event log validation.
>
> 2. It ensures that all necessary information required for continued IMA
> log validation is made available via the userspace interface at all
> times.
>
> 3. It provides a simple mechanism for userspace applications to determine
> if the event log has been unexpectedly trimmed.
>
> 4. The duration for which the IMA Measurement list mutex must be held (for
> trimming) is minimal.
>
> ==========================================================================
> > B. Solution |
> ==========================================================================
>
> --------------------------------------------------------------------------
> > B.1 Overview |
> --------------------------------------------------------------------------
>
> The kernel trims the IMA event log based on PCR values supplied by userspace.
> The core principles leveraged are as follows:
>
> - Given an IMA event log, PCR values for each IMA event can be obtained by
> recalulating the PCR extension for each event. Thus processing N events
> from the start will yield PCR values as of event N. This is referred to
> as "IMA event log replay".
>
> - To get the PCR value for event N + 1, only the PCR value as of event N
> is needed. If this can be known, events till and including N can be
> safely purged.
>
> Putting it all together, we get the following userspace + kernel flow:
>
> 1. A userspace application replays the IMA event log to generate PCR
> values and then triggers a trim by providing these values to the kernel
> (by writing to a pseudo file).
>
> Optionally, the userspace application may verify these PCR values
> against the corresponding TPM quote, and trigger trimming only if
> the calculated PCR values match up to the expectations in the quote's
> PCR digest.
>
> 2. The kernel uses the userspace supplied PCR values to trim the IMA
> measurements list at a specific point, and so these are referred to as
> "trim-to PCR values" in this context.
>
> Note that the kernel doesn't really understand what these userspace
> provided PCR values mean or what IMA event they correspond to, and so
> it does its own IMA event replay till either the replayed PCR values
> match with the userspace provided ones, or it runs out of events.
>
> If a match is found, the kernel can proceed with trimming the IMA
> measurements list. This is done in two steps, to keep locking context
> minimal.
>
> step 1: Find and return the list entry (as a count from head) of exact
> match. This does not lock the measurements list mutex, ensuring
> new events can be appended to the log.
>
> step 2: Lock the measurements list mutex and trim the measurements list
> at the previously identified list entry.
>
> If the trim is successful, the trim-to PCR values are saved as "starting
> PCR values". The next time userspace wants to replay the IMA event log,
> it will use the starting PCR values as the base for the IMA event log
> replay.
>
> --------------------------------------------------------------------------
> > B.2 Kernel Interfaces |
> --------------------------------------------------------------------------
>
> A new configfs pseudo file /sys/kernel/config/ima/pcrs that supports the
> following operations is exposed.
>
> read: returns starting PCR values stored in the kernel (within IMA
> specifically).
>
> write: writes trim-to PCR values to trigger trimming. If trimming is
> successful, trim-to PCR values are stored as starting PCR values.
> requires root privileges.
>
> --------------------------------------------------------------------------
> > B.3 Walk-through with a real example |
> --------------------------------------------------------------------------
>
> This is a real example from a test run.
>
> Suppose this IMA policy is deployed:
>
> measure func=FILE_CHECK mask=MAY_READ pcr=10
> measure func=FILE_CHECK mask=MAY_WRITE pcr=11
>
> When the policy is deployed, a zero digest starting PCR value will be set
> for each PCR used. If the TPM supports multiple hashbanks, there will be
> one starting PCR value per PCR, per TPM hashbank. This can be seen in the
> following hexdump:
>
> $ sudo hexdump -vC /sys/kernel/config/ima/pcrs
> 00000000 70 63 72 31 30 3a 73 68 61 31 3a 00 00 00 00 00 |pcr10:sha1:.....|
> 00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 70 |...............p|
> 00000020 63 72 31 31 3a 73 68 61 31 3a 00 00 00 00 00 00 |cr11:sha1:......|
> 00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 70 63 |..............pc|
> 00000040 72 31 30 3a 73 68 61 32 35 36 3a 00 00 00 00 00 |r10:sha256:.....|
> 00000050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
> 00000060 00 00 00 00 00 00 00 00 00 00 00 70 63 72 31 31 |...........pcr11|
> 00000070 3a 73 68 61 32 35 36 3a 00 00 00 00 00 00 00 00 |:sha256:........|
> 00000080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
> 00000090 00 00 00 00 00 00 00 00 70 63 72 31 30 3a 73 68 |........pcr10:sh|
> 000000a0 61 33 38 34 3a 00 00 00 00 00 00 00 00 00 00 00 |a384:...........|
> 000000b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
> 000000c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
> 000000d0 00 00 00 00 00 70 63 72 31 31 3a 73 68 61 33 38 |.....pcr11:sha38|
> 000000e0 34 3a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |4:..............|
> 000000f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
> 00000100 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
> 00000110 00 00 |..|
> 00000112
>
> Let's say that a userspace utility replays the IMA event log, and triggers
> trimming by writing the following PCR values (i.e. trim-to PCR values) to the
> pseudo file:
>
> pcr10:sha256:8268782906555cf3aefc179f815c878527dd4e67eaa836572ebabab31977922c
> pcr11:sha256:4c7f31927183eacb53d51d95b0162916fd3fca51a8d1efc6dde3805eb891fe41
>
> The trim is successful,
>
> 1. Some number of entries from the measurements log will disappear. This
> can be verified by reading out the ASCII or binary IMA measurements
> file.
>
> 2. The trim-to PCR values are saved as starting PCR values. This can be
> verified by reading out the pseudo file again as shown below. Note that
> even through only sha256 PCR values were written, the kernel populated
> sha1 and sha384 starting values as well.
>
> $ sudo hexdump -vC /sys/kernel/config/ima/pcrs
>
> 00000000 70 63 72 31 30 3a 73 68 61 31 3a c4 7f 9d 00 68 |pcr10:sha1:....h|
> 00000010 e4 86 71 bf bc ae f0 10 12 ff 68 e2 9e 74 e4 70 |..q.......h..t.p|
> 00000020 63 72 31 31 3a 73 68 61 31 3a 90 d7 17 ac 60 4d |cr11:sha1:....`M|
> 00000030 c8 25 ce 77 7d 9d 94 cf 44 7b b2 2e 2e e2 70 63 |.%.w}...D{....pc|
> 00000040 72 31 30 3a 73 68 61 32 35 36 3a 82 68 78 29 06 |r10:sha256:.hx).|
> 00000050 55 5c f3 ae fc 17 9f 81 5c 87 85 27 dd 4e 67 ea |U\......\..'.Ng.|
> 00000060 a8 36 57 2e ba ba b3 19 77 92 2c 70 63 72 31 31 |.6W.....w.,pcr11|
> 00000070 3a 73 68 61 32 35 36 3a 4c 7f 31 92 71 83 ea cb |:sha256:L.1.q...|
> 00000080 53 d5 1d 95 b0 16 29 16 fd 3f ca 51 a8 d1 ef c6 |S.....)..?.Q....|
> 00000090 dd e3 80 5e b8 91 fe 41 70 63 72 31 30 3a 73 68 |...^...Apcr10:sh|
> 000000a0 61 33 38 34 3a 8e d6 12 18 b1 d6 cd 95 16 98 33 |a384:..........3|
> 000000b0 2b 7d a2 d6 d9 05 c7 e8 5b 15 b0 91 c5 fc 23 d1 |+}......[.....#.|
> 000000c0 f9 a8 8d 60 50 5c e9 64 5f d7 b3 b2 f1 9c 90 0a |...`P\.d_.......|
> 000000d0 45 53 5d b2 57 70 63 72 31 31 3a 73 68 61 33 38 |ES].Wpcr11:sha38|
> 000000e0 34 3a 25 fc 21 28 31 5a f7 c6 fb 0f 40 c9 06 e6 |4:%.!(1Z....@...|
> 000000f0 c5 da ed 20 61 a1 03 54 4f 67 18 88 82 0f 48 d1 |... a..TOg....H.|
> 00000100 2f e0 3d 36 46 5e 94 a4 88 51 f8 91 39 7e e5 97 |/.=6F^...Q..9~..|
> 00000110 2c c5 |,.|
> 00000112
>
> --------------------------------------------------------------------------
> > C. Footnotes |
> --------------------------------------------------------------------------
>
> 1. The 'pcrs' pseudo file is currently part of configfs. This was due to
> some early internal feedback in a different context. This can as well be
> in securityfs with the rest of the IMA pseudo files.
>
> 2. PCR values are never read out of the TPM at any point. All PCR values
> used are derived from IMA event log replay.
>
> 3. Code is "RFC quality". Refinements can be made if the method is accepted.
>
> 4. For functional validation, base kernel version was 6.17 stable, with the
> most recent tested version being 6.17.8.
>
> 5. Code has been validated to some degree using a python-based internal test
> tool. This can be published if there is community interest.
>
> Steven Chen (1):
> ima: Implement IMA event log trimming
>
> drivers/Kconfig | 2 +
> drivers/Makefile | 1 +
> drivers/ima/Kconfig | 13 +
> drivers/ima/Makefile | 2 +
> drivers/ima/ima_config_pcrs.c | 291 ++++++++++++++++++
> include/linux/ima.h | 27 ++
> security/integrity/ima/Makefile | 4 +
> security/integrity/ima/ima.h | 8 +
> security/integrity/ima/ima_init.c | 44 +++
> security/integrity/ima/ima_log_trim.c | 421 ++++++++++++++++++++++++++
> security/integrity/ima/ima_policy.c | 7 +-
> security/integrity/ima/ima_queue.c | 5 +-
> 12 files changed, 821 insertions(+), 4 deletions(-)
> create mode 100644 drivers/ima/Kconfig
> create mode 100644 drivers/ima/Makefile
> create mode 100644 drivers/ima/ima_config_pcrs.c
> create mode 100644 security/integrity/ima/ima_log_trim.c
>
^ permalink raw reply
* Re: Re: [BUG] landlock: sleeping function called from invalid context in hook_sb_delete()
From: 许佳凯 @ 2025-11-20 10:52 UTC (permalink / raw)
To: Günther Noack
Cc: linux-kernel, linux-security-module, Günther Noack,
Serge E. Hallyn, Mickaël Salaün
In-Reply-To: <20251120.c5c17c664315@gnoack.org>
Hello Günther,
Thanks a lot for your detailed reply.
Your explanation makes perfect sense. I agree this is indeed a false positive.
Thanks for pointing that out and for the clarification.
I appreciate your time and the helpful analysis.
Best regards,
-Jiakai
^ permalink raw reply
* Re: [PATCH -next] ima: Handle error code returned by ima_filter_rule_match()
From: Roberto Sassu @ 2025-11-20 9:17 UTC (permalink / raw)
To: Zhao Yipeng, zohar, roberto.sassu, dmitry.kasatkin, eric.snowberg
Cc: lujialin4, linux-integrity, linux-security-module, linux-kernel
In-Reply-To: <20251120071805.1604864-1-zhaoyipeng5@huawei.com>
On Thu, 2025-11-20 at 15:18 +0800, Zhao Yipeng wrote:
> In ima_match_rules(), if ima_filter_rule_match() returns -ENOENT due to
> the rule being NULL, the function incorrectly skips the 'if (!rc)' check
> and sets 'result = true'. The LSM rule is considered a match, causing
> extra files to be measured by IMA.
>
> This issue can be reproduced in the following scenario:
> After unloading the SELinux policy module via 'semodule -d', if an IMA
> measurement is triggered before ima_lsm_rules is updated,
> in ima_match_rules(), the first call to ima_filter_rule_match() returns
> -ESTALE. This causes the code to enter the 'if (rc == -ESTALE &&
> !rule_reinitialized)' block, perform ima_lsm_copy_rule() and retry. In
> ima_lsm_copy_rule(), since the SELinux module has been removed, the rule
> becomes NULL, and the second call to ima_filter_rule_match() returns
> -ENOENT. This bypasses the 'if (!rc)' check and results in a false match.
>
> Call trace:
> selinux_audit_rule_match+0x310/0x3b8
> security_audit_rule_match+0x60/0xa0
> ima_match_rules+0x2e4/0x4a0
> ima_match_policy+0x9c/0x1e8
> ima_get_action+0x48/0x60
> process_measurement+0xf8/0xa98
> ima_bprm_check+0x98/0xd8
> security_bprm_check+0x5c/0x78
> search_binary_handler+0x6c/0x318
> exec_binprm+0x58/0x1b8
> bprm_execve+0xb8/0x130
> do_execveat_common.isra.0+0x1a8/0x258
> __arm64_sys_execve+0x48/0x68
> invoke_syscall+0x50/0x128
> el0_svc_common.constprop.0+0xc8/0xf0
> do_el0_svc+0x24/0x38
> el0_svc+0x44/0x200
> el0t_64_sync_handler+0x100/0x130
> el0t_64_sync+0x3c8/0x3d0
>
> Fix this by changing 'if (!rc)' to 'if (rc <= 0)' to ensure that error
> codes like -ENOENT do not bypass the check and accidentally result in a
> successful match.
Thanks, it makes sense.
Reviewed-by: Roberto Sassu <roberto.sassu@huawei.com>
Roberto
> Fixes: 4af4662fa4a9d ("integrity: IMA policy")
> Signed-off-by: Zhao Yipeng <zhaoyipeng5@huawei.com>
> ---
> security/integrity/ima/ima_policy.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c
> index 128fab897930..db6d55af5a80 100644
> --- a/security/integrity/ima/ima_policy.c
> +++ b/security/integrity/ima/ima_policy.c
> @@ -674,7 +674,7 @@ static bool ima_match_rules(struct ima_rule_entry *rule,
> goto retry;
> }
> }
> - if (!rc) {
> + if (rc <= 0) {
> result = false;
> goto out;
> }
^ permalink raw reply
* Re: [PATCH v2] lockdown: Only log restrictions once
From: Daniel Tang @ 2025-11-20 9:26 UTC (permalink / raw)
To: Nicolas Bouchinet, Xiu Jianfeng, Paul Moore, Xiujianfeng
Cc: linux-security-module, linux-kernel, Nathan Lynch,
Matthew Garrett, Kees Cook, David Howells, James Morris
In-Reply-To: <2f4a1af8-adc6-4cbc-813f-4cc8e9bc75ae@huaweicloud.com>
On Thursday, 20 November 2025, 02:37:56 EST Xiujianfeng <xiujianfeng@huaweicloud.com> wrote:
> Is it possible to adjust the printk_ratelimit & printk_ratelimit_burst
> in /proc/sys/kernel/ to reduce the logs in your scenario?
It's not working. Watching the console after setting the sysctl and
repeatedly clicking org.freedesktop.login1.Manager.CanSuspend in
qdbusviewer (simulating what the lockscreen does), I see:
```console
root@daniel-desktop3:~# uname -a
Linux daniel-desktop3 6.17.0-6-generic #6-Ubuntu SMP PREEMPT_DYNAMIC Tue Oct 7 13:34:17 UTC 2025 x86_64 GNU/Linux
root@daniel-desktop3:~# sysctl kernel.printk_ratelimit_burst=1
kernel.printk_ratelimit_burst = 1
root@daniel-desktop3:~# sysctl kernel.printk_ratelimit=999999
kernel.printk_ratelimit = 999999
root@daniel-desktop3:~# dmesg -W
[14385.334698] lockdown_is_locked_down: 3 callbacks suppressed
[14385.334701] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
[14385.614738] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
[14385.878857] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
[14386.166744] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
[14386.454771] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
[14386.750900] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
[14387.038795] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
[14387.334770] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
[14387.622696] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
[14387.926763] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
[14390.366582] lockdown_is_locked_down: 7 callbacks suppressed
[14390.366585] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
[14390.798744] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
[14391.118802] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
[14391.422728] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
[14391.742754] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
[14392.046735] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
[14392.350745] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
[14392.654992] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
[14392.974797] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
[14393.270741] Lockdown: systemd-logind: hibernation is restricted; see man kernel_lockdown.7
```
At my desk, I lock my screen every 5 hours. In public, I might lock my
screen every 1 minute, 5 minute, or 15 *minutes*. printk_ratelimit
seems to be targeted towards things that happen every N *seconds*.
> logs here serve a purpose similar to auditing. Based on this, I think
> this change will meaningfully degrade the quality of the logs, making it
> hard for users to find out what happens when lockdown is active,
> especially after a long time running.
For v3 in December, I'm thinking of adding a code path to special-case
*reads* from /sys/power/state. What do you think?
^ permalink raw reply
* Re: [BUG] landlock: sleeping function called from invalid context in hook_sb_delete()
From: Günther Noack @ 2025-11-20 8:48 UTC (permalink / raw)
To: 许佳凯
Cc: linux-kernel, linux-security-module, Günther Noack,
Serge E. Hallyn, Mickaël Salaün
In-Reply-To: <20dd8187.9d18.19a75eadc43.Coremail.xujiakai24@mails.ucas.ac.cn>
Hello!
Thanks for the report!
CC-ing Mickaël, who authored that code
On Wed, Nov 12, 2025 at 10:35:17AM +0800, 许佳凯 wrote:
> The call trace indicates that hook_sb_delete() holds s_inode_list_lock (a spinlock) while invoking operations that may eventually call iput(), which can sleep.
> This violates the locking context expectations and triggers __might_sleep() warnings.
> The issue seems to be related to how Landlock handles superblock cleanup during security_sb_delete().
>
>
> I’m currently only reporting this issue to the community; the exact fix will likely need to be confirmed and implemented by the Landlock and filesystem maintainers.
This looks like a false positive to me.
There are three places where iput() is being called in hook_sb_delete,
two of them are in places where it is *not* holding the
s_inode_list_lock. The one that *is* holding the s_inode_list_lock
has the following comment:
/*
* At this point, we own the ihold() reference that was
* originally set up by get_inode_object() and the
* __iget() reference that we just set in this loop
* walk. Therefore the following call to iput() will
* not sleep nor drop the inode because there is now at
* least two references to it.
*/
That seems to indicate that the sleepability concern was taken into
consideration. iput() only sleeps if the refcount reaches zero, and
if you can exclude that, it won't sleep.
—Günther
--
^ permalink raw reply
* Re: [PATCH v2] lsm: use unrcu_pointer() for current->cred in security_init()
From: Xiu Jianfeng @ 2025-11-20 7:48 UTC (permalink / raw)
To: Paul Moore, linux-security-module
In-Reply-To: <20251119153636.15243-2-paul@paul-moore.com>
On 11/19/2025 11:36 PM, Paul Moore wrote:
> We need to directly allocate the cred's LSM state for the initial task
> when we initialize the LSM framework. Unfortunately, this results in a
> RCU related type mismatch, use the unrcu_pointer() macro to handle this
> a bit more elegantly.
>
> The explicit type casting still remains as we need to work around the
> constification of current->cred in this particular case.
>
> Signed-off-by: Paul Moore <paul@paul-moore.com>
Reviewed-by: Xiu Jianfeng <xiujianfeng@huawei.com>
> ---
> security/lsm_init.c | 3 ++-
> 1 file changed, 2 insertions(+), 1 deletion(-)
>
> diff --git a/security/lsm_init.c b/security/lsm_init.c
> index 6bb67d41ce52..05bd52e6b1f2 100644
> --- a/security/lsm_init.c
> +++ b/security/lsm_init.c
> @@ -467,7 +467,8 @@ int __init security_init(void)
> blob_sizes.lbs_inode, 0,
> SLAB_PANIC, NULL);
>
> - if (lsm_cred_alloc((struct cred __rcu *)current->cred, GFP_KERNEL))
> + if (lsm_cred_alloc((struct cred *)unrcu_pointer(current->cred),
> + GFP_KERNEL))
> panic("early LSM cred alloc failed\n");
> if (lsm_task_alloc(current))
> panic("early LSM task alloc failed\n");
^ permalink raw reply
* Re: [PATCH v2] lockdown: Only log restrictions once
From: Xiujianfeng @ 2025-11-20 7:37 UTC (permalink / raw)
To: Daniel Tang, Nicolas Bouchinet, Xiu Jianfeng, Paul Moore
Cc: linux-security-module, linux-kernel, Nathan Lynch,
Matthew Garrett, Kees Cook, David Howells, James Morris
In-Reply-To: <1961790.USuA9gRusQ@daniel-desktop3>
Hi Daniel,
On 11/20/2025 2:07 AM, Daniel Tang wrote:
> KDE's lockscreen causes systemd-logind to spam dmesg about hibernation.
> systemd declined to cache /sys/power/state due to runtime changeability.
>
> Link: https://github.com/systemd/systemd/pull/39802
> Signed-off-by: Daniel Tang <danielzgtg.opensource@gmail.com>
> ---
> security/lockdown/lockdown.c | 7 +++++--
> 1 file changed, 5 insertions(+), 2 deletions(-)
>
> diff --git a/security/lockdown/lockdown.c b/security/lockdown/lockdown.c
> index cf83afa1d879..83b50de52f21 100644
> --- a/security/lockdown/lockdown.c
> +++ b/security/lockdown/lockdown.c
> @@ -58,13 +58,16 @@ early_param("lockdown", lockdown_param);
> */
> static int lockdown_is_locked_down(enum lockdown_reason what)
> {
> + static volatile unsigned long lockdown_reasons_seen;
> + static_assert(ARRAY_SIZE(lockdown_reasons) < sizeof(lockdown_reasons_seen) * 8);
> +
> if (WARN(what >= LOCKDOWN_CONFIDENTIALITY_MAX,
> "Invalid lockdown reason"))
> return -EPERM;
>
> if (kernel_locked_down >= what) {
> - if (lockdown_reasons[what])
> - pr_notice_ratelimited("Lockdown: %s: %s is restricted; see man kernel_lockdown.7\n",
> + if (lockdown_reasons[what] && !test_and_set_bit(what, &lockdown_reasons_seen))
> + pr_notice("Lockdown: %s: %s is restricted; see man kernel_lockdown.7\n",
> current->comm, lockdown_reasons[what]);
Currently lockdown does not support the audit function, so I believe the
logs here serve a purpose similar to auditing. Based on this, I think
this change will meaningfully degrade the quality of the logs, making it
hard for users to find out what happens when lockdown is active,
especially after a long time running.
Is it possible to adjust the printk_ratelimit & printk_ratelimit_burst
in /proc/sys/kernel/ to reduce the logs in your scenario?
Anyway, I will wait for Nicolas's comment.
> return -EPERM;
> }
^ permalink raw reply
* [PATCH -next] ima: Handle error code returned by ima_filter_rule_match()
From: Zhao Yipeng @ 2025-11-20 7:18 UTC (permalink / raw)
To: zohar, roberto.sassu, dmitry.kasatkin, eric.snowberg
Cc: lujialin4, linux-integrity, linux-security-module, linux-kernel
In ima_match_rules(), if ima_filter_rule_match() returns -ENOENT due to
the rule being NULL, the function incorrectly skips the 'if (!rc)' check
and sets 'result = true'. The LSM rule is considered a match, causing
extra files to be measured by IMA.
This issue can be reproduced in the following scenario:
After unloading the SELinux policy module via 'semodule -d', if an IMA
measurement is triggered before ima_lsm_rules is updated,
in ima_match_rules(), the first call to ima_filter_rule_match() returns
-ESTALE. This causes the code to enter the 'if (rc == -ESTALE &&
!rule_reinitialized)' block, perform ima_lsm_copy_rule() and retry. In
ima_lsm_copy_rule(), since the SELinux module has been removed, the rule
becomes NULL, and the second call to ima_filter_rule_match() returns
-ENOENT. This bypasses the 'if (!rc)' check and results in a false match.
Call trace:
selinux_audit_rule_match+0x310/0x3b8
security_audit_rule_match+0x60/0xa0
ima_match_rules+0x2e4/0x4a0
ima_match_policy+0x9c/0x1e8
ima_get_action+0x48/0x60
process_measurement+0xf8/0xa98
ima_bprm_check+0x98/0xd8
security_bprm_check+0x5c/0x78
search_binary_handler+0x6c/0x318
exec_binprm+0x58/0x1b8
bprm_execve+0xb8/0x130
do_execveat_common.isra.0+0x1a8/0x258
__arm64_sys_execve+0x48/0x68
invoke_syscall+0x50/0x128
el0_svc_common.constprop.0+0xc8/0xf0
do_el0_svc+0x24/0x38
el0_svc+0x44/0x200
el0t_64_sync_handler+0x100/0x130
el0t_64_sync+0x3c8/0x3d0
Fix this by changing 'if (!rc)' to 'if (rc <= 0)' to ensure that error
codes like -ENOENT do not bypass the check and accidentally result in a
successful match.
Fixes: 4af4662fa4a9d ("integrity: IMA policy")
Signed-off-by: Zhao Yipeng <zhaoyipeng5@huawei.com>
---
security/integrity/ima/ima_policy.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c
index 128fab897930..db6d55af5a80 100644
--- a/security/integrity/ima/ima_policy.c
+++ b/security/integrity/ima/ima_policy.c
@@ -674,7 +674,7 @@ static bool ima_match_rules(struct ima_rule_entry *rule,
goto retry;
}
}
- if (!rc) {
+ if (rc <= 0) {
result = false;
goto out;
}
--
2.34.1
^ permalink raw reply related
* Re: [PATCH] security: sctp: Format type and permission checks tables
From: Bagas Sanjaya @ 2025-11-20 1:39 UTC (permalink / raw)
To: Paul Moore, Linux Kernel Mailing List, Linux Documentation,
Linux Security Module
Cc: Jonathan Corbet, Jarkko Sakkinen, Christian Brauner, Jeff Layton,
Kees Cook, Mickaël Salaün, Stuart Yoder
In-Reply-To: <d5efca7df0d2e12ad9d6f3d35afa2828@paul-moore.com>
[-- Attachment #1: Type: text/plain, Size: 519 bytes --]
On Wed, Nov 19, 2025 at 07:10:31PM -0500, Paul Moore wrote:
> I rendered the patched file to HTML, and given that large portions of
> the file are still rendered as monospaced preformatted text, the new
> table rendering looks a bit out of place.
>
> Let's stick with the existing table format until the entire document
> is converted to something that is at least as constitent and
> aesthetically pleasing as the current revision.
OK, thanks!
--
An old man doll... just what I always wanted! - Clara
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
* Re: [PATCH] security: sctp: Format type and permission checks tables
From: Paul Moore @ 2025-11-20 0:10 UTC (permalink / raw)
To: Bagas Sanjaya, Linux Kernel Mailing List, Linux Documentation,
Linux Security Module
Cc: Jonathan Corbet, Jarkko Sakkinen, Christian Brauner,
Bagas Sanjaya, Jeff Layton, Kees Cook, Mickaël Salaün,
Stuart Yoder
In-Reply-To: <20251103113922.61232-2-bagasdotme@gmail.com>
On Nov 3, 2025 Bagas Sanjaya <bagasdotme@gmail.com> wrote:
>
> Use reST grid tables for both type and permission checks tables.
>
> Signed-off-by: Bagas Sanjaya <bagasdotme@gmail.com>
> ---
> This patch is based on lsm tree.
>
> Documentation/security/SCTP.rst | 48 +++++++++++++++++++++------------
> 1 file changed, 31 insertions(+), 17 deletions(-)
I rendered the patched file to HTML, and given that large portions of
the file are still rendered as monospaced preformatted text, the new
table rendering looks a bit out of place.
Let's stick with the existing table format until the entire document
is converted to something that is at least as constitent and
aesthetically pleasing as the current revision.
--
paul-moore.com
^ permalink raw reply
* [RFC v1 1/1] ima: Implement IMA event log trimming
From: Anirudh Venkataramanan @ 2025-11-19 21:33 UTC (permalink / raw)
To: linux-integrity
Cc: Mimi Zohar, Roberto Sassu, Dmitry Kasatkin, Eric Snowberg,
Paul Moore, James Morris, Serge E . Hallyn, linux-security-module,
Anirudh Venkataramanan, Steven Chen, Gregory Lumen,
Lakshmi Ramasubramanian, Sush Shringarputale
In-Reply-To: <20251119213359.39397-1-anirudhve@linux.microsoft.com>
From: Steven Chen <chenste@linux.microsoft.com>
Implement a PCR value based method for IMA event log trimming by
exposing a new pseudo file /sys/kernel/config/ima/pcrs. This
pseudo file allows userspace to:
- read IMA starting PCR values.
- write trim-to PCR values to trigger IMA log trimming.
If a trimming operation is successful, one or more entries will be
purged from the IMA measurements list in the kernel.
Signed-off-by: Steven Chen <chenste@linux.microsoft.com>
Signed-off-by: Anirudh Venkataramanan <anirudhve@linux.microsoft.com>
---
drivers/Kconfig | 2 +
drivers/Makefile | 1 +
drivers/ima/Kconfig | 13 +
drivers/ima/Makefile | 2 +
drivers/ima/ima_config_pcrs.c | 291 ++++++++++++++++++
include/linux/ima.h | 27 ++
security/integrity/ima/Makefile | 4 +
security/integrity/ima/ima.h | 8 +
security/integrity/ima/ima_init.c | 44 +++
security/integrity/ima/ima_log_trim.c | 421 ++++++++++++++++++++++++++
security/integrity/ima/ima_policy.c | 7 +-
security/integrity/ima/ima_queue.c | 5 +-
12 files changed, 821 insertions(+), 4 deletions(-)
create mode 100644 drivers/ima/Kconfig
create mode 100644 drivers/ima/Makefile
create mode 100644 drivers/ima/ima_config_pcrs.c
create mode 100644 security/integrity/ima/ima_log_trim.c
diff --git a/drivers/Kconfig b/drivers/Kconfig
index 4915a63866b0..35b83be86c42 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -251,4 +251,6 @@ source "drivers/hte/Kconfig"
source "drivers/cdx/Kconfig"
+source "drivers/ima/Kconfig"
+
endmenu
diff --git a/drivers/Makefile b/drivers/Makefile
index 8e1ffa4358d5..3aad6096d416 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -197,3 +197,4 @@ obj-$(CONFIG_DPLL) += dpll/
obj-$(CONFIG_DIBS) += dibs/
obj-$(CONFIG_S390) += s390/
+obj-$(CONFIG_IMA_PCRS) += ima/
diff --git a/drivers/ima/Kconfig b/drivers/ima/Kconfig
new file mode 100644
index 000000000000..9e465fbe3adb
--- /dev/null
+++ b/drivers/ima/Kconfig
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config IMA_PCRS
+ bool "IMA Event Log Trimming"
+ default n
+ help
+ Say Y here if you want support for IMA Event Log Trimming.
+
+ This creates the configfs file /sys/kernel/config/ima/pcrs.
+ Userspace
+ - writes to this file to trigger IMA event log trimming
+ - reads this file to get IMA starting PCR values
+
+ If unsure, say N.
diff --git a/drivers/ima/Makefile b/drivers/ima/Makefile
new file mode 100644
index 000000000000..ac9b9b96b5cb
--- /dev/null
+++ b/drivers/ima/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_IMA_PCRS) += ima_config_pcrs.o
diff --git a/drivers/ima/ima_config_pcrs.c b/drivers/ima/ima_config_pcrs.c
new file mode 100644
index 000000000000..f329665f2b90
--- /dev/null
+++ b/drivers/ima/ima_config_pcrs.c
@@ -0,0 +1,291 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 Microsoft Corporation
+ *
+ * Authors:
+ * Steven Chen <chenste@linux.microsoft.com>
+ *
+ * File: ima_config_pcrs.c
+ *
+ * Implements IMA interface to allow userspace to read IMA starting PCR
+ * values and trigger IMA event log trimming.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/configfs.h>
+#include <linux/printk.h>
+#include <linux/tpm.h>
+#include <linux/ima.h>
+/*
+ * The IMA PCR configfs is a childless subsystem. It cannot create
+ * any config_items. It just has attribute pcrs for read and write.
+ */
+
+#define MAX_HASH_ALGO_NAME_LENGTH 16 /*sha1, sha256, ...*/
+#define PCR_NAME_LEN 6 /*pcrxx:*/
+#define MIN_PCR_RECORD_LENGTH 20
+#define OFFSET_IDX_1 3
+#define OFFSET_IDX_2 4
+#define OFFSET_COLON 5
+
+/*
+ * mutex protects atomicity of trimming measurement list
+ * and updating ima_start_point_pcr_values
+ * protects concurrent writes to the IMA PCR values
+ * This also not allow memory allocation while waiting the mutex
+ */
+static DEFINE_MUTEX(ima_pcr_write_mutex);
+
+/* show current IMA Start Point PCR values in ascii format */
+static ssize_t ima_config_pcrs_ascii_show(struct config_item *item, char *page)
+{
+ int len = 0;
+
+ for (int i = 0; i < num_tpm_banks; i++) {
+ for (int j = 0; j < num_pcr_configured; j++) {
+ int algorithm_id = ima_start_point_pcr_values[i].alg_id;
+
+ /*
+ * Write "pcrXX:AAA:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ * where XX is the PCR index (00-23),
+ * where AAA is the algorithm name, such as sha1, sha256, sha384..
+ * and x...x is the digest data in hex format
+ */
+ len += sprintf(page + len, "pcr%02d:", digests_pcr_map[j]);
+ len += sprintf(page + len, "%s:", hash_algo_name[algorithm_id]);
+ for (int k = 0; k < hash_digest_size[algorithm_id]; k++)
+ len += sprintf(page + len, "%02x",
+ ima_start_point_pcr_values[i].digests[j][k]);
+ len += sprintf(page + len, "\n");
+ }
+ }
+ return len;
+}
+
+/* show current IMA Start Point PCR values */
+static ssize_t ima_config_pcrs_show(struct config_item *item, char *page)
+{
+ int len = 0;
+
+ for (int i = 0; i < num_tpm_banks; i++) {
+ int algorithm_id = ima_start_point_pcr_values[i].alg_id;
+
+ for (int j = 0; j < TPM2_PLATFORM_PCR; ++j) {
+ int digest_index = pcr_digests_map[j];
+
+ if (digest_index == -1)
+ continue;
+ /*
+ * Write "pcrXX:AAA:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ * where XX is the PCR index (00-23),
+ * where AAA is the algorithm name, such as sha1, sha256, sha384..
+ * and x...x is the digest data in raw format
+ */
+ len += sprintf(page + len, "pcr%02d:", j);
+ len += sprintf(page + len, "%s:", hash_algo_name[algorithm_id]);
+
+ memcpy(page + len, ima_start_point_pcr_values[i].digests[digest_index],
+ hash_digest_size[algorithm_id]);
+
+ len += hash_digest_size[algorithm_id];
+ }
+ }
+
+ return len;
+}
+
+/* get PCR index from input string */
+static int get_pcr_idx(char *p, int offset)
+{
+ /* validate digits first */
+ if (!isdigit((unsigned char)p[offset + OFFSET_IDX_1]) ||
+ !isdigit((unsigned char)p[offset + OFFSET_IDX_2]))
+ return -EINVAL;
+
+ return (p[offset + OFFSET_IDX_1] - '0') * 10 + (p[offset + OFFSET_IDX_2] - '0');
+}
+
+/*
+ * Parse the input data
+ * Get new PCR values and trim IMA event log
+ */
+static ssize_t ima_config_pcrs_store(struct config_item *item, const char *page, size_t count)
+{
+ struct ima_pcr_value *ima_pcr_values;
+ unsigned long pcr_found = 0;
+ char *p = (char *)page;
+ int pcr_count;
+ int offset = 0;
+ int ret = -EINVAL;
+
+ if (count <= 0)
+ return ret;
+
+ if (num_pcr_configured <= 0)
+ return -ENOMEM;
+
+ /*
+ * Only one thread can write to the PCRs at a time
+ * Not allocate memory while waiting the mutex
+ */
+
+ mutex_lock(&ima_pcr_write_mutex);
+
+ pcr_count = num_pcr_configured;
+
+ ima_pcr_values = kcalloc(num_pcr_configured, sizeof(*ima_pcr_values), GFP_KERNEL);
+
+ if (!ima_pcr_values) {
+ ret = -ENOMEM;
+ goto out_notrim_unlock;
+ }
+
+ /*
+ * parse the input data
+ * each entry is like: pcrX:AAA:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ * where XX is the PCR index (00-23),
+ * where AAA is the algorithm name, e.g. sha1, sha256, sha384..
+ * and x...x is the digest data in binary format
+ * multiple entries are concatenated together
+ */
+ while (offset < count) {
+ int index, mapped_index, algo_id, algo_name_len = 0;
+ char algo_name[MAX_HASH_ALGO_NAME_LENGTH];
+ u8 digest_len;
+
+ /* Need at least "pcrXX:AAA:" */
+ if (offset + MIN_PCR_RECORD_LENGTH > count)
+ goto out_nottrim;
+
+ if (memcmp(p + offset, "pcr", 3) != 0 || p[offset + OFFSET_COLON] != ':') {
+ pr_alert("invalid input format\n");
+ goto out_nottrim;
+ }
+
+ /* Get the PCR index*/
+ index = get_pcr_idx(p, offset);
+ if (index < 0 || index >= TPM2_PLATFORM_PCR) {
+ pr_alert("invalid PCR index: %d\n", index);
+ goto out_nottrim;
+ }
+
+ offset += PCR_NAME_LEN;
+
+ while (offset + algo_name_len < count && p[offset + algo_name_len] != ':')
+ ++algo_name_len;
+
+ /* Check actual algo name length */
+ if (algo_name_len == 0 || algo_name_len >= MAX_HASH_ALGO_NAME_LENGTH) {
+ pr_err("ima pcr configuration: invalid algorithm name length\n");
+ goto out_nottrim;
+ }
+
+ memcpy(algo_name, p + offset, algo_name_len);
+ algo_name[algo_name_len] = '\0';
+
+ algo_id = match_string(hash_algo_name, HASH_ALGO__LAST, algo_name);
+ /* validate algo_id */
+ if (algo_id < HASH_ALGO_SHA1 || algo_id >= HASH_ALGO__LAST) {
+ pr_err("ima pcr configuration: invalid algorithm ID\n");
+ goto out_nottrim;
+ }
+ digest_len = hash_digest_size[algo_id];
+ offset += algo_name_len + 1;
+ /* validate we have enough data for the digest */
+ if (offset + digest_len > count)
+ goto out_nottrim;
+
+ mapped_index = pcr_digests_map[index];
+
+ if (pcr_digests_map[index] != -1 && !test_bit(index, &pcr_found)) {
+ ima_pcr_values[mapped_index].algo_id = algo_id;
+ set_bit(index, &pcr_found);
+ memcpy(ima_pcr_values[mapped_index].digest, p + offset, digest_len);
+ --pcr_count;
+ } else {
+ pr_alert("invalid PCR index or duplicate PCR set index: %d\n", index);
+ goto out_nottrim;
+ }
+ offset += digest_len;
+ }
+
+ if (pcr_count != 0) {
+ /* not all PCRs are provided */
+ pr_alert("not all PCRs are provided\n");
+ goto out_nottrim;
+ }
+
+ /*
+ * all configured PCRs values are provided, now recalculate the IMA PCRs
+ * and check if they can match the these PCR values
+ * Trim the IMA event logs if the given PCR values match
+ */
+ ret = ima_trim_event_log((const void *)ima_pcr_values);
+ if (ret >= 0)
+ ret = count;
+
+out_nottrim:
+ kfree(ima_pcr_values);
+out_notrim_unlock:
+ mutex_unlock(&ima_pcr_write_mutex);
+ return ret;
+}
+
+CONFIGFS_ATTR(ima_config_, pcrs);
+CONFIGFS_ATTR_RO(ima_config_, pcrs_ascii);
+
+static struct configfs_attribute *ima_config_pcr_attrs[] = {
+ &ima_config_attr_pcrs,
+ &ima_config_attr_pcrs_ascii,
+ NULL,
+};
+
+static const struct config_item_type ima_config_pcr_type = {
+ .ct_attrs = ima_config_pcr_attrs,
+ .ct_owner = THIS_MODULE,
+};
+
+static struct configfs_subsystem config_ima_pcrs = {
+ .su_group = {
+ .cg_item = {
+ .ci_namebuf = "ima",
+ .ci_type = &ima_config_pcr_type,
+ },
+ },
+};
+
+static int __init configfs_ima_pcrs_init(void)
+{
+ struct tpm_chip *tpm_chip;
+ int ret;
+
+ tpm_chip = tpm_default_chip();
+ if (!tpm_chip) {
+ pr_err("No TPM chip found, IMA PCR configfs disabled\n");
+ return -ENODEV;
+ }
+
+ config_group_init(&config_ima_pcrs.su_group);
+ mutex_init(&config_ima_pcrs.su_mutex);
+ ret = configfs_register_subsystem(&config_ima_pcrs);
+ if (ret) {
+ pr_err("Error %d while registering subsystem %s\n",
+ ret, config_ima_pcrs.su_group.cg_item.ci_namebuf);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void __exit configfs_ima_pcrs_exit(void)
+{
+ configfs_unregister_subsystem(&config_ima_pcrs);
+}
+
+module_init(configfs_ima_pcrs_init);
+module_exit(configfs_ima_pcrs_exit);
+MODULE_DESCRIPTION("IMA PCRS configfs module");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/ima.h b/include/linux/ima.h
index 8e29cb4e6a01..f8a6209b9423 100644
--- a/include/linux/ima.h
+++ b/include/linux/ima.h
@@ -12,6 +12,8 @@
#include <linux/security.h>
#include <linux/kexec.h>
#include <crypto/hash_info.h>
+#include <linux/tpm.h>
+
struct linux_binprm;
#ifdef CONFIG_IMA
@@ -24,6 +26,31 @@ extern int ima_measure_critical_data(const char *event_label,
const void *buf, size_t buf_len,
bool hash, u8 *digest, size_t digest_len);
+#ifdef CONFIG_IMA_PCRS
+struct ima_pcr_value {
+ u8 digest[HASH_MAX_DIGESTSIZE]; /* PCR value */
+ u8 algo_id; /* Hash algorithm ID */
+};
+
+struct tpm_bank_pcr_values {
+ u16 alg_id;
+ u8 **digests; /* Array of pointers, one per configured PCR */
+};
+
+extern u8 num_tpm_banks;
+extern u8 num_pcr_configured;
+
+extern signed char algo_pcr_bank_map[HASH_ALGO__LAST];
+extern signed char pcr_digests_map[TPM2_PLATFORM_PCR];
+extern signed char digests_pcr_map[TPM2_PLATFORM_PCR];
+
+extern struct tpm_bank_pcr_values *ima_start_point_pcr_values;
+
+extern int ima_trim_event_log(const void *bin);
+#else
+static inline int ima_trim_event_log(const void *bin) { return 0; }
+#endif /* CONFIG_IMA_PCRS */
+
#ifdef CONFIG_IMA_APPRAISE_BOOTPARAM
extern void ima_appraise_parse_cmdline(void);
#else
diff --git a/security/integrity/ima/Makefile b/security/integrity/ima/Makefile
index b376d38b4ee6..ae9ce210638d 100644
--- a/security/integrity/ima/Makefile
+++ b/security/integrity/ima/Makefile
@@ -18,3 +18,7 @@ ima-$(CONFIG_IMA_QUEUE_EARLY_BOOT_KEYS) += ima_queue_keys.o
ifeq ($(CONFIG_EFI),y)
ima-$(CONFIG_IMA_SECURE_AND_OR_TRUSTED_BOOT) += ima_efi.o
endif
+
+ifeq ($(CONFIG_IMA_PCRS),y)
+ima-y += ima_log_trim.o
+endif
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index e3d71d8d56e3..d9accf504e42 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -55,6 +55,8 @@ struct ima_algo_desc {
enum hash_algo algo;
};
+extern struct mutex ima_extend_list_mutex; /* protects extending measurement list */
+
/* set during initialization */
extern int ima_hash_algo __ro_after_init;
extern int ima_sha1_idx __ro_after_init;
@@ -250,6 +252,12 @@ void ima_measure_kexec_event(const char *event_name);
static inline void ima_measure_kexec_event(const char *event_name) {}
#endif
+#ifdef CONFIG_IMA_PCRS
+extern int ima_add_configured_pcr(int new_pcr_index);
+#else
+static inline int ima_add_configured_pcr(int new_pcr_index) { return 0; }
+#endif /* CONFIG_IMA_PCRS */
+
/*
* The default binary_runtime_measurements list format is defined as the
* platform native format. The canonical format is defined as little-endian.
diff --git a/security/integrity/ima/ima_init.c b/security/integrity/ima/ima_init.c
index a2f34f2d8ad7..1102d2073336 100644
--- a/security/integrity/ima/ima_init.c
+++ b/security/integrity/ima/ima_init.c
@@ -24,6 +24,16 @@
const char boot_aggregate_name[] = "boot_aggregate";
struct tpm_chip *ima_tpm_chip;
+#ifdef CONFIG_IMA_PCRS
+u8 num_tpm_banks;
+u8 num_pcr_configured;
+signed char algo_pcr_bank_map[HASH_ALGO__LAST];
+signed char digests_pcr_map[TPM2_PLATFORM_PCR];
+signed char pcr_digests_map[TPM2_PLATFORM_PCR];
+
+struct tpm_bank_pcr_values *ima_start_point_pcr_values;
+#endif
+
/* Add the boot aggregate to the IMA measurement list and extend
* the PCR register.
*
@@ -134,6 +144,40 @@ int __init ima_init(void)
if (rc != 0)
return rc;
+#ifdef CONFIG_IMA_PCRS
+
+ num_tpm_banks = 0;
+ num_pcr_configured = 0;
+
+ for (int i = 0; i < TPM2_PLATFORM_PCR; i++) {
+ pcr_digests_map[i] = -1;
+ digests_pcr_map[i] = -1;
+ }
+
+ for (int i = 0; i < HASH_ALGO__LAST; i++)
+ algo_pcr_bank_map[i] = -1;
+
+ if (ima_tpm_chip) {
+ num_tpm_banks = ima_tpm_chip->nr_allocated_banks;
+ /* Allocate memory for the ima start point PCR values */
+ ima_start_point_pcr_values = kcalloc(num_tpm_banks,
+ sizeof(*ima_start_point_pcr_values),
+ GFP_KERNEL);
+
+ if (!ima_start_point_pcr_values)
+ return -ENOMEM;
+
+ for (int i = 0; i < num_tpm_banks; i++) {
+ int algo_id = ima_tpm_chip->allocated_banks[i].crypto_id;
+
+ ima_start_point_pcr_values[i].alg_id = algo_id;
+ algo_pcr_bank_map[algo_id] = i;
+ }
+
+ ima_add_configured_pcr(CONFIG_IMA_MEASURE_PCR_IDX);
+ }
+#endif
+
/* It can be called before ima_init_digests(), it does not use TPM. */
ima_load_kexec_buffer();
diff --git a/security/integrity/ima/ima_log_trim.c b/security/integrity/ima/ima_log_trim.c
new file mode 100644
index 000000000000..85025d502db2
--- /dev/null
+++ b/security/integrity/ima/ima_log_trim.c
@@ -0,0 +1,421 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 Microsoft Corporation
+ *
+ * Authors:
+ * Steven Chen <chenste@linux.microsoft.com>
+ *
+ * File: ima_log_trim.c
+ * Implements PCR value based IMA Event Log Trimming
+ */
+
+#include <linux/fcntl.h>
+#include <linux/kernel_read_file.h>
+#include <linux/slab.h>
+#include <linux/seq_file.h>
+#include <linux/rculist.h>
+#include <linux/rcupdate.h>
+#include <linux/vmalloc.h>
+#include <linux/ima.h>
+#include <crypto/hash_info.h>
+
+#include "ima.h"
+
+struct digest_value {
+ u8 digest[HASH_MAX_DIGESTSIZE]; /* digest value */
+};
+
+/* Average IMA event log data length */
+#define EXTEND_BUF_LEN 300
+
+static struct digest_value *ima_extended_pcr;
+static struct ima_pcr_value *target_pcr_values;
+static unsigned long pcr_matched;
+static int pcr_match_needed;
+
+/* Calculate the hash digest for the given data using the specified algorithm */
+static int ima_calculate_pcr(const void *data, size_t len, u8 *md, int algo)
+{
+ struct crypto_shash *tfm;
+ struct shash_desc *desc;
+ int ret = 0, size;
+
+ tfm = crypto_alloc_shash(hash_algo_name[algo], 0, 0);
+
+ if (IS_ERR(tfm)) {
+ ret = -ENOMEM;
+ goto out_nocleanup;
+ }
+
+ /* Allocate memory for the shash_desc structure and initialize it */
+ size = sizeof(struct shash_desc) + crypto_shash_descsize(tfm);
+
+ desc = kzalloc(size, GFP_KERNEL);
+ if (!desc) {
+ crypto_free_shash(tfm);
+ ret = -ENOMEM;
+ goto out_nocleanup;
+ }
+
+ desc->tfm = tfm;
+
+ if (crypto_shash_init(desc)) {
+ ret = -EINVAL;
+ goto out_cleanup;
+ }
+
+ /* Update the hash with the input data */
+ if (crypto_shash_update(desc, data, len)) {
+ ret = -EINVAL;
+ goto out_cleanup;
+ }
+
+ /* Finalize the hash and retrieve the digest */
+ ret = crypto_shash_final(desc, md);
+ if (ret)
+ goto out_cleanup;
+
+ ret = crypto_shash_digestsize(tfm);
+
+out_cleanup:
+ kfree(desc);
+ crypto_free_shash(tfm);
+out_nocleanup:
+ return ret;
+}
+
+/* Add a new configured PCR */
+int ima_add_configured_pcr(int new_pcr_index)
+{
+ if (!ima_tpm_chip)
+ return -1;
+
+ if (new_pcr_index < 0 || new_pcr_index >= TPM2_PLATFORM_PCR)
+ return -1;
+
+ if (pcr_digests_map[new_pcr_index] != -1)
+ return 0;
+
+ /*
+ * For each TPM bank, expand one more entry for the new PCR
+ * Allocate new PCR digest buffers for the new PCR
+ */
+ for (int i = 0; i < num_tpm_banks; ++i) {
+ int length = hash_digest_size[ima_start_point_pcr_values[i].alg_id];
+ u8 *new_pcr = kmalloc(sizeof(u8) * length, GFP_KERNEL);
+ u8 **new_buf;
+
+ if (!new_pcr)
+ return -ENOMEM;
+ memset(new_pcr, 0, sizeof(u8) * length);
+ new_buf = (u8 **)krealloc(ima_start_point_pcr_values[i].digests,
+ (num_pcr_configured + 1) * sizeof(u8 *), GFP_KERNEL);
+ if (!new_buf) {
+ kfree(new_pcr);
+ return -ENOMEM;
+ }
+ new_buf[num_pcr_configured] = new_pcr;
+ ima_start_point_pcr_values[i].digests = new_buf;
+ }
+
+ pcr_digests_map[new_pcr_index] = num_pcr_configured;
+ digests_pcr_map[num_pcr_configured] = new_pcr_index;
+
+ ++num_pcr_configured;
+ return 0;
+}
+
+/* Delete the IMA event logs */
+static int ima_purge_event_log(int number_logs)
+{
+ struct ima_queue_entry *qe;
+ int cur = 0;
+
+ mutex_lock(&ima_extend_list_mutex);
+ rcu_read_lock();
+
+ /*
+ * Remove this entry from both hash table and the measurement list
+ * When removing from hash table, decrease the length counter
+ * so that the hash table re-sizing logic works correctly
+ */
+ list_for_each_entry_rcu(qe, &ima_measurements, later) {
+ /* if CONFIG_IMA_DISABLE_HTABLE is set, the hash table is not used */
+ if (!IS_ENABLED(CONFIG_IMA_DISABLE_HTABLE))
+ hlist_del_rcu(&qe->hnext);
+
+ atomic_long_dec(&ima_htable.len);
+ list_del_rcu(&qe->later);
+ ++cur;
+ if (cur >= number_logs)
+ break;
+ }
+
+ rcu_read_unlock();
+ mutex_unlock(&ima_extend_list_mutex);
+ return cur;
+}
+
+/* compare the target PCR values with IMA Start Point PCR Values */
+static int ima_compare_pcr_values(struct ima_pcr_value *target_pcr_val)
+{
+ int count_not_matched = 0;
+
+ for (int i = 0; i < num_pcr_configured; ++i) {
+ int algo_id_tmp;
+ int bank_id_tmp;
+ int hash_size;
+
+ algo_id_tmp = target_pcr_val[i].algo_id;
+ bank_id_tmp = algo_pcr_bank_map[algo_id_tmp];
+ hash_size = hash_digest_size[algo_id_tmp];
+
+ if (bank_id_tmp < 0 || bank_id_tmp >= num_tpm_banks)
+ return -EINVAL;
+
+ /* already matched this PCR, skip it */
+ if (memcmp(ima_start_point_pcr_values[bank_id_tmp].digests[i],
+ target_pcr_val[i].digest, hash_size) == 0) {
+ set_bit(i, &pcr_matched);
+ continue;
+ }
+ count_not_matched++;
+ }
+ return count_not_matched;
+}
+
+static int ima_recalculate_check_pcrs(int pcr_map_index, char *org_pointer, char *digest_pointer,
+ int total_length, bool digest_zero)
+{
+ int ret = 0;
+
+ /* Recalculate the PCR for each bank */
+ for (int i = 0; i < num_tpm_banks; i++) {
+ int digest_len, crypto_id, hash_size, idx, len;
+ u8 digest[HASH_MAX_DIGESTSIZE];
+
+ crypto_id = ima_start_point_pcr_values[i].alg_id;
+ hash_size = hash_digest_size[crypto_id];
+ digest_len = 0;
+ /* Prepare digest buffer */
+ if (digest_zero) {
+ /* all zero digest, use 0xFF to extend */
+ memset(digest, 0xFF, HASH_MAX_DIGESTSIZE);
+ digest_len = hash_size;
+ } else {
+ memcpy(digest_pointer, org_pointer, total_length);
+ digest_len = ima_calculate_pcr(digest_pointer, total_length, digest,
+ crypto_id);
+ if (digest_len < 0) {
+ ret = digest_len;
+ break;
+ }
+ }
+
+ len = digest_len + hash_size;
+
+ idx = i * num_pcr_configured + pcr_map_index;
+ memcpy(digest_pointer, ima_extended_pcr[idx].digest, hash_size);
+ memcpy(digest_pointer + hash_size, digest, digest_len);
+
+ /* Recalculate the PCR starting with the IMA Start Point PCR value */
+ digest_len = ima_calculate_pcr(digest_pointer, len, ima_extended_pcr[idx].digest,
+ crypto_id);
+
+ if (digest_len < 0) {
+ ret = digest_len;
+ break;
+ }
+
+ /*
+ * Check if the extended PCR value matches the target PCR value
+ * if matched, mark this PCR as matched
+ * if all PCRs matched, set the entry_found flag
+ */
+ if (crypto_id == target_pcr_values[pcr_map_index].algo_id) {
+ if (memcmp(ima_extended_pcr[idx].digest,
+ target_pcr_values[pcr_map_index].digest, hash_size) == 0) {
+ set_bit(pcr_map_index, &pcr_matched);
+ --pcr_match_needed;
+ }
+ }
+ }
+
+ return ret;
+}
+
+static int ima_get_log_count(void)
+{
+ u8 algo_digest_buffer[EXTEND_BUF_LEN];
+ u8 digest_buffer[EXTEND_BUF_LEN];
+ struct ima_queue_entry *qe;
+ int count = 0;
+ int ret = 0;
+ unsigned int hash_digest_length;
+
+ /* Event log digests algorithm is SHA1 */
+ hash_digest_length = hash_digest_size[HASH_ALGO_SHA1];
+ list_for_each_entry_rcu(qe, &ima_measurements, later) {
+ char *org_digest_pointer, *digest_pointer;
+ int pcr_idx, pcr_map_index, total_length;
+ struct ima_template_entry *e = qe->entry;
+ bool digest_zero;
+
+ if (!qe->entry) {
+ ret = -EINVAL;
+ break;
+ }
+ pcr_idx = e->pcr;
+ pcr_map_index = pcr_digests_map[pcr_idx];
+
+ if (test_bit(pcr_map_index, &pcr_matched) || pcr_digests_map[pcr_idx] == -1) {
+ /* already matched this PCR, something wrong */
+ ret = -EINVAL;
+ break;
+ }
+ /* The original digest buffer is used to save data for multiple banks/algorithms */
+ org_digest_pointer = digest_buffer;
+ digest_pointer = algo_digest_buffer;
+
+ total_length = e->template_data_len;
+
+ /* Allocate large memory for the original and digest buffers if needed */
+ if (total_length > EXTEND_BUF_LEN) {
+ org_digest_pointer = kzalloc(total_length, GFP_KERNEL);
+ if (!org_digest_pointer) {
+ ret = -ENOMEM;
+ break;
+ }
+ digest_pointer = kzalloc(total_length, GFP_KERNEL);
+ if (!digest_pointer) {
+ kfree(org_digest_pointer);
+ ret = -ENOMEM;
+ break;
+ }
+ }
+
+ digest_zero = true;
+ /*
+ * Check if the original digest is all zeros or not
+ * if not all zero, use template data to recalculate PCR
+ */
+ if (memchr_inv(e->digests->digest, 0, hash_digest_length) != NULL) {
+ int offset = 0;
+
+ for (int i = 0; i < e->template_desc->num_fields; i++) {
+ memcpy(org_digest_pointer + offset, &e->template_data[i].len,
+ sizeof(e->template_data[i].len));
+ offset += sizeof(e->template_data[i].len);
+ memcpy(org_digest_pointer + offset, e->template_data[i].data,
+ e->template_data[i].len);
+ offset += e->template_data[i].len;
+ }
+ digest_zero = false;
+ }
+
+ count++;
+
+ /* Check if this log entry can match the target PCRs */
+ ret = ima_recalculate_check_pcrs(pcr_map_index, org_digest_pointer,
+ digest_pointer, total_length, digest_zero);
+
+ if (total_length > EXTEND_BUF_LEN) {
+ kfree(org_digest_pointer);
+ kfree(digest_pointer);
+ }
+
+ /* If entry found or error occurred, break the loop */
+ if (ret < 0 || pcr_match_needed <= 0)
+ break;
+ }
+
+ if (ret < 0)
+ return ret;
+
+ if (pcr_match_needed <= 0)
+ return count;
+ else
+ return 0;
+}
+
+/*
+ * Trim the IMA event log to match the given PCR values
+ * Return:
+ * >0: number of log entries removed
+ * 0: no log entries removed
+ * -1: error
+ * -ENOENT: no matching log entry found
+ * -EIO: I/O error when removing log entries
+ * -EINVAL: invalid parameter
+ * -ENOMEM: memory allocation failure
+ */
+int ima_trim_event_log(const void *bin)
+{
+ int count, ret = -1;
+
+ count = 0;
+ target_pcr_values = (struct ima_pcr_value *)bin;
+ pcr_matched = 0;
+
+ pcr_match_needed = ima_compare_pcr_values(target_pcr_values);
+
+ /* No need to trim */
+ if (pcr_match_needed <= 0) {
+ ret = 0;
+ goto out_nofree;
+ }
+
+ ima_extended_pcr = kcalloc(num_tpm_banks * num_pcr_configured,
+ sizeof(*ima_extended_pcr), GFP_KERNEL);
+
+ if (!ima_extended_pcr) {
+ ret = -ENOMEM;
+ goto out_nofree;
+ }
+
+ /* Initialize ima_extended_pcr with ima_start_point_pcr_values */
+ for (int i = 0; i < num_tpm_banks; i++) {
+ int length = hash_digest_size[ima_start_point_pcr_values[i].alg_id];
+
+ for (int j = 0; j < num_pcr_configured; ++j) {
+ int record_index = i * num_pcr_configured + j;
+
+ memcpy(ima_extended_pcr[record_index].digest,
+ ima_start_point_pcr_values[i].digests[j], length);
+ }
+ }
+
+ ret = ima_get_log_count();
+
+ if (ret <= 0)
+ goto out_notrim;
+
+ count = ret;
+
+ /* Remove logs from the IMA log list */
+ ret = ima_purge_event_log(count);
+
+ if (ret == count) {
+ /* Update the IMA Start Point PCR values */
+ for (int i = 0; i < num_tpm_banks; i++) {
+ int algorithm_id = ima_start_point_pcr_values[i].alg_id;
+ int hash_size = hash_digest_size[algorithm_id];
+
+ for (int j = 0; j < num_pcr_configured; j++) {
+ int ext_idx = i * num_pcr_configured + j;
+
+ memcpy(ima_start_point_pcr_values[i].digests[j],
+ ima_extended_pcr[ext_idx].digest, hash_size);
+ }
+ }
+ } else {
+ /* something wrong, should not happen */
+ ret = -EIO;
+ }
+
+out_notrim:
+ kfree(ima_extended_pcr);
+
+out_nofree:
+ return ret;
+}
diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c
index 7468afaab686..fe537827ac1f 100644
--- a/security/integrity/ima/ima_policy.c
+++ b/security/integrity/ima/ima_policy.c
@@ -1895,10 +1895,13 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry)
ima_log_string(ab, "pcr", args[0].from);
result = kstrtoint(args[0].from, 10, &entry->pcr);
- if (result || INVALID_PCR(entry->pcr))
+ if (result || INVALID_PCR(entry->pcr)) {
result = -EINVAL;
- else
+ } else {
entry->flags |= IMA_PCR;
+ if (ima_add_configured_pcr(entry->pcr) < 0)
+ result = -EINVAL;
+ }
break;
case Opt_template:
diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c
index 590637e81ad1..7bbfdd2ce3b0 100644
--- a/security/integrity/ima/ima_queue.c
+++ b/security/integrity/ima/ima_queue.c
@@ -39,11 +39,12 @@ struct ima_h_table ima_htable = {
.queue[0 ... IMA_MEASURE_HTABLE_SIZE - 1] = HLIST_HEAD_INIT
};
-/* mutex protects atomicity of extending measurement list
+/*
+ * This mutex protects atomicity of extending measurement list
* and extending the TPM PCR aggregate. Since tpm_extend can take
* long (and the tpm driver uses a mutex), we can't use the spinlock.
*/
-static DEFINE_MUTEX(ima_extend_list_mutex);
+DEFINE_MUTEX(ima_extend_list_mutex);
/*
* Used internally by the kernel to suspend measurements.
--
2.43.0
^ permalink raw reply related
* [RFC v1 0/1] Implement IMA Event Log Trimming
From: Anirudh Venkataramanan @ 2025-11-19 21:33 UTC (permalink / raw)
To: linux-integrity
Cc: Mimi Zohar, Roberto Sassu, Dmitry Kasatkin, Eric Snowberg,
Paul Moore, James Morris, Serge E . Hallyn, linux-security-module,
Anirudh Venkataramanan, Steven Chen, Gregory Lumen,
Lakshmi Ramasubramanian, Sush Shringarputale
==========================================================================
| A. Introduction |
==========================================================================
IMA events are kept in kernel memory and preserved across kexec soft
reboots. This can lead to increased kernel memory usage over time,
especially with aggressive IMA policies that measure everything. To reduce
memory pressure, it becomes necessary to discard IMA events but given that
IMA events are extended into PCRs in the TPM, just discarding events will
break the PCR extension chain, making future verification of the IMA event
log impossible.
This patch series proposes a method to discard IMA events while keeping the
log verifiable. While reducing memory pressure is the primary objective,
the second order benefit of trimming the IMA log is that IMA log verifiers
(local userspace daemon or a remote cloud service) can process smaller IMA
logs on a rolling basis, thus avoiding re-verification of previously
verified events.
The method has other advantages too:
1. It provides a userspace interface that can be used to precisely control
trim point, allowing for trim points to be optionally aligned with
userspace IMA event log validation.
2. It ensures that all necessary information required for continued IMA
log validation is made available via the userspace interface at all
times.
3. It provides a simple mechanism for userspace applications to determine
if the event log has been unexpectedly trimmed.
4. The duration for which the IMA Measurement list mutex must be held (for
trimming) is minimal.
==========================================================================
| B. Solution |
==========================================================================
--------------------------------------------------------------------------
| B.1 Overview |
--------------------------------------------------------------------------
The kernel trims the IMA event log based on PCR values supplied by userspace.
The core principles leveraged are as follows:
- Given an IMA event log, PCR values for each IMA event can be obtained by
recalulating the PCR extension for each event. Thus processing N events
from the start will yield PCR values as of event N. This is referred to
as "IMA event log replay".
- To get the PCR value for event N + 1, only the PCR value as of event N
is needed. If this can be known, events till and including N can be
safely purged.
Putting it all together, we get the following userspace + kernel flow:
1. A userspace application replays the IMA event log to generate PCR
values and then triggers a trim by providing these values to the kernel
(by writing to a pseudo file).
Optionally, the userspace application may verify these PCR values
against the corresponding TPM quote, and trigger trimming only if
the calculated PCR values match up to the expectations in the quote's
PCR digest.
2. The kernel uses the userspace supplied PCR values to trim the IMA
measurements list at a specific point, and so these are referred to as
"trim-to PCR values" in this context.
Note that the kernel doesn't really understand what these userspace
provided PCR values mean or what IMA event they correspond to, and so
it does its own IMA event replay till either the replayed PCR values
match with the userspace provided ones, or it runs out of events.
If a match is found, the kernel can proceed with trimming the IMA
measurements list. This is done in two steps, to keep locking context
minimal.
step 1: Find and return the list entry (as a count from head) of exact
match. This does not lock the measurements list mutex, ensuring
new events can be appended to the log.
step 2: Lock the measurements list mutex and trim the measurements list
at the previously identified list entry.
If the trim is successful, the trim-to PCR values are saved as "starting
PCR values". The next time userspace wants to replay the IMA event log,
it will use the starting PCR values as the base for the IMA event log
replay.
--------------------------------------------------------------------------
| B.2 Kernel Interfaces |
--------------------------------------------------------------------------
A new configfs pseudo file /sys/kernel/config/ima/pcrs that supports the
following operations is exposed.
read: returns starting PCR values stored in the kernel (within IMA
specifically).
write: writes trim-to PCR values to trigger trimming. If trimming is
successful, trim-to PCR values are stored as starting PCR values.
requires root privileges.
--------------------------------------------------------------------------
| B.3 Walk-through with a real example |
--------------------------------------------------------------------------
This is a real example from a test run.
Suppose this IMA policy is deployed:
measure func=FILE_CHECK mask=MAY_READ pcr=10
measure func=FILE_CHECK mask=MAY_WRITE pcr=11
When the policy is deployed, a zero digest starting PCR value will be set
for each PCR used. If the TPM supports multiple hashbanks, there will be
one starting PCR value per PCR, per TPM hashbank. This can be seen in the
following hexdump:
$ sudo hexdump -vC /sys/kernel/config/ima/pcrs
00000000 70 63 72 31 30 3a 73 68 61 31 3a 00 00 00 00 00 |pcr10:sha1:.....|
00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 70 |...............p|
00000020 63 72 31 31 3a 73 68 61 31 3a 00 00 00 00 00 00 |cr11:sha1:......|
00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 70 63 |..............pc|
00000040 72 31 30 3a 73 68 61 32 35 36 3a 00 00 00 00 00 |r10:sha256:.....|
00000050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000060 00 00 00 00 00 00 00 00 00 00 00 70 63 72 31 31 |...........pcr11|
00000070 3a 73 68 61 32 35 36 3a 00 00 00 00 00 00 00 00 |:sha256:........|
00000080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000090 00 00 00 00 00 00 00 00 70 63 72 31 30 3a 73 68 |........pcr10:sh|
000000a0 61 33 38 34 3a 00 00 00 00 00 00 00 00 00 00 00 |a384:...........|
000000b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000000c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000000d0 00 00 00 00 00 70 63 72 31 31 3a 73 68 61 33 38 |.....pcr11:sha38|
000000e0 34 3a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |4:..............|
000000f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000100 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000110 00 00 |..|
00000112
Let's say that a userspace utility replays the IMA event log, and triggers
trimming by writing the following PCR values (i.e. trim-to PCR values) to the
pseudo file:
pcr10:sha256:8268782906555cf3aefc179f815c878527dd4e67eaa836572ebabab31977922c
pcr11:sha256:4c7f31927183eacb53d51d95b0162916fd3fca51a8d1efc6dde3805eb891fe41
The trim is successful,
1. Some number of entries from the measurements log will disappear. This
can be verified by reading out the ASCII or binary IMA measurements
file.
2. The trim-to PCR values are saved as starting PCR values. This can be
verified by reading out the pseudo file again as shown below. Note that
even through only sha256 PCR values were written, the kernel populated
sha1 and sha384 starting values as well.
$ sudo hexdump -vC /sys/kernel/config/ima/pcrs
00000000 70 63 72 31 30 3a 73 68 61 31 3a c4 7f 9d 00 68 |pcr10:sha1:....h|
00000010 e4 86 71 bf bc ae f0 10 12 ff 68 e2 9e 74 e4 70 |..q.......h..t.p|
00000020 63 72 31 31 3a 73 68 61 31 3a 90 d7 17 ac 60 4d |cr11:sha1:....`M|
00000030 c8 25 ce 77 7d 9d 94 cf 44 7b b2 2e 2e e2 70 63 |.%.w}...D{....pc|
00000040 72 31 30 3a 73 68 61 32 35 36 3a 82 68 78 29 06 |r10:sha256:.hx).|
00000050 55 5c f3 ae fc 17 9f 81 5c 87 85 27 dd 4e 67 ea |U\......\..'.Ng.|
00000060 a8 36 57 2e ba ba b3 19 77 92 2c 70 63 72 31 31 |.6W.....w.,pcr11|
00000070 3a 73 68 61 32 35 36 3a 4c 7f 31 92 71 83 ea cb |:sha256:L.1.q...|
00000080 53 d5 1d 95 b0 16 29 16 fd 3f ca 51 a8 d1 ef c6 |S.....)..?.Q....|
00000090 dd e3 80 5e b8 91 fe 41 70 63 72 31 30 3a 73 68 |...^...Apcr10:sh|
000000a0 61 33 38 34 3a 8e d6 12 18 b1 d6 cd 95 16 98 33 |a384:..........3|
000000b0 2b 7d a2 d6 d9 05 c7 e8 5b 15 b0 91 c5 fc 23 d1 |+}......[.....#.|
000000c0 f9 a8 8d 60 50 5c e9 64 5f d7 b3 b2 f1 9c 90 0a |...`P\.d_.......|
000000d0 45 53 5d b2 57 70 63 72 31 31 3a 73 68 61 33 38 |ES].Wpcr11:sha38|
000000e0 34 3a 25 fc 21 28 31 5a f7 c6 fb 0f 40 c9 06 e6 |4:%.!(1Z....@...|
000000f0 c5 da ed 20 61 a1 03 54 4f 67 18 88 82 0f 48 d1 |... a..TOg....H.|
00000100 2f e0 3d 36 46 5e 94 a4 88 51 f8 91 39 7e e5 97 |/.=6F^...Q..9~..|
00000110 2c c5 |,.|
00000112
--------------------------------------------------------------------------
| C. Footnotes |
--------------------------------------------------------------------------
1. The 'pcrs' pseudo file is currently part of configfs. This was due to
some early internal feedback in a different context. This can as well be
in securityfs with the rest of the IMA pseudo files.
2. PCR values are never read out of the TPM at any point. All PCR values
used are derived from IMA event log replay.
3. Code is "RFC quality". Refinements can be made if the method is accepted.
4. For functional validation, base kernel version was 6.17 stable, with the
most recent tested version being 6.17.8.
5. Code has been validated to some degree using a python-based internal test
tool. This can be published if there is community interest.
Steven Chen (1):
ima: Implement IMA event log trimming
drivers/Kconfig | 2 +
drivers/Makefile | 1 +
drivers/ima/Kconfig | 13 +
drivers/ima/Makefile | 2 +
drivers/ima/ima_config_pcrs.c | 291 ++++++++++++++++++
include/linux/ima.h | 27 ++
security/integrity/ima/Makefile | 4 +
security/integrity/ima/ima.h | 8 +
security/integrity/ima/ima_init.c | 44 +++
security/integrity/ima/ima_log_trim.c | 421 ++++++++++++++++++++++++++
security/integrity/ima/ima_policy.c | 7 +-
security/integrity/ima/ima_queue.c | 5 +-
12 files changed, 821 insertions(+), 4 deletions(-)
create mode 100644 drivers/ima/Kconfig
create mode 100644 drivers/ima/Makefile
create mode 100644 drivers/ima/ima_config_pcrs.c
create mode 100644 security/integrity/ima/ima_log_trim.c
--
2.43.0
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox