* Re: [PATCH v4 07/35] lockdep: Annotate lockdep assertions for context analysis
From: Marco Elver @ 2025-12-11 13:24 UTC (permalink / raw)
To: Peter Zijlstra
Cc: Boqun Feng, Ingo Molnar, Will Deacon, 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: <20251211114302.GC3911114@noisy.programming.kicks-ass.net>
On Thu, 11 Dec 2025 at 12:43, Peter Zijlstra <peterz@infradead.org> wrote:
>
> On Thu, Nov 20, 2025 at 04:09:32PM +0100, Marco Elver wrote:
>
> > 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)
>
> Since I typically read patches without first reading the Changelog --
> because when I read the code later, I also don't see changelogs.
>
> I must admit to getting most terribly confused here -- *again*, as I
> then search back to previous discussions and found I was previously also
> confused.
>
> As such, I think we want a comment here that explains that assume_ctx
> thing.
>
> It is *NOT* (as the clang naming suggests) an assertion of holding the
> lock (which is requires_ctx), but rather an annotation that forces the
> ctx to be considered held.
Noted. I'll add some appropriate wording above the
__assumes_ctx_guard() attribute, so this is not lost in the commit
logs.
^ permalink raw reply
* Re: [PATCH v4 06/35] cleanup: Basic compatibility with context analysis
From: Marco Elver @ 2025-12-11 13:19 UTC (permalink / raw)
To: Peter Zijlstra
Cc: Boqun Feng, Ingo Molnar, Will Deacon, 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: <20251211121659.GH3911114@noisy.programming.kicks-ass.net>
On Thu, 11 Dec 2025 at 13:17, Peter Zijlstra <peterz@infradead.org> wrote:
>
> On Thu, Nov 20, 2025 at 04:09:31PM +0100, Marco Elver wrote:
> > 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().
> Moo, so the alias analysis didn't help here?
Unfortunately no, because intra-procedural alias analysis for these
kinds of diagnostics is infeasible. The compiler can only safely
perform alias analysis for local variables that do not escape the
function. The layers of wrapping here make this a bit tricky.
The compiler (unlike before) is now able to deal with things like:
{
spinlock_t *lock_scope __attribute__((cleanup(spin_unlock))) = &lock;
spin_lock(&lock); // lock through &lock
... critical section ...
} // unlock through lock_scope (alias -> &lock)
> What is the scope of this __assumes_ctx stuff? The way it is used in the
> lock initializes seems to suggest it escapes scope. But then something
> like:
It escapes scope.
> scoped_guard (mutex, &foo) {
> ...
> }
> // context analysis would still assume foo held
>
> is somewhat sub-optimal, no?
Correct. We're trading false negatives over false positives at this
point, just to get things to compile cleanly.
> > Better support for Linux's scoped guard design could be added in
> > future if deemed critical.
>
> I would think so, per the above I don't think this is 'right'.
It's not sound, but we'll avoid false positives for the time being.
Maybe we can wrangle the jigsaw of macros to let it correctly acquire
and then release (via a 2nd cleanup function), it might be as simple
as marking the 'constructor' with the right __acquires(..), and then
have a 2nd __attribute__((cleanup)) variable that just does a no-op
release via __release(..) so we get the already supported pattern
above.
^ permalink raw reply
* Re: [PATCH v4 02/35] compiler-context-analysis: Add infrastructure for Context Analysis with Clang
From: Marco Elver @ 2025-12-11 13:12 UTC (permalink / raw)
To: Peter Zijlstra
Cc: Boqun Feng, Ingo Molnar, Will Deacon, 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: <20251211120441.GG3911114@noisy.programming.kicks-ass.net>
On Thu, 11 Dec 2025 at 13:04, Peter Zijlstra <peterz@infradead.org> wrote:
>
> On Thu, Nov 20, 2025 at 03:49:04PM +0100, Marco Elver wrote:
>
> > +/**
> > + * 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
>
> -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;
>
>
> I must say I find the 'guard' naming here somewhat confusing. This is
> not a guard, but an actual lock type.
The switch to "context analysis" required us coming up with a name for
the actual objects (previously: "capability") that "guard" those
contexts.
The reasoning was that these are guards for entering a particular
context. The lock guards the given context, but the context itself !=
lock. Clang's naming of "capability" was a lot clearer in isolation,
but the problem that Linus raised is that "capability" is already
overloaded in the kernel.
The fact it overlaps in naming with the other guard(..) infrastructure
is not entirely coincidental, but I see the confusion.
What's a better name?
context_lock_struct -> and call it "context lock" rather than "context
guard"; it might work also for things like RCU, PREEMPT, BH, etc. that
aren't normal "locks", but could claim they are "context locks".
context_handle_struct -> "context handle" ...
?
^ permalink raw reply
* Re: [PATCH v4 16/35] kref: Add context-analysis annotations
From: Peter Zijlstra @ 2025-12-11 12:26 UTC (permalink / raw)
To: Marco Elver
Cc: Boqun Feng, Ingo Molnar, Will Deacon, 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-17-elver@google.com>
On Thu, Nov 20, 2025 at 04:09:41PM +0100, Marco Elver wrote:
> Mark functions that conditionally acquire the passed lock.
>
> Signed-off-by: Marco Elver <elver@google.com>
> ---
> include/linux/kref.h | 2 ++
> 1 file changed, 2 insertions(+)
>
> diff --git a/include/linux/kref.h b/include/linux/kref.h
> index 88e82ab1367c..9bc6abe57572 100644
> --- a/include/linux/kref.h
> +++ b/include/linux/kref.h
> @@ -81,6 +81,7 @@ static inline int kref_put(struct kref *kref, void (*release)(struct kref *kref)
> static inline int kref_put_mutex(struct kref *kref,
> void (*release)(struct kref *kref),
> struct mutex *mutex)
> + __cond_acquires(true, mutex)
> {
> if (refcount_dec_and_mutex_lock(&kref->refcount, mutex)) {
> release(kref);
> @@ -102,6 +103,7 @@ static inline int kref_put_mutex(struct kref *kref,
> static inline int kref_put_lock(struct kref *kref,
> void (*release)(struct kref *kref),
> spinlock_t *lock)
> + __cond_acquires(true, lock)
> {
> if (refcount_dec_and_lock(&kref->refcount, lock)) {
> release(kref);
> --
> 2.52.0.rc1.455.g30608eb744-goog
>
Note that both use the underlying refcount_dec_and_*lock() functions.
Its a bit sad that annotation those isn't sufficient. These are inline
functions after all, the compiler should be able to see through all that.
^ permalink raw reply
* Re: [PATCH v4 06/35] cleanup: Basic compatibility with context analysis
From: Peter Zijlstra @ 2025-12-11 12:16 UTC (permalink / raw)
To: Marco Elver
Cc: Boqun Feng, Ingo Molnar, Will Deacon, 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>
On Thu, Nov 20, 2025 at 04:09:31PM +0100, Marco Elver wrote:
> 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().
What is the scope of this __assumes_ctx stuff? The way it is used in the
lock initializes seems to suggest it escapes scope. But then something
like:
scoped_guard (mutex, &foo) {
...
}
// context analysis would still assume foo held
is somewhat sub-optimal, no?
> Better support for Linux's scoped guard design could be added in
> future if deemed critical.
I would think so, per the above I don't think this is 'right'.
^ permalink raw reply
* Re: [PATCH v4 02/35] compiler-context-analysis: Add infrastructure for Context Analysis with Clang
From: Peter Zijlstra @ 2025-12-11 12:04 UTC (permalink / raw)
To: Marco Elver
Cc: Boqun Feng, Ingo Molnar, Will Deacon, 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-4-elver@google.com>
On Thu, Nov 20, 2025 at 03:49:04PM +0100, Marco Elver wrote:
> +/**
> + * 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
-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;
I must say I find the 'guard' naming here somewhat confusing. This is
not a guard, but an actual lock type.
^ permalink raw reply
* Re: [PATCH v4 06/35] cleanup: Basic compatibility with context analysis
From: Peter Zijlstra @ 2025-12-11 11:51 UTC (permalink / raw)
To: Marco Elver
Cc: Boqun Feng, Ingo Molnar, Will Deacon, 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>
On Thu, Nov 20, 2025 at 04:09:31PM +0100, Marco Elver wrote:
> +#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;
When you rebase this series; you'll find cleanup.h moved to
__always_inline (because compilers are weird) and these should probably
also switch.
^ permalink raw reply
* Re: [PATCH v4 08/35] locking/rwlock, spinlock: Support Clang's context analysis
From: Peter Zijlstra @ 2025-12-11 11:49 UTC (permalink / raw)
To: Marco Elver
Cc: Boqun Feng, Ingo Molnar, Will Deacon, 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-9-elver@google.com>
On Thu, Nov 20, 2025 at 04:09:33PM +0100, Marco Elver wrote:
> To avoid warnings in constructors, the initialization functions mark a
> lock as acquired when initialized before guarded variables.
> 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
This is again somewhat magical and confused the living daylight out of
me. I know (from having looked back on previous discussions) that I was
confused about this before, and clearly it didn't stick.
So obviously I'll be confused again when I look at this code in a years
time or so :/
Can we get a comment near this __assume_ctx_guard() thing (because
putting it all over the lock initializers would probably be duplicating
things too much)?
^ permalink raw reply
* Re: [PATCH v4 02/35] compiler-context-analysis: Add infrastructure for Context Analysis with Clang
From: Peter Zijlstra @ 2025-12-11 11:44 UTC (permalink / raw)
To: Marco Elver
Cc: Boqun Feng, Ingo Molnar, Will Deacon, 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-4-elver@google.com>
On Thu, Nov 20, 2025 at 03:49:04PM +0100, Marco Elver wrote:
> +/**
> + * __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__)))
I must express pure hatred for this '.. code-block:: c' thing.
^ permalink raw reply
* Re: [PATCH v4 07/35] lockdep: Annotate lockdep assertions for context analysis
From: Peter Zijlstra @ 2025-12-11 11:43 UTC (permalink / raw)
To: Marco Elver
Cc: Boqun Feng, Ingo Molnar, Will Deacon, 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-8-elver@google.com>
On Thu, Nov 20, 2025 at 04:09:32PM +0100, Marco Elver wrote:
> 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)
Since I typically read patches without first reading the Changelog --
because when I read the code later, I also don't see changelogs.
I must admit to getting most terribly confused here -- *again*, as I
then search back to previous discussions and found I was previously also
confused.
As such, I think we want a comment here that explains that assume_ctx
thing.
It is *NOT* (as the clang naming suggests) an assertion of holding the
lock (which is requires_ctx), but rather an annotation that forces the
ctx to be considered held.
>
> #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
* Re: [RFC][PATCH] ima: Add support for staging measurements for deletion
From: Roberto Sassu @ 2025-12-11 10:18 UTC (permalink / raw)
To: steven chen, corbet, zohar, dmitry.kasatkin, eric.snowberg, paul,
jmorris, serge
Cc: linux-doc, linux-kernel, linux-integrity, linux-security-module,
gregorylumen, nramas, Roberto Sassu
In-Reply-To: <9cb4cbab-bcca-4ac8-a7a5-0cf3de67353e@linux.microsoft.com>
On Wed, 2025-12-10 at 16:03 -0800, steven chen wrote:
> On 12/9/2025 2:17 AM, Roberto Sassu wrote:
> > From: Roberto Sassu <roberto.sassu@huawei.com>
> >
> > Introduce the ability of staging the entire of the IMA measurement list, or
> > a portion, for deletion. Staging means moving the current content of the
> > measurement list to a separate location, and allowing users to read and
> > delete it. This causes the measurement list to be atomically truncated
> > before new measurements can be added. Staging can be done only once at a
> > time.
> >
> > User space is responsible to concatenate the staged IMA measurements list
> > portions following the temporal order in which the operations were done,
> > together with the current measurement list. Then, it can send the collected
> > data to the remote verifiers.
> >
> > The benefit of this solution is the ability to free precious kernel memory,
> > in exchange of delegating user space to reconstruct the full measurement
> > list from the chunks. No trust needs to be given to user space, since the
> > integrity of the measurement list is protected by the TPM.
> >
> > By default, staging the measurements list for deletion does not alter the
> > hash table. When staging is done, IMA is still able to detect collisions on
> > the staged and later deleted measurement entries, by keeping the entry
> > digests (only template data are freed).
> >
> > However, since during the measurements list serialization only the SHA1
> > digest is passed, and since there are no template data to recalculate the
> > other digests from, the hash table is currently not populated with digests
> > from staged/deleted entries after kexec().
> >
> > Introduce the new kernel option ima_flush_htable to decide whether or not
> > the digests of staged measurement entries are flushed from the hash table.
> >
> > Then, introduce ascii_runtime_measurements_staged_<algo> and
> > binary_runtime_measurement_staged_<algo> interfaces to stage/delete the
> > measurements. Use 'echo A > <IMA interface>' and 'echo D > <IMA interface>'
> > to respectively stage and delete the entire measurements list. Use
> > 'echo N > <IMA interface>', with N between 1 and ULONG_MAX, to stage the
> > selected portion of the measurements list.
> >
> > The ima_measure_users counter (protected by the ima_measure_lock mutex) has
> > been introduced to protect access to the measurement list and the staged
> > part. The open method of all the measurement interfaces has been extended
> > to allow only one writer at a time or, in alternative, multiple readers.
> > The write permission is used to stage/delete the measurements, the read
> > permission to read them. Write requires also the CAP_SYS_ADMIN capability.
>
> Hi Roberto,
>
> I released version 2 of trim N entries patch as bellow:
>
> [PATCH v2 0/1] Trim N entries of IMA event logs
> <https://lore.kernel.org/linux-integrity/20251210235314.3341-1-chenste@linux.microsoft.com/T/#t>
>
> I adapted some of your idea and I think trim N has following advantages:
> 1: less measurement list hold time than your current implementation
> 2. operation much simple for user space
> 3. less kernel code change
> 4. no potential issue as Gregory mentioned.
Please have a look at:
https://marc.info/?l=linux-integrity&m=176545085325473&w=2
and let me know if I'm missing something.
Thanks
Roberto
> Thanks,
>
> Steven
>
> > Finally, introduce the _notrim version of the run-time measurements count
> > and the binary measurements list size, to display them in the kexec-related
> > critical data records.
> >
> > Note: This code derives from the Alt-IMA Huawei project, and is being
> > released under the dual license model (GPL-2.0 OR MIT).
> >
> > Link: https://github.com/linux-integrity/linux/issues/1
> > Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
> > ---
> > .../admin-guide/kernel-parameters.txt | 4 +
> > security/integrity/ima/ima.h | 10 +-
> > security/integrity/ima/ima_fs.c | 222 +++++++++++++++++-
> > security/integrity/ima/ima_kexec.c | 13 +-
> > security/integrity/ima/ima_queue.c | 111 ++++++++-
> > 5 files changed, 340 insertions(+), 20 deletions(-)
> >
> > diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
> > index 6c42061ca20e..355d8930e3ac 100644
> > --- a/Documentation/admin-guide/kernel-parameters.txt
> > +++ b/Documentation/admin-guide/kernel-parameters.txt
> > @@ -2215,6 +2215,10 @@
> > Use the canonical format for the binary runtime
> > measurements, instead of host native format.
> >
> > + ima_flush_htable [IMA]
> > + Flush the measurement list hash table when staging all
> > + or a part of it for deletion.
> > +
> > ima_hash= [IMA]
> > Format: { md5 | sha1 | rmd160 | sha256 | sha384
> > | sha512 | ... }
> > diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
> > index e3d71d8d56e3..d7aa4a0f79b1 100644
> > --- a/security/integrity/ima/ima.h
> > +++ b/security/integrity/ima/ima.h
> > @@ -117,6 +117,8 @@ struct ima_queue_entry {
> > struct ima_template_entry *entry;
> > };
> > extern struct list_head ima_measurements; /* list of all measurements */
> > +extern struct list_head ima_measurements_staged; /* list of staged meas. */
> > +extern bool ima_measurements_staged_exist; /* If there are staged meas. */
> >
> > /* Some details preceding the binary serialized measurement list */
> > struct ima_kexec_hdr {
> > @@ -281,10 +283,12 @@ struct ima_template_desc *ima_template_desc_current(void);
> > struct ima_template_desc *ima_template_desc_buf(void);
> > struct ima_template_desc *lookup_template_desc(const char *name);
> > bool ima_template_has_modsig(const struct ima_template_desc *ima_template);
> > +int ima_queue_stage(unsigned long req_value);
> > +int ima_queue_delete_staged(void);
> > int ima_restore_measurement_entry(struct ima_template_entry *entry);
> > int ima_restore_measurement_list(loff_t bufsize, void *buf);
> > int ima_measurements_show(struct seq_file *m, void *v);
> > -unsigned long ima_get_binary_runtime_size(void);
> > +unsigned long ima_get_binary_runtime_size(bool notrim);
> > int ima_init_template(void);
> > void ima_init_template_list(void);
> > int __init ima_init_digests(void);
> > @@ -298,11 +302,13 @@ int ima_lsm_policy_change(struct notifier_block *nb, unsigned long event,
> > extern spinlock_t ima_queue_lock;
> >
> > struct ima_h_table {
> > - atomic_long_t len; /* number of stored measurements in the list */
> > + atomic_long_t len; /* current num of stored meas. in the list */
> > + atomic_long_t len_notrim; /* total num of stored meas. in the list */
> > atomic_long_t violations;
> > struct hlist_head queue[IMA_MEASURE_HTABLE_SIZE];
> > };
> > extern struct ima_h_table ima_htable;
> > +extern struct mutex ima_extend_list_mutex;
> >
> > static inline unsigned int ima_hash_key(u8 *digest)
> > {
> > diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
> > index 87045b09f120..321c98ae0e55 100644
> > --- a/security/integrity/ima/ima_fs.c
> > +++ b/security/integrity/ima/ima_fs.c
> > @@ -24,7 +24,12 @@
> >
> > #include "ima.h"
> >
> > +/* Requests: ('A', [1, ULONG_MAX])\n (stage all/N) or D\n (delete staged) */
> > +#define STAGED_REQ_LENGTH 21
> > +
> > static DEFINE_MUTEX(ima_write_mutex);
> > +static DEFINE_MUTEX(ima_measure_lock);
> > +static long ima_measure_users;
> >
> > bool ima_canonical_fmt;
> > static int __init default_canonical_fmt_setup(char *str)
> > @@ -74,14 +79,15 @@ static const struct file_operations ima_measurements_count_ops = {
> > };
> >
> > /* returns pointer to hlist_node */
> > -static void *ima_measurements_start(struct seq_file *m, loff_t *pos)
> > +static void *_ima_measurements_start(struct seq_file *m, loff_t *pos,
> > + struct list_head *head)
> > {
> > loff_t l = *pos;
> > struct ima_queue_entry *qe;
> >
> > /* we need a lock since pos could point beyond last element */
> > rcu_read_lock();
> > - list_for_each_entry_rcu(qe, &ima_measurements, later) {
> > + list_for_each_entry_rcu(qe, head, later) {
> > if (!l--) {
> > rcu_read_unlock();
> > return qe;
> > @@ -91,7 +97,18 @@ static void *ima_measurements_start(struct seq_file *m, loff_t *pos)
> > return NULL;
> > }
> >
> > -static void *ima_measurements_next(struct seq_file *m, void *v, loff_t *pos)
> > +static void *ima_measurements_start(struct seq_file *m, loff_t *pos)
> > +{
> > + return _ima_measurements_start(m, pos, &ima_measurements);
> > +}
> > +
> > +static void *ima_measurements_staged_start(struct seq_file *m, loff_t *pos)
> > +{
> > + return _ima_measurements_start(m, pos, &ima_measurements_staged);
> > +}
> > +
> > +static void *_ima_measurements_next(struct seq_file *m, void *v, loff_t *pos,
> > + struct list_head *head)
> > {
> > struct ima_queue_entry *qe = v;
> >
> > @@ -103,7 +120,18 @@ static void *ima_measurements_next(struct seq_file *m, void *v, loff_t *pos)
> > rcu_read_unlock();
> > (*pos)++;
> >
> > - return (&qe->later == &ima_measurements) ? NULL : qe;
> > + return (&qe->later == head) ? NULL : qe;
> > +}
> > +
> > +static void *ima_measurements_next(struct seq_file *m, void *v, loff_t *pos)
> > +{
> > + return _ima_measurements_next(m, v, pos, &ima_measurements);
> > +}
> > +
> > +static void *ima_measurements_staged_next(struct seq_file *m, void *v,
> > + loff_t *pos)
> > +{
> > + return _ima_measurements_next(m, v, pos, &ima_measurements_staged);
> > }
> >
> > static void ima_measurements_stop(struct seq_file *m, void *v)
> > @@ -202,16 +230,138 @@ static const struct seq_operations ima_measurments_seqops = {
> > .show = ima_measurements_show
> > };
> >
> > +static int _ima_measurements_open(struct inode *inode, struct file *file,
> > + const struct seq_operations *seq_ops)
> > +{
> > + bool write = !!(file->f_mode & FMODE_WRITE);
> > + int ret;
> > +
> > + if (write && !capable(CAP_SYS_ADMIN))
> > + return -EPERM;
> > +
> > + mutex_lock(&ima_measure_lock);
> > + if ((write && ima_measure_users != 0) ||
> > + (!write && ima_measure_users < 0)) {
> > + mutex_unlock(&ima_measure_lock);
> > + return -EBUSY;
> > + }
> > +
> > + ret = seq_open(file, seq_ops);
> > + if (ret < 0) {
> > + mutex_unlock(&ima_measure_lock);
> > + return ret;
> > + }
> > +
> > + if (write)
> > + ima_measure_users--;
> > + else
> > + ima_measure_users++;
> > +
> > + mutex_unlock(&ima_measure_lock);
> > + return ret;
> > +}
> > +
> > static int ima_measurements_open(struct inode *inode, struct file *file)
> > {
> > - return seq_open(file, &ima_measurments_seqops);
> > + return _ima_measurements_open(inode, file, &ima_measurments_seqops);
> > +}
> > +
> > +static int ima_measurements_release(struct inode *inode, struct file *file)
> > +{
> > + bool write = !!(file->f_mode & FMODE_WRITE);
> > + int ret;
> > +
> > + mutex_lock(&ima_measure_lock);
> > + ret = seq_release(inode, file);
> > + if (!ret) {
> > + if (write)
> > + ima_measure_users++;
> > + else
> > + ima_measure_users--;
> > + }
> > +
> > + mutex_unlock(&ima_measure_lock);
> > + return ret;
> > }
> >
> > static const struct file_operations ima_measurements_ops = {
> > .open = ima_measurements_open,
> > .read = seq_read,
> > .llseek = seq_lseek,
> > - .release = seq_release,
> > + .release = ima_measurements_release,
> > +};
> > +
> > +static const struct seq_operations ima_measurments_staged_seqops = {
> > + .start = ima_measurements_staged_start,
> > + .next = ima_measurements_staged_next,
> > + .stop = ima_measurements_stop,
> > + .show = ima_measurements_show
> > +};
> > +
> > +static int ima_measurements_staged_open(struct inode *inode, struct file *file)
> > +{
> > + return _ima_measurements_open(inode, file,
> > + &ima_measurments_staged_seqops);
> > +}
> > +
> > +static ssize_t ima_measurements_staged_read(struct file *file, char __user *buf,
> > + size_t size, loff_t *ppos)
> > +{
> > + if (!ima_measurements_staged_exist)
> > + return -ENOENT;
> > +
> > + return seq_read(file, buf, size, ppos);
> > +}
> > +
> > +static ssize_t ima_measurements_staged_write(struct file *file,
> > + const char __user *buf,
> > + size_t datalen, loff_t *ppos)
> > +{
> > + char req[STAGED_REQ_LENGTH], *req_ptr = req;
> > + unsigned long req_value;
> > + int ret;
> > +
> > + if (*ppos > 0 || datalen < 2 || datalen > STAGED_REQ_LENGTH)
> > + return -EINVAL;
> > +
> > + ret = copy_from_user(req, buf, datalen);
> > + if (ret < 0)
> > + return ret;
> > +
> > + if (strsep(&req_ptr, "\n") == NULL)
> > + return -EINVAL;
> > +
> > + switch (req[0]) {
> > + case 'A':
> > + if (datalen != 2 || req[1] != '\0')
> > + return -EINVAL;
> > +
> > + ret = ima_queue_stage(ULONG_MAX);
> > + break;
> > + case 'D':
> > + if (datalen != 2 || req[1] != '\0')
> > + return -EINVAL;
> > +
> > + ret = ima_queue_delete_staged();
> > + break;
> > + default:
> > + ret = kstrtoul(req, 0, &req_value);
> > + if (!ret)
> > + ret = ima_queue_stage(req_value);
> > + }
> > +
> > + if (ret < 0)
> > + return ret;
> > +
> > + return datalen;
> > +}
> > +
> > +static const struct file_operations ima_measurements_staged_ops = {
> > + .open = ima_measurements_staged_open,
> > + .read = ima_measurements_staged_read,
> > + .write = ima_measurements_staged_write,
> > + .llseek = seq_lseek,
> > + .release = ima_measurements_release,
> > };
> >
> > void ima_print_digest(struct seq_file *m, u8 *digest, u32 size)
> > @@ -279,14 +429,37 @@ static const struct seq_operations ima_ascii_measurements_seqops = {
> >
> > static int ima_ascii_measurements_open(struct inode *inode, struct file *file)
> > {
> > - return seq_open(file, &ima_ascii_measurements_seqops);
> > + return _ima_measurements_open(inode, file,
> > + &ima_ascii_measurements_seqops);
> > }
> >
> > static const struct file_operations ima_ascii_measurements_ops = {
> > .open = ima_ascii_measurements_open,
> > .read = seq_read,
> > .llseek = seq_lseek,
> > - .release = seq_release,
> > + .release = ima_measurements_release,
> > +};
> > +
> > +static const struct seq_operations ima_ascii_measurements_staged_seqops = {
> > + .start = ima_measurements_staged_start,
> > + .next = ima_measurements_staged_next,
> > + .stop = ima_measurements_stop,
> > + .show = ima_ascii_measurements_show
> > +};
> > +
> > +static int ima_ascii_measurements_staged_open(struct inode *inode,
> > + struct file *file)
> > +{
> > + return _ima_measurements_open(inode, file,
> > + &ima_ascii_measurements_staged_seqops);
> > +}
> > +
> > +static const struct file_operations ima_ascii_measurements_staged_ops = {
> > + .open = ima_ascii_measurements_staged_open,
> > + .read = ima_measurements_staged_read,
> > + .write = ima_measurements_staged_write,
> > + .llseek = seq_lseek,
> > + .release = ima_measurements_release,
> > };
> >
> > static ssize_t ima_read_policy(char *path)
> > @@ -419,6 +592,25 @@ static int __init create_securityfs_measurement_lists(void)
> > &ima_measurements_ops);
> > if (IS_ERR(dentry))
> > return PTR_ERR(dentry);
> > +
> > + sprintf(file_name, "ascii_runtime_measurements_staged_%s",
> > + hash_algo_name[algo]);
> > + dentry = securityfs_create_file(file_name,
> > + S_IRUSR | S_IRGRP | S_IWUSR | S_IWGRP,
> > + ima_dir, (void *)(uintptr_t)i,
> > + &ima_ascii_measurements_staged_ops);
> > + if (IS_ERR(dentry))
> > + return PTR_ERR(dentry);
> > +
> > + sprintf(file_name, "binary_runtime_measurements_staged_%s",
> > + hash_algo_name[algo]);
> > + dentry = securityfs_create_file(file_name,
> > + S_IRUSR | S_IRGRP |
> > + S_IWUSR | S_IWGRP,
> > + ima_dir, (void *)(uintptr_t)i,
> > + &ima_measurements_staged_ops);
> > + if (IS_ERR(dentry))
> > + return PTR_ERR(dentry);
> > }
> >
> > return 0;
> > @@ -528,6 +720,20 @@ int __init ima_fs_init(void)
> > goto out;
> > }
> >
> > + dentry = securityfs_create_symlink("binary_runtime_measurements_staged",
> > + ima_dir, "binary_runtime_measurements_staged_sha1", NULL);
> > + if (IS_ERR(dentry)) {
> > + ret = PTR_ERR(dentry);
> > + goto out;
> > + }
> > +
> > + dentry = securityfs_create_symlink("ascii_runtime_measurements_staged",
> > + ima_dir, "ascii_runtime_measurements_staged_sha1", NULL);
> > + if (IS_ERR(dentry)) {
> > + ret = PTR_ERR(dentry);
> > + goto out;
> > + }
> > +
> > dentry = securityfs_create_file("runtime_measurements_count",
> > S_IRUSR | S_IRGRP, ima_dir, NULL,
> > &ima_measurements_count_ops);
> > diff --git a/security/integrity/ima/ima_kexec.c b/security/integrity/ima/ima_kexec.c
> > index 7362f68f2d8b..23a20300da7b 100644
> > --- a/security/integrity/ima/ima_kexec.c
> > +++ b/security/integrity/ima/ima_kexec.c
> > @@ -40,8 +40,8 @@ void ima_measure_kexec_event(const char *event_name)
> > long len;
> > int n;
> >
> > - buf_size = ima_get_binary_runtime_size();
> > - len = atomic_long_read(&ima_htable.len);
> > + buf_size = ima_get_binary_runtime_size(true);
> > + len = atomic_long_read(&ima_htable.len_notrim);
> >
> > n = scnprintf(ima_kexec_event, IMA_KEXEC_EVENT_LEN,
> > "kexec_segment_size=%lu;ima_binary_runtime_size=%lu;"
> > @@ -93,8 +93,10 @@ static int ima_dump_measurement_list(unsigned long *buffer_size, void **buffer,
> >
> > memset(&khdr, 0, sizeof(khdr));
> > khdr.version = 1;
> > - /* This is an append-only list, no need to hold the RCU read lock */
> > - list_for_each_entry_rcu(qe, &ima_measurements, later, true) {
> > +
> > + /* It can race with ima_queue_stage(). */
> > + mutex_lock(&ima_extend_list_mutex);
> > + list_for_each_entry(qe, &ima_measurements, later) {
> > if (ima_kexec_file.count < ima_kexec_file.size) {
> > khdr.count++;
> > ima_measurements_show(&ima_kexec_file, qe);
> > @@ -103,6 +105,7 @@ static int ima_dump_measurement_list(unsigned long *buffer_size, void **buffer,
> > break;
> > }
> > }
> > + mutex_unlock(&ima_extend_list_mutex);
> >
> > /*
> > * fill in reserved space with some buffer details
> > @@ -157,7 +160,7 @@ void ima_add_kexec_buffer(struct kimage *image)
> > else
> > extra_memory = CONFIG_IMA_KEXEC_EXTRA_MEMORY_KB * 1024;
> >
> > - binary_runtime_size = ima_get_binary_runtime_size() + extra_memory;
> > + binary_runtime_size = ima_get_binary_runtime_size(false) + extra_memory;
> >
> > if (binary_runtime_size >= ULONG_MAX - PAGE_SIZE)
> > kexec_segment_size = ULONG_MAX;
> > diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c
> > index 590637e81ad1..868f216ac343 100644
> > --- a/security/integrity/ima/ima_queue.c
> > +++ b/security/integrity/ima/ima_queue.c
> > @@ -22,19 +22,32 @@
> >
> > #define AUDIT_CAUSE_LEN_MAX 32
> >
> > +bool ima_flush_htable;
> > +static int __init ima_flush_htable_setup(char *str)
> > +{
> > + ima_flush_htable = true;
> > + return 1;
> > +}
> > +__setup("ima_flush_htable", ima_flush_htable_setup);
> > +
> > /* pre-allocated array of tpm_digest structures to extend a PCR */
> > static struct tpm_digest *digests;
> >
> > LIST_HEAD(ima_measurements); /* list of all measurements */
> > +LIST_HEAD(ima_measurements_staged); /* list of staged measurements */
> > +bool ima_measurements_staged_exist; /* If there are staged measurements */
> > #ifdef CONFIG_IMA_KEXEC
> > static unsigned long binary_runtime_size;
> > +static unsigned long binary_runtime_size_notrim;
> > #else
> > static unsigned long binary_runtime_size = ULONG_MAX;
> > +static unsigned long binary_runtime_size_notrim = ULONG_MAX;
> > #endif
> >
> > /* key: inode (before secure-hashing a file) */
> > struct ima_h_table ima_htable = {
> > .len = ATOMIC_LONG_INIT(0),
> > + .len_notrim = ATOMIC_LONG_INIT(0),
> > .violations = ATOMIC_LONG_INIT(0),
> > .queue[0 ... IMA_MEASURE_HTABLE_SIZE - 1] = HLIST_HEAD_INIT
> > };
> > @@ -43,7 +56,7 @@ struct ima_h_table ima_htable = {
> > * 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.
> > @@ -114,15 +127,19 @@ static int ima_add_digest_entry(struct ima_template_entry *entry,
> > list_add_tail_rcu(&qe->later, &ima_measurements);
> >
> > atomic_long_inc(&ima_htable.len);
> > + atomic_long_inc(&ima_htable.len_notrim);
> > if (update_htable) {
> > key = ima_hash_key(entry->digests[ima_hash_algo_idx].digest);
> > hlist_add_head_rcu(&qe->hnext, &ima_htable.queue[key]);
> > }
> >
> > - if (binary_runtime_size != ULONG_MAX) {
> > + if (binary_runtime_size_notrim != ULONG_MAX) {
> > int size;
> >
> > size = get_binary_runtime_size(entry);
> > + binary_runtime_size_notrim =
> > + (binary_runtime_size_notrim < ULONG_MAX - size) ?
> > + binary_runtime_size_notrim + size : ULONG_MAX;
> > binary_runtime_size = (binary_runtime_size < ULONG_MAX - size) ?
> > binary_runtime_size + size : ULONG_MAX;
> > }
> > @@ -134,12 +151,18 @@ static int ima_add_digest_entry(struct ima_template_entry *entry,
> > * entire binary_runtime_measurement list, including the ima_kexec_hdr
> > * structure.
> > */
> > -unsigned long ima_get_binary_runtime_size(void)
> > +unsigned long ima_get_binary_runtime_size(bool notrim)
> > {
> > - if (binary_runtime_size >= (ULONG_MAX - sizeof(struct ima_kexec_hdr)))
> > + unsigned long *val;
> > +
> > + mutex_lock(&ima_extend_list_mutex);
> > + val = (notrim) ? &binary_runtime_size_notrim : &binary_runtime_size;
> > + mutex_unlock(&ima_extend_list_mutex);
> > +
> > + if (*val >= (ULONG_MAX - sizeof(struct ima_kexec_hdr)))
> > return ULONG_MAX;
> > else
> > - return binary_runtime_size + sizeof(struct ima_kexec_hdr);
> > + return *val + sizeof(struct ima_kexec_hdr);
> > }
> >
> > static int ima_pcr_extend(struct tpm_digest *digests_arg, int pcr)
> > @@ -220,6 +243,84 @@ int ima_add_template_entry(struct ima_template_entry *entry, int violation,
> > return result;
> > }
> >
> > +int ima_queue_stage(unsigned long req_value)
> > +{
> > + unsigned long req_value_copy = req_value, to_remove = 0;
> > + struct ima_queue_entry *qe;
> > +
> > + if (ima_measurements_staged_exist)
> > + return -EEXIST;
> > +
> > + mutex_lock(&ima_extend_list_mutex);
> > + if (list_empty(&ima_measurements)) {
> > + mutex_unlock(&ima_extend_list_mutex);
> > + return -ENOENT;
> > + }
> > +
> > + if (req_value == ULONG_MAX) {
> > + list_replace(&ima_measurements, &ima_measurements_staged);
> > + INIT_LIST_HEAD(&ima_measurements);
> > + atomic_long_set(&ima_htable.len, 0);
> > + if (IS_ENABLED(CONFIG_IMA_KEXEC))
> > + binary_runtime_size = 0;
> > + } else {
> > + list_for_each_entry(qe, &ima_measurements, later) {
> > + to_remove += get_binary_runtime_size(qe->entry);
> > + if (--req_value_copy == 0)
> > + break;
> > + }
> > +
> > + if (req_value_copy > 0) {
> > + mutex_unlock(&ima_extend_list_mutex);
> > + return -ENOENT;
> > + }
> > +
> > + __list_cut_position(&ima_measurements_staged, &ima_measurements,
> > + &qe->later);
> > + atomic_long_sub(req_value, &ima_htable.len);
> > + if (IS_ENABLED(CONFIG_IMA_KEXEC))
> > + binary_runtime_size -= to_remove;
> > + }
> > +
> > + if (ima_flush_htable) {
> > + list_for_each_entry(qe, &ima_measurements_staged, later)
> > + /* It can race with ima_lookup_digest_entry(). */
> > + hlist_del_rcu(&qe->hnext);
> > + }
> > +
> > + mutex_unlock(&ima_extend_list_mutex);
> > + ima_measurements_staged_exist = true;
> > + return 0;
> > +}
> > +
> > +int ima_queue_delete_staged(void)
> > +{
> > + struct ima_queue_entry *qe, *qe_tmp;
> > + unsigned int i;
> > +
> > + if (!ima_measurements_staged_exist)
> > + return -ENOENT;
> > +
> > + list_for_each_entry_safe(qe, qe_tmp, &ima_measurements_staged, later) {
> > + for (i = 0; i < qe->entry->template_desc->num_fields; i++) {
> > + kfree(qe->entry->template_data[i].data);
> > + qe->entry->template_data[i].data = NULL;
> > + qe->entry->template_data[i].len = 0;
> > + }
> > +
> > + list_del(&qe->later);
> > +
> > + if (ima_flush_htable) {
> > + kfree(qe->entry->digests);
> > + kfree(qe->entry);
> > + kfree(qe);
> > + }
> > + }
> > +
> > + ima_measurements_staged_exist = false;
> > + return 0;
> > +}
> > +
> > int ima_restore_measurement_entry(struct ima_template_entry *entry)
> > {
> > int result = 0;
>
^ permalink raw reply
* Re: [PATCH V2 1/1] IMA event log trimming
From: Roberto Sassu @ 2025-12-11 10:14 UTC (permalink / raw)
To: steven chen, linux-integrity
Cc: zohar, roberto.sassu, dmitry.kasatkin, eric.snowberg, corbet,
serge, paul, jmorris, linux-security-module, anirudhve,
gregorylumen, nramas, sushring, linux-doc
In-Reply-To: <20251210235314.3341-2-chenste@linux.microsoft.com>
On Wed, 2025-12-10 at 15:53 -0800, steven chen wrote:
> This patch is for trimming N entries of the IMA event logs. It will also
> cleaning the hash table if ima_flush_htable is set.
>
> It provides a userspace interface ima_trim_log that can be used to input
> number N to let kernel to trim N entries of IMA event logs. When read
> this interface, it returns number of entries trimmed last time.
I was trying to find a common solution for both approaches, and instead
you took part of my code and removed the part that you didn't like.
This is not nice.
Let's do the following. Please review my patch, and check if it makes
you achieve your goal. Then, please send follow-up patches with the
additional functionality you need (e.g. the new critical data
measurement entry).
Thanks
Roberto
> Signed-off-by: steven chen <chenste@linux.microsoft.com>
> ---
> .../admin-guide/kernel-parameters.txt | 4 +
> security/integrity/ima/ima.h | 2 +
> security/integrity/ima/ima_fs.c | 175 +++++++++++++++++-
> security/integrity/ima/ima_queue.c | 64 +++++++
> 4 files changed, 241 insertions(+), 4 deletions(-)
>
> diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
> index e92c0056e4e0..cd1a1d0bf0e2 100644
> --- a/Documentation/admin-guide/kernel-parameters.txt
> +++ b/Documentation/admin-guide/kernel-parameters.txt
> @@ -2197,6 +2197,10 @@
> Use the canonical format for the binary runtime
> measurements, instead of host native format.
>
> + ima_flush_htable [IMA]
> + Flush the measurement list hash table when trim all
> + or a part of it for deletion.
> +
> ima_hash= [IMA]
> Format: { md5 | sha1 | rmd160 | sha256 | sha384
> | sha512 | ... }
> diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
> index e3d71d8d56e3..ab0e30ee25ea 100644
> --- a/security/integrity/ima/ima.h
> +++ b/security/integrity/ima/ima.h
> @@ -246,8 +246,10 @@ void ima_post_key_create_or_update(struct key *keyring, struct key *key,
>
> #ifdef CONFIG_IMA_KEXEC
> void ima_measure_kexec_event(const char *event_name);
> +long ima_purge_event_log(long number_logs);
> #else
> static inline void ima_measure_kexec_event(const char *event_name) {}
> +static inline long ima_purge_event_log(long number_logs) { return 0; }
> #endif
>
> /*
> diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
> index 87045b09f120..410f7d03c43f 100644
> --- a/security/integrity/ima/ima_fs.c
> +++ b/security/integrity/ima/ima_fs.c
> @@ -21,6 +21,9 @@
> #include <linux/rcupdate.h>
> #include <linux/parser.h>
> #include <linux/vmalloc.h>
> +#include <linux/ktime.h>
> +#include <linux/timekeeping.h>
> +#include <linux/ima.h>
>
> #include "ima.h"
>
> @@ -38,6 +41,14 @@ __setup("ima_canonical_fmt", default_canonical_fmt_setup);
>
> static int valid_policy = 1;
>
> +#define IMA_LOG_TRIM_REQ_LENGTH 11
> +#define IMA_LOG_TRIM_EVENT_LEN 256
> +
> +static long trimcount;
> +/* mutex protects atomicity of trimming measurement list requests */
> +static DEFINE_MUTEX(ima_measure_lock);
> +static long ima_measure_users;
> +
> static ssize_t ima_show_htable_value(char __user *buf, size_t count,
> loff_t *ppos, atomic_long_t *val)
> {
> @@ -202,16 +213,65 @@ static const struct seq_operations ima_measurments_seqops = {
> .show = ima_measurements_show
> };
>
> +static int _ima_measurements_open(struct inode *inode, struct file *file,
> + const struct seq_operations *seq_ops)
> +{
> + bool write = !!(file->f_mode & FMODE_WRITE);
> + int ret;
> +
> + if (write && !capable(CAP_SYS_ADMIN))
> + return -EPERM;
> +
> + mutex_lock(&ima_measure_lock);
> + if ((write && ima_measure_users != 0) ||
> + (!write && ima_measure_users < 0)) {
> + mutex_unlock(&ima_measure_lock);
> + return -EBUSY;
> + }
> +
> + ret = seq_open(file, seq_ops);
> + if (ret < 0) {
> + mutex_unlock(&ima_measure_lock);
> + return ret;
> + }
> +
> + if (write)
> + ima_measure_users--;
> + else
> + ima_measure_users++;
> +
> + mutex_unlock(&ima_measure_lock);
> + return ret;
> +}
> +
> static int ima_measurements_open(struct inode *inode, struct file *file)
> {
> - return seq_open(file, &ima_measurments_seqops);
> + return _ima_measurements_open(inode, file, &ima_measurments_seqops);
> +}
> +
> +static int ima_measurements_release(struct inode *inode, struct file *file)
> +{
> + bool write = !!(file->f_mode & FMODE_WRITE);
> + int ret;
> +
> + mutex_lock(&ima_measure_lock);
> + ret = seq_release(inode, file);
> + if (!ret) {
> + if (write)
> + ima_measure_users++;
> + else
> + ima_measure_users--;
> + }
> +
> + mutex_unlock(&ima_measure_lock);
> + return ret;
> }
>
> static const struct file_operations ima_measurements_ops = {
> .open = ima_measurements_open,
> .read = seq_read,
> .llseek = seq_lseek,
> - .release = seq_release,
> + .release = ima_measurements_release,
> };
>
> void ima_print_digest(struct seq_file *m, u8 *digest, u32 size)
> @@ -279,14 +339,111 @@ static const struct seq_operations ima_ascii_measurements_seqops = {
>
> static int ima_ascii_measurements_open(struct inode *inode, struct file *file)
> {
> - return seq_open(file, &ima_ascii_measurements_seqops);
> + return _ima_measurements_open(inode, file, &ima_ascii_measurements_seqops);
> }
>
> static const struct file_operations ima_ascii_measurements_ops = {
> .open = ima_ascii_measurements_open,
> .read = seq_read,
> .llseek = seq_lseek,
> - .release = seq_release,
> + .release = ima_measurements_release,
> +};
> +
> +static void ima_measure_trim_event(const long number_logs)
> +{
> + char ima_log_trim_event[IMA_LOG_TRIM_EVENT_LEN];
> + struct timespec64 ts;
> + u64 time_ns;
> + int n;
> +
> + ktime_get_real_ts64(&ts);
> + time_ns = (u64)ts.tv_sec * 1000000000ULL + ts.tv_nsec;
> + n = scnprintf(ima_log_trim_event, IMA_LOG_TRIM_EVENT_LEN,
> + "time=%llu; log trim this time=%lu;",
> + time_ns, number_logs);
> +
> + ima_measure_critical_data("ima_log_trim", "trim ima event logs", ima_log_trim_event, n, false, NULL, 0);
> +}
> +
> +static int ima_log_trim_open(struct inode *inode, struct file *file)
> +{
> + bool write = !!(file->f_mode & FMODE_WRITE);
> +
> + if (!write && capable(CAP_SYS_ADMIN))
> + return 0;
> + else if (!capable(CAP_SYS_ADMIN))
> + return -EPERM;
> +
> + return _ima_measurements_open(inode, file, &ima_measurments_seqops);
> +}
> +
> +static ssize_t ima_log_trim_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
> +{
> + char tmpbuf[IMA_LOG_TRIM_REQ_LENGTH]; /* greater than largest 'long' string value */
> + ssize_t len;
> +
> + len = scnprintf(tmpbuf, sizeof(tmpbuf), "%li\n", trimcount);
> + return simple_read_from_buffer(buf, size, ppos, tmpbuf, len);
> +}
> +
> +static ssize_t ima_log_trim_write(struct file *file,
> + const char __user *buf, size_t datalen, loff_t *ppos)
> +{
> + unsigned char req[IMA_LOG_TRIM_REQ_LENGTH];
> + long count, n;
> + int ret;
> +
> + if (*ppos > 0 || datalen > IMA_LOG_TRIM_REQ_LENGTH || datalen < 2) {
> + ret = -EINVAL;
> + goto out;
> + }
> +
> + n = (int)datalen;
> +
> + ret = copy_from_user(req, buf, datalen);
> + if (ret < 0)
> + goto out;
> +
> + count = 0;
> + for (int i = 0; i < n; ++i) {
> + if (req[i] < '0' || req[i] > '9') {
> + ret = -EINVAL;
> + goto out;
> + }
> + count = count * 10 + req[i] - '0';
> + }
> + ret = ima_purge_event_log(count);
> +
> + if (ret < 0)
> + goto out;
> +
> + trimcount = ret;
> +
> + if (trimcount > 0)
> + ima_measure_trim_event(trimcount);
> +
> + ret = datalen;
> +out:
> + return ret;
> +}
> +
> +static int ima_log_trim_release(struct inode *inode, struct file *file)
> +{
> + bool write = !!(file->f_mode & FMODE_WRITE);
> + if (!write && capable(CAP_SYS_ADMIN))
> + return 0;
> + else if (!capable(CAP_SYS_ADMIN))
> + return -EPERM;
> +
> + return ima_measurements_release(inode, file);
> +}
> +
> +static const struct file_operations ima_log_trim_ops = {
> + .open = ima_log_trim_open,
> + .read = ima_log_trim_read,
> + .write = ima_log_trim_write,
> + .llseek = generic_file_llseek,
> + .release = ima_log_trim_release
> };
>
> static ssize_t ima_read_policy(char *path)
> @@ -528,6 +685,16 @@ int __init ima_fs_init(void)
> goto out;
> }
>
> + dentry = securityfs_create_file("ima_trim_log",
> + S_IRUSR | S_IRGRP | S_IWUSR | S_IWGRP,
> + ima_dir, NULL, &ima_log_trim_ops);
> + if (IS_ERR(dentry)) {
> + ret = PTR_ERR(dentry);
> + goto out;
> + }
> +
> + trimcount = 0;
> +
> dentry = securityfs_create_file("runtime_measurements_count",
> S_IRUSR | S_IRGRP, ima_dir, NULL,
> &ima_measurements_count_ops);
> diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c
> index 590637e81ad1..77ab52469727 100644
> --- a/security/integrity/ima/ima_queue.c
> +++ b/security/integrity/ima/ima_queue.c
> @@ -22,6 +22,14 @@
>
> #define AUDIT_CAUSE_LEN_MAX 32
>
> +bool ima_flush_htable;
> +static int __init ima_flush_htable_setup(char *str)
> +{
> + ima_flush_htable = true;
> + return 1;
> +}
> +__setup("ima_flush_htable", ima_flush_htable_setup);
> +
> /* pre-allocated array of tpm_digest structures to extend a PCR */
> static struct tpm_digest *digests;
>
> @@ -220,6 +228,62 @@ int ima_add_template_entry(struct ima_template_entry *entry, int violation,
> return result;
> }
>
> +/* Delete the IMA event logs */
> +long ima_purge_event_log(long number_logs)
> +{
> + struct ima_queue_entry *qe, *qe_tmp;
> + LIST_HEAD(ima_measurements_staged);
> + unsigned int i;
> + long cur = number_logs;
> +
> + if (number_logs <= 0)
> + return number_logs;
> +
> + mutex_lock(&ima_extend_list_mutex);
> +
> +
> + list_for_each_entry(qe, &ima_measurements, later) {
> + if (--number_logs == 0)
> + break;
> + }
> +
> + if (number_logs > 0) {
> + mutex_unlock(&ima_extend_list_mutex);
> + return -ENOENT;
> + }
> +
> + __list_cut_position(&ima_measurements_staged, &ima_measurements,
> + &qe->later);
> + atomic_long_sub(cur, &ima_htable.len);
> +
> + if (!IS_ENABLED(CONFIG_IMA_DISABLE_HTABLE) && ima_flush_htable) {
> + list_for_each_entry(qe, &ima_measurements_staged, later)
> + /* It can race with ima_lookup_digest_entry(). */
> + hlist_del_rcu(&qe->hnext);
> + }
> +
> + mutex_unlock(&ima_extend_list_mutex);
> +
> +
> + list_for_each_entry_safe(qe, qe_tmp, &ima_measurements_staged, later) {
> + for (i = 0; i < qe->entry->template_desc->num_fields; i++) {
> + kfree(qe->entry->template_data[i].data);
> + qe->entry->template_data[i].data = NULL;
> + qe->entry->template_data[i].len = 0;
> + }
> +
> + list_del(&qe->later);
> +
> + if (ima_flush_htable) {
> + kfree(qe->entry->digests);
> + kfree(qe->entry);
> + kfree(qe);
> + }
> + }
> +
> + return cur;
> +}
> +
> int ima_restore_measurement_entry(struct ima_template_entry *entry)
> {
> int result = 0;
^ permalink raw reply
* Re: [RFC][PATCH] ima: Add support for staging measurements for deletion
From: Roberto Sassu @ 2025-12-11 9:56 UTC (permalink / raw)
To: Gregory Lumen
Cc: corbet, zohar, dmitry.kasatkin, eric.snowberg, paul, jmorris,
serge, linux-doc, linux-kernel, linux-integrity,
linux-security-module, chenste, nramas, Roberto Sassu
In-Reply-To: <207fd6d7-53c-57bb-36d8-13a0902052d1@linux.microsoft.com>
On Wed, 2025-12-10 at 11:12 -0800, Gregory Lumen wrote:
> Roberto,
>
> The proposed approach appears to be workable. However, if our primary goal
> here is to enable UM to free kernel memory consumed by the IMA log with an
> absolute minimum of kernel functionality/change, then I would argue that
> the proposed Stage-then-delete approach still represents unnecessary
> complexity when compared to a trim-to-N solution. Specifically:
>
> - Any functional benefit offered through the introduction of a staged
> measurement list could be equally achieved in UM with a trim-to-N solution
> coupled with the proposed ima_measure_users counter for access locking.
Ok, let's quantify the complexity of each solution. Let's assume that
the IMA measurements list has M entries and you want to trim at N < M.
Staging:
1st. trim at N
(kernel)
1. list lock (write side) -> list replace (swap the heads) -> list unlock
2. read M -> file (file contains 0..M)
3. for each 0..M -> delete entry
(user)
1. for each 0..N in file -> replay PCR
2. trim at N (keep N + 1..M)
2nd. trim at O
(kernel)
1. list lock -> list replace (swap the heads) -> list unlock
2. read P -> file (file contains N + 1..P)
3. for each M + 1..P -> delete entry
(user)
1. for each N + 1..O in file -> replay PCR
2. trim at O (keep O + 1..P)
Trimming:
1st. trim at N
(kernel)
1. list lock (read side) -> for each 0..M -> read in file (file now contains 0..M) -> list unlock
(user)
1. for each 0..N -> replay PCR
2. discard N + 1..M
(kernel)
1. list lock (write side) -> for each 0..N -> trim -> list unlock
2nd. trim at O
(kernel)
1. list lock (read side) -> for each N + 1..P -> read in file (file now contains N + 1..P) -> list unlock
(user)
1. for each N + 1..O -> replay PCR
2. discard O + 1..P
(kernel)
1. list lock (write side) -> for each N + 1..O -> trim -> list unlock
You can try to optimize it a bit by prematurely ending the reading
before M and P, and by replaying the PCR on a partial buffer.
But still:
I just swap list heads in the hot path (still need to do the same for
the hash table, postponed to later), and do the free later once there
is no contention with new measurements.
In your case you are taking the lock and walking the list two times,
once as a reader and once as a writer, and discarding measurements in
user space that you already have.
I think your solution is more complex.
> - There exists a potential UM measurement-loss race condition introduced
> by the staging functionality that would not exist with a trim-to-N
> approach. (Occurs if a kexec call occurs after a UM agent has staged
> measurements for deletion, but has not completed copying them to
> userspace). This could be avoided by persisting staged measurements across
> kexec calls at the cost of making the proposed change larger.
The solution is to coordinate the staging with kexec in user space.
Roberto
^ permalink raw reply
* Re: [PATCH v4 06/35] cleanup: Basic compatibility with context analysis
From: Peter Zijlstra @ 2025-12-11 9:55 UTC (permalink / raw)
To: Marco Elver
Cc: Boqun Feng, Ingo Molnar, Will Deacon, 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>
On Thu, Nov 20, 2025 at 04:09:31PM +0100, Marco Elver wrote:
> 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.
Moo, so the alias analysis didn't help here?
^ permalink raw reply
* [syzbot] [lsm?] WARNING in collect_domain_accesses (2)
From: syzbot @ 2025-12-11 6:22 UTC (permalink / raw)
To: gnoack, jmorris, linux-kernel, linux-security-module, mic, paul,
serge, syzkaller-bugs
Hello,
syzbot found the following issue on:
HEAD commit: 05c93f3395ed Merge branch 'for-next/core' into for-kernelci
git tree: git://git.kernel.org/pub/scm/linux/kernel/git/arm64/linux.git for-kernelci
console output: https://syzkaller.appspot.com/x/log.txt?x=1077da1a580000
kernel config: https://syzkaller.appspot.com/x/.config?x=3b5338ad1e59a06c
dashboard link: https://syzkaller.appspot.com/bug?extid=d818ec6483755ab6b0c0
compiler: Debian clang version 20.1.8 (++20250708063551+0c9f909b7976-1~exp1~20250708183702.136), Debian LLD 20.1.8
userspace arch: arm64
syz repro: https://syzkaller.appspot.com/x/repro.syz?x=1518b992580000
C reproducer: https://syzkaller.appspot.com/x/repro.c?x=1212deb4580000
Downloadable assets:
disk image: https://storage.googleapis.com/syzbot-assets/6b5c913e373c/disk-05c93f33.raw.xz
vmlinux: https://storage.googleapis.com/syzbot-assets/15e75f1266ef/vmlinux-05c93f33.xz
kernel image: https://storage.googleapis.com/syzbot-assets/dd930129c578/Image-05c93f33.gz.xz
IMPORTANT: if you fix the issue, please add the following tag to the commit:
Reported-by: syzbot+d818ec6483755ab6b0c0@syzkaller.appspotmail.com
------------[ cut here ]------------
WARNING: CPU: 0 PID: 6724 at security/landlock/fs.c:1066 collect_domain_accesses+0x208/0x258 security/landlock/fs.c:-1
Modules linked in:
CPU: 0 UID: 0 PID: 6724 Comm: syz.0.17 Not tainted syzkaller #0 PREEMPT
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 10/03/2025
pstate: 83400005 (Nzcv daif +PAN -UAO +TCO +DIT -SSBS BTYPE=--)
pc : collect_domain_accesses+0x208/0x258 security/landlock/fs.c:-1
lr : collect_domain_accesses+0x204/0x258 security/landlock/fs.c:1066
sp : ffff8000a0fc7830
x29: ffff8000a0fc7830 x28: ffff0000ed05d608 x27: ffff8000a0fc7c48
x26: ffff7000141f8f20 x25: dfff800000000000 x24: ffff0000ed01aeb0
x23: 0000000000002004 x22: ffff0000ed01aeb0 x21: ffff0000eb595700
x20: ffff0000ed05d608 x19: 0000000000000000 x18: 0000000000000000
x17: 0000000000000000 x16: ffff800082defd48 x15: 0000000000000001
x14: 0000000000000000 x13: 0000000000000002 x12: 0000000000ff0100
x11: ffff0000c66f0000 x10: 0000000000ff0100 x9 : 0000000000000000
x8 : ffff0000c66f0000 x7 : ffff800082a23b24 x6 : 0000000000000000
x5 : 0000000000000000 x4 : 0000000000000000 x3 : 0000000000000010
x2 : ffff8000a0fc7920 x1 : 0000000000002004 x0 : ffff0000ed01af00
Call trace:
collect_domain_accesses+0x208/0x258 security/landlock/fs.c:-1 (P)
current_check_refer_path+0x4e8/0xaec security/landlock/fs.c:1202
hook_path_rename+0x4c/0x60 security/landlock/fs.c:1524
security_path_rename+0x1dc/0x444 security/security.c:2065
do_renameat2+0x4b4/0x8a0 fs/namei.c:5352
__do_sys_renameat2 fs/namei.c:5398 [inline]
__se_sys_renameat2 fs/namei.c:5395 [inline]
__arm64_sys_renameat2+0xd8/0xf4 fs/namei.c:5395
__invoke_syscall arch/arm64/kernel/syscall.c:35 [inline]
invoke_syscall+0x98/0x254 arch/arm64/kernel/syscall.c:49
el0_svc_common+0xe8/0x23c arch/arm64/kernel/syscall.c:132
do_el0_svc+0x48/0x58 arch/arm64/kernel/syscall.c:151
el0_svc+0x5c/0x254 arch/arm64/kernel/entry-common.c:724
el0t_64_sync_handler+0x84/0x12c arch/arm64/kernel/entry-common.c:743
el0t_64_sync+0x198/0x19c arch/arm64/kernel/entry.S:596
irq event stamp: 2148
hardirqs last enabled at (2147): [<ffff800080dcd64c>] seqcount_lockdep_reader_access+0x7c/0xf8 include/linux/seqlock.h:74
hardirqs last disabled at (2148): [<ffff80008adf99f4>] el1_brk64+0x20/0x54 arch/arm64/kernel/entry-common.c:412
softirqs last enabled at (1234): [<ffff8000803d9408>] softirq_handle_end kernel/softirq.c:468 [inline]
softirqs last enabled at (1234): [<ffff8000803d9408>] handle_softirqs+0xaf8/0xc88 kernel/softirq.c:650
softirqs last disabled at (1127): [<ffff800080022024>] __do_softirq+0x14/0x20 kernel/softirq.c:656
---[ end trace 0000000000000000 ]---
---
This report is generated by a bot. It may contain errors.
See https://goo.gl/tpsmEJ for more information about syzbot.
syzbot engineers can be reached at syzkaller@googlegroups.com.
syzbot will keep track of this issue. See:
https://goo.gl/tpsmEJ#status for how to communicate with syzbot.
If the report is already addressed, let syzbot know by replying with:
#syz fix: exact-commit-title
If you want syzbot to run the reproducer, reply with:
#syz test: git://repo/address.git branch-or-commit-hash
If you attach or paste a git patch, syzbot will apply it before testing.
If you want to overwrite report's subsystems, reply with:
#syz set subsystems: new-subsystem
(See the list of subsystem names on the web dashboard)
If the report is a duplicate of another one, reply with:
#syz dup: exact-subject-of-another-report
If you want to undo deduplication, reply with:
#syz undup
^ permalink raw reply
* [RFC 11/11] selftests/hornet: Add a selftest for the Hornet LSM
From: Blaise Boscaccy @ 2025-12-11 2:12 UTC (permalink / raw)
To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
linux-security-module, linux-doc, linux-kernel, bpf
In-Reply-To: <20251211021257.1208712-1-bboscaccy@linux.microsoft.com>
This selftest contains a testcase that utilizes light skeleton eBPF
loaders and exercises hornet's map validation.
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
tools/testing/selftests/Makefile | 1 +
tools/testing/selftests/hornet/Makefile | 63 ++++++++++++++++++++
tools/testing/selftests/hornet/loader.c | 21 +++++++
tools/testing/selftests/hornet/trivial.bpf.c | 33 ++++++++++
4 files changed, 118 insertions(+)
create mode 100644 tools/testing/selftests/hornet/Makefile
create mode 100644 tools/testing/selftests/hornet/loader.c
create mode 100644 tools/testing/selftests/hornet/trivial.bpf.c
diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index c46ebdb9b8ef7..4631893f0e91e 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -43,6 +43,7 @@ TARGETS += ftrace
TARGETS += futex
TARGETS += gpio
TARGETS += hid
+TARGETS += hornet
TARGETS += intel_pstate
TARGETS += iommu
TARGETS += ipc
diff --git a/tools/testing/selftests/hornet/Makefile b/tools/testing/selftests/hornet/Makefile
new file mode 100644
index 0000000000000..ccb4d503425d2
--- /dev/null
+++ b/tools/testing/selftests/hornet/Makefile
@@ -0,0 +1,63 @@
+# SPDX-License-Identifier: GPL-2.0
+include ../../../build/Build.include
+include ../../../scripts/Makefile.arch
+include ../../../scripts/Makefile.include
+
+CLANG ?= clang
+CFLAGS := -g -O2 -Wall
+BPFTOOL ?= $(TOOLSDIR)/bpf/bpftool/bpftool
+SCRIPTSDIR := $(abspath ../../../../scripts/hornet)
+TOOLSDIR := $(abspath ../../..)
+LIBDIR := $(TOOLSDIR)/lib
+BPFDIR := $(LIBDIR)/bpf
+TOOLSINCDIR := $(TOOLSDIR)/include
+APIDIR := $(TOOLSINCDIR)/uapi
+CERTDIR := $(abspath ../../../../certs)
+PKG_CONFIG ?= $(CROSS_COMPILE)pkg-config
+
+TEST_GEN_PROGS := loader
+TEST_GEN_FILES := vmlinux.h loader.h trivial.bpf.o map.bin sig.bin insn.bin signed_loader.h
+$(TEST_GEN_PROGS): LDLIBS += -lbpf
+$(TEST_GEN_PROGS): $(TEST_GEN_FILES)
+
+include ../lib.mk
+
+BPF_CFLAGS := -target bpf \
+ -D__TARGET_ARCH_$(ARCH) \
+ -I/usr/include/$(shell uname -m)-linux-gnu \
+ $(KHDR_INCLUDES)
+
+vmlinux.h:
+ $(BPFTOOL) btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
+
+trivial.bpf.o: trivial.bpf.c vmlinux.h
+ $(CLANG) $(CFLAGS) $(BPF_CFLAGS) -c $< -o $@
+
+loader.h: trivial.bpf.o
+ $(BPFTOOL) gen skeleton -S -k $(CERTDIR)/signing_key.pem -i $(CERTDIR)/signing_key.x509 \
+ -L $< name trivial > $@
+
+insn.bin: loader.h
+ $(SCRIPTSDIR)/extract-insn.sh $< > $@
+
+map.bin: loader.h
+ $(SCRIPTSDIR)/extract-map.sh $< > $@
+
+$(OUTPUT)/gen_sig: ../../../../scripts/hornet/gen_sig.c
+ $(call msg,GEN_SIG,,$@)
+ $(Q)$(CC) $(shell $(PKG_CONFIG) --cflags libcrypto 2> /dev/null) \
+ $< -o $@ \
+ $(shell $(PKG_CONFIG) --libs libcrypto 2> /dev/null || echo -lcrypto)
+
+sig.bin: insn.bin map.bin $(OUTPUT)/gen_sig
+ $(OUTPUT)/gen_sig -key $(CERTDIR)/signing_key.pem -cert $(CERTDIR)/signing_key.x509 \
+ -data insn.bin --add-hash map.bin -out sig.bin
+
+signed_loader.h: sig.bin
+ $(SCRIPTSDIR)/write-sig.sh loader.h sig.bin > $@
+
+loader: loader.c signed_loader.h
+ $(CC) $(CFLAGS) -I$(LIBDIR) -I$(APIDIR) $< -o $@ -lbpf
+
+
+EXTRA_CLEAN = $(OUTPUT)/gen_sig
diff --git a/tools/testing/selftests/hornet/loader.c b/tools/testing/selftests/hornet/loader.c
new file mode 100644
index 0000000000000..f27580c7262b3
--- /dev/null
+++ b/tools/testing/selftests/hornet/loader.c
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stddef.h>
+#include <sys/resource.h>
+#include <bpf/libbpf.h>
+#include <errno.h>
+#include "signed_loader.h"
+
+int main(int argc, char **argv)
+{
+ struct trivial *skel;
+
+ skel = trivial__open_and_load();
+ if (!skel)
+ return -1;
+
+ trivial__destroy(skel);
+ return 0;
+}
diff --git a/tools/testing/selftests/hornet/trivial.bpf.c b/tools/testing/selftests/hornet/trivial.bpf.c
new file mode 100644
index 0000000000000..d38c5b53ff932
--- /dev/null
+++ b/tools/testing/selftests/hornet/trivial.bpf.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+
+#include "vmlinux.h"
+
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include <bpf/bpf_core_read.h>
+
+char LICENSE[] SEC("license") = "Dual BSD/GPL";
+
+int monitored_pid = 0;
+
+SEC("tracepoint/syscalls/sys_enter_unlinkat")
+int handle_enter_unlink(struct trace_event_raw_sys_enter *ctx)
+{
+ char filename[128] = { 0 };
+ struct task_struct *task;
+ unsigned long start_time = 0;
+ int pid = bpf_get_current_pid_tgid() >> 32;
+ char *pathname_ptr = (char *) BPF_CORE_READ(ctx, args[1]);
+
+ bpf_probe_read_str(filename, sizeof(filename), pathname_ptr);
+ task = (struct task_struct *)bpf_get_current_task();
+ start_time = BPF_CORE_READ(task, start_time);
+
+ bpf_printk("BPF triggered unlinkat by PID: %d, start_time %ld. pathname = %s",
+ pid, start_time, filename);
+
+ if (monitored_pid == pid)
+ bpf_printk("target pid found");
+
+ return 0;
+}
--
2.52.0
^ permalink raw reply related
* [RFC 10/11] hornet: Add a light skeleton data extractor scripts
From: Blaise Boscaccy @ 2025-12-11 2:12 UTC (permalink / raw)
To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
linux-security-module, linux-doc, linux-kernel, bpf
In-Reply-To: <20251211021257.1208712-1-bboscaccy@linux.microsoft.com>
These script eases light skeleton development against Hornet by
generating a data payloads which can be used for signing a light
skeleton binary using gen_sig.
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
| 27 +++++++++++++++++++++++++++
| 27 +++++++++++++++++++++++++++
| 27 +++++++++++++++++++++++++++
3 files changed, 81 insertions(+)
create mode 100755 scripts/hornet/extract-insn.sh
create mode 100755 scripts/hornet/extract-map.sh
create mode 100755 scripts/hornet/extract-skel.sh
--git a/scripts/hornet/extract-insn.sh b/scripts/hornet/extract-insn.sh
new file mode 100755
index 0000000000000..399b9ca500e3f
--- /dev/null
+++ b/scripts/hornet/extract-insn.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (c) 2025 Microsoft Corporation
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of version 2 of the GNU General Public
+# License as published by the Free Software Foundation.
+
+function usage() {
+ echo "Sample script for extracting instructions"
+ echo "autogenerated eBPF lskel headers"
+ echo ""
+ echo "USAGE: header_file"
+ exit
+}
+
+ARGC=$#
+
+EXPECTED_ARGS=1
+
+if [ $ARGC -ne $EXPECTED_ARGS ] ; then
+ usage
+else
+ printf $(gcc -E $1 | grep "opts\.insns =" | \
+ awk -F")" '{print $2}' | sed 's/;\+$//' | sed 's/\"//g')
+fi
--git a/scripts/hornet/extract-map.sh b/scripts/hornet/extract-map.sh
new file mode 100755
index 0000000000000..ca5d912dc5654
--- /dev/null
+++ b/scripts/hornet/extract-map.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (c) 2025 Microsoft Corporation
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of version 2 of the GNU General Public
+# License as published by the Free Software Foundation.
+
+function usage() {
+ echo "Sample script for extracting instructions"
+ echo "autogenerated eBPF lskel headers"
+ echo ""
+ echo "USAGE: header_file"
+ exit
+}
+
+ARGC=$#
+
+EXPECTED_ARGS=1
+
+if [ $ARGC -ne $EXPECTED_ARGS ] ; then
+ usage
+else
+ printf $(gcc -E $1 | grep "opts\.data =" | \
+ awk -F")" '{print $2}' | sed 's/;\+$//' | sed 's/\"//g')
+fi
--git a/scripts/hornet/extract-skel.sh b/scripts/hornet/extract-skel.sh
new file mode 100755
index 0000000000000..6550a86b89917
--- /dev/null
+++ b/scripts/hornet/extract-skel.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (c) 2025 Microsoft Corporation
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of version 2 of the GNU General Public
+# License as published by the Free Software Foundation.
+
+function usage() {
+ echo "Sample script for extracting instructions and map data out of"
+ echo "autogenerated eBPF lskel headers"
+ echo ""
+ echo "USAGE: header_file field"
+ exit
+}
+
+ARGC=$#
+
+EXPECTED_ARGS=2
+
+if [ $ARGC -ne $EXPECTED_ARGS ] ; then
+ usage
+else
+ printf $(gcc -E $1 | grep "static const char opts_$2" | \
+ awk -F"=" '{print $2}' | sed 's/;\+$//' | sed 's/\"//g')
+fi
--
2.52.0
^ permalink raw reply related
* [RFC 09/11] hornet: Introduce gen_sig
From: Blaise Boscaccy @ 2025-12-11 2:12 UTC (permalink / raw)
To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
linux-security-module, linux-doc, linux-kernel, bpf
In-Reply-To: <20251211021257.1208712-1-bboscaccy@linux.microsoft.com>
This introduces the gen_sig tool. It creates a pkcs#7 signature of a
data payload. Additionally it appends a signed attribute containing a
set of hashes.
Typical usage is to provide a payload containing the light skeleton
ebpf syscall program binary and it's associated maps, which can be
extracted from the auto-generated skeleton header.
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
scripts/Makefile | 1 +
scripts/hornet/Makefile | 5 +
scripts/hornet/gen_sig.c | 392 ++++++++++++++++++++++++++++++++++++
scripts/hornet/write-sig.sh | 27 +++
4 files changed, 425 insertions(+)
create mode 100644 scripts/hornet/Makefile
create mode 100644 scripts/hornet/gen_sig.c
create mode 100755 scripts/hornet/write-sig.sh
diff --git a/scripts/Makefile b/scripts/Makefile
index 46f860529df5e..a2cace05d7342 100644
--- a/scripts/Makefile
+++ b/scripts/Makefile
@@ -57,6 +57,7 @@ subdir-$(CONFIG_GENKSYMS) += genksyms
subdir-$(CONFIG_GENDWARFKSYMS) += gendwarfksyms
subdir-$(CONFIG_SECURITY_SELINUX) += selinux
subdir-$(CONFIG_SECURITY_IPE) += ipe
+subdir-$(CONFIG_SECURITY_HORNET) += hornet
# Let clean descend into subdirs
subdir- += basic dtc gdb kconfig mod
diff --git a/scripts/hornet/Makefile b/scripts/hornet/Makefile
new file mode 100644
index 0000000000000..3ee41e5e9a9ff
--- /dev/null
+++ b/scripts/hornet/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+hostprogs-always-y := gen_sig
+
+HOSTCFLAGS_gen_sig.o = $(shell $(HOSTPKG_CONFIG) --cflags libcrypto 2> /dev/null)
+HOSTLDLIBS_gen_sig = $(shell $(HOSTPKG_CONFIG) --libs libcrypto 2> /dev/null || echo -lcrypto)
diff --git a/scripts/hornet/gen_sig.c b/scripts/hornet/gen_sig.c
new file mode 100644
index 0000000000000..1d501efeb8f13
--- /dev/null
+++ b/scripts/hornet/gen_sig.c
@@ -0,0 +1,392 @@
+/* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
+ *
+ * Generate a signature for an eBPF program along with appending
+ * map hashes as signed attributes
+ *
+ * Copyright © 2025 Microsoft Corporation.
+ *
+ * Authors: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the licence, or (at your option) any later version.
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <err.h>
+
+#include <openssl/cms.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/pkcs7.h>
+#include <openssl/x509.h>
+#include <openssl/pem.h>
+#include <openssl/objects.h>
+#include <openssl/asn1.h>
+#include <openssl/asn1t.h>
+#include <openssl/opensslv.h>
+#include <openssl/bio.h>
+#include <openssl/stack.h>
+
+#if OPENSSL_VERSION_MAJOR >= 3
+# define USE_PKCS11_PROVIDER
+# include <openssl/provider.h>
+# include <openssl/store.h>
+#else
+# if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0)
+# define USE_PKCS11_ENGINE
+# include <openssl/engine.h>
+# endif
+#endif
+#include "../ssl-common.h"
+
+#define SHA256_LEN 32
+#define BUF_SIZE (1 << 15) // 32 KiB
+#define MAX_HASHES 64
+
+struct hash_spec {
+ char *file;
+};
+
+typedef struct {
+ ASN1_INTEGER *index;
+ ASN1_OCTET_STRING *hash;
+
+} HORNET_MAP;
+
+DECLARE_ASN1_FUNCTIONS(HORNET_MAP)
+ASN1_SEQUENCE(HORNET_MAP) = {
+ ASN1_SIMPLE(HORNET_MAP, index, ASN1_INTEGER),
+ ASN1_SIMPLE(HORNET_MAP, hash, ASN1_OCTET_STRING)
+} ASN1_SEQUENCE_END(HORNET_MAP);
+
+IMPLEMENT_ASN1_FUNCTIONS(HORNET_MAP)
+
+DEFINE_STACK_OF(HORNET_MAP)
+
+typedef struct {
+ STACK_OF(HORNET_MAP) * maps;
+} MAP_SET;
+
+DECLARE_ASN1_FUNCTIONS(MAP_SET)
+ASN1_SEQUENCE(MAP_SET) = {
+ ASN1_SET_OF(MAP_SET, maps, HORNET_MAP)
+} ASN1_SEQUENCE_END(MAP_SET);
+
+IMPLEMENT_ASN1_FUNCTIONS(MAP_SET)
+
+#define DIE(...) do { fprintf(stderr, __VA_ARGS__); fputc('\n', stderr); \
+ exit(EXIT_FAILURE); } while (0)
+
+static BIO *bio_open_wr(const char *path)
+{
+ BIO *b = BIO_new_file(path, "wb");
+
+ if (!b) {
+ perror(path);
+ ERR_print_errors_fp(stderr);
+ exit(EXIT_FAILURE);
+ }
+ return b;
+}
+
+static void usage(const char *prog)
+{
+ fprintf(stderr,
+ "Usage:\n"
+ " %s -data content.bin -cert signer.crt -key signer.key [-pass pass]\n"
+ " -out newsig.p7b \n"
+ " --add-hash FILE [--add-hash FILE ...]\n",
+ prog);
+}
+
+static const char *key_pass;
+
+static int pem_pw_cb(char *buf, int len, int w, void *v)
+{
+ int pwlen;
+
+ if (!key_pass)
+ return -1;
+
+ pwlen = strlen(key_pass);
+ if (pwlen >= len)
+ return -1;
+
+ strcpy(buf, key_pass);
+
+ key_pass = NULL;
+
+ return pwlen;
+}
+
+static EVP_PKEY *read_private_key(const char *private_key_name)
+{
+ EVP_PKEY *private_key;
+ BIO *b;
+
+ b = BIO_new_file(private_key_name, "rb");
+ ERR(!b, "%s", private_key_name);
+ private_key = PEM_read_bio_PrivateKey(b, NULL, pem_pw_cb,
+ NULL);
+ ERR(!private_key, "%s", private_key_name);
+ BIO_free(b);
+
+ return private_key;
+}
+
+static X509 *read_x509(const char *x509_name)
+{
+ unsigned char buf[2];
+ X509 *x509;
+ BIO *b;
+ int n;
+
+ b = BIO_new_file(x509_name, "rb");
+ ERR(!b, "%s", x509_name);
+
+ /* Look at the first two bytes of the file to determine the encoding */
+ n = BIO_read(b, buf, 2);
+ if (n != 2) {
+ if (BIO_should_retry(b)) {
+ fprintf(stderr, "%s: Read wanted retry\n", x509_name);
+ exit(1);
+ }
+ if (n >= 0) {
+ fprintf(stderr, "%s: Short read\n", x509_name);
+ exit(1);
+ }
+ ERR(1, "%s", x509_name);
+ }
+
+ ERR(BIO_reset(b) != 0, "%s", x509_name);
+
+ if (buf[0] == 0x30 && buf[1] >= 0x81 && buf[1] <= 0x84)
+ /* Assume raw DER encoded X.509 */
+ x509 = d2i_X509_bio(b, NULL);
+ else
+ /* Assume PEM encoded X.509 */
+ x509 = PEM_read_bio_X509(b, NULL, NULL, NULL);
+
+ BIO_free(b);
+ ERR(!x509, "%s", x509_name);
+
+ return x509;
+}
+
+static int sha256(const char *path, unsigned char out[SHA256_LEN], unsigned int *out_len)
+{
+ FILE *f;
+ int rc;
+ EVP_MD_CTX *ctx;
+ unsigned char buf[BUF_SIZE];
+ size_t n;
+ unsigned int mdlen = 0;
+
+ if (!path || !out)
+ return -1;
+
+ f = fopen(path, "rb");
+ if (!f) {
+ perror("fopen");
+ return -2;
+ }
+
+ ERR_load_crypto_strings();
+
+ rc = -3;
+ ctx = EVP_MD_CTX_new();
+ if (!ctx) {
+ rc = -4;
+ goto done;
+ }
+
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+ if (EVP_DigestInit_ex2(ctx, EVP_sha256(), NULL) != 1) {
+ rc = -5;
+ goto done;
+ }
+#else
+ if (EVP_DigestInit_ex(ctx, EVP_sha256(), NULL) != 1) {
+ rc = -5;
+ goto done;
+ }
+#endif
+ while ((n = fread(buf, 1, sizeof(buf), f)) > 0) {
+ if (EVP_DigestUpdate(ctx, buf, n) != 1) {
+ rc = -6;
+ goto done;
+ }
+ }
+ if (ferror(f)) {
+ rc = -7;
+ goto done;
+ }
+
+ if (EVP_DigestFinal_ex(ctx, out, &mdlen) != 1) {
+ rc = -8;
+ goto done;
+ }
+ if (mdlen != SHA256_LEN) {
+ rc = -9;
+ goto done;
+ }
+
+ if (out_len)
+ *out_len = mdlen;
+ rc = 0;
+
+done:
+ EVP_MD_CTX_free(ctx);
+ fclose(f);
+ ERR_free_strings();
+ return rc;
+}
+
+static void add_hash(MAP_SET *set, unsigned char *buffer, int buffer_len, int index)
+{
+ HORNET_MAP *map = NULL;
+
+ map = HORNET_MAP_new();
+ ASN1_INTEGER_set(map->index, index);
+ ASN1_OCTET_STRING_set(map->hash, buffer, buffer_len);
+ sk_HORNET_MAP_push(set->maps, map);
+}
+
+int main(int argc, char **argv)
+{
+ const char *cert_path = NULL;
+ const char *key_path = NULL;
+ const char *data_path = NULL;
+ const char *out_path = NULL;
+
+ X509 *signer;
+ EVP_PKEY *pkey;
+ BIO *data_in;
+ CMS_ContentInfo *cms_out;
+ struct hash_spec hashes[MAX_HASHES];
+ int hash_count = 0;
+ int flags;
+ CMS_SignerInfo *si;
+ MAP_SET *set;
+ unsigned char hash_buffer[SHA256_LEN];
+ unsigned int hash_len;
+ ASN1_OBJECT *oid;
+ unsigned char *der = NULL;
+ int der_len;
+ int err;
+ BIO *b_out;
+ int i;
+
+ for (i = 1; i < argc; i++) {
+ if (!strcmp(argv[i], "-cert") && i+1 < argc)
+ cert_path = argv[++i];
+ else if (!strcmp(argv[i], "-data") && i+1 < argc)
+ data_path = argv[++i];
+ else if (!strcmp(argv[i], "-key") && i+1 < argc)
+ key_path = argv[++i];
+ else if (!strcmp(argv[i], "-pass") && i+1 < argc)
+ key_pass = argv[++i];
+ else if (!strcmp(argv[i], "-out") && i+1 < argc)
+ out_path = argv[++i];
+ else if (!strcmp(argv[i], "--add-skip-check")) {
+ hashes[hash_count++].file = NULL;
+ i++;
+ } else if (!strcmp(argv[i], "--add-hash") && i+1 < argc) {
+ hashes[hash_count++].file = argv[++i];
+ } else {
+ usage(argv[0]);
+ return EXIT_FAILURE;
+ }
+ }
+
+ if (!cert_path || !key_path || !out_path || !data_path) {
+ usage(argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ OpenSSL_add_all_algorithms();
+ ERR_load_crypto_strings();
+
+ signer = read_x509(cert_path);
+ if (!signer) {
+ ERR_print_errors_fp(stderr);
+ DIE("Load cert failed");
+ }
+
+ pkey = read_private_key(key_path);
+ if (!pkey) {
+ ERR_print_errors_fp(stderr);
+ DIE("Load key failed");
+ }
+
+ data_in = BIO_new_file(data_path, "rb");
+ if (!data_in) {
+ ERR_print_errors_fp(stderr);
+ DIE("Load data failed");
+ }
+
+ cms_out = CMS_sign(NULL, NULL, NULL, NULL,
+ CMS_NOCERTS | CMS_PARTIAL | CMS_BINARY | CMS_DETACHED);
+
+ if (!cms_out) {
+ ERR_print_errors_fp(stderr);
+ DIE("create cms failed");
+ }
+
+ flags = CMS_NOCERTS | CMS_PARTIAL | CMS_BINARY | CMS_NOSMIMECAP | CMS_DETACHED;
+
+ si = CMS_add1_signer(cms_out, signer, pkey, EVP_sha256(), flags);
+ if (!si)
+ DIE("add signer failed");
+
+ set = MAP_SET_new();
+ set->maps = sk_HORNET_MAP_new_null();
+
+ for (i = 0; i < hash_count; i++) {
+ if (hashes[i].file) {
+ sha256(hashes[i].file, hash_buffer, &hash_len);
+ } else {
+ memset(hash_buffer, 0, SHA256_LEN);
+ hash_len = SHA256_LEN;
+ }
+ add_hash(set, hash_buffer, hash_len, i);
+ }
+
+ oid = OBJ_txt2obj("2.25.316487325684022475439036912669789383960", 1);
+ if (!oid) {
+ ERR_print_errors_fp(stderr);
+ DIE("create oid failed");
+ }
+
+ der_len = ASN1_item_i2d((ASN1_VALUE *)set, &der, ASN1_ITEM_rptr(MAP_SET));
+ CMS_signed_add1_attr_by_OBJ(si, oid, V_ASN1_SEQUENCE, der, der_len);
+
+ err = CMS_final(cms_out, data_in, NULL, CMS_NOCERTS | CMS_BINARY);
+ if (err == 0)
+ ERR_print_errors_fp(stderr);
+
+ OPENSSL_free(der);
+ MAP_SET_free(set);
+
+ b_out = bio_open_wr(out_path);
+ if (!b_out) {
+ ERR_print_errors_fp(stderr);
+ DIE("opening output path failed");
+ }
+
+ i2d_CMS_bio_stream(b_out, cms_out, NULL, 0);
+
+ BIO_free(data_in);
+ BIO_free(b_out);
+ EVP_cleanup();
+ ERR_free_strings();
+ return 0;
+}
diff --git a/scripts/hornet/write-sig.sh b/scripts/hornet/write-sig.sh
new file mode 100755
index 0000000000000..7eaabe3bab9aa
--- /dev/null
+++ b/scripts/hornet/write-sig.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# Copyright (c) 2025 Microsoft Corporation
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of version 2 of the GNU General Public
+# License as published by the Free Software Foundation.
+
+function usage() {
+ echo "Sample for rewriting an autogenerated eBPF lskel headers"
+ echo "with a new signature"
+ echo ""
+ echo "USAGE: header_file sig"
+ exit
+}
+
+ARGC=$#
+
+EXPECTED_ARGS=2
+
+if [ $ARGC -ne $EXPECTED_ARGS ] ; then
+ usage
+else
+ SIG=$(xxd -p $2 | tr -d '\n' | sed 's/\(..\)/\\\\x\1/g')
+ sed '/const char opts_sig/,/;/c\\tstatic const char opts_sig[] __attribute__((__aligned__(8))) = "\\\n'"$(printf '%s\n' "$SIG")"'\";' $1
+fi
--
2.52.0
^ permalink raw reply related
* [RFC 08/11] security: Hornet LSM
From: Blaise Boscaccy @ 2025-12-11 2:12 UTC (permalink / raw)
To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
linux-security-module, linux-doc, linux-kernel, bpf
In-Reply-To: <20251211021257.1208712-1-bboscaccy@linux.microsoft.com>
This adds the Hornet Linux Security Module which provides enhanced
signature verification and data validation for eBPF programs. This
allows users to continue to maintain an invariant that all code
running inside of the kernel has actually been signed and verified, by
the kernel.
This effort builds upon the currently excepted upstream solution. It
further hardens it by providing deterministic, in-kernel checking of
map hashes to solidify auditing along with preventing TOCTOU attacks
against lskel map hashes.
Target map hashes are passed in via PKCS#7 signed attributes. Hornet
determines the extent which the eBFP program is signed and defers to
other LSMs for policy decisions.
Signed-off-by: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
---
Documentation/admin-guide/LSM/Hornet.rst | 38 +++++
Documentation/admin-guide/LSM/index.rst | 1 +
MAINTAINERS | 9 +
include/linux/oid_registry.h | 3 +
include/uapi/linux/lsm.h | 1 +
security/Kconfig | 3 +-
security/Makefile | 1 +
security/hornet/Kconfig | 11 ++
security/hornet/Makefile | 7 +
security/hornet/hornet.asn1 | 13 ++
security/hornet/hornet_lsm.c | 201 +++++++++++++++++++++++
11 files changed, 287 insertions(+), 1 deletion(-)
create mode 100644 Documentation/admin-guide/LSM/Hornet.rst
create mode 100644 security/hornet/Kconfig
create mode 100644 security/hornet/Makefile
create mode 100644 security/hornet/hornet.asn1
create mode 100644 security/hornet/hornet_lsm.c
diff --git a/Documentation/admin-guide/LSM/Hornet.rst b/Documentation/admin-guide/LSM/Hornet.rst
new file mode 100644
index 0000000000000..0fb5920e9b68f
--- /dev/null
+++ b/Documentation/admin-guide/LSM/Hornet.rst
@@ -0,0 +1,38 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+======
+Hornet
+======
+
+Hornet is a Linux Security Module that provides extensible signature
+verification for eBPF programs. This is selectable at build-time with
+``CONFIG_SECURITY_HORNET``.
+
+Overview
+========
+
+Hornet addresses concerns from users who require strict audit
+trails and verification guarantees, especially in security-sensitive
+environments. Map hashes for extended verification are passed in via
+the existing PKCS#7 uapi and verifified by the crypto
+subsystem. Hornet then calculates the verification state of the
+program (full, partial, bad, etc) and then invokes a new downstream
+LSM hook to delegate policy decisions.
+
+Tooling
+=======
+
+Some tooling is provided to aid with the development of signed eBPF
+light-skeletons.
+
+extract-skel.sh
+---------------
+
+This shell script extracts the instructions and map data used by the
+light skeleton from the autogenerated header file created by bpftool.
+
+gen_sig
+---------
+
+gen_sig creates a pkcs#7 signature of a data payload. Additionally it
+appends a signed attribute containing a set of hashes.
diff --git a/Documentation/admin-guide/LSM/index.rst b/Documentation/admin-guide/LSM/index.rst
index b44ef68f6e4da..57f6e9fbe5fd1 100644
--- a/Documentation/admin-guide/LSM/index.rst
+++ b/Documentation/admin-guide/LSM/index.rst
@@ -49,3 +49,4 @@ subdirectories.
SafeSetID
ipe
landlock
+ Hornet
diff --git a/MAINTAINERS b/MAINTAINERS
index 3da2c26a796b8..64c9aaff6a219 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11399,6 +11399,15 @@ S: Maintained
F: Documentation/devicetree/bindings/iio/pressure/honeywell,mprls0025pa.yaml
F: drivers/iio/pressure/mprls0025pa*
+HORNET SECURITY MODULE
+M: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
+L: linux-security-module@vger.kernel.org
+S: Supported
+T: git https://github.com/blaiseboscaccy/hornet.git
+F: Documentation/admin-guide/LSM/Hornet.rst
+F: scripts/hornet/
+F: security/hornet/
+
HP BIOSCFG DRIVER
M: Jorge Lopez <jorge.lopez2@hp.com>
L: platform-driver-x86@vger.kernel.org
diff --git a/include/linux/oid_registry.h b/include/linux/oid_registry.h
index 6de479ebbe5da..94e7c1a3fc639 100644
--- a/include/linux/oid_registry.h
+++ b/include/linux/oid_registry.h
@@ -145,6 +145,9 @@ enum OID {
OID_id_rsassa_pkcs1_v1_5_with_sha3_384, /* 2.16.840.1.101.3.4.3.15 */
OID_id_rsassa_pkcs1_v1_5_with_sha3_512, /* 2.16.840.1.101.3.4.3.16 */
+ /* Hornet LSM */
+ OID_hornet_data, /* 2.25.316487325684022475439036912669789383960 */
+
OID__NR
};
diff --git a/include/uapi/linux/lsm.h b/include/uapi/linux/lsm.h
index 938593dfd5daf..2ff9bcdd551e2 100644
--- a/include/uapi/linux/lsm.h
+++ b/include/uapi/linux/lsm.h
@@ -65,6 +65,7 @@ struct lsm_ctx {
#define LSM_ID_IMA 111
#define LSM_ID_EVM 112
#define LSM_ID_IPE 113
+#define LSM_ID_HORNET 114
/*
* LSM_ATTR_XXX definitions identify different LSM attributes
diff --git a/security/Kconfig b/security/Kconfig
index 285f284dfcac4..8cbe314fd9238 100644
--- a/security/Kconfig
+++ b/security/Kconfig
@@ -230,6 +230,7 @@ source "security/safesetid/Kconfig"
source "security/lockdown/Kconfig"
source "security/landlock/Kconfig"
source "security/ipe/Kconfig"
+source "security/hornet/Kconfig"
source "security/integrity/Kconfig"
@@ -274,7 +275,7 @@ config LSM
default "landlock,lockdown,yama,loadpin,safesetid,apparmor,selinux,smack,tomoyo,ipe,bpf" if DEFAULT_SECURITY_APPARMOR
default "landlock,lockdown,yama,loadpin,safesetid,tomoyo,ipe,bpf" if DEFAULT_SECURITY_TOMOYO
default "landlock,lockdown,yama,loadpin,safesetid,ipe,bpf" if DEFAULT_SECURITY_DAC
- default "landlock,lockdown,yama,loadpin,safesetid,selinux,smack,tomoyo,apparmor,ipe,bpf"
+ default "landlock,lockdown,yama,loadpin,safesetid,selinux,smack,tomoyo,apparmor,ipe,hornet,bpf"
help
A comma-separated list of LSMs, in initialization order.
Any LSMs left off this list, except for those with order
diff --git a/security/Makefile b/security/Makefile
index 22ff4c8bd8cec..e24bccd951f88 100644
--- a/security/Makefile
+++ b/security/Makefile
@@ -26,6 +26,7 @@ obj-$(CONFIG_CGROUPS) += device_cgroup.o
obj-$(CONFIG_BPF_LSM) += bpf/
obj-$(CONFIG_SECURITY_LANDLOCK) += landlock/
obj-$(CONFIG_SECURITY_IPE) += ipe/
+obj-$(CONFIG_SECURITY_HORNET) += hornet/
# Object integrity file lists
obj-$(CONFIG_INTEGRITY) += integrity/
diff --git a/security/hornet/Kconfig b/security/hornet/Kconfig
new file mode 100644
index 0000000000000..19406aa237ac6
--- /dev/null
+++ b/security/hornet/Kconfig
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config SECURITY_HORNET
+ bool "Hornet support"
+ depends on SECURITY
+ default n
+ help
+ This selects Hornet.
+ Further information can be found in
+ Documentation/admin-guide/LSM/Hornet.rst.
+
+ If you are unsure how to answer this question, answer N.
diff --git a/security/hornet/Makefile b/security/hornet/Makefile
new file mode 100644
index 0000000000000..342142c5ff8a4
--- /dev/null
+++ b/security/hornet/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_SECURITY_HORNET) := hornet.o
+
+hornet-y := hornet_lsm.o \
+ hornet.asn1.o
+
+$(obj)/hornet.asn1.o: $(obj)/hornet.asn1.c $(obj)/hornet.asn1.h
diff --git a/security/hornet/hornet.asn1 b/security/hornet/hornet.asn1
new file mode 100644
index 0000000000000..c8d47b16b65d7
--- /dev/null
+++ b/security/hornet/hornet.asn1
@@ -0,0 +1,13 @@
+-- SPDX-License-Identifier: BSD-3-Clause
+--
+-- Copyright (C) 2009 IETF Trust and the persons identified as authors
+-- of the code
+--
+-- https://www.rfc-editor.org/rfc/rfc5652#section-3
+
+HornetData ::= SET OF Map
+
+Map ::= SEQUENCE {
+ index INTEGER ({ hornet_map_index }),
+ sha OCTET STRING ({ hornet_map_hash })
+} ({ hornet_next_map })
diff --git a/security/hornet/hornet_lsm.c b/security/hornet/hornet_lsm.c
new file mode 100644
index 0000000000000..a8499ee108ad3
--- /dev/null
+++ b/security/hornet/hornet_lsm.c
@@ -0,0 +1,201 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Hornet Linux Security Module
+ *
+ * Author: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
+ *
+ * Copyright (C) 2025 Microsoft Corporation
+ */
+
+#include <linux/lsm_hooks.h>
+#include <uapi/linux/lsm.h>
+#include <linux/bpf.h>
+#include <linux/verification.h>
+#include <crypto/public_key.h>
+#include <linux/module_signature.h>
+#include <crypto/pkcs7.h>
+#include <linux/sort.h>
+#include <linux/asn1_decoder.h>
+#include <linux/oid_registry.h>
+#include "hornet.asn1.h"
+
+#define MAX_USED_MAPS 64
+
+struct hornet_maps {
+ bpfptr_t fd_array;
+};
+
+struct hornet_parse_context {
+ size_t indexes[MAX_USED_MAPS];
+ bool skips[MAX_USED_MAPS];
+ unsigned char hashes[SHA256_DIGEST_SIZE * MAX_USED_MAPS];
+ int hash_count;
+};
+
+static int hornet_verify_hashes(struct hornet_maps *maps,
+ struct hornet_parse_context *ctx)
+{
+ int map_fd;
+ u32 i;
+ struct bpf_map *map;
+ int err = 0;
+ unsigned char hash[SHA256_DIGEST_SIZE];
+
+ for (i = 0; i < ctx->hash_count; i++) {
+ if (ctx->skips[i])
+ continue;
+
+ err = copy_from_bpfptr_offset(&map_fd, maps->fd_array,
+ ctx->indexes[i] * sizeof(map_fd),
+ sizeof(map_fd));
+ if (err < 0)
+ return LSM_INT_VERDICT_BADSIG;
+
+ CLASS(fd, f)(map_fd);
+ if (fd_empty(f))
+ return LSM_INT_VERDICT_BADSIG;
+ if (unlikely(fd_file(f)->f_op != &bpf_map_fops))
+ return LSM_INT_VERDICT_BADSIG;
+
+ if (!map->frozen)
+ return LSM_INT_VERDICT_BADSIG;
+
+ map = fd_file(f)->private_data;
+ map->ops->map_get_hash(map, SHA256_DIGEST_SIZE, hash);
+
+ err = (memcmp(hash, &ctx->hashes[ctx->indexes[i] * SHA256_DIGEST_SIZE],
+ SHA256_DIGEST_SIZE));
+ if (!err)
+ return LSM_INT_VERDICT_BADSIG;
+ }
+ return LSM_INT_VERDICT_OK;
+}
+
+int hornet_next_map(void *context, size_t hdrlen,
+ unsigned char tag,
+ const void *value, size_t vlen)
+{
+ struct hornet_parse_context *ctx = (struct hornet_parse_context *)value;
+
+ ctx->hash_count++;
+ return 0;
+}
+
+
+int hornet_map_index(void *context, size_t hdrlen,
+ unsigned char tag,
+ const void *value, size_t vlen)
+{
+ struct hornet_parse_context *ctx = (struct hornet_parse_context *)value;
+
+ ctx->hashes[ctx->hash_count] = *(int *)value;
+ return 0;
+}
+
+int hornet_map_hash(void *context, size_t hdrlen,
+ unsigned char tag,
+ const void *value, size_t vlen)
+
+{
+ struct hornet_parse_context *ctx = (struct hornet_parse_context *)value;
+
+ if (vlen != SHA256_DIGEST_SIZE && vlen != 0)
+ return -EINVAL;
+
+ if (vlen != 0) {
+ ctx->skips[ctx->hash_count] = false;
+ memcpy(&ctx->hashes[ctx->hash_count * SHA256_DIGEST_SIZE], value, vlen);
+ } else
+ ctx->skips[ctx->hash_count] = true;
+
+ return 0;
+}
+
+static int hornet_check_program(struct bpf_prog *prog, union bpf_attr *attr,
+ struct bpf_token *token, bool is_kernel)
+{
+ struct hornet_maps maps = {0};
+ bpfptr_t usig = make_bpfptr(attr->signature, is_kernel);
+ struct pkcs7_message *msg;
+ struct hornet_parse_context *ctx;
+ void *sig;
+ int err;
+ const void *authattrs;
+ size_t authattrs_len;
+
+ if (!attr->signature)
+ return LSM_INT_VERDICT_UNSIGNED;
+
+ ctx = kzalloc(sizeof(struct hornet_parse_context), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ maps.fd_array = make_bpfptr(attr->fd_array, is_kernel);
+ sig = kzalloc(attr->signature_size, GFP_KERNEL);
+ if (!sig) {
+ err = -ENOMEM;
+ goto out;
+ }
+ err = copy_from_bpfptr(sig, usig, attr->signature_size);
+ if (err != 0)
+ goto out;
+
+ msg = pkcs7_parse_message(sig, attr->signature_size);
+ if (IS_ERR(msg)) {
+ err = LSM_INT_VERDICT_BADSIG;
+ goto out;
+ }
+
+ if (validate_pkcs7_trust(msg, VERIFY_USE_SECONDARY_KEYRING)) {
+ err = LSM_INT_VERDICT_PARTIALSIG;
+ goto out;
+ }
+ if (pkcs7_get_authattr(msg, OID_hornet_data,
+ &authattrs, &authattrs_len) == -ENODATA) {
+ err = LSM_INT_VERDICT_PARTIALSIG;
+ goto out;
+ }
+
+ err = asn1_ber_decoder(&hornet_decoder, ctx, authattrs, authattrs_len);
+ if (err < 0 || authattrs == NULL) {
+ err = LSM_INT_VERDICT_PARTIALSIG;
+ goto out;
+ }
+ err = hornet_verify_hashes(&maps, ctx);
+out:
+ kfree(ctx);
+ return err;
+}
+
+static const struct lsm_id hornet_lsmid = {
+ .name = "hornet",
+ .id = LSM_ID_HORNET,
+};
+
+static int hornet_bpf_prog_load_integrity(struct bpf_prog *prog, union bpf_attr *attr,
+ struct bpf_token *token, bool is_kernel)
+{
+ int result = hornet_check_program(prog, attr, token, is_kernel);
+
+ if (result < 0)
+ return result;
+
+ return security_bpf_prog_load_post_integrity(prog, attr, token, is_kernel,
+ &hornet_lsmid, result);
+}
+
+static struct security_hook_list hornet_hooks[] __ro_after_init = {
+ LSM_HOOK_INIT(bpf_prog_load_integrity, hornet_bpf_prog_load_integrity),
+};
+
+static int __init hornet_init(void)
+{
+ pr_info("Hornet: eBPF signature verification enabled\n");
+ security_add_hooks(hornet_hooks, ARRAY_SIZE(hornet_hooks), &hornet_lsmid);
+ return 0;
+}
+
+DEFINE_LSM(hornet) = {
+ .name = "hornet",
+ .init = hornet_init,
+};
--
2.52.0
^ permalink raw reply related
* [RFC 07/11] crypto: pkcs7: add tests for pkcs7_get_authattr
From: Blaise Boscaccy @ 2025-12-11 2:12 UTC (permalink / raw)
To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
linux-security-module, linux-doc, linux-kernel, bpf
In-Reply-To: <20251211021257.1208712-1-bboscaccy@linux.microsoft.com>
From: James Bottomley <James.Bottomley@HansenPartnership.com>
Add example code to the test module pkcs7_key_type.c that verifies a
message and then pulls out a known authenticated attribute.
Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
Acked-by: David Howells <dhowells@redhat.com>
---
crypto/asymmetric_keys/pkcs7_key_type.c | 42 ++++++++++++++++++++++++-
1 file changed, 41 insertions(+), 1 deletion(-)
diff --git a/crypto/asymmetric_keys/pkcs7_key_type.c b/crypto/asymmetric_keys/pkcs7_key_type.c
index b930d3bbf1af5..5a1ecb5501b2b 100644
--- a/crypto/asymmetric_keys/pkcs7_key_type.c
+++ b/crypto/asymmetric_keys/pkcs7_key_type.c
@@ -12,6 +12,7 @@
#include <linux/verification.h>
#include <linux/key-type.h>
#include <keys/user-type.h>
+#include <crypto/pkcs7.h>
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("PKCS#7 testing key type");
@@ -51,16 +52,55 @@ static int pkcs7_view_content(void *ctx, const void *data, size_t len,
static int pkcs7_preparse(struct key_preparsed_payload *prep)
{
enum key_being_used_for usage = pkcs7_usage;
+ int ret;
+ struct pkcs7_message *pkcs7;
+ const void *data;
+ size_t len;
if (usage >= NR__KEY_BEING_USED_FOR) {
pr_err("Invalid usage type %d\n", usage);
return -EINVAL;
}
- return verify_pkcs7_signature(NULL, 0,
+ ret = verify_pkcs7_signature(NULL, 0,
prep->data, prep->datalen,
VERIFY_USE_SECONDARY_KEYRING, usage,
pkcs7_view_content, prep);
+ if (ret)
+ return ret;
+
+ pkcs7 = pkcs7_parse_message(prep->data, prep->datalen);
+ if (IS_ERR(pkcs7)) {
+ pr_err("pkcs7 parse error\n");
+ return PTR_ERR(pkcs7);
+ }
+
+ /*
+ * the parsed message has no trusted signer, so nothing should
+ * be returned here
+ */
+ ret = pkcs7_get_authattr(pkcs7, OID_messageDigest, &data, &len);
+ if (ret == 0) {
+ pr_err("OID returned when no trust in signer\n");
+ goto out;
+ }
+ /* add trust and check again */
+ ret = validate_pkcs7_trust(pkcs7, VERIFY_USE_SECONDARY_KEYRING);
+ if (ret) {
+ pr_err("validate_pkcs7_trust failed!!\n");
+ goto out;
+ }
+ /* now we should find the OID */
+ ret = pkcs7_get_authattr(pkcs7, OID_messageDigest, &data, &len);
+ if (ret) {
+ pr_err("Failed to get message digest\n");
+ goto out;
+ }
+ pr_info("Correctly Got message hash, size=%ld\n", len);
+
+ out:
+ pkcs7_free_message(pkcs7);
+ return 0;
}
/*
--
2.52.0
^ permalink raw reply related
* [RFC 06/11] crypto: pkcs7: add ability to extract signed attributes by OID
From: Blaise Boscaccy @ 2025-12-11 2:12 UTC (permalink / raw)
To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
linux-security-module, linux-doc, linux-kernel, bpf
In-Reply-To: <20251211021257.1208712-1-bboscaccy@linux.microsoft.com>
From: James Bottomley <James.Bottomley@HansenPartnership.com>
Signers may add any information they like in signed attributes and
sometimes this information turns out to be relevant to specific
signing cases, so add an api pkcs7_get_authattr() to extract the value
of an authenticated attribute by specific OID. The current
implementation is designed for the single signer use case and simply
terminates the search when it finds the relevant OID.
Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
---
crypto/asymmetric_keys/Makefile | 4 +-
crypto/asymmetric_keys/pkcs7_aa.asn1 | 18 ++++++
crypto/asymmetric_keys/pkcs7_parser.c | 87 +++++++++++++++++++++++++++
include/crypto/pkcs7.h | 4 ++
4 files changed, 112 insertions(+), 1 deletion(-)
create mode 100644 crypto/asymmetric_keys/pkcs7_aa.asn1
diff --git a/crypto/asymmetric_keys/Makefile b/crypto/asymmetric_keys/Makefile
index bc65d3b98dcbf..f99b7169ae7cd 100644
--- a/crypto/asymmetric_keys/Makefile
+++ b/crypto/asymmetric_keys/Makefile
@@ -53,12 +53,14 @@ clean-files += pkcs8.asn1.c pkcs8.asn1.h
obj-$(CONFIG_PKCS7_MESSAGE_PARSER) += pkcs7_message.o
pkcs7_message-y := \
pkcs7.asn1.o \
+ pkcs7_aa.asn1.o \
pkcs7_parser.o \
pkcs7_trust.o \
pkcs7_verify.o
-$(obj)/pkcs7_parser.o: $(obj)/pkcs7.asn1.h
+$(obj)/pkcs7_parser.o: $(obj)/pkcs7.asn1.h $(obj)/pkcs7_aa.asn1.h
$(obj)/pkcs7.asn1.o: $(obj)/pkcs7.asn1.c $(obj)/pkcs7.asn1.h
+$(obj)/pkcs7_aa.asn1.o: $(obj)/pkcs7_aa.asn1.c $(obj)/pkcs7_aa.asn1.h
#
# PKCS#7 parser testing key
diff --git a/crypto/asymmetric_keys/pkcs7_aa.asn1 b/crypto/asymmetric_keys/pkcs7_aa.asn1
new file mode 100644
index 0000000000000..7a8857bdf56e1
--- /dev/null
+++ b/crypto/asymmetric_keys/pkcs7_aa.asn1
@@ -0,0 +1,18 @@
+-- SPDX-License-Identifier: BSD-3-Clause
+--
+-- Copyright (C) 2009 IETF Trust and the persons identified as authors
+-- of the code
+--
+-- https://www.rfc-editor.org/rfc/rfc5652#section-3
+
+AA ::= CHOICE {
+ aaSet [0] IMPLICIT AASet,
+ aaSequence [2] EXPLICIT SEQUENCE OF AuthenticatedAttribute
+}
+
+AASet ::= SET OF AuthenticatedAttribute
+
+AuthenticatedAttribute ::= SEQUENCE {
+ type OBJECT IDENTIFIER ({ pkcs7_aa_note_OID }),
+ values SET OF ANY ({ pkcs7_aa_note_attr })
+}
diff --git a/crypto/asymmetric_keys/pkcs7_parser.c b/crypto/asymmetric_keys/pkcs7_parser.c
index 423d13c475452..55bdcbad70952 100644
--- a/crypto/asymmetric_keys/pkcs7_parser.c
+++ b/crypto/asymmetric_keys/pkcs7_parser.c
@@ -15,6 +15,7 @@
#include <crypto/public_key.h>
#include "pkcs7_parser.h"
#include "pkcs7.asn1.h"
+#include "pkcs7_aa.asn1.h"
MODULE_DESCRIPTION("PKCS#7 parser");
MODULE_AUTHOR("Red Hat, Inc.");
@@ -197,6 +198,92 @@ int pkcs7_get_content_data(const struct pkcs7_message *pkcs7,
}
EXPORT_SYMBOL_GPL(pkcs7_get_content_data);
+struct pkcs7_aa_context {
+ bool found;
+ enum OID oid_to_find;
+ const void *data;
+ size_t len;
+};
+
+int pkcs7_aa_note_OID(void *context, size_t hdrlen,
+ unsigned char tag,
+ const void *value, size_t vlen)
+{
+ struct pkcs7_aa_context *ctx = context;
+ enum OID oid = look_up_OID(value, vlen);
+
+ ctx->found = (oid == ctx->oid_to_find);
+
+ return 0;
+}
+
+int pkcs7_aa_note_attr(void *context, size_t hdrlen,
+ unsigned char tag,
+ const void *value, size_t vlen)
+{
+ struct pkcs7_aa_context *ctx = context;
+
+ if (ctx->found) {
+ ctx->data = value;
+ ctx->len = vlen;
+ }
+
+ return 0;
+}
+
+/**
+ * pkcs7_get_authattr - get authenticated attribute by OID
+ * @pkcs7: The preparsed PKCS#7 message
+ * @oid: the enum value of the OID to find
+ * @_data: Place to return a pointer to the attribute value
+ * @_len: length of the attribute value
+ *
+ * Searches the authenticated attributes until one is found with a
+ * matching OID. Note that because the attributes are per signer
+ * there could be multiple signers with different values, but this
+ * routine will simply return the first one in parse order.
+ *
+ * Returns -ENODATA if the attribute can't be found
+ */
+int pkcs7_get_authattr(const struct pkcs7_message *pkcs7,
+ enum OID oid,
+ const void **_data, size_t *_len)
+{
+ struct pkcs7_signed_info *sinfo = pkcs7->signed_infos;
+ struct pkcs7_aa_context ctx;
+
+ ctx.data = NULL;
+ ctx.oid_to_find = oid;
+
+ for (; sinfo; sinfo = sinfo->next) {
+ int ret;
+
+ /* only extract OIDs from validated signers */
+ if (!sinfo->verified)
+ continue;
+
+ /*
+ * Note: authattrs is missing the initial tag for
+ * digesting reasons. Step one back in the stream to
+ * point to the initial tag for fully formed ASN.1
+ */
+ ret = asn1_ber_decoder(&pkcs7_aa_decoder, &ctx,
+ sinfo->authattrs - 1,
+ sinfo->authattrs_len + 1);
+ if (ret < 0 || ctx.data != NULL)
+ break;
+ }
+
+ if (!ctx.data)
+ return -ENODATA;
+
+ *_data = ctx.data;
+ *_len = ctx.len;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(pkcs7_get_authattr);
+
/*
* Note an OID when we find one for later processing when we know how
* to interpret it.
diff --git a/include/crypto/pkcs7.h b/include/crypto/pkcs7.h
index 38ec7f5f90411..bd83202cd805c 100644
--- a/include/crypto/pkcs7.h
+++ b/include/crypto/pkcs7.h
@@ -25,6 +25,10 @@ extern void pkcs7_free_message(struct pkcs7_message *pkcs7);
extern int pkcs7_get_content_data(const struct pkcs7_message *pkcs7,
const void **_data, size_t *_datalen,
size_t *_headerlen);
+extern int pkcs7_get_authattr(const struct pkcs7_message *pkcs7,
+ enum OID oid,
+ const void **_data, size_t *_len);
+
/*
* pkcs7_trust.c
--
2.52.0
^ permalink raw reply related
* [RFC 05/11] crypto: pkcs7: allow pkcs7_digest() to be called from pkcs7_trust
From: Blaise Boscaccy @ 2025-12-11 2:12 UTC (permalink / raw)
To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
linux-security-module, linux-doc, linux-kernel, bpf
In-Reply-To: <20251211021257.1208712-1-bboscaccy@linux.microsoft.com>
From: James Bottomley <James.Bottomley@HansenPartnership.com>
Trying to run pkcs7_validate_trust() on something that parsed
correctly but is not verified doesn't work because the signature
digest hasn't been calculated. Fix this by adding a digest calclation
in to pkcs7_validate_one(). This is almost a nop if the digest exists.
Additionally, the trust validation doesn't know the data payload, so
adjust the digest calculator to skip checking the data digest if
pkcs7->data is NULL. A check is added in pkcs7_verify() for
pkcs7->data being null (returning -EBADMSG) to guard against someone
forgetting to supply data and getting an invalid success return.
Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
---
crypto/asymmetric_keys/pkcs7_parser.h | 3 +++
crypto/asymmetric_keys/pkcs7_trust.c | 8 ++++++++
crypto/asymmetric_keys/pkcs7_verify.c | 13 +++++++++----
3 files changed, 20 insertions(+), 4 deletions(-)
diff --git a/crypto/asymmetric_keys/pkcs7_parser.h b/crypto/asymmetric_keys/pkcs7_parser.h
index 344340cfa6c13..179cd1cdbe22d 100644
--- a/crypto/asymmetric_keys/pkcs7_parser.h
+++ b/crypto/asymmetric_keys/pkcs7_parser.h
@@ -63,3 +63,6 @@ struct pkcs7_message {
size_t data_hdrlen; /* Length of Data ASN.1 header */
const void *data; /* Content Data (or 0) */
};
+
+int pkcs7_digest(struct pkcs7_message *pkcs7,
+ struct pkcs7_signed_info *sinfo);
diff --git a/crypto/asymmetric_keys/pkcs7_trust.c b/crypto/asymmetric_keys/pkcs7_trust.c
index 78ebfb6373b61..7cb0a6bc7b32e 100644
--- a/crypto/asymmetric_keys/pkcs7_trust.c
+++ b/crypto/asymmetric_keys/pkcs7_trust.c
@@ -30,6 +30,14 @@ static int pkcs7_validate_trust_one(struct pkcs7_message *pkcs7,
kenter(",%u,", sinfo->index);
+ /*
+ * if we're being called immediately after parse, the
+ * signature won't have a calculated digest yet, so calculate
+ * one. This function returns immediately if a digest has
+ * already been calculated
+ */
+ pkcs7_digest(pkcs7, sinfo);
+
if (sinfo->unsupported_crypto) {
kleave(" = -ENOPKG [cached]");
return -ENOPKG;
diff --git a/crypto/asymmetric_keys/pkcs7_verify.c b/crypto/asymmetric_keys/pkcs7_verify.c
index 6d6475e3a9bf2..19b3999381e6f 100644
--- a/crypto/asymmetric_keys/pkcs7_verify.c
+++ b/crypto/asymmetric_keys/pkcs7_verify.c
@@ -19,8 +19,8 @@
/*
* Digest the relevant parts of the PKCS#7 data
*/
-static int pkcs7_digest(struct pkcs7_message *pkcs7,
- struct pkcs7_signed_info *sinfo)
+int pkcs7_digest(struct pkcs7_message *pkcs7,
+ struct pkcs7_signed_info *sinfo)
{
struct public_key_signature *sig = sinfo->sig;
struct crypto_shash *tfm;
@@ -85,8 +85,8 @@ static int pkcs7_digest(struct pkcs7_message *pkcs7,
goto error;
}
- if (memcmp(sig->digest, sinfo->msgdigest,
- sinfo->msgdigest_len) != 0) {
+ if (pkcs7->data && memcmp(sig->digest, sinfo->msgdigest,
+ sinfo->msgdigest_len) != 0) {
pr_warn("Sig %u: Message digest doesn't match\n",
sinfo->index);
ret = -EKEYREJECTED;
@@ -439,6 +439,11 @@ int pkcs7_verify(struct pkcs7_message *pkcs7,
return -EINVAL;
}
+ if (!pkcs7->data) {
+ pr_warn("Data not supplied to verify operation\n");
+ return -EBADMSG;
+ }
+
for (sinfo = pkcs7->signed_infos; sinfo; sinfo = sinfo->next) {
ret = pkcs7_verify_one(pkcs7, sinfo);
if (sinfo->blacklisted) {
--
2.52.0
^ permalink raw reply related
* [RFC 04/11] crypto: pkcs7: add flag for validated trust on a signed info block
From: Blaise Boscaccy @ 2025-12-11 2:11 UTC (permalink / raw)
To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
linux-security-module, linux-doc, linux-kernel, bpf
In-Reply-To: <20251211021257.1208712-1-bboscaccy@linux.microsoft.com>
From: James Bottomley <James.Bottomley@HansenPartnership.com>
Allow consumers of struct pkcs7_message to tell if any of the sinfo
fields has passed a trust validation. Note that this does not happen
in parsing, pkcs7_validate_trust() must be explicitly called or called
via validate_pkcs7_trust().
Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
---
crypto/asymmetric_keys/pkcs7_parser.h | 1 +
crypto/asymmetric_keys/pkcs7_trust.c | 1 +
2 files changed, 2 insertions(+)
diff --git a/crypto/asymmetric_keys/pkcs7_parser.h b/crypto/asymmetric_keys/pkcs7_parser.h
index e17f7ce4fb434..344340cfa6c13 100644
--- a/crypto/asymmetric_keys/pkcs7_parser.h
+++ b/crypto/asymmetric_keys/pkcs7_parser.h
@@ -20,6 +20,7 @@ struct pkcs7_signed_info {
unsigned index;
bool unsupported_crypto; /* T if not usable due to missing crypto */
bool blacklisted;
+ bool verified; /* T if this signer has validated trust */
/* Message digest - the digest of the Content Data (or NULL) */
const void *msgdigest;
diff --git a/crypto/asymmetric_keys/pkcs7_trust.c b/crypto/asymmetric_keys/pkcs7_trust.c
index 9a87c34ed1733..78ebfb6373b61 100644
--- a/crypto/asymmetric_keys/pkcs7_trust.c
+++ b/crypto/asymmetric_keys/pkcs7_trust.c
@@ -127,6 +127,7 @@ static int pkcs7_validate_trust_one(struct pkcs7_message *pkcs7,
for (p = sinfo->signer; p != x509; p = p->signer)
p->verified = true;
}
+ sinfo->verified = true;
kleave(" = 0");
return 0;
}
--
2.52.0
^ permalink raw reply related
* [RFC 03/11] certs: break out pkcs7 check into its own function
From: Blaise Boscaccy @ 2025-12-11 2:11 UTC (permalink / raw)
To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
linux-security-module, linux-doc, linux-kernel, bpf
In-Reply-To: <20251211021257.1208712-1-bboscaccy@linux.microsoft.com>
From: James Bottomley <James.Bottomley@HansenPartnership.com>
Add new validate_pkcs7_trust() function which can operate on the
system keyrings and is simply some of the innards of
verify_pkcs7_message_sig().
Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
---
certs/system_keyring.c | 76 +++++++++++++++++++++---------------
include/linux/verification.h | 2 +
2 files changed, 47 insertions(+), 31 deletions(-)
diff --git a/certs/system_keyring.c b/certs/system_keyring.c
index 9de610bf1f4b2..807ab4a6fc7ea 100644
--- a/certs/system_keyring.c
+++ b/certs/system_keyring.c
@@ -298,42 +298,19 @@ late_initcall(load_system_certificate_list);
#ifdef CONFIG_SYSTEM_DATA_VERIFICATION
/**
- * verify_pkcs7_message_sig - Verify a PKCS#7-based signature on system data.
- * @data: The data to be verified (NULL if expecting internal data).
- * @len: Size of @data.
+ * validate_pkcs7_trust - add trust markers based on keyring
* @pkcs7: The PKCS#7 message that is the signature.
* @trusted_keys: Trusted keys to use (NULL for builtin trusted keys only,
* (void *)1UL for all trusted keys).
- * @usage: The use to which the key is being put.
- * @view_content: Callback to gain access to content.
- * @ctx: Context for callback.
*/
-int verify_pkcs7_message_sig(const void *data, size_t len,
- struct pkcs7_message *pkcs7,
- struct key *trusted_keys,
- enum key_being_used_for usage,
- int (*view_content)(void *ctx,
- const void *data, size_t len,
- size_t asn1hdrlen),
- void *ctx)
+int validate_pkcs7_trust(struct pkcs7_message *pkcs7, struct key *trusted_keys)
{
int ret;
- /* The data should be detached - so we need to supply it. */
- if (data && pkcs7_supply_detached_data(pkcs7, data, len) < 0) {
- pr_err("PKCS#7 signature with non-detached data\n");
- ret = -EBADMSG;
- goto error;
- }
-
- ret = pkcs7_verify(pkcs7, usage);
- if (ret < 0)
- goto error;
-
ret = is_key_on_revocation_list(pkcs7);
if (ret != -ENOKEY) {
pr_devel("PKCS#7 key is on revocation list\n");
- goto error;
+ return ret;
}
if (!trusted_keys) {
@@ -351,18 +328,55 @@ int verify_pkcs7_message_sig(const void *data, size_t len,
trusted_keys = NULL;
#endif
if (!trusted_keys) {
- ret = -ENOKEY;
pr_devel("PKCS#7 platform keyring is not available\n");
- goto error;
+ return -ENOKEY;
}
}
ret = pkcs7_validate_trust(pkcs7, trusted_keys);
- if (ret < 0) {
- if (ret == -ENOKEY)
- pr_devel("PKCS#7 signature not signed with a trusted key\n");
+ if (ret == -ENOKEY)
+ pr_devel("PKCS#7 signature not signed with a trusted key\n");
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(validate_pkcs7_trust);
+
+/**
+ * verify_pkcs7_message_sig - Verify a PKCS#7-based signature on system data.
+ * @data: The data to be verified (NULL if expecting internal data).
+ * @len: Size of @data.
+ * @pkcs7: The PKCS#7 message that is the signature.
+ * @trusted_keys: Trusted keys to use (NULL for builtin trusted keys only,
+ * (void *)1UL for all trusted keys).
+ * @usage: The use to which the key is being put.
+ * @view_content: Callback to gain access to content.
+ * @ctx: Context for callback.
+ */
+int verify_pkcs7_message_sig(const void *data, size_t len,
+ struct pkcs7_message *pkcs7,
+ struct key *trusted_keys,
+ enum key_being_used_for usage,
+ int (*view_content)(void *ctx,
+ const void *data, size_t len,
+ size_t asn1hdrlen),
+ void *ctx)
+{
+ int ret;
+
+ /* The data should be detached - so we need to supply it. */
+ if (data && pkcs7_supply_detached_data(pkcs7, data, len) < 0) {
+ pr_err("PKCS#7 signature with non-detached data\n");
+ ret = -EBADMSG;
goto error;
}
+ ret = pkcs7_verify(pkcs7, usage);
+ if (ret < 0)
+ goto error;
+
+ ret = validate_pkcs7_trust(pkcs7, trusted_keys);
+ if (ret < 0)
+ goto error;
+
if (view_content) {
size_t asn1hdrlen;
diff --git a/include/linux/verification.h b/include/linux/verification.h
index dec7f2beabfd4..57f1460d36f13 100644
--- a/include/linux/verification.h
+++ b/include/linux/verification.h
@@ -44,6 +44,8 @@ enum key_being_used_for {
struct key;
struct pkcs7_message;
+extern int validate_pkcs7_trust(struct pkcs7_message *pkcs7,
+ struct key *trusted_keys);
extern int verify_pkcs7_signature(const void *data, size_t len,
const void *raw_pkcs7, size_t pkcs7_len,
struct key *trusted_keys,
--
2.52.0
^ permalink raw reply related
* [RFC 02/11] oid_registry: allow arbitrary size OIDs
From: Blaise Boscaccy @ 2025-12-11 2:11 UTC (permalink / raw)
To: Blaise Boscaccy, Jonathan Corbet, Paul Moore, James Morris,
Serge E. Hallyn, Mickaël Salaün, Günther Noack,
Dr. David Alan Gilbert, Andrew Morton, James.Bottomley, dhowells,
linux-security-module, linux-doc, linux-kernel, bpf
In-Reply-To: <20251211021257.1208712-1-bboscaccy@linux.microsoft.com>
From: James Bottomley <James.Bottomley@HansenPartnership.com>
The current OID registry parser uses 64 bit arithmetic which limits us
to supporting 64 bit or smaller OIDs. This isn't usually a problem
except that it prevents us from representing the 2.25. prefix OIDs
which are the OID representation of UUIDs and have a 128 bit number
following the prefix. Rather than import not often used perl
arithmetic modules, replace the current perl 64 bit arithmetic with a
callout to bc, which is arbitrary precision, for decimal to base 2
conversion, then do pure string operations on the base 2 number.
Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
---
lib/build_OID_registry | 26 ++++++++++++++++++--------
1 file changed, 18 insertions(+), 8 deletions(-)
diff --git a/lib/build_OID_registry b/lib/build_OID_registry
index 8267e8d71338b..30493ac190c0c 100755
--- a/lib/build_OID_registry
+++ b/lib/build_OID_registry
@@ -60,10 +60,12 @@ for (my $i = 0; $i <= $#names; $i++) {
# Determine the encoded length of this OID
my $size = $#components;
for (my $loop = 2; $loop <= $#components; $loop++) {
- my $c = $components[$loop];
+ $ENV{'BC_LINE_LENGTH'} = "0";
+ my $c = `echo "ibase=10; obase=2; $components[$loop]" | bc`;
+ chomp($c);
# We will base128 encode the number
- my $tmp = ($c == 0) ? 0 : int(log($c)/log(2));
+ my $tmp = length($c) - 1;
$tmp = int($tmp / 7);
$size += $tmp;
}
@@ -100,16 +102,24 @@ for (my $i = 0; $i <= $#names; $i++) {
push @octets, $components[0] * 40 + $components[1];
for (my $loop = 2; $loop <= $#components; $loop++) {
- my $c = $components[$loop];
+ # get the base 2 representation of the component
+ $ENV{'BC_LINE_LENGTH'} = "0";
+ my $c = `echo "ibase=10; obase=2; $components[$loop]" | bc`;
+ chomp($c);
- # Base128 encode the number
- my $tmp = ($c == 0) ? 0 : int(log($c)/log(2));
+ my $tmp = length($c) - 1;
$tmp = int($tmp / 7);
- for (; $tmp > 0; $tmp--) {
- push @octets, (($c >> $tmp * 7) & 0x7f) | 0x80;
+ # zero pad upto length multiple of 7
+ $c = substr("0000000", 0, ($tmp + 1) * 7 - length($c)).$c;
+
+ # Base128 encode the number
+ for (my $j = 0; $j < $tmp; $j++) {
+ my $b = oct("0b".substr($c, $j * 7, 7));
+
+ push @octets, $b | 0x80;
}
- push @octets, $c & 0x7f;
+ push @octets, oct("0b".substr($c, $tmp * 7, 7));
}
push @encoded_oids, \@octets;
--
2.52.0
^ permalink raw reply related
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