* Re: [PATCH 04/14] security/Kconfig.hardening: Remove tautological condition from CC_HAS_RANDSTRUCT
From: Nicolas Schier @ 2026-05-05 15:28 UTC (permalink / raw)
To: Nathan Chancellor
Cc: Bill Wendling, Justin Stitt, Nick Desaulniers, linux-kernel, llvm,
linux-kbuild, Kees Cook, Gustavo A. R. Silva, linux-hardening,
linux-security-module
In-Reply-To: <20260428-bump-minimum-supported-llvm-version-to-17-v1-4-81d9b2e8ee75@kernel.org>
On Tue, Apr 28, 2026 at 10:59:10PM -0400, Nathan Chancellor wrote:
> Now that the minimum supported version of LLVM for building the kernel
> has been raised to 17.0.1, the '!Clang || Clang >= 16' dependency for
> CONFIG_CC_HAS_RANDSTRUCT is always true, so it can be removed.
>
> Signed-off-by: Nathan Chancellor <nathan@kernel.org>
> ---
> Cc: Kees Cook <kees@kernel.org>
> Cc: Gustavo A. R. Silva <gustavoars@kernel.org>
> Cc: linux-hardening@vger.kernel.org
> Cc: linux-security-module@vger.kernel.org
> ---
> security/Kconfig.hardening | 3 ---
> 1 file changed, 3 deletions(-)
>
> diff --git a/security/Kconfig.hardening b/security/Kconfig.hardening
> index e4f23c08a17a..b90cf9ed4642 100644
> --- a/security/Kconfig.hardening
> +++ b/security/Kconfig.hardening
> @@ -274,9 +274,6 @@ endmenu
>
> config CC_HAS_RANDSTRUCT
> def_bool $(cc-option,-frandomize-layout-seed-file=/dev/null)
> - # Randstruct was first added in Clang 15, but it isn't safe to use until
> - # Clang 16 due to https://github.com/llvm/llvm-project/issues/60349
> - depends on !CC_IS_CLANG || CLANG_VERSION >= 160000
>
> choice
> prompt "Randomize layout of sensitive kernel structures"
>
Reviewed-by: Nicolas Schier <nsc@kernel.org>
^ permalink raw reply
* Re: [PATCH 03/14] security/Kconfig.hardening: Remove tautological condition from FORTIFY_SOURCE
From: Nicolas Schier @ 2026-05-05 15:28 UTC (permalink / raw)
To: Nathan Chancellor
Cc: Bill Wendling, Justin Stitt, Nick Desaulniers, linux-kernel, llvm,
linux-kbuild, Kees Cook, Gustavo A. R. Silva, linux-hardening,
linux-security-module
In-Reply-To: <20260428-bump-minimum-supported-llvm-version-to-17-v1-3-81d9b2e8ee75@kernel.org>
On Tue, Apr 28, 2026 at 10:59:09PM -0400, Nathan Chancellor wrote:
> Now that the minimum supported version of LLVM for building the kernel
> has been raised to 17.0.1, the '!X86_32 || !Clang || Clang > 16'
> dependency of CONFIG_FORTIFY_SOURCE is always true, so it can be
> removed.
>
> Signed-off-by: Nathan Chancellor <nathan@kernel.org>
> ---
> Cc: Kees Cook <kees@kernel.org>
> Cc: Gustavo A. R. Silva <gustavoars@kernel.org>
> Cc: linux-hardening@vger.kernel.org
> Cc: linux-security-module@vger.kernel.org
> ---
> security/Kconfig.hardening | 2 --
> 1 file changed, 2 deletions(-)
>
Reviewed-by: Nicolas Schier <nsc@kernel.org>
^ permalink raw reply
* Re: [PATCH 02/14] security/Kconfig.hardening: Remove tautological condition from CC_HAS_ZERO_CALL_USED_REGS
From: Nicolas Schier @ 2026-05-05 15:28 UTC (permalink / raw)
To: Nathan Chancellor
Cc: Bill Wendling, Justin Stitt, Nick Desaulniers, linux-kernel, llvm,
linux-kbuild, Kees Cook, Gustavo A. R. Silva, linux-hardening,
linux-security-module
In-Reply-To: <20260428-bump-minimum-supported-llvm-version-to-17-v1-2-81d9b2e8ee75@kernel.org>
On Tue, Apr 28, 2026 at 10:59:08PM -0400, Nathan Chancellor wrote:
> Now that the minimum supported version of LLVM for building the kernel
> has been raised to 17.0.1, the '!Clang || Clang > 15.0.6' dependency for
> CONFIG_CC_HAS_ZERO_CALL_USED_REGS is always true, so it can be removed.
>
> Signed-off-by: Nathan Chancellor <nathan@kernel.org>
> ---
> Cc: Kees Cook <kees@kernel.org>
> Cc: Gustavo A. R. Silva <gustavoars@kernel.org>
> Cc: linux-hardening@vger.kernel.org
> Cc: linux-security-module@vger.kernel.org
> ---
> security/Kconfig.hardening | 3 ---
> 1 file changed, 3 deletions(-)
>
Reviewed-by: Nicolas Schier <nsc@kernel.org>
^ permalink raw reply
* Re: [RFC PATCH 2/3] firmware: arm_ffa: initialise ff-a after finalising pKVM initialisation
From: Yeoreum Yun @ 2026-05-05 15:06 UTC (permalink / raw)
To: Sudeep Holla
Cc: linux-integrity, keyrings, linux-security-module, linux-kernel,
linux-arm-kernel, kvmarm, jarkko, zohar, roberto.sassu,
dmitry.kasatkin, eric.snowberg, paul, jmorris, serge, maz, oupton,
joey.gouly, suzuki.poulose, yuzenghui, catalin.marinas, will
In-Reply-To: <20260505-super-gecko-of-argument-655030@sudeepholla>
Hi Sudeep,
> On Tue, May 05, 2026 at 10:54:08AM +0100, Yeoreum Yun wrote:
> > When pKVM is enabled, the FF-A driver must be initialised after pKVM.
> > Otherwise, pKVM cannot negotiate the FF-A version or obtain the RX/TX
> > buffer information, leading to failures in FF-A calls.
> >
> > Currently, pKVM initialisation completes at device_initcall_sync,
> > while ffa_init() runs at the device_initcall level.
> >
> > So far, linker deployes kvm_arm_init() before ffa_init(), and SMCs can
> > still be trapped even before finalise_pkvm() is invoked.
> > As a result, this issue has not been observed.
> >
> > However, relying on above stuff is fragile.
> > Therefore, when pKVM is enabled, the FF-A infrastructure should be
> > initialised only after pKVM initialisation has been fully finalised.
> >
> > To achieve this, introduce an ffa_root_dev ("arm-ffa") and
> > a corresponding driver to defer initialisation of the FF-A infrastructure
> > until pKVM initialisation is complete, and to defer probing of all FF-A devices until then
> > when pKVM is enabled.
> >
>
> I don't like this whole ffa root device design.
> Two question for now:
> 1. Can FF-A be a module on systems with pKVM which removes the need for all
> this dance done here ?
But this means we reduce the other feature e.x) IMA with TPM over
FF-A and pKVM feature. Since IMA must be a built-in, we couldn't avoid
to build FF-A driver with built-in.
> 2. If it is a requirement to have this built-in, I prefer to add a probe
> and defer it instead of this root ffa device.
But, How? anyway all of FF-A device & driver couldn't be probed unless
FF-A initialisation is finished and How can we trigger FF-A initailise
after pKVM finish its initialisation?
The problem is ff-a intiailisation happens before pKVM finish its
initailasation and to do defer probe, it should use dd-model and
As we discussed in other thread, notifier couldn't be a soluation.
Could you let me share other way I'm missing?
Thanks!
> --
> Regards,
> Sudeep
--
Sincerely,
Yeoreum Yun
^ permalink raw reply
* Re: [RFC PATCH 2/3] firmware: arm_ffa: initialise ff-a after finalising pKVM initialisation
From: Sudeep Holla @ 2026-05-05 14:39 UTC (permalink / raw)
To: Yeoreum Yun
Cc: linux-integrity, keyrings, Sudeep Holla, linux-security-module,
linux-kernel, linux-arm-kernel, kvmarm, jarkko, zohar,
roberto.sassu, dmitry.kasatkin, eric.snowberg, paul, jmorris,
serge, maz, oupton, joey.gouly, suzuki.poulose, yuzenghui,
catalin.marinas, will
In-Reply-To: <20260505095409.1948371-3-yeoreum.yun@arm.com>
On Tue, May 05, 2026 at 10:54:08AM +0100, Yeoreum Yun wrote:
> When pKVM is enabled, the FF-A driver must be initialised after pKVM.
> Otherwise, pKVM cannot negotiate the FF-A version or obtain the RX/TX
> buffer information, leading to failures in FF-A calls.
>
> Currently, pKVM initialisation completes at device_initcall_sync,
> while ffa_init() runs at the device_initcall level.
>
> So far, linker deployes kvm_arm_init() before ffa_init(), and SMCs can
> still be trapped even before finalise_pkvm() is invoked.
> As a result, this issue has not been observed.
>
> However, relying on above stuff is fragile.
> Therefore, when pKVM is enabled, the FF-A infrastructure should be
> initialised only after pKVM initialisation has been fully finalised.
>
> To achieve this, introduce an ffa_root_dev ("arm-ffa") and
> a corresponding driver to defer initialisation of the FF-A infrastructure
> until pKVM initialisation is complete, and to defer probing of all FF-A devices until then
> when pKVM is enabled.
>
I don't like this whole ffa root device design.
Two question for now:
1. Can FF-A be a module on systems with pKVM which removes the need for all
this dance done here ?
2. If it is a requirement to have this built-in, I prefer to add a probe
and defer it instead of this root ffa device.
--
Regards,
Sudeep
^ permalink raw reply
* Re: [PATCH v2 1/2] bpf: add bpf_init_inode_xattr kfunc for atomic inode labeling
From: Paul Moore @ 2026-05-05 13:49 UTC (permalink / raw)
To: Song Liu
Cc: David Windsor, Alexander Viro, Christian Brauner,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, KP Singh,
Matt Bobrowski, James Morris, Serge E. Hallyn, Mimi Zohar,
Roberto Sassu, Dmitry Kasatkin, Stephen Smalley, Casey Schaufler,
Jan Kara, John Fastabend, Martin KaFai Lau, Yonghong Song,
Jiri Olsa, Eric Snowberg, Ondrej Mosnacek, linux-fsdevel,
linux-kernel, bpf, linux-security-module, linux-integrity,
selinux
In-Reply-To: <CAPhsuW5zRktottTujy_O1=8VkdJGDO71M3DVVM4ezffwT_dm+A@mail.gmail.com>
On Tue, May 5, 2026 at 5:00 AM Song Liu <song@kernel.org> wrote:
> On Tue, May 5, 2026 at 4:02 AM Paul Moore <paul@paul-moore.com> wrote:
> > On Mon, May 4, 2026 at 7:09 PM Song Liu <song@kernel.org> wrote:
> > > On Tue, May 5, 2026 at 12:42 AM Paul Moore <paul@paul-moore.com> wrote:
> > > [...]
> > > > > > Perhaps I'm simply not seeing it, but is there a check to ensure that
> > > > > > there is only one BPF LSM calling into security_inode_init_security()
> > > > > > at any given time? With the BPF LSM only reserving a single xattr
> > > > > > slot, multiple loaded BPF LSM programs providing
> > > > > > security_inode_init_security() callbacks will be a problem.
> > > > >
> > > > > I don't think there is such a check. Also, a single BPF LSM function
> > > > > may call the kfunc multiple times, which is also problematic.
> > > > >
> > > > > I think we will need to make the default bigger, and also introduce
> > > > > some realloc mechanism for the worst case scenario. This should
> > > > > work, but the code might be a bit messy.
> > > >
> > > > Thanks for the clarification, that is what I was afraid of when
> > > > looking at the code, but I was hoping I was just missing it.
> > > >
> > > > Increasing the default is an option, but I don't think we want to
> > > > support a dynamic reallocation scheme for the xattr slots, that will
> > > > likely get extremely messy with synchronization between the LSM
> > > > framework and BPF LSM hook registrations as well as special code to
> > > > handle inodes with lifetimes that are disjoint from the BPF LSM
> > > > programs ... I suppose there may be a way to do it, but it will surely
> > > > be ugly and come at a cost.
> > >
> > > BPF trampoline already handles all the synchronizations, such as
> > > add hook, remove hook, etc. properly. So this is not that hard.
> >
> > How do you plan to handle the issue of disjoint lifetimes?
> >
> > > All we really need is to allocate a new array, copy pointers, and free
> > > the old array. And we only really need this in the worst case
> > > scenarios.
> >
> > Oh, is that all? :D
> >
> > Keep in mind that the code must also handle arbitrary ordering of
> > LSMs; in other words, you must handle a BPF LSM that isn't at the end
> > of the LSM order. While a BPF LSM at the end of the LSM list is the
> > most common, and recommended ordering for the vast majority of users,
> > we've been working to make the ordering as generalized as possible.
>
> All the BPF LSM hooks are called together, so it should be fine.
> Maybe I missed some corner cases.
I was thinking about the LSM framework as a whole, not just the
potential for multiple BPF programs attached to the BPF LSM.
> Either way, I agree with David that we don't need too many xattrs.
> Since BPF LSM is reserved to the privileged users only, it is safe
> to put a reasonable limit, say 4 or 8, and do not handle the realloc.
> If the admin would like to brick a system with BPF LSM, there are
> many other ways to do it.
That is definitely the easier route. However, please add code to
ensure the number of attached BPF programs is limited to the number of
requested slots.
--
paul-moore.com
^ permalink raw reply
* Re: [RFC PATCH 0/3] initalise ff-a after finalising pKVM
From: Yeoreum Yun @ 2026-05-05 11:33 UTC (permalink / raw)
To: Ben Horgan
Cc: linux-integrity, keyrings, linux-security-module, linux-kernel,
linux-arm-kernel, kvmarm, jarkko, zohar, roberto.sassu,
dmitry.kasatkin, eric.snowberg, paul, jmorris, serge, maz, oupton,
joey.gouly, suzuki.poulose, yuzenghui, catalin.marinas, will,
sudeep.holla
In-Reply-To: <9dd2b09b-cfb1-40e5-9fdd-1e004ad784c0@arm.com>
> Hi Levi,
>
> On 5/5/26 12:16, Yeoreum Yun wrote:
> >> Hi Ben,
> >>
> >>> Hi Levi,
> >>>
> >>> On 5/5/26 10:54, Yeoreum Yun wrote:
> >>>> This patch is split out from the patchset [0] --
> >>>> fix FF-A call failure with pKVM when the FF-A driver is built-in,
> >>>> specifically the IMA-related part.
> >>>>
> >>>> When pKVM is enabled, the FF-A driver must be initialised after pKVM.
> >>>> Otherwise, pKVM cannot negotiate the FF-A version or obtain the RX/TX
> >>>> buffer information, leading to failures in FF-A calls.
> >>>>
> >>>> Currently, pKVM initialisation completes at device_initcall_sync,
> >>>> while ffa_init() runs at the device_initcall level.
> >>>>
> >>>> So far, linker deployes kvm_arm_init() before ffa_init(), and SMCs can
> >>>> still be trapped even before finalise_pkvm() is invoked.
> >>>> As a result, this issue has not been observed.
> >>>>
> >>>> However, relying on above stuff is fragile.
> >>>> Therefore, when pKVM is enabled, the FF-A infrastructure should be
> >>>> initialised only after pKVM initialisation has been fully finalised.
> >>>>
> >>>> To achieve this, introduce an ffa_root_dev ("arm-ffa") and
> >>>> a corresponding driver to defer initialisation of the FF-A infrastructure
> >>>> until pKVM initialisation is complete, and to defer probing of all FF-A devices until then
> >>>> when pKVM is enabled.
> >>>>
> >>>> This patch is based on v7.1-rc2
> >>>>
> >>>> Question:
> >>>>
> >>>> FF-A initialisation can occur at late_initcall. Because it may be deferred,
> >>>> some FF-A requests cannot be serviced at that stage.
> >>>> A typical example is the EFI runtime variable service using DIRECT_MSG_REQ.
> >>>>
> >>>> Depending on the platform, the EFI runtime variable service runs with StandaloneMm
> >>>> and uses FF-A DIRECT_REQ. However, when pKVM is enabled, FF-A initialisation
> >>>> may be deferred to late_initcall. In this case, load_uefi_certs()
> >>>> can fail if it is invoked before the FF-A driver is initialised
> >>>> via deferred_probe_initcall().
> >>>>
> >>>> Moving load_uefi_certs() to late_initcall_sync, as in the third patch,
> >>>> seems not to have any problem since late_initcall and
> >>>> late_initcall_sync are both of do_basic_setup() and it's before loading
> >>>> init process. However, it is still unclear whether
> >>>> it would be better to allow DIRECT_MSG_REQ in kvm_host_ffa_handler()
> >>>
> >>> The spec doesn't allow this. Looking at DEN0077A 1.2 REL0:
> >>>
> >>> Section 13.2.2 says:
> >>>
> >>> "If they are compatible, it enables them to determine which Framework functionalities can be used. Hence, negotiation of
> >>> the version must happen before an invocation of any other FF-A ABI."
> >>>
> >>> and a bit further down
> >>>
> >>> "Once the caller invokes any FF-A ABI other than FFA_VERSION, the version negotiation phase is complete."
> >>>
> >>> I would have thought that an SP would only go into the waiting state once the version negotiation is done.
> >>
> >> I mean the negotiation between hypervisor and ff-a driver.
> >> actually the version negotiation is done with SPMC in
> >> hyp_ffa_init() but the negotiaion between hypervisor and ff-a driver
> >> just choose the lower version between version requested from ff-a driver
> >> and negotiated version with hypervisor and SPMC.
> >
> > Sorry. re-parse the word, not choose "re-negotiate" when
> > FF-A driver request lowever version.
> >
> >>
> >> So, the version negotiation is already done with SPMC
> >> but with FF-A driver with hypervisor is not yet.
> >> However, DIRECT_MSG_REQ has supported from v1.0
> >> In this situation, is there any reason not to send DIRECT_REQ_MSG?
> >
> > IOW, question is that some of ff-a request can be allowed
> > before version negotiation with FF-A driver but
> > using negotiated version via hyp_ffa_init() first or not.
>
> I don't think so. Isn't it more a continuation of the negotiation rather than a re-negotiation?
Might be. However, in the case I mentioned, I’m asking because
it’s somewhat unusual in that the FF-A request occurs without an “FF-A driver.”
If the FF-A request goes through the FF-A driver, then as you said,
it can reasonably be considered a continuation of the negotiation.
But in this case, I was wondering whether it would be acceptable to
introduce additional exception handling for situations
where an FF-A request occurs without the FF-A driver.
From that perspective, even if the FF-A request does not go through
the FF-A driver, it would ultimately still have to wait until
the FF-A driver initialization is complete.
So my question was whether certain operations could be handled
as exceptions in such cases.
Thanks.
--
Sincerely,
Yeoreum Yun
^ permalink raw reply
* Re: [RFC PATCH 0/3] initalise ff-a after finalising pKVM
From: Ben Horgan @ 2026-05-05 11:24 UTC (permalink / raw)
To: Yeoreum Yun
Cc: linux-integrity, keyrings, linux-security-module, linux-kernel,
linux-arm-kernel, kvmarm, jarkko, zohar, roberto.sassu,
dmitry.kasatkin, eric.snowberg, paul, jmorris, serge, maz, oupton,
joey.gouly, suzuki.poulose, yuzenghui, catalin.marinas, will,
sudeep.holla
In-Reply-To: <afnRptWilWr7nABJ@e129823.arm.com>
Hi Levi,
On 5/5/26 12:16, Yeoreum Yun wrote:
>> Hi Ben,
>>
>>> Hi Levi,
>>>
>>> On 5/5/26 10:54, Yeoreum Yun wrote:
>>>> This patch is split out from the patchset [0] --
>>>> fix FF-A call failure with pKVM when the FF-A driver is built-in,
>>>> specifically the IMA-related part.
>>>>
>>>> When pKVM is enabled, the FF-A driver must be initialised after pKVM.
>>>> Otherwise, pKVM cannot negotiate the FF-A version or obtain the RX/TX
>>>> buffer information, leading to failures in FF-A calls.
>>>>
>>>> Currently, pKVM initialisation completes at device_initcall_sync,
>>>> while ffa_init() runs at the device_initcall level.
>>>>
>>>> So far, linker deployes kvm_arm_init() before ffa_init(), and SMCs can
>>>> still be trapped even before finalise_pkvm() is invoked.
>>>> As a result, this issue has not been observed.
>>>>
>>>> However, relying on above stuff is fragile.
>>>> Therefore, when pKVM is enabled, the FF-A infrastructure should be
>>>> initialised only after pKVM initialisation has been fully finalised.
>>>>
>>>> To achieve this, introduce an ffa_root_dev ("arm-ffa") and
>>>> a corresponding driver to defer initialisation of the FF-A infrastructure
>>>> until pKVM initialisation is complete, and to defer probing of all FF-A devices until then
>>>> when pKVM is enabled.
>>>>
>>>> This patch is based on v7.1-rc2
>>>>
>>>> Question:
>>>>
>>>> FF-A initialisation can occur at late_initcall. Because it may be deferred,
>>>> some FF-A requests cannot be serviced at that stage.
>>>> A typical example is the EFI runtime variable service using DIRECT_MSG_REQ.
>>>>
>>>> Depending on the platform, the EFI runtime variable service runs with StandaloneMm
>>>> and uses FF-A DIRECT_REQ. However, when pKVM is enabled, FF-A initialisation
>>>> may be deferred to late_initcall. In this case, load_uefi_certs()
>>>> can fail if it is invoked before the FF-A driver is initialised
>>>> via deferred_probe_initcall().
>>>>
>>>> Moving load_uefi_certs() to late_initcall_sync, as in the third patch,
>>>> seems not to have any problem since late_initcall and
>>>> late_initcall_sync are both of do_basic_setup() and it's before loading
>>>> init process. However, it is still unclear whether
>>>> it would be better to allow DIRECT_MSG_REQ in kvm_host_ffa_handler()
>>>
>>> The spec doesn't allow this. Looking at DEN0077A 1.2 REL0:
>>>
>>> Section 13.2.2 says:
>>>
>>> "If they are compatible, it enables them to determine which Framework functionalities can be used. Hence, negotiation of
>>> the version must happen before an invocation of any other FF-A ABI."
>>>
>>> and a bit further down
>>>
>>> "Once the caller invokes any FF-A ABI other than FFA_VERSION, the version negotiation phase is complete."
>>>
>>> I would have thought that an SP would only go into the waiting state once the version negotiation is done.
>>
>> I mean the negotiation between hypervisor and ff-a driver.
>> actually the version negotiation is done with SPMC in
>> hyp_ffa_init() but the negotiaion between hypervisor and ff-a driver
>> just choose the lower version between version requested from ff-a driver
>> and negotiated version with hypervisor and SPMC.
>
> Sorry. re-parse the word, not choose "re-negotiate" when
> FF-A driver request lowever version.
>
>>
>> So, the version negotiation is already done with SPMC
>> but with FF-A driver with hypervisor is not yet.
>> However, DIRECT_MSG_REQ has supported from v1.0
>> In this situation, is there any reason not to send DIRECT_REQ_MSG?
>
> IOW, question is that some of ff-a request can be allowed
> before version negotiation with FF-A driver but
> using negotiated version via hyp_ffa_init() first or not.
I don't think so. Isn't it more a continuation of the negotiation rather than a re-negotiation?
Thanks,
Ben
>
> [...]
>
> Thanks.
>
^ permalink raw reply
* Re: [RFC PATCH 0/3] initalise ff-a after finalising pKVM
From: Yeoreum Yun @ 2026-05-05 11:16 UTC (permalink / raw)
To: Ben Horgan
Cc: linux-integrity, keyrings, linux-security-module, linux-kernel,
linux-arm-kernel, kvmarm, jarkko, zohar, roberto.sassu,
dmitry.kasatkin, eric.snowberg, paul, jmorris, serge, maz, oupton,
joey.gouly, suzuki.poulose, yuzenghui, catalin.marinas, will,
sudeep.holla
In-Reply-To: <afnLpG4osopTzpip@e129823.arm.com>
> Hi Ben,
>
> > Hi Levi,
> >
> > On 5/5/26 10:54, Yeoreum Yun wrote:
> > > This patch is split out from the patchset [0] --
> > > fix FF-A call failure with pKVM when the FF-A driver is built-in,
> > > specifically the IMA-related part.
> > >
> > > When pKVM is enabled, the FF-A driver must be initialised after pKVM.
> > > Otherwise, pKVM cannot negotiate the FF-A version or obtain the RX/TX
> > > buffer information, leading to failures in FF-A calls.
> > >
> > > Currently, pKVM initialisation completes at device_initcall_sync,
> > > while ffa_init() runs at the device_initcall level.
> > >
> > > So far, linker deployes kvm_arm_init() before ffa_init(), and SMCs can
> > > still be trapped even before finalise_pkvm() is invoked.
> > > As a result, this issue has not been observed.
> > >
> > > However, relying on above stuff is fragile.
> > > Therefore, when pKVM is enabled, the FF-A infrastructure should be
> > > initialised only after pKVM initialisation has been fully finalised.
> > >
> > > To achieve this, introduce an ffa_root_dev ("arm-ffa") and
> > > a corresponding driver to defer initialisation of the FF-A infrastructure
> > > until pKVM initialisation is complete, and to defer probing of all FF-A devices until then
> > > when pKVM is enabled.
> > >
> > > This patch is based on v7.1-rc2
> > >
> > > Question:
> > >
> > > FF-A initialisation can occur at late_initcall. Because it may be deferred,
> > > some FF-A requests cannot be serviced at that stage.
> > > A typical example is the EFI runtime variable service using DIRECT_MSG_REQ.
> > >
> > > Depending on the platform, the EFI runtime variable service runs with StandaloneMm
> > > and uses FF-A DIRECT_REQ. However, when pKVM is enabled, FF-A initialisation
> > > may be deferred to late_initcall. In this case, load_uefi_certs()
> > > can fail if it is invoked before the FF-A driver is initialised
> > > via deferred_probe_initcall().
> > >
> > > Moving load_uefi_certs() to late_initcall_sync, as in the third patch,
> > > seems not to have any problem since late_initcall and
> > > late_initcall_sync are both of do_basic_setup() and it's before loading
> > > init process. However, it is still unclear whether
> > > it would be better to allow DIRECT_MSG_REQ in kvm_host_ffa_handler()
> >
> > The spec doesn't allow this. Looking at DEN0077A 1.2 REL0:
> >
> > Section 13.2.2 says:
> >
> > "If they are compatible, it enables them to determine which Framework functionalities can be used. Hence, negotiation of
> > the version must happen before an invocation of any other FF-A ABI."
> >
> > and a bit further down
> >
> > "Once the caller invokes any FF-A ABI other than FFA_VERSION, the version negotiation phase is complete."
> >
> > I would have thought that an SP would only go into the waiting state once the version negotiation is done.
>
> I mean the negotiation between hypervisor and ff-a driver.
> actually the version negotiation is done with SPMC in
> hyp_ffa_init() but the negotiaion between hypervisor and ff-a driver
> just choose the lower version between version requested from ff-a driver
> and negotiated version with hypervisor and SPMC.
Sorry. re-parse the word, not choose "re-negotiate" when
FF-A driver request lowever version.
>
> So, the version negotiation is already done with SPMC
> but with FF-A driver with hypervisor is not yet.
> However, DIRECT_MSG_REQ has supported from v1.0
> In this situation, is there any reason not to send DIRECT_REQ_MSG?
IOW, question is that some of ff-a request can be allowed
before version negotiation with FF-A driver but
using negotiated version via hyp_ffa_init() first or not.
[...]
Thanks.
--
Sincerely,
Yeoreum Yun
^ permalink raw reply
* Re: [RFC PATCH 0/3] initalise ff-a after finalising pKVM
From: Yeoreum Yun @ 2026-05-05 10:51 UTC (permalink / raw)
To: Ben Horgan
Cc: linux-integrity, keyrings, linux-security-module, linux-kernel,
linux-arm-kernel, kvmarm, jarkko, zohar, roberto.sassu,
dmitry.kasatkin, eric.snowberg, paul, jmorris, serge, maz, oupton,
joey.gouly, suzuki.poulose, yuzenghui, catalin.marinas, will,
sudeep.holla
In-Reply-To: <8942c12e-6315-493e-98c5-d55f4e6341f4@arm.com>
Hi Ben,
> Hi Levi,
>
> On 5/5/26 10:54, Yeoreum Yun wrote:
> > This patch is split out from the patchset [0] --
> > fix FF-A call failure with pKVM when the FF-A driver is built-in,
> > specifically the IMA-related part.
> >
> > When pKVM is enabled, the FF-A driver must be initialised after pKVM.
> > Otherwise, pKVM cannot negotiate the FF-A version or obtain the RX/TX
> > buffer information, leading to failures in FF-A calls.
> >
> > Currently, pKVM initialisation completes at device_initcall_sync,
> > while ffa_init() runs at the device_initcall level.
> >
> > So far, linker deployes kvm_arm_init() before ffa_init(), and SMCs can
> > still be trapped even before finalise_pkvm() is invoked.
> > As a result, this issue has not been observed.
> >
> > However, relying on above stuff is fragile.
> > Therefore, when pKVM is enabled, the FF-A infrastructure should be
> > initialised only after pKVM initialisation has been fully finalised.
> >
> > To achieve this, introduce an ffa_root_dev ("arm-ffa") and
> > a corresponding driver to defer initialisation of the FF-A infrastructure
> > until pKVM initialisation is complete, and to defer probing of all FF-A devices until then
> > when pKVM is enabled.
> >
> > This patch is based on v7.1-rc2
> >
> > Question:
> >
> > FF-A initialisation can occur at late_initcall. Because it may be deferred,
> > some FF-A requests cannot be serviced at that stage.
> > A typical example is the EFI runtime variable service using DIRECT_MSG_REQ.
> >
> > Depending on the platform, the EFI runtime variable service runs with StandaloneMm
> > and uses FF-A DIRECT_REQ. However, when pKVM is enabled, FF-A initialisation
> > may be deferred to late_initcall. In this case, load_uefi_certs()
> > can fail if it is invoked before the FF-A driver is initialised
> > via deferred_probe_initcall().
> >
> > Moving load_uefi_certs() to late_initcall_sync, as in the third patch,
> > seems not to have any problem since late_initcall and
> > late_initcall_sync are both of do_basic_setup() and it's before loading
> > init process. However, it is still unclear whether
> > it would be better to allow DIRECT_MSG_REQ in kvm_host_ffa_handler()
>
> The spec doesn't allow this. Looking at DEN0077A 1.2 REL0:
>
> Section 13.2.2 says:
>
> "If they are compatible, it enables them to determine which Framework functionalities can be used. Hence, negotiation of
> the version must happen before an invocation of any other FF-A ABI."
>
> and a bit further down
>
> "Once the caller invokes any FF-A ABI other than FFA_VERSION, the version negotiation phase is complete."
>
> I would have thought that an SP would only go into the waiting state once the version negotiation is done.
I mean the negotiation between hypervisor and ff-a driver.
actually the version negotiation is done with SPMC in
hyp_ffa_init() but the negotiaion between hypervisor and ff-a driver
just choose the lower version between version requested from ff-a driver
and negotiated version with hypervisor and SPMC.
So, the version negotiation is already done with SPMC
but with FF-A driver with hypervisor is not yet.
However, DIRECT_MSG_REQ has supported from v1.0
In this situation, is there any reason not to send DIRECT_REQ_MSG?
>
> > even before FF-A version negotiation since handler’s purpose seems to hook
> > certain memory operations, and DIRECT_MSG_REQ has been available
> > since FF-A specification v1.0.
> >
> > Any feedback or alternative suggestions would be appreciated!
> >
> > Link: https://lore.kernel.org/all/20260422162449.1814615-1-yeoreum.yun@arm.com/ [0]
> >
> > Yeoreum Yun (3):
> > arm64: KVM: defer kvm_init() to finalise_pkvm() when pKVM is enabled
> > firmware: arm_ffa: initialise ff-a after finalising pKVM
> > initialisation
> > security: integrity: call load_uefi_certs() at late_initcall_sync
> >
> > arch/arm64/kvm/arm.c | 8 +-
> > arch/arm64/kvm/pkvm.c | 15 ++-
> > drivers/firmware/arm_ffa/bus.c | 125 +++++++++++++++++-
> > drivers/firmware/arm_ffa/common.h | 13 +-
> > drivers/firmware/arm_ffa/driver.c | 21 ++-
> > drivers/firmware/arm_ffa/smccc.c | 2 +-
> > security/integrity/platform_certs/load_uefi.c | 2 +-
> > 7 files changed, 166 insertions(+), 20 deletions(-)
> >
> >
> > base-commit: 7fd2df204f342fc17d1a0bfcd474b24232fb0f32
>
--
Sincerely,
Yeoreum Yun
^ permalink raw reply
* Re: [RFC PATCH 0/3] initalise ff-a after finalising pKVM
From: Ben Horgan @ 2026-05-05 10:45 UTC (permalink / raw)
To: Yeoreum Yun, linux-integrity, keyrings, linux-security-module,
linux-kernel, linux-arm-kernel, kvmarm
Cc: jarkko, zohar, roberto.sassu, dmitry.kasatkin, eric.snowberg,
paul, jmorris, serge, maz, oupton, joey.gouly, suzuki.poulose,
yuzenghui, catalin.marinas, will, sudeep.holla
In-Reply-To: <20260505095409.1948371-1-yeoreum.yun@arm.com>
Hi Levi,
On 5/5/26 10:54, Yeoreum Yun wrote:
> This patch is split out from the patchset [0] --
> fix FF-A call failure with pKVM when the FF-A driver is built-in,
> specifically the IMA-related part.
>
> When pKVM is enabled, the FF-A driver must be initialised after pKVM.
> Otherwise, pKVM cannot negotiate the FF-A version or obtain the RX/TX
> buffer information, leading to failures in FF-A calls.
>
> Currently, pKVM initialisation completes at device_initcall_sync,
> while ffa_init() runs at the device_initcall level.
>
> So far, linker deployes kvm_arm_init() before ffa_init(), and SMCs can
> still be trapped even before finalise_pkvm() is invoked.
> As a result, this issue has not been observed.
>
> However, relying on above stuff is fragile.
> Therefore, when pKVM is enabled, the FF-A infrastructure should be
> initialised only after pKVM initialisation has been fully finalised.
>
> To achieve this, introduce an ffa_root_dev ("arm-ffa") and
> a corresponding driver to defer initialisation of the FF-A infrastructure
> until pKVM initialisation is complete, and to defer probing of all FF-A devices until then
> when pKVM is enabled.
>
> This patch is based on v7.1-rc2
>
> Question:
>
> FF-A initialisation can occur at late_initcall. Because it may be deferred,
> some FF-A requests cannot be serviced at that stage.
> A typical example is the EFI runtime variable service using DIRECT_MSG_REQ.
>
> Depending on the platform, the EFI runtime variable service runs with StandaloneMm
> and uses FF-A DIRECT_REQ. However, when pKVM is enabled, FF-A initialisation
> may be deferred to late_initcall. In this case, load_uefi_certs()
> can fail if it is invoked before the FF-A driver is initialised
> via deferred_probe_initcall().
>
> Moving load_uefi_certs() to late_initcall_sync, as in the third patch,
> seems not to have any problem since late_initcall and
> late_initcall_sync are both of do_basic_setup() and it's before loading
> init process. However, it is still unclear whether
> it would be better to allow DIRECT_MSG_REQ in kvm_host_ffa_handler()
The spec doesn't allow this. Looking at DEN0077A 1.2 REL0:
Section 13.2.2 says:
"If they are compatible, it enables them to determine which Framework functionalities can be used. Hence, negotiation of
the version must happen before an invocation of any other FF-A ABI."
and a bit further down
"Once the caller invokes any FF-A ABI other than FFA_VERSION, the version negotiation phase is complete."
I would have thought that an SP would only go into the waiting state once the version negotiation is done.
Thanks,
Ben
> even before FF-A version negotiation since handler’s purpose seems to hook
> certain memory operations, and DIRECT_MSG_REQ has been available
> since FF-A specification v1.0.
>
> Any feedback or alternative suggestions would be appreciated!
>
> Link: https://lore.kernel.org/all/20260422162449.1814615-1-yeoreum.yun@arm.com/ [0]
>
> Yeoreum Yun (3):
> arm64: KVM: defer kvm_init() to finalise_pkvm() when pKVM is enabled
> firmware: arm_ffa: initialise ff-a after finalising pKVM
> initialisation
> security: integrity: call load_uefi_certs() at late_initcall_sync
>
> arch/arm64/kvm/arm.c | 8 +-
> arch/arm64/kvm/pkvm.c | 15 ++-
> drivers/firmware/arm_ffa/bus.c | 125 +++++++++++++++++-
> drivers/firmware/arm_ffa/common.h | 13 +-
> drivers/firmware/arm_ffa/driver.c | 21 ++-
> drivers/firmware/arm_ffa/smccc.c | 2 +-
> security/integrity/platform_certs/load_uefi.c | 2 +-
> 7 files changed, 166 insertions(+), 20 deletions(-)
>
>
> base-commit: 7fd2df204f342fc17d1a0bfcd474b24232fb0f32
^ permalink raw reply
* [RFC PATCH 3/3] security: integrity: call load_uefi_certs() at late_initcall_sync
From: Yeoreum Yun @ 2026-05-05 9:54 UTC (permalink / raw)
To: linux-integrity, keyrings, linux-security-module, linux-kernel,
linux-arm-kernel, kvmarm
Cc: jarkko, zohar, roberto.sassu, dmitry.kasatkin, eric.snowberg,
paul, jmorris, serge, maz, oupton, joey.gouly, suzuki.poulose,
yuzenghui, catalin.marinas, will, sudeep.holla, Yeoreum Yun
In-Reply-To: <20260505095409.1948371-1-yeoreum.yun@arm.com>
In the arm64 pKVM environment, all FF-A requests fail until the FF-A
driver is initialized, as the FF-A version is not negotiated with the
hypervisor beforehand.
When FF-A is built-in and pKVM is enabled, pKVM
finalises its initialization at the device_initcall_sync level,
while the FF-A driver is initialized later at the late_initcall stage
via deferred probe.
When the EFI variable service runs with StandaloneMm, it uses
FFA_DIRECT_MSG to access EFI variables. As a result,
load_uefi_certs() always fails in this environment.
To address this issue, defer load_uefi_certs() to the
late_initcall_sync level.
Signed-off-by: Yeoreum Yun <yeoreum.yun@arm.com>
---
security/integrity/platform_certs/load_uefi.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/security/integrity/platform_certs/load_uefi.c b/security/integrity/platform_certs/load_uefi.c
index c0d6948446c3..cc2b879df5b6 100644
--- a/security/integrity/platform_certs/load_uefi.c
+++ b/security/integrity/platform_certs/load_uefi.c
@@ -235,4 +235,4 @@ static int __init load_uefi_certs(void)
return rc;
}
-late_initcall(load_uefi_certs);
+late_initcall_sync(load_uefi_certs);
--
LEVI:{C3F47F37-75D8-414A-A8BA-3980EC8A46D7}
^ permalink raw reply related
* [RFC PATCH 2/3] firmware: arm_ffa: initialise ff-a after finalising pKVM initialisation
From: Yeoreum Yun @ 2026-05-05 9:54 UTC (permalink / raw)
To: linux-integrity, keyrings, linux-security-module, linux-kernel,
linux-arm-kernel, kvmarm
Cc: jarkko, zohar, roberto.sassu, dmitry.kasatkin, eric.snowberg,
paul, jmorris, serge, maz, oupton, joey.gouly, suzuki.poulose,
yuzenghui, catalin.marinas, will, sudeep.holla, Yeoreum Yun
In-Reply-To: <20260505095409.1948371-1-yeoreum.yun@arm.com>
When pKVM is enabled, the FF-A driver must be initialised after pKVM.
Otherwise, pKVM cannot negotiate the FF-A version or obtain the RX/TX
buffer information, leading to failures in FF-A calls.
Currently, pKVM initialisation completes at device_initcall_sync,
while ffa_init() runs at the device_initcall level.
So far, linker deployes kvm_arm_init() before ffa_init(), and SMCs can
still be trapped even before finalise_pkvm() is invoked.
As a result, this issue has not been observed.
However, relying on above stuff is fragile.
Therefore, when pKVM is enabled, the FF-A infrastructure should be
initialised only after pKVM initialisation has been fully finalised.
To achieve this, introduce an ffa_root_dev ("arm-ffa") and
a corresponding driver to defer initialisation of the FF-A infrastructure
until pKVM initialisation is complete, and to defer probing of all FF-A devices until then
when pKVM is enabled.
This change slightly alters the sysfs layout.
Previously, all FF-A devices were located directly under /sys/devices/,
for example:
ls /sys/devices/
...
arm-ffa-1
arm-ffa-2
arm-ffa-3
...
After this patch, all FF-A devices are placed under the FF-A root device
/sys/devices/arm-ffa/:
ls /sys/devices/arm-ffa/
...
arm-ffa-1
arm-ffa-2
arm-ffa-3
...
Signed-off-by: Yeoreum Yun <yeoreum.yun@arm.com>
---
drivers/firmware/arm_ffa/bus.c | 125 ++++++++++++++++++++++++++++--
drivers/firmware/arm_ffa/common.h | 13 +++-
drivers/firmware/arm_ffa/driver.c | 21 +++--
drivers/firmware/arm_ffa/smccc.c | 2 +-
4 files changed, 146 insertions(+), 15 deletions(-)
diff --git a/drivers/firmware/arm_ffa/bus.c b/drivers/firmware/arm_ffa/bus.c
index 9576862d89c4..ddb352806d59 100644
--- a/drivers/firmware/arm_ffa/bus.c
+++ b/drivers/firmware/arm_ffa/bus.c
@@ -13,11 +13,14 @@
#include <linux/slab.h>
#include <linux/types.h>
+#include <asm/virt.h>
+
#include "common.h"
#define FFA_UEVENT_MODALIAS_FMT "arm_ffa:%04x:%pUb"
static DEFINE_IDA(ffa_bus_id);
+static struct ffa_device *ffa_root_dev;
static int ffa_device_match(struct device *dev, const struct device_driver *drv)
{
@@ -27,7 +30,13 @@ static int ffa_device_match(struct device *dev, const struct device_driver *drv)
id_table = to_ffa_driver(drv)->id_table;
ffa_dev = to_ffa_dev(dev);
- while (!uuid_is_null(&id_table->uuid)) {
+ if (is_ffa_root_device(ffa_dev)) {
+ if (!id_table)
+ return 1;
+ return 0;
+ }
+
+ while (id_table && !uuid_is_null(&id_table->uuid)) {
/*
* FF-A v1.0 doesn't provide discovery of UUIDs, just the
* partition IDs, so match it unconditionally here and handle
@@ -50,7 +59,7 @@ static int ffa_device_probe(struct device *dev)
struct ffa_device *ffa_dev = to_ffa_dev(dev);
/* UUID can be still NULL with FF-A v1.0, so just skip probing them */
- if (uuid_is_null(&ffa_dev->uuid))
+ if (!is_ffa_root_device(ffa_dev) && uuid_is_null(&ffa_dev->uuid))
return -ENODEV;
return ffa_drv->probe(ffa_dev);
@@ -68,6 +77,9 @@ static int ffa_device_uevent(const struct device *dev, struct kobj_uevent_env *e
{
const struct ffa_device *ffa_dev = to_ffa_dev(dev);
+ if (is_ffa_root_device(ffa_dev))
+ return 0;
+
return add_uevent_var(env, "MODALIAS=" FFA_UEVENT_MODALIAS_FMT,
ffa_dev->vm_id, &ffa_dev->uuid);
}
@@ -114,7 +126,6 @@ const struct bus_type ffa_bus_type = {
.probe = ffa_device_probe,
.remove = ffa_device_remove,
.uevent = ffa_device_uevent,
- .dev_groups = ffa_device_attributes_groups,
};
EXPORT_SYMBOL_GPL(ffa_bus_type);
@@ -149,13 +160,18 @@ static void ffa_release_device(struct device *dev)
{
struct ffa_device *ffa_dev = to_ffa_dev(dev);
- ida_free(&ffa_bus_id, ffa_dev->id);
+ if (!is_ffa_root_device(ffa_dev))
+ ida_free(&ffa_bus_id, ffa_dev->id);
kfree(ffa_dev);
}
static int __ffa_devices_unregister(struct device *dev, void *data)
{
- device_unregister(dev);
+ struct ffa_device *ffa_dev = to_ffa_dev(dev);
+
+ /* ffa_root_dev will be removed in arm_ffa_bus_exit(). */
+ if (!is_ffa_root_device(ffa_dev))
+ device_unregister(dev);
return 0;
}
@@ -195,6 +211,10 @@ ffa_device_register(const struct ffa_partition_info *part_info,
int id, ret;
struct device *dev;
struct ffa_device *ffa_dev;
+ struct device_link *link;
+
+ if (!ffa_root_dev)
+ return NULL;
if (!part_info)
return NULL;
@@ -213,6 +233,8 @@ ffa_device_register(const struct ffa_partition_info *part_info,
dev->bus = &ffa_bus_type;
dev->release = ffa_release_device;
dev->dma_mask = &dev->coherent_dma_mask;
+ dev->parent = &ffa_root_dev->dev;
+ dev->groups = ffa_device_attributes_groups;
dev_set_name(&ffa_dev->dev, "arm-ffa-%d", id);
ffa_dev->id = id;
@@ -221,7 +243,18 @@ ffa_device_register(const struct ffa_partition_info *part_info,
ffa_dev->ops = ops;
uuid_copy(&ffa_dev->uuid, &part_info->uuid);
- ret = device_register(&ffa_dev->dev);
+ device_initialize(dev);
+
+ link = device_link_add(dev, &ffa_root_dev->dev,
+ DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOPROBE_CONSUMER);
+ if (!link) {
+ dev_err(dev, "unable to link device %s\n", dev_name(dev));
+ device_link_del(link);
+ put_device(dev);
+ return NULL;
+ }
+
+ ret = device_add(dev);
if (ret) {
dev_err(dev, "unable to register device %s err=%d\n",
dev_name(dev), ret);
@@ -242,15 +275,93 @@ void ffa_device_unregister(struct ffa_device *ffa_dev)
}
EXPORT_SYMBOL_GPL(ffa_device_unregister);
+static int __init ffa_root_device_register(void)
+{
+ int ret;
+ struct device *dev;
+ struct ffa_device *ffa_dev;
+
+ ffa_dev = kzalloc_obj(*ffa_dev);
+ if (!ffa_dev) {
+ return -ENOMEM;
+ }
+
+ dev = &ffa_dev->dev;
+ dev->bus = &ffa_bus_type;
+ dev->release = ffa_release_device;
+ dev->dma_mask = &dev->coherent_dma_mask;
+ dev_set_name(&ffa_dev->dev, "%s", "arm-ffa");
+
+ ffa_dev->id = FFA_ROOT_DEV_ID;
+
+ ret = device_register(&ffa_dev->dev);
+ if (ret) {
+ dev_err(dev, "unable to register root device. err=%d\n", ret);
+ put_device(dev);
+ return ret;
+ }
+
+ ffa_root_dev = ffa_dev;
+
+ return 0;
+}
+
+static void ffa_root_device_unregister(void)
+{
+ ffa_device_unregister(ffa_root_dev);
+ ffa_root_dev = NULL;
+}
+
+static int ffa_root_device_probe(struct ffa_device *ffa_dev)
+{
+ if (!is_ffa_root_device(ffa_dev))
+ return -EINVAL;
+
+ if (is_protected_kvm_enabled() && !is_pkvm_initialized())
+ return -EPROBE_DEFER;
+
+ return ffa_core_init();
+}
+
+static struct ffa_driver ffa_root_dev_driver = {
+ .name = "arm-ffa",
+ .probe = ffa_root_device_probe,
+ .id_table = NULL,
+};
+
+int ffa_root_device_driver_register(void)
+{
+ return ffa_driver_register(&ffa_root_dev_driver, THIS_MODULE, KBUILD_MODNAME);
+}
+
+static void ffa_root_device_driver_unregister(void)
+{
+ ffa_driver_unregister(&ffa_root_dev_driver);
+}
+
static int __init arm_ffa_bus_init(void)
{
- return bus_register(&ffa_bus_type);
+ int ret;
+
+ ret = bus_register(&ffa_bus_type);
+ if (ret)
+ return ret;
+
+ ret = ffa_root_device_register();
+ if (ret) {
+ bus_unregister(&ffa_bus_type);
+ return ret;
+ }
+
+ return 0;
}
subsys_initcall(arm_ffa_bus_init);
static void __exit arm_ffa_bus_exit(void)
{
ffa_devices_unregister();
+ ffa_root_device_unregister();
+ ffa_root_device_driver_unregister();
bus_unregister(&ffa_bus_type);
ida_destroy(&ffa_bus_id);
}
diff --git a/drivers/firmware/arm_ffa/common.h b/drivers/firmware/arm_ffa/common.h
index 9c6425a81d0d..32ffe41c3cfc 100644
--- a/drivers/firmware/arm_ffa/common.h
+++ b/drivers/firmware/arm_ffa/common.h
@@ -10,17 +10,26 @@
#include <linux/arm-smccc.h>
#include <linux/err.h>
+#define FFA_ROOT_DEV_ID (0)
+
typedef struct arm_smccc_1_2_regs ffa_value_t;
typedef void (ffa_fn)(ffa_value_t, ffa_value_t *);
bool ffa_device_is_valid(struct ffa_device *ffa_dev);
void ffa_device_match_uuid(struct ffa_device *ffa_dev, const uuid_t *uuid);
+int ffa_root_device_driver_register(void);
+int ffa_core_init(void);
+
+static __always_inline bool is_ffa_root_device(const struct ffa_device *ffa_dev)
+{
+ return ffa_dev->id == FFA_ROOT_DEV_ID;
+}
#ifdef CONFIG_ARM_FFA_SMCCC
-int __init ffa_transport_init(ffa_fn **invoke_ffa_fn);
+int ffa_transport_init(ffa_fn **invoke_ffa_fn);
#else
-static inline int __init ffa_transport_init(ffa_fn **invoke_ffa_fn)
+static inline int ffa_transport_init(ffa_fn **invoke_ffa_fn)
{
return -EOPNOTSUPP;
}
diff --git a/drivers/firmware/arm_ffa/driver.c b/drivers/firmware/arm_ffa/driver.c
index eb2782848283..c41e7ed4eac8 100644
--- a/drivers/firmware/arm_ffa/driver.c
+++ b/drivers/firmware/arm_ffa/driver.c
@@ -1610,6 +1610,9 @@ ffa_bus_notifier(struct notifier_block *nb, unsigned long action, void *data)
struct ffa_driver *ffa_drv = to_ffa_driver(dev->driver);
const struct ffa_device_id *id_table = ffa_drv->id_table;
+ if (is_ffa_root_device(fdev))
+ return NOTIFY_DONE;
+
/*
* FF-A v1.1 provides UUID for each partition as part of the
* discovery API, the discovered UUID must be populated in the
@@ -2029,7 +2032,7 @@ static void ffa_notifications_setup(void)
ffa_notifications_cleanup();
}
-static int __init ffa_init(void)
+int ffa_core_init(void)
{
int ret;
u32 buf_sz;
@@ -2106,16 +2109,24 @@ static int __init ffa_init(void)
kfree(drv_info);
return ret;
}
-rootfs_initcall(ffa_init);
+
+static int __init ffa_init(void)
+{
+ return ffa_root_device_driver_register();
+}
+device_initcall(ffa_init);
static void __exit ffa_exit(void)
{
ffa_notifications_cleanup();
ffa_partitions_cleanup();
ffa_rxtx_unmap();
- free_pages_exact(drv_info->tx_buffer, drv_info->rxtx_bufsz);
- free_pages_exact(drv_info->rx_buffer, drv_info->rxtx_bufsz);
- kfree(drv_info);
+
+ if (drv_info) {
+ free_pages_exact(drv_info->tx_buffer, drv_info->rxtx_bufsz);
+ free_pages_exact(drv_info->rx_buffer, drv_info->rxtx_bufsz);
+ kfree(drv_info);
+ }
}
module_exit(ffa_exit);
diff --git a/drivers/firmware/arm_ffa/smccc.c b/drivers/firmware/arm_ffa/smccc.c
index 4d85bfff0a4e..e6125dd9f58f 100644
--- a/drivers/firmware/arm_ffa/smccc.c
+++ b/drivers/firmware/arm_ffa/smccc.c
@@ -17,7 +17,7 @@ static void __arm_ffa_fn_hvc(ffa_value_t args, ffa_value_t *res)
arm_smccc_1_2_hvc(&args, res);
}
-int __init ffa_transport_init(ffa_fn **invoke_ffa_fn)
+int ffa_transport_init(ffa_fn **invoke_ffa_fn)
{
enum arm_smccc_conduit conduit;
--
LEVI:{C3F47F37-75D8-414A-A8BA-3980EC8A46D7}
^ permalink raw reply related
* [RFC PATCH 1/3] arm64: KVM: defer kvm_init() to finalise_pkvm() when pKVM is enabled
From: Yeoreum Yun @ 2026-05-05 9:54 UTC (permalink / raw)
To: linux-integrity, keyrings, linux-security-module, linux-kernel,
linux-arm-kernel, kvmarm
Cc: jarkko, zohar, roberto.sassu, dmitry.kasatkin, eric.snowberg,
paul, jmorris, serge, maz, oupton, joey.gouly, suzuki.poulose,
yuzenghui, catalin.marinas, will, sudeep.holla, Yeoreum Yun
In-Reply-To: <20260505095409.1948371-1-yeoreum.yun@arm.com>
This patch is a preparatory change to address dependency issues
between the FF-A driver and pKVM.
kvm_init() should be invoked from finalise_pkvm(),
as this is the point where pKVM initialisation is finalised and
the system transitions into the protected mode.
Deferring kvm_init() ensures that KVM is initialised only after pKVM has
fully established its protected environment.
Signed-off-by: Yeoreum Yun <yeoreum.yun@arm.com>
---
arch/arm64/kvm/arm.c | 8 +++++---
arch/arm64/kvm/pkvm.c | 15 ++++++++++++++-
2 files changed, 19 insertions(+), 4 deletions(-)
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 8bb2c7422cc8..663b1d447a9b 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -3025,9 +3025,11 @@ static __init int kvm_arm_init(void)
* FIXME: Do something reasonable if kvm_init() fails after pKVM
* hypervisor protection is finalized.
*/
- err = kvm_init(sizeof(struct kvm_vcpu), 0, THIS_MODULE);
- if (err)
- goto out_subs;
+ if (!is_protected_kvm_enabled()) {
+ err = kvm_init(sizeof(struct kvm_vcpu), 0, THIS_MODULE);
+ if (err)
+ goto out_subs;
+ }
/*
* This should be called after initialization is done and failure isn't
diff --git a/arch/arm64/kvm/pkvm.c b/arch/arm64/kvm/pkvm.c
index 053e4f733e4b..48b06d384570 100644
--- a/arch/arm64/kvm/pkvm.c
+++ b/arch/arm64/kvm/pkvm.c
@@ -17,6 +17,7 @@
#include "hyp_constants.h"
DEFINE_STATIC_KEY_FALSE(kvm_protected_mode_initialized);
+EXPORT_SYMBOL_GPL(kvm_protected_mode_initialized);
static struct memblock_region *hyp_memory = kvm_nvhe_sym(hyp_memory);
static unsigned int *hyp_memblock_nr_ptr = &kvm_nvhe_sym(hyp_memblock_nr);
@@ -289,10 +290,22 @@ static int __init finalize_pkvm(void)
kmemleak_free_part(__hyp_rodata_start, __hyp_rodata_end - __hyp_rodata_start);
kmemleak_free_part_phys(hyp_mem_base, hyp_mem_size);
- ret = pkvm_drop_host_privileges();
+ ret = kvm_init(sizeof(struct kvm_vcpu), 0, THIS_MODULE);
if (ret)
+ goto out_err;
+
+ ret = pkvm_drop_host_privileges();
+ if (ret) {
pr_err("Failed to finalize Hyp protection: %d\n", ret);
+ kvm_exit();
+ goto out_err;
+ }
+
+ return 0;
+out_err:
+ kvm_unregister_perf_callbacks();
+ kvm_arm_vmid_alloc_free();
return ret;
}
device_initcall_sync(finalize_pkvm);
--
LEVI:{C3F47F37-75D8-414A-A8BA-3980EC8A46D7}
^ permalink raw reply related
* [RFC PATCH 0/3] initalise ff-a after finalising pKVM
From: Yeoreum Yun @ 2026-05-05 9:54 UTC (permalink / raw)
To: linux-integrity, keyrings, linux-security-module, linux-kernel,
linux-arm-kernel, kvmarm
Cc: jarkko, zohar, roberto.sassu, dmitry.kasatkin, eric.snowberg,
paul, jmorris, serge, maz, oupton, joey.gouly, suzuki.poulose,
yuzenghui, catalin.marinas, will, sudeep.holla, Yeoreum Yun
This patch is split out from the patchset [0] --
fix FF-A call failure with pKVM when the FF-A driver is built-in,
specifically the IMA-related part.
When pKVM is enabled, the FF-A driver must be initialised after pKVM.
Otherwise, pKVM cannot negotiate the FF-A version or obtain the RX/TX
buffer information, leading to failures in FF-A calls.
Currently, pKVM initialisation completes at device_initcall_sync,
while ffa_init() runs at the device_initcall level.
So far, linker deployes kvm_arm_init() before ffa_init(), and SMCs can
still be trapped even before finalise_pkvm() is invoked.
As a result, this issue has not been observed.
However, relying on above stuff is fragile.
Therefore, when pKVM is enabled, the FF-A infrastructure should be
initialised only after pKVM initialisation has been fully finalised.
To achieve this, introduce an ffa_root_dev ("arm-ffa") and
a corresponding driver to defer initialisation of the FF-A infrastructure
until pKVM initialisation is complete, and to defer probing of all FF-A devices until then
when pKVM is enabled.
This patch is based on v7.1-rc2
Question:
FF-A initialisation can occur at late_initcall. Because it may be deferred,
some FF-A requests cannot be serviced at that stage.
A typical example is the EFI runtime variable service using DIRECT_MSG_REQ.
Depending on the platform, the EFI runtime variable service runs with StandaloneMm
and uses FF-A DIRECT_REQ. However, when pKVM is enabled, FF-A initialisation
may be deferred to late_initcall. In this case, load_uefi_certs()
can fail if it is invoked before the FF-A driver is initialised
via deferred_probe_initcall().
Moving load_uefi_certs() to late_initcall_sync, as in the third patch,
seems not to have any problem since late_initcall and
late_initcall_sync are both of do_basic_setup() and it's before loading
init process. However, it is still unclear whether
it would be better to allow DIRECT_MSG_REQ in kvm_host_ffa_handler()
even before FF-A version negotiation since handler’s purpose seems to hook
certain memory operations, and DIRECT_MSG_REQ has been available
since FF-A specification v1.0.
Any feedback or alternative suggestions would be appreciated!
Link: https://lore.kernel.org/all/20260422162449.1814615-1-yeoreum.yun@arm.com/ [0]
Yeoreum Yun (3):
arm64: KVM: defer kvm_init() to finalise_pkvm() when pKVM is enabled
firmware: arm_ffa: initialise ff-a after finalising pKVM
initialisation
security: integrity: call load_uefi_certs() at late_initcall_sync
arch/arm64/kvm/arm.c | 8 +-
arch/arm64/kvm/pkvm.c | 15 ++-
drivers/firmware/arm_ffa/bus.c | 125 +++++++++++++++++-
drivers/firmware/arm_ffa/common.h | 13 +-
drivers/firmware/arm_ffa/driver.c | 21 ++-
drivers/firmware/arm_ffa/smccc.c | 2 +-
security/integrity/platform_certs/load_uefi.c | 2 +-
7 files changed, 166 insertions(+), 20 deletions(-)
base-commit: 7fd2df204f342fc17d1a0bfcd474b24232fb0f32
--
LEVI:{C3F47F37-75D8-414A-A8BA-3980EC8A46D7}
^ permalink raw reply
* Re: [PATCH] lockdown: remove useless decrement operation
From: Nicolas Bouchinet @ 2026-05-05 9:51 UTC (permalink / raw)
To: Kalevi Kolttonen; +Cc: linux-security-module, Xiujianfeng, Xiujianfeng
In-Reply-To: <20260501174448.47154-1-kalevi@kolttonen.fi>
Hi Kalevi, thanks for your contribution,
While it is true the len variable decrementing is not used anywhere and
that it would be cleaner to remove it, ideally we should go through a
more in depth Lockdown code cleaning.
I'll include it with Cai's one[1] when a more consequent patch will be
ready. I'll thus keep it somewhere with the patch until then.
I have a two week holiday starting this Friday and will thus not be available
until the 26th of may. Xiu, and Cai, if you want to work on it I'll gladly
review the patch set.
[1]: https://lore.kernel.org/all/20260119091226.3195309-1-caixinchen1@huawei.com/
Best regards,
Nicolas
^ permalink raw reply
* Re: [PATCH] lockdown: remove useless decrement operation
From: Nicolas Bouchinet @ 2026-05-05 9:20 UTC (permalink / raw)
To: Kalevi Kolttonen
Cc: linux-security-module, Cai Xinchen, Xiujianfeng, Xiujianfeng
In-Reply-To: <20260501174448.47154-1-kalevi@kolttonen.fi>
Hi Kalevi,
Thanks for your contribution,
While it is true that the len variable decrement is not used anywhere
and that removing it would be cleaner, this patch does not otherwise
bring any direct benefits.
Ideally we should go through the full Lockdown code to clean it, I'll
include it with Cai's one[1] when a more consequent patch will be ready.
I'll thus keep those somewhere until then.
I have a two week holiday starting this Friday and will thus not be
available until the 26th of may. Xiu, and Cai, if you want to work on it
I'll gladly review the patchset.
Best regards,
Nicolas
[1]: https://lore.kernel.org/all/20260119091226.3195309-1-caixinchen1@huawei.com/
^ permalink raw reply
* [PATCH v5 13/14] kbuild: move handling of module stripping to Makefile.lib
From: Thomas Weißschuh @ 2026-05-05 9:05 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Nathan Chancellor,
Nicolas Schier, Arnd Bergmann, Luis Chamberlain, Petr Pavlu,
Sami Tolvanen, Daniel Gomez, Paul Moore, James Morris,
Serge E. Hallyn, Jonathan Corbet, Madhavan Srinivasan,
Michael Ellerman, Nicholas Piggin, Naveen N Rao, Mimi Zohar,
Roberto Sassu, Dmitry Kasatkin, Eric Snowberg, Nicolas Schier,
Daniel Gomez, Aaron Tomlin, Christophe Leroy (CS GROUP),
Nicolas Bouchinet, Xiu Jianfeng, Christophe Leroy
Cc: Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa, bpf,
Fabian Grünbichler, Arnout Engelen, Mattia Rizzolo, kpcyrd,
Christian Heusel, Câju Mihai-Drosi, Eric Biggers,
Sebastian Andrzej Siewior, linux-kbuild, linux-kernel, linux-arch,
linux-modules, linux-security-module, linux-doc, linuxppc-dev,
linux-integrity, debian-kernel, Thomas Weißschuh
In-Reply-To: <20260505-module-hashes-v5-0-e174a5a49fce@weissschuh.net>
To allow CONFIG_MODULE_HASHES in combination with INSTALL_MOD_STRIP,
this logc will also be used by Makefile.modfinal.
Move it to a shared location to enable reuse.
Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
---
scripts/Makefile.lib | 32 ++++++++++++++++++++++++++++++++
scripts/Makefile.modinst | 37 +++++--------------------------------
2 files changed, 37 insertions(+), 32 deletions(-)
diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib
index 0718e39cedda..bb713a1a11be 100644
--- a/scripts/Makefile.lib
+++ b/scripts/Makefile.lib
@@ -484,6 +484,38 @@ define sed-offsets
s:->::; p;}'
endef
+#
+# Module Installation
+#
+quiet_cmd_install_mod = INSTALL $@
+ cmd_install_mod = cp $< $@
+
+# Module Strip
+# ---------------------------------------------------------------------------
+#
+# INSTALL_MOD_STRIP, if defined, will cause modules to be stripped after they
+# are installed. If INSTALL_MOD_STRIP is '1', then the default option
+# --strip-debug will be used. Otherwise, INSTALL_MOD_STRIP value will be used
+# as the options to the strip command.
+ifeq ($(INSTALL_MOD_STRIP),1)
+mod-strip-option := --strip-debug
+else
+mod-strip-option := $(INSTALL_MOD_STRIP)
+endif
+
+# Strip
+ifdef INSTALL_MOD_STRIP
+
+quiet_cmd_strip_mod = STRIP $@
+ cmd_strip_mod = $(STRIP) $(mod-strip-option) $@
+
+else
+
+quiet_cmd_strip_mod =
+ cmd_strip_mod = :
+
+endif
+
# Use filechk to avoid rebuilds when a header changes, but the resulting file
# does not
define filechk_offsets
diff --git a/scripts/Makefile.modinst b/scripts/Makefile.modinst
index 68708a039a62..b95f613e23c8 100644
--- a/scripts/Makefile.modinst
+++ b/scripts/Makefile.modinst
@@ -8,6 +8,7 @@ __modinst:
include $(objtree)/include/config/auto.conf
include $(srctree)/scripts/Kbuild.include
+include $(srctree)/scripts/Makefile.lib
install-y :=
@@ -36,7 +37,7 @@ install-y += $(addprefix $(MODLIB)/, modules.builtin modules.builtin.modinfo)
install-$(CONFIG_BUILTIN_MODULE_RANGES) += $(MODLIB)/modules.builtin.ranges
$(addprefix $(MODLIB)/, modules.builtin modules.builtin.modinfo modules.builtin.ranges): $(MODLIB)/%: % FORCE
- $(call cmd,install)
+ $(call cmd,install_mod)
endif
@@ -65,40 +66,12 @@ install-$(CONFIG_MODULES) += $(modules)
__modinst: $(install-y)
@:
-#
-# Installation
-#
-quiet_cmd_install = INSTALL $@
- cmd_install = cp $< $@
-
-# Strip
-#
-# INSTALL_MOD_STRIP, if defined, will cause modules to be stripped after they
-# are installed. If INSTALL_MOD_STRIP is '1', then the default option
-# --strip-debug will be used. Otherwise, INSTALL_MOD_STRIP value will be used
-# as the options to the strip command.
-ifdef INSTALL_MOD_STRIP
-
ifdef CONFIG_MODULE_HASHES
ifeq ($(KBUILD_EXTMOD),)
+ifdef INSTALL_MOD_STRIP
$(error CONFIG_MODULE_HASHES and INSTALL_MOD_STRIP are mutually exclusive)
endif
endif
-
-ifeq ($(INSTALL_MOD_STRIP),1)
-strip-option := --strip-debug
-else
-strip-option := $(INSTALL_MOD_STRIP)
-endif
-
-quiet_cmd_strip = STRIP $@
- cmd_strip = $(STRIP) $(strip-option) $@
-
-else
-
-quiet_cmd_strip =
- cmd_strip = :
-
endif
#
@@ -131,8 +104,8 @@ endif
$(foreach dir, $(sort $(dir $(install-y))), $(shell mkdir -p $(dir)))
$(dst)/%.ko: %.ko FORCE
- $(call cmd,install)
- $(call cmd,strip)
+ $(call cmd,install_mod)
+ $(call cmd,strip_mod)
$(call cmd,sign)
ifdef CONFIG_MODULES
--
2.54.0
^ permalink raw reply related
* [PATCH v5 12/14] module: Introduce hash-based integrity checking
From: Thomas Weißschuh @ 2026-05-05 9:05 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Nathan Chancellor,
Nicolas Schier, Arnd Bergmann, Luis Chamberlain, Petr Pavlu,
Sami Tolvanen, Daniel Gomez, Paul Moore, James Morris,
Serge E. Hallyn, Jonathan Corbet, Madhavan Srinivasan,
Michael Ellerman, Nicholas Piggin, Naveen N Rao, Mimi Zohar,
Roberto Sassu, Dmitry Kasatkin, Eric Snowberg, Nicolas Schier,
Daniel Gomez, Aaron Tomlin, Christophe Leroy (CS GROUP),
Nicolas Bouchinet, Xiu Jianfeng, Christophe Leroy
Cc: Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa, bpf,
Fabian Grünbichler, Arnout Engelen, Mattia Rizzolo, kpcyrd,
Christian Heusel, Câju Mihai-Drosi, Eric Biggers,
Sebastian Andrzej Siewior, linux-kbuild, linux-kernel, linux-arch,
linux-modules, linux-security-module, linux-doc, linuxppc-dev,
linux-integrity, debian-kernel, Thomas Weißschuh
In-Reply-To: <20260505-module-hashes-v5-0-e174a5a49fce@weissschuh.net>
The current signature-based module integrity checking has some drawbacks
in combination with reproducible builds. Either the module signing key
is generated at build time, which makes the build unreproducible, or a
static signing key is used, which precludes rebuilds by third parties
and makes the whole build and packaging process much more complicated.
The goal is to reach bit-for-bit reproducibility. Excluding certain
parts of the build output from the reproducibility analysis would be
error-prone and force each downstream consumer to introduce new tooling.
Introduce a new mechanism to ensure only well-known modules are loaded
by embedding a merkle tree root of all modules built as part of the full
kernel build into vmlinux.
Out-of-tree modules can be validated as before through signatures.
Normally the .ko module files depend on a fully built vmlinux to be
available for modpost validation and BTF generation. With
CONFIG_MODULE_HASHES, vmlinux now depends on the modules
to build a merkle tree. This introduces a dependency cycle which is
impossible to satisfy. Work around this by building the modules during
link-vmlinux.sh, after vmlinux is complete enough for modpost and BTF
but before the final module hashes are
The PKCS7 format which is used for regular module signatures can not
represent Merkle proofs, so a new kind of module signature is
introduced. As this signature type is only ever used for builtin
modules, no compatibility issues can arise.
Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
---
.gitignore | 1 +
Documentation/kbuild/reproducible-builds.rst | 5 +-
Makefile | 7 +-
include/asm-generic/vmlinux.lds.h | 11 +
include/linux/module_hashes.h | 29 ++
include/uapi/linux/module_signature.h | 1 +
kernel/module/Kconfig | 21 +-
kernel/module/Makefile | 1 +
kernel/module/auth.c | 6 +
kernel/module/hashes.c | 95 ++++++
kernel/module/hashes_root.c | 6 +
kernel/module/internal.h | 1 +
scripts/.gitignore | 1 +
scripts/Makefile | 4 +
scripts/Makefile.modinst | 11 +
scripts/Makefile.vmlinux | 32 +++
scripts/include/xalloc.h | 29 ++
scripts/link-vmlinux.sh | 3 +-
scripts/modules-merkle-tree.c | 416 +++++++++++++++++++++++++++
security/lockdown/Kconfig | 2 +-
tools/include/uapi/linux/module_signature.h | 1 +
21 files changed, 677 insertions(+), 6 deletions(-)
diff --git a/.gitignore b/.gitignore
index 3044b9590f05..78cf799401e6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,6 +36,7 @@
*.lz4
*.lzma
*.lzo
+*.merkle
*.mod
*.mod.c
*.o
diff --git a/Documentation/kbuild/reproducible-builds.rst b/Documentation/kbuild/reproducible-builds.rst
index bc1eb82211df..b15019678aae 100644
--- a/Documentation/kbuild/reproducible-builds.rst
+++ b/Documentation/kbuild/reproducible-builds.rst
@@ -84,7 +84,10 @@ generate a different temporary key for each build, resulting in the
modules being unreproducible. However, including a signing key with
your source would presumably defeat the purpose of signing modules.
-One approach to this is to divide up the build process so that the
+Instead ``CONFIG_MODULE_HASHES`` can be used to embed a static list
+of valid modules to load.
+
+Another approach to this is to divide up the build process so that the
unreproducible parts can be treated as sources:
1. Generate a persistent signing key. Add the certificate for the key
diff --git a/Makefile b/Makefile
index e27c91ea56fc..def4a2413c43 100644
--- a/Makefile
+++ b/Makefile
@@ -1650,7 +1650,9 @@ ifdef CONFIG_MODULES
# By default, build modules as well
+ifndef CONFIG_MODULE_HASHES
all: modules
+endif
# When we're building modules with modversions, we need to consider
# the built-in objects during the descend as well, in order to
@@ -1666,8 +1668,10 @@ endif
# is an exception.
ifdef CONFIG_DEBUG_INFO_BTF_MODULES
KBUILD_BUILTIN := y
+ifndef CONFIG_MODULE_HASHES
modules: vmlinux
endif
+endif
modules: modules_prepare
@@ -2068,7 +2072,7 @@ modules.order: $(build-dir)
# KBUILD_MODPOST_NOFINAL can be set to skip the final link of modules.
# This is solely useful to speed up test compiles.
modules: modpost
-ifneq ($(KBUILD_MODPOST_NOFINAL),1)
+ifneq ($(CONFIG_MODULE_HASHES)|$(KBUILD_MODPOST_NOFINAL),|1)
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modfinal
endif
@@ -2162,6 +2166,7 @@ clean: $(clean-dirs)
-o -name '*.c.[012]*.*' \
-o -name '*.ll' \
-o -name '*.gcno' \
+ -o -name '*.merkle' \
\) -type f -print \
-o -name '.tmp_*' -print \
| xargs rm -rf
diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h
index 60c8c22fd3e4..661881e5ef96 100644
--- a/include/asm-generic/vmlinux.lds.h
+++ b/include/asm-generic/vmlinux.lds.h
@@ -508,6 +508,8 @@
\
PRINTK_INDEX \
\
+ MODULE_HASHES \
+ \
/* Kernel symbol table */ \
__ksymtab : AT(ADDR(__ksymtab) - LOAD_OFFSET) { \
__start___ksymtab = .; \
@@ -913,6 +915,15 @@
#define PRINTK_INDEX
#endif
+#ifdef CONFIG_MODULE_HASHES
+#define MODULE_HASHES \
+ .module_hashes : AT(ADDR(.module_hashes) - LOAD_OFFSET) { \
+ KEEP(*(SORT(.module_hashes))) \
+ }
+#else
+#define MODULE_HASHES
+#endif
+
/*
* Discard .note.GNU-stack, which is emitted as PROGBITS by the compiler.
* Otherwise, the type of .notes section would become PROGBITS instead of NOTES.
diff --git a/include/linux/module_hashes.h b/include/linux/module_hashes.h
new file mode 100644
index 000000000000..53b34fa12f2d
--- /dev/null
+++ b/include/linux/module_hashes.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef _LINUX_MODULE_HASHES_H
+#define _LINUX_MODULE_HASHES_H
+
+#include <linux/compiler_attributes.h>
+#include <linux/types.h>
+#include <crypto/sha2.h>
+
+#define __module_hashes_section __section(".module_hashes")
+#define MODULE_HASHES_HASH_SIZE SHA256_DIGEST_SIZE
+
+struct module_hash {
+ u8 h[MODULE_HASHES_HASH_SIZE];
+};
+
+struct module_hashes_proof {
+ __be32 pos;
+ struct module_hash hash_sigs[];
+} __packed;
+
+struct module_hashes_root {
+ u32 levels;
+ struct module_hash hash;
+};
+
+extern const struct module_hashes_root module_hashes_root;
+
+#endif /* _LINUX_MODULE_HASHES_H */
diff --git a/include/uapi/linux/module_signature.h b/include/uapi/linux/module_signature.h
index 634c9f1c8fc2..78e206996eed 100644
--- a/include/uapi/linux/module_signature.h
+++ b/include/uapi/linux/module_signature.h
@@ -16,6 +16,7 @@
enum module_signature_type {
MODULE_SIGNATURE_TYPE_PKCS7 = 2, /* Signature in PKCS#7 message */
+ MODULE_SIGNATURE_TYPE_MERKLE = 3, /* Merkle proof for modules, opaque structure */
};
/*
diff --git a/kernel/module/Kconfig b/kernel/module/Kconfig
index 84297da666ff..acbbda58e7c8 100644
--- a/kernel/module/Kconfig
+++ b/kernel/module/Kconfig
@@ -272,7 +272,7 @@ config MODULE_SIG
inclusion into an initramfs that wants the module size reduced.
config MODULE_AUTH
- def_bool MODULE_SIG
+ def_bool MODULE_SIG || MODULE_HASHES
config MODULE_SIG_FORCE
bool "Require modules to be validly signed"
@@ -291,7 +291,7 @@ config MODULE_SIG_ALL
modules must be signed manually, using the scripts/sign-file tool.
comment "Do not forget to sign required modules with scripts/sign-file"
- depends on MODULE_SIG_FORCE && !MODULE_SIG_ALL
+ depends on MODULE_SIG_FORCE && !MODULE_SIG_ALL && !MODULE_HASHES
choice
prompt "Hash algorithm to sign modules"
@@ -406,6 +406,23 @@ config MODULE_DECOMPRESS
endif # MODULE_COMPRESS
+config MODULE_HASHES
+ bool "Hash-based module authentication"
+ depends on !MODULE_SIG_ALL
+ depends on !IMA_APPRAISE_MODSIG
+ select MODULE_SIG_FORMAT
+ select CRYPTO_LIB_SHA256
+ help
+ Validate modules by their hashes.
+ Only modules built together with the main kernel image can be
+ validated that way.
+
+ This is a reproducible-build compatible alternative to a build-time
+ generated module keyring, as enabled by
+ CONFIG_MODULE_SIG_KEY=certs/signing_key.pem.
+
+ Also see the warning in MODULE_SIG about stripping modules.
+
config MODULE_ALLOW_MISSING_NAMESPACE_IMPORTS
bool "Allow loading of modules with missing namespace imports"
help
diff --git a/kernel/module/Makefile b/kernel/module/Makefile
index c7200e293d04..da9420f140e9 100644
--- a/kernel/module/Makefile
+++ b/kernel/module/Makefile
@@ -26,3 +26,4 @@ obj-$(CONFIG_KGDB_KDB) += kdb.o
obj-$(CONFIG_MODVERSIONS) += version.o
obj-$(CONFIG_MODULE_UNLOAD_TAINT_TRACKING) += tracking.o
obj-$(CONFIG_MODULE_STATS) += stats.o
+obj-$(CONFIG_MODULE_HASHES) += hashes.o hashes_root.o
diff --git a/kernel/module/auth.c b/kernel/module/auth.c
index 2ee512d26790..cf3fe3f8bd89 100644
--- a/kernel/module/auth.c
+++ b/kernel/module/auth.c
@@ -42,6 +42,9 @@ static __always_inline bool mod_sig_type_valid(enum module_signature_type id_typ
if (id_type == MODULE_SIGNATURE_TYPE_PKCS7 && IS_ENABLED(CONFIG_MODULE_SIG))
return true;
+ if (id_type == MODULE_SIGNATURE_TYPE_MERKLE && IS_ENABLED(CONFIG_MODULE_HASHES))
+ return true;
+
return false;
}
@@ -72,6 +75,9 @@ static int mod_verify_sig(const void *mod, struct load_info *info)
if (ms.id_type == MODULE_SIGNATURE_TYPE_PKCS7 && IS_ENABLED(CONFIG_MODULE_SIG))
return module_sig_check(mod, modlen, mod + modlen, sig_len);
+ if (ms.id_type == MODULE_SIGNATURE_TYPE_MERKLE && IS_ENABLED(CONFIG_MODULE_HASHES))
+ return module_hash_check(mod, modlen, mod + modlen, sig_len);
+
return 0;
}
diff --git a/kernel/module/hashes.c b/kernel/module/hashes.c
new file mode 100644
index 000000000000..3d3cf0366f75
--- /dev/null
+++ b/kernel/module/hashes.c
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* Module hash-based integrity checker
+ *
+ * Copyright (C) 2025 Thomas Weißschuh <linux@weissschuh.net>
+ * Copyright (C) 2025 Sebastian Andrzej Siewior <sebastian@breakpoint.cc>
+ *
+ * The structure of the Merkle tree is documented in scripts/modules-merkle-tree.c.
+ */
+
+#define pr_fmt(fmt) "module/hash: " fmt
+
+#include <linux/module_hashes.h>
+#include <linux/module.h>
+#include <linux/unaligned.h>
+
+#include <crypto/sha2.h>
+
+#include "internal.h"
+
+static __init __maybe_unused int module_hashes_init(void)
+{
+ pr_debug("root: levels=%u hash=%*phN\n",
+ module_hashes_root.levels,
+ (int)sizeof(module_hashes_root.hash), &module_hashes_root.hash);
+
+ return 0;
+}
+
+#if IS_ENABLED(CONFIG_MODULE_DEBUG)
+early_initcall(module_hashes_init);
+#endif
+
+static void hash_entry(const struct module_hash *left, const struct module_hash *right,
+ struct module_hash *out)
+{
+ struct sha256_ctx ctx;
+ u8 magic = 0x02;
+
+ sha256_init(&ctx);
+ sha256_update(&ctx, &magic, sizeof(magic));
+ sha256_update(&ctx, left->h, sizeof(left->h));
+ sha256_update(&ctx, right->h, sizeof(right->h));
+ sha256_final(&ctx, out->h);
+}
+
+static void hash_data(const u8 *d, size_t len, unsigned int pos, struct module_hash *out)
+{
+ struct sha256_ctx ctx;
+ u8 magic = 0x01;
+ __be32 pos_be;
+
+ pos_be = cpu_to_be32(pos);
+
+ sha256_init(&ctx);
+ sha256_update(&ctx, &magic, sizeof(magic));
+ sha256_update(&ctx, (const u8 *)&pos_be, sizeof(pos_be));
+ sha256_update(&ctx, d, len);
+ sha256_final(&ctx, out->h);
+}
+
+static bool module_hashes_verify_proof(u32 pos, const struct module_hash *hash_sigs,
+ struct module_hash *cur)
+{
+ for (unsigned int i = 0; i < module_hashes_root.levels; i++, pos >>= 1) {
+ if ((pos & 1) == 0)
+ hash_entry(cur, &hash_sigs[i], cur);
+ else
+ hash_entry(&hash_sigs[i], cur, cur);
+ }
+
+ return !memcmp(cur, &module_hashes_root.hash, sizeof(module_hashes_root.hash));
+}
+
+int module_hash_check(const void *mod, size_t mod_len, const void *sig, size_t sig_len)
+{
+ const struct module_hashes_proof *proof;
+ struct module_hash modhash;
+ size_t proof_size;
+ u32 pos;
+
+ proof_size = struct_size(proof, hash_sigs, module_hashes_root.levels);
+
+ if (sig_len != proof_size)
+ return -ENOPKG;
+
+ proof = (const struct module_hashes_proof *)sig;
+ pos = get_unaligned_be32(&proof->pos);
+
+ hash_data(mod, mod_len, pos, &modhash);
+
+ if (!module_hashes_verify_proof(pos, proof->hash_sigs, &modhash))
+ return -ENOKEY;
+
+ return 0;
+}
diff --git a/kernel/module/hashes_root.c b/kernel/module/hashes_root.c
new file mode 100644
index 000000000000..ffb6adfc2193
--- /dev/null
+++ b/kernel/module/hashes_root.c
@@ -0,0 +1,6 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <linux/module_hashes.h>
+
+/* Blank dummy data. Will be replaced by the read data during the build */
+const struct module_hashes_root module_hashes_root __module_hashes_section = {};
diff --git a/kernel/module/internal.h b/kernel/module/internal.h
index aabe7f8e1af4..259e8ca5cb25 100644
--- a/kernel/module/internal.h
+++ b/kernel/module/internal.h
@@ -336,6 +336,7 @@ void module_mark_ro_after_init(const Elf_Ehdr *hdr, Elf_Shdr *sechdrs,
const char *secstrings);
int module_sig_check(const void *mod, size_t mod_len, const void *sig, size_t sig_len);
+int module_hash_check(const void *mod, size_t mod_len, const void *sig, size_t sig_len);
#ifdef CONFIG_MODULE_AUTH
int module_auth_check(struct load_info *info, int flags);
diff --git a/scripts/.gitignore b/scripts/.gitignore
index 4215c2208f7e..8dad9b0d3b2d 100644
--- a/scripts/.gitignore
+++ b/scripts/.gitignore
@@ -5,6 +5,7 @@
/insert-sys-cert
/kallsyms
/module.lds
+/modules-merkle-tree
/recordmcount
/rustdoc_test_builder
/rustdoc_test_gen
diff --git a/scripts/Makefile b/scripts/Makefile
index c983e09be78c..b6291595d9e8 100644
--- a/scripts/Makefile
+++ b/scripts/Makefile
@@ -11,6 +11,7 @@ hostprogs-always-y += sign-file
hostprogs-always-$(CONFIG_SYSTEM_EXTRA_CERTIFICATE) += insert-sys-cert
hostprogs-always-$(CONFIG_RUST_KERNEL_DOCTESTS) += rustdoc_test_builder
hostprogs-always-$(CONFIG_RUST_KERNEL_DOCTESTS) += rustdoc_test_gen
+hostprogs-always-$(CONFIG_MODULE_HASHES) += modules-merkle-tree
hostprogs-always-$(CONFIG_TRACEPOINTS) += tracepoint-update
sorttable-objs := sorttable.o elf-parse.o
@@ -37,6 +38,9 @@ HOSTCFLAGS_asn1_compiler.o = -I$(srctree)/include
HOSTCFLAGS_sign-file.o = $(shell $(HOSTPKG_CONFIG) --cflags libcrypto 2> /dev/null)
HOSTCFLAGS_sign-file.o += -I$(srctree)/tools/include/uapi/
HOSTLDLIBS_sign-file = $(shell $(HOSTPKG_CONFIG) --libs libcrypto 2> /dev/null || echo -lcrypto)
+HOSTCFLAGS_modules-merkle-tree.o = $(shell $(HOSTPKG_CONFIG) --cflags libcrypto 2> /dev/null)
+HOSTCFLAGS_modules-merkle-tree.o += -I$(srctree)/tools/include/uapi/
+HOSTLDLIBS_modules-merkle-tree = $(shell $(HOSTPKG_CONFIG) --libs libcrypto 2> /dev/null || echo -lcrypto)
ifdef CONFIG_UNWINDER_ORC
ifeq ($(ARCH),x86_64)
diff --git a/scripts/Makefile.modinst b/scripts/Makefile.modinst
index 9ba45e5b32b1..68708a039a62 100644
--- a/scripts/Makefile.modinst
+++ b/scripts/Makefile.modinst
@@ -79,6 +79,12 @@ quiet_cmd_install = INSTALL $@
# as the options to the strip command.
ifdef INSTALL_MOD_STRIP
+ifdef CONFIG_MODULE_HASHES
+ifeq ($(KBUILD_EXTMOD),)
+$(error CONFIG_MODULE_HASHES and INSTALL_MOD_STRIP are mutually exclusive)
+endif
+endif
+
ifeq ($(INSTALL_MOD_STRIP),1)
strip-option := --strip-debug
else
@@ -116,6 +122,11 @@ quiet_cmd_sign :=
cmd_sign := :
endif
+ifeq ($(KBUILD_EXTMOD)|$(CONFIG_MODULE_HASHES),|y)
+quiet_cmd_sign = MERKLE [M] $@
+ cmd_sign = cat $(objtree)/$*.merkle >> $@
+endif
+
# Create necessary directories
$(foreach dir, $(sort $(dir $(install-y))), $(shell mkdir -p $(dir)))
diff --git a/scripts/Makefile.vmlinux b/scripts/Makefile.vmlinux
index 6cc661e5292b..a0332c06bde1 100644
--- a/scripts/Makefile.vmlinux
+++ b/scripts/Makefile.vmlinux
@@ -78,6 +78,33 @@ ifdef CONFIG_BUILDTIME_TABLE_SORT
vmlinux.unstripped: scripts/sorttable
endif
+ifdef CONFIG_MODULE_HASHES
+targets += .tmp_module_hashes.c
+
+modules.order: vmlinux.unstripped FORCE
+ $(Q)echo " MAKE modules"
+ $(Q)$(MAKE) -f $(srctree)/Makefile modules
+
+quiet_cmd_modules_merkle_tree = MERKLE $@
+ cmd_modules_merkle_tree = $< $@ .ko
+
+targets += .tmp_module_hashes.c
+.tmp_module_hashes.c: $(objtree)/scripts/modules-merkle-tree modules.order FORCE
+ $(call if_changed,modules_merkle_tree)
+
+targets += .tmp_module_hashes.o
+.tmp_module_hashes.o: .tmp_module_hashes.c FORCE
+
+quiet_cmd_modules_merkle_tree_root = GEN $@
+ cmd_modules_merkle_tree_root = $(OBJCOPY) --dump-section .module_hashes=$@ $<
+
+targets += .tmp_module_hashes.bin
+.tmp_module_hashes.bin: .tmp_module_hashes.o FORCE
+ $(call if_changed,modules_merkle_tree_root)
+
+vmlinux: .tmp_module_hashes.bin
+endif
+
# vmlinux
# ---------------------------------------------------------------------------
@@ -95,6 +122,11 @@ quiet_cmd_objcopy_vmlinux = OBJCOPY $@
cmd_objcopy_vmlinux = $(OBJCOPY) $(patsubst %,--set-section-flags %=noload,$(remove-section-y)) $< $@; \
$(OBJCOPY) $(addprefix --remove-section=,$(remove-section-y)) $(remove-symbols) $@
+ifdef CONFIG_MODULE_HASHES
+# Patch module hashes root into vmlinux after modules have been built.
+ cmd_objcopy_vmlinux += ; $(OBJCOPY) --update-section .module_hashes=.tmp_module_hashes.bin $@
+endif
+
targets += vmlinux
vmlinux: vmlinux.unstripped FORCE
$(call if_changed,objcopy_vmlinux)
diff --git a/scripts/include/xalloc.h b/scripts/include/xalloc.h
index cdadb07d0592..8294bc0b836f 100644
--- a/scripts/include/xalloc.h
+++ b/scripts/include/xalloc.h
@@ -3,6 +3,7 @@
#ifndef XALLOC_H
#define XALLOC_H
+#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
@@ -50,4 +51,32 @@ static inline char *xstrndup(const char *s, size_t n)
return p;
}
+static inline void *xreallocarray(void *oldp, size_t n, size_t size)
+{
+ void *p;
+
+ p = reallocarray(oldp, n, size);
+ if (!p)
+ exit(1);
+
+ return p;
+}
+
+#ifdef _GNU_SOURCE
+static inline char *xasprintf(const char *fmt, ...)
+{
+ va_list ap;
+ char *strp;
+ int ret;
+
+ va_start(ap, fmt);
+ ret = vasprintf(&strp, fmt, ap);
+ va_end(ap);
+ if (ret == -1)
+ exit(1);
+
+ return strp;
+}
+#endif /* _GNU_SOURCE */
+
#endif /* XALLOC_H */
diff --git a/scripts/link-vmlinux.sh b/scripts/link-vmlinux.sh
index f99e196abeea..0edec5ee34dc 100755
--- a/scripts/link-vmlinux.sh
+++ b/scripts/link-vmlinux.sh
@@ -103,7 +103,7 @@ vmlinux_link()
${ld} ${ldflags} -o ${output} \
${wl}--whole-archive ${objs} ${wl}--no-whole-archive \
${wl}--start-group ${libs} ${wl}--end-group \
- ${kallsymso} ${btf_vmlinux_bin_o} ${arch_vmlinux_o} ${ldlibs}
+ ${kallsymso} ${btf_vmlinux_bin_o} ${module_hashes_o} ${arch_vmlinux_o} ${ldlibs}
}
# Create ${2}.o file with all symbols from the ${1} object file
@@ -183,6 +183,7 @@ fi
btf_vmlinux_bin_o=
btfids_vmlinux=
kallsymso=
+module_hashes_o=
strip_debug=
generate_map=
diff --git a/scripts/modules-merkle-tree.c b/scripts/modules-merkle-tree.c
new file mode 100644
index 000000000000..10e3455d5d7a
--- /dev/null
+++ b/scripts/modules-merkle-tree.c
@@ -0,0 +1,416 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Compute hashes for modules files and build a merkle tree.
+ *
+ * Copyright (C) 2025 Sebastian Andrzej Siewior <sebastian@breakpoint.cc>
+ * Copyright (C) 2025 Thomas Weißschuh <linux@weissschuh.net>
+ *
+ * Structure of the Merkle tree:
+ *
+ * The full built modules are leaf nodes. They are hashed pairwise in the order
+ * of modules.order to create internal nodes. These in turn are also hashed
+ * pairwise to create the next higher level of internal nodes. This is repeated
+ * up to a single root node. In case of an uneven amount of node on a level, the
+ * last node is paired with itself.
+ *
+ * The single root node can then be embedded into vmlinux to validate all modules.
+ */
+
+#define _GNU_SOURCE 1
+#include <endian.h>
+#include <err.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#include <openssl/evp.h>
+#include <openssl/err.h>
+
+#include "ssl-common.h"
+
+#include <linux/module_signature.h>
+#include <xalloc.h>
+
+static int hash_size;
+static EVP_MD_CTX *ctx;
+
+struct hash {
+ uint8_t h[32]; /* For sha256 */
+};
+
+struct file_entry {
+ char *name;
+ unsigned int pos;
+ struct hash hash;
+};
+
+static struct file_entry *fh_list;
+static size_t num_files;
+
+struct mtree {
+ struct hash **level_hashes;
+ unsigned int *entries;
+ unsigned int num_levels;
+};
+
+static unsigned int log2_roundup(uint32_t val)
+{
+ if (val <= 1)
+ return 1;
+ return 32 - __builtin_clz(val - 1);
+}
+
+static void hash_data(unsigned int pos, unsigned char *data, size_t size, struct hash *ret_hash)
+{
+ uint8_t magic = 0x01; /* domain separation prefix */
+ uint32_t pos_be;
+
+ pos_be = htobe32(pos);
+
+ ERR(EVP_DigestInit_ex(ctx, NULL, NULL) != 1, "EVP_DigestInit_ex()");
+ ERR(EVP_DigestUpdate(ctx, &magic, sizeof(magic)) != 1, "EVP_DigestUpdate(magic)");
+ ERR(EVP_DigestUpdate(ctx, &pos_be, sizeof(pos_be)) != 1, "EVP_DigestUpdate(pos)");
+ ERR(EVP_DigestUpdate(ctx, data, size) != 1, "EVP_DigestUpdate(data)");
+ ERR(EVP_DigestFinal_ex(ctx, ret_hash->h, NULL) != 1, "EVP_DigestFinal_ex()");
+}
+
+static void hash_entry(const struct hash *left, const struct hash *right, struct hash *ret_hash)
+{
+ uint8_t magic = 0x02; /* domain separation prefix */
+
+ ERR(EVP_DigestInit_ex(ctx, NULL, NULL) != 1, "EVP_DigestInit_ex()");
+ ERR(EVP_DigestUpdate(ctx, &magic, sizeof(magic)) != 1, "EVP_DigestUpdate(magic)");
+ ERR(EVP_DigestUpdate(ctx, left, hash_size) != 1, "EVP_DigestUpdate(left)");
+ ERR(EVP_DigestUpdate(ctx, right, hash_size) != 1, "EVP_DigestUpdate(right)");
+ ERR(EVP_DigestFinal_ex(ctx, ret_hash->h, NULL) != 1, "EVP_DigestFinal_ex()");
+}
+
+static void hash_file(struct file_entry *fe)
+{
+ unsigned char *mem;
+ struct stat sb;
+ int fd, ret;
+
+ fd = open(fe->name, O_RDONLY);
+ if (fd < 0)
+ err(1, "Failed to open %s", fe->name);
+
+ ret = fstat(fd, &sb);
+ if (ret)
+ err(1, "Failed to stat %s", fe->name);
+
+ mem = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
+ if (mem == MAP_FAILED)
+ err(1, "Failed to mmap %s", fe->name);
+
+ hash_data(fe->pos, mem, sb.st_size, &fe->hash);
+
+ munmap(mem, sb.st_size);
+ close(fd);
+}
+
+static struct mtree *build_merkle(struct file_entry *files, size_t num_files)
+{
+ unsigned int num_cur_le, num_prev_le;
+ struct mtree *mt;
+
+ if (!num_files)
+ return NULL;
+
+ mt = xmalloc(sizeof(*mt));
+ mt->num_levels = log2_roundup(num_files);
+
+ mt->level_hashes = xcalloc(sizeof(*mt->level_hashes), mt->num_levels);
+
+ mt->entries = xcalloc(sizeof(*mt->entries), mt->num_levels);
+ num_cur_le = (num_files + 1) / 2;
+ mt->entries[0] = num_cur_le;
+ mt->level_hashes[0] = xcalloc(sizeof(**mt->level_hashes), num_cur_le);
+
+ /* First level of pairs */
+ for (size_t i = 0; i < num_files; i += 2) {
+ /* Hash the pair, or the last file with itself if it's odd. */
+ const struct hash *right = i + 1 < num_files ? &files[i + 1].hash : &files[i].hash;
+
+ hash_entry(&files[i].hash, right, &mt->level_hashes[0][i / 2]);
+ }
+
+ for (unsigned int i = 1; i < mt->num_levels; i++) {
+ num_prev_le = num_cur_le;
+
+ num_cur_le = (num_prev_le + 1) / 2;
+ mt->entries[i] = num_cur_le;
+ mt->level_hashes[i] = xcalloc(sizeof(**mt->level_hashes), num_cur_le);
+
+ for (unsigned int n = 0; n < num_prev_le; n += 2) {
+ /* Hash the pair, or the last with itself if it's odd. */
+ const struct hash *right = n + 1 < num_prev_le ?
+ &mt->level_hashes[i - 1][n + 1] :
+ &mt->level_hashes[i - 1][n];
+ hash_entry(&mt->level_hashes[i - 1][n], right,
+ &mt->level_hashes[i][n / 2]);
+ }
+ }
+
+ /* FIXME assert single hash in root */
+
+ return mt;
+}
+
+static void free_mtree(struct mtree *mt)
+{
+ if (!mt)
+ return;
+
+ for (unsigned int i = 0; i < mt->num_levels; i++)
+ free(mt->level_hashes[i]);
+
+ free(mt->level_hashes);
+ free(mt->entries);
+ free(mt);
+}
+
+static void write_be_int(int fd, unsigned int v)
+{
+ unsigned int be_val = htobe32(v);
+
+ if (write(fd, &be_val, sizeof(be_val)) != sizeof(be_val))
+ err(1, "Failed writing to file");
+}
+
+static void write_hash(int fd, const struct hash *hash)
+{
+ if (write(fd, hash->h, hash_size) != hash_size)
+ err(1, "Failed writing to file");
+}
+
+static void build_proof(struct mtree *mt, unsigned int n, int fd)
+{
+ struct file_entry *fe, *fe_sib;
+
+ fe = &fh_list[n];
+
+ if ((n & 1) == 0) {
+ /* No pair, hash with itself */
+ if (n + 1 == num_files)
+ fe_sib = fe;
+ else
+ fe_sib = &fh_list[n + 1];
+ } else {
+ fe_sib = &fh_list[n - 1];
+ }
+ /* First comes the node position into the file */
+ write_be_int(fd, n);
+
+ /* Next is the sibling hash, followed by hashes in the tree */
+ write_hash(fd, &fe_sib->hash);
+
+ for (unsigned int i = 0; i < mt->num_levels - 1; i++) {
+ n >>= 1;
+ if ((n & 1) == 0) {
+ const struct hash *h;
+
+ /* No pair, hash with itself */
+ if (n + 1 == mt->entries[i])
+ h = &mt->level_hashes[i][n];
+ else
+ h = &mt->level_hashes[i][n + 1];
+
+ write_hash(fd, h);
+ } else {
+ write_hash(fd, &mt->level_hashes[i][n - 1]);
+ }
+ }
+}
+
+static void append_module_signature_magic(int fd, unsigned int sig_len)
+{
+ const struct module_signature sig_info = {
+ .id_type = MODULE_SIGNATURE_TYPE_MERKLE,
+ .sig_len = htobe32(sig_len),
+ };
+ const size_t sig_str_len = sizeof(MODULE_SIGNATURE_MARKER) - 1;
+ const char *sig_str = MODULE_SIGNATURE_MARKER;
+
+ if (write(fd, &sig_info, sizeof(sig_info)) != sizeof(sig_info))
+ err(1, "write(sig_info) failed");
+
+ if (write(fd, sig_str, sig_str_len) != sig_str_len)
+ err(1, "write(magic_number) failed");
+}
+
+static void write_merkle_root(struct mtree *mt, const char *filename)
+{
+ unsigned int num_levels;
+ struct hash *h;
+ FILE *f;
+
+ if (mt) {
+ num_levels = mt->num_levels;
+ h = &mt->level_hashes[mt->num_levels - 1][0];
+ } else {
+ num_levels = 0;
+ h = xcalloc(1, hash_size);
+ }
+
+ f = fopen(filename, "w");
+ if (!f)
+ err(1, "Failed to create %s", filename);
+
+ fprintf(f, "#include <linux/module_hashes.h>\n\n");
+ fprintf(f, "const struct\n");
+ fprintf(f, "module_hashes_root module_hashes_root __module_hashes_section = {\n");
+
+ fprintf(f, "\t.levels = %u,\n", num_levels);
+ fprintf(f, "\t.hash = {{");
+ for (unsigned int i = 0; i < hash_size; i++) {
+ char *space = "";
+
+ if (!(i % 8))
+ fprintf(f, "\n\t\t");
+
+ if ((i + 1) % 8)
+ space = " ";
+
+ fprintf(f, "0x%02x,%s", h->h[i], space);
+ }
+ fprintf(f, "\n\t}},");
+
+ fprintf(f, "\n};\n");
+
+ if (fclose(f))
+ err(1, "Failed to write %s", filename);
+
+ if (!mt)
+ free(h);
+}
+
+static char *xstrdup_replace_suffix(const char *str, const char *old_suffix, const char *new_suffix)
+{
+ size_t str_len, old_suffix_len, base_len;
+
+ str_len = strlen(str);
+ old_suffix_len = strlen(old_suffix);
+ base_len = str_len - old_suffix_len;
+
+ if (old_suffix_len > str_len || memcmp(str + base_len, old_suffix, old_suffix_len) != 0)
+ errx(1, "'%s' does not end in '%s'", str, old_suffix);
+
+ return xasprintf("%.*s%s", (int)base_len, str, new_suffix);
+}
+
+static void trim_newline(char *line)
+{
+ size_t len;
+
+ if (!line)
+ return;
+
+ len = strlen(line);
+ if (!len)
+ return;
+
+ if (line[len - 1] == '\n')
+ line[len - 1] = '\0';
+}
+
+static void read_modules_order(const char *fname, const char *suffix)
+{
+ char line[PATH_MAX];
+ FILE *in;
+
+ in = fopen(fname, "r");
+ if (!in)
+ err(1, "Failed to open %s", fname);
+
+ while (fgets(line, PATH_MAX, in)) {
+ struct file_entry *entry;
+
+ trim_newline(line);
+
+ fh_list = xreallocarray(fh_list, num_files + 1, sizeof(*fh_list));
+ entry = &fh_list[num_files];
+
+ entry->pos = num_files;
+ entry->name = xstrdup_replace_suffix(line, ".o", suffix);
+ hash_file(entry);
+
+ num_files++;
+ }
+
+ if (ferror(in))
+ errx(1, "Failed to read %s", fname);
+
+ fclose(in);
+}
+
+static __attribute__((noreturn))
+void usage(void)
+{
+ fprintf(stderr,
+ "Usage: scripts/modules-merkle-tree <kmod suffix> <root definition>\n");
+ exit(2);
+}
+
+int main(int argc, char *argv[])
+{
+ const char *kmod_suffix;
+ const EVP_MD *hash_evp;
+ struct mtree *mt;
+
+ if (argc != 3)
+ usage();
+
+ kmod_suffix = argv[2];
+
+ hash_evp = EVP_sha256();
+ ERR(!hash_evp, "EVP_sha256()");
+
+ ctx = EVP_MD_CTX_new();
+ ERR(!ctx, "EVP_MD_CTX_new()");
+
+ hash_size = EVP_MD_get_size(hash_evp);
+ ERR(hash_size <= 0, "EVP_get_digestbyname");
+
+ if (hash_size != sizeof(struct hash))
+ errx(1, "Invalid hash size");
+
+ if (EVP_DigestInit_ex(ctx, hash_evp, NULL) != 1)
+ ERR(1, "EVP_DigestInit_ex()");
+
+ read_modules_order("modules.order", kmod_suffix);
+
+ mt = build_merkle(fh_list, num_files);
+ write_merkle_root(mt, argv[1]);
+ for (size_t i = 0; i < num_files; i++) {
+ char *signame;
+ int fd;
+
+ signame = xstrdup_replace_suffix(fh_list[i].name, kmod_suffix, ".merkle");
+
+ fd = open(signame, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ if (fd < 0)
+ err(1, "Can't create %s", signame);
+
+ build_proof(mt, i, fd);
+ append_module_signature_magic(fd, lseek(fd, 0, SEEK_CUR));
+ if (close(fd))
+ err(1, "Can't write %s", signame);
+ }
+
+ free_mtree(mt);
+ for (size_t i = 0; i < num_files; i++)
+ free(fh_list[i].name);
+ free(fh_list);
+
+ EVP_MD_CTX_free(ctx);
+ return 0;
+}
diff --git a/security/lockdown/Kconfig b/security/lockdown/Kconfig
index 155959205b8e..60b240e3ef1f 100644
--- a/security/lockdown/Kconfig
+++ b/security/lockdown/Kconfig
@@ -1,7 +1,7 @@
config SECURITY_LOCKDOWN_LSM
bool "Basic module for enforcing kernel lockdown"
depends on SECURITY
- depends on !MODULES || MODULE_SIG
+ depends on !MODULES || MODULE_SIG || MODULE_HASHES
help
Build support for an LSM that enforces a coarse kernel lockdown
behaviour.
diff --git a/tools/include/uapi/linux/module_signature.h b/tools/include/uapi/linux/module_signature.h
index 634c9f1c8fc2..78e206996eed 100644
--- a/tools/include/uapi/linux/module_signature.h
+++ b/tools/include/uapi/linux/module_signature.h
@@ -16,6 +16,7 @@
enum module_signature_type {
MODULE_SIGNATURE_TYPE_PKCS7 = 2, /* Signature in PKCS#7 message */
+ MODULE_SIGNATURE_TYPE_MERKLE = 3, /* Merkle proof for modules, opaque structure */
};
/*
--
2.54.0
^ permalink raw reply related
* [PATCH v5 14/14] kbuild: make CONFIG_MODULE_HASHES compatible with module stripping
From: Thomas Weißschuh @ 2026-05-05 9:05 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Nathan Chancellor,
Nicolas Schier, Arnd Bergmann, Luis Chamberlain, Petr Pavlu,
Sami Tolvanen, Daniel Gomez, Paul Moore, James Morris,
Serge E. Hallyn, Jonathan Corbet, Madhavan Srinivasan,
Michael Ellerman, Nicholas Piggin, Naveen N Rao, Mimi Zohar,
Roberto Sassu, Dmitry Kasatkin, Eric Snowberg, Nicolas Schier,
Daniel Gomez, Aaron Tomlin, Christophe Leroy (CS GROUP),
Nicolas Bouchinet, Xiu Jianfeng, Christophe Leroy
Cc: Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa, bpf,
Fabian Grünbichler, Arnout Engelen, Mattia Rizzolo, kpcyrd,
Christian Heusel, Câju Mihai-Drosi, Eric Biggers,
Sebastian Andrzej Siewior, linux-kbuild, linux-kernel, linux-arch,
linux-modules, linux-security-module, linux-doc, linuxppc-dev,
linux-integrity, debian-kernel, Thomas Weißschuh
In-Reply-To: <20260505-module-hashes-v5-0-e174a5a49fce@weissschuh.net>
CONFIG_MODULE_HASHES needs to process the modules at build time in the
exact form they will be loaded at runtime. If the modules are stripped
afterwards they will not be loadable anymore.
Also evaluate INSTALL_MOD_STRIP at build time and build the hashes based
on modules stripped this way.
If users specify inconsistent values of INSTALL_MOD_STRIP between build
and installation time, an error is reported.
Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
---
.gitignore | 1 +
kernel/module/Kconfig | 5 +++++
scripts/Makefile.modfinal | 9 +++++++++
scripts/Makefile.modinst | 4 ++--
scripts/Makefile.vmlinux | 2 +-
5 files changed, 18 insertions(+), 3 deletions(-)
diff --git a/.gitignore b/.gitignore
index 78cf799401e6..6ce10623c5a3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,6 +30,7 @@
*.gz
*.i
*.ko
+*.ko.stripped
*.lex.c
*.ll
*.lst
diff --git a/kernel/module/Kconfig b/kernel/module/Kconfig
index acbbda58e7c8..48be498a4452 100644
--- a/kernel/module/Kconfig
+++ b/kernel/module/Kconfig
@@ -423,6 +423,11 @@ config MODULE_HASHES
Also see the warning in MODULE_SIG about stripping modules.
+# To validate the consistency of INSTALL_MOD_STRIP for MODULE_HASHES
+config MODULE_INSTALL_STRIP
+ string
+ default "$(INSTALL_MOD_STRIP)"
+
config MODULE_ALLOW_MISSING_NAMESPACE_IMPORTS
bool "Allow loading of modules with missing namespace imports"
help
diff --git a/scripts/Makefile.modfinal b/scripts/Makefile.modfinal
index 44a382689a5a..9924a7bb73c5 100644
--- a/scripts/Makefile.modfinal
+++ b/scripts/Makefile.modfinal
@@ -64,7 +64,16 @@ ifdef CONFIG_DEBUG_INFO_BTF_MODULES
endif
+$(call cmd,check_tracepoint)
+%.ko.stripped: %.ko $(wildcard include/config/MODULE_INSTALL_STRIP)
+ $(call cmd,install_mod)
+ $(call cmd,strip_mod)
+
+ifneq ($(CONFIG_MODULE_INSTALL_STRIP),)
+modules.order: $(modules:%.o=%.ko.stripped)
+endif
+
targets += $(modules:%.o=%.ko) $(modules:%.o=%.mod.o) .module-common.o
+targets += $(modules:%.o=%.ko.stripped)
# Update modules.order when a module is (re-)built.
# Allow using it as target dependency.
diff --git a/scripts/Makefile.modinst b/scripts/Makefile.modinst
index b95f613e23c8..fd1fb89bb0bd 100644
--- a/scripts/Makefile.modinst
+++ b/scripts/Makefile.modinst
@@ -68,8 +68,8 @@ __modinst: $(install-y)
ifdef CONFIG_MODULE_HASHES
ifeq ($(KBUILD_EXTMOD),)
-ifdef INSTALL_MOD_STRIP
-$(error CONFIG_MODULE_HASHES and INSTALL_MOD_STRIP are mutually exclusive)
+ifneq ($(INSTALL_MOD_STRIP),$(CONFIG_MODULE_INSTALL_STRIP))
+$(error Inconsistent values for INSTALL_MOD_STRIP between build and installation)
endif
endif
endif
diff --git a/scripts/Makefile.vmlinux b/scripts/Makefile.vmlinux
index a0332c06bde1..a2d170241a2f 100644
--- a/scripts/Makefile.vmlinux
+++ b/scripts/Makefile.vmlinux
@@ -86,7 +86,7 @@ modules.order: vmlinux.unstripped FORCE
$(Q)$(MAKE) -f $(srctree)/Makefile modules
quiet_cmd_modules_merkle_tree = MERKLE $@
- cmd_modules_merkle_tree = $< $@ .ko
+ cmd_modules_merkle_tree = $< $@ $(if $(CONFIG_MODULE_INSTALL_STRIP),.ko.stripped,.ko)
targets += .tmp_module_hashes.c
.tmp_module_hashes.c: $(objtree)/scripts/modules-merkle-tree modules.order FORCE
--
2.54.0
^ permalink raw reply related
* [PATCH v5 10/14] module: Prepare for additional module authentication mechanisms
From: Thomas Weißschuh @ 2026-05-05 9:05 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Nathan Chancellor,
Nicolas Schier, Arnd Bergmann, Luis Chamberlain, Petr Pavlu,
Sami Tolvanen, Daniel Gomez, Paul Moore, James Morris,
Serge E. Hallyn, Jonathan Corbet, Madhavan Srinivasan,
Michael Ellerman, Nicholas Piggin, Naveen N Rao, Mimi Zohar,
Roberto Sassu, Dmitry Kasatkin, Eric Snowberg, Nicolas Schier,
Daniel Gomez, Aaron Tomlin, Christophe Leroy (CS GROUP),
Nicolas Bouchinet, Xiu Jianfeng, Christophe Leroy
Cc: Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa, bpf,
Fabian Grünbichler, Arnout Engelen, Mattia Rizzolo, kpcyrd,
Christian Heusel, Câju Mihai-Drosi, Eric Biggers,
Sebastian Andrzej Siewior, linux-kbuild, linux-kernel, linux-arch,
linux-modules, linux-security-module, linux-doc, linuxppc-dev,
linux-integrity, debian-kernel, Thomas Weißschuh
In-Reply-To: <20260505-module-hashes-v5-0-e174a5a49fce@weissschuh.net>
Reorganize the code to make it easier to add the new hash-based module
authentication.
Also drop the now unnecessary stub for module_sig_check().
Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
---
kernel/module/auth.c | 17 ++++++++++++++---
kernel/module/internal.h | 8 --------
2 files changed, 14 insertions(+), 11 deletions(-)
diff --git a/kernel/module/auth.c b/kernel/module/auth.c
index 21e49eb4967c..2ee512d26790 100644
--- a/kernel/module/auth.c
+++ b/kernel/module/auth.c
@@ -37,6 +37,14 @@ void set_module_sig_enforced(void)
sig_enforce = true;
}
+static __always_inline bool mod_sig_type_valid(enum module_signature_type id_type)
+{
+ if (id_type == MODULE_SIGNATURE_TYPE_PKCS7 && IS_ENABLED(CONFIG_MODULE_SIG))
+ return true;
+
+ return false;
+}
+
static int mod_verify_sig(const void *mod, struct load_info *info)
{
struct module_signature ms;
@@ -48,8 +56,8 @@ static int mod_verify_sig(const void *mod, struct load_info *info)
memcpy(&ms, mod + (modlen - sizeof(ms)), sizeof(ms));
- if (ms.id_type != MODULE_SIGNATURE_TYPE_PKCS7) {
- pr_err("module: not signed with expected PKCS#7 message\n");
+ if (!mod_sig_type_valid(ms.id_type)) {
+ pr_err("module: not signed with expected signature\n");
return -ENOPKG;
}
@@ -61,7 +69,10 @@ static int mod_verify_sig(const void *mod, struct load_info *info)
modlen -= sig_len + sizeof(ms);
info->len = modlen;
- return module_sig_check(mod, modlen, mod + modlen, sig_len);
+ if (ms.id_type == MODULE_SIGNATURE_TYPE_PKCS7 && IS_ENABLED(CONFIG_MODULE_SIG))
+ return module_sig_check(mod, modlen, mod + modlen, sig_len);
+
+ return 0;
}
int module_auth_check(struct load_info *info, int flags)
diff --git a/kernel/module/internal.h b/kernel/module/internal.h
index d923e31a5d8e..aabe7f8e1af4 100644
--- a/kernel/module/internal.h
+++ b/kernel/module/internal.h
@@ -335,15 +335,7 @@ int module_enforce_rwx_sections(const Elf_Ehdr *hdr, const Elf_Shdr *sechdrs,
void module_mark_ro_after_init(const Elf_Ehdr *hdr, Elf_Shdr *sechdrs,
const char *secstrings);
-#ifdef CONFIG_MODULE_SIG
int module_sig_check(const void *mod, size_t mod_len, const void *sig, size_t sig_len);
-#else /* !CONFIG_MODULE_SIG */
-static inline int module_sig_check(const void *mod, size_t mod_len,
- const void *sig, size_t sig_len)
-{
- return 0;
-}
-#endif /* !CONFIG_MODULE_SIG */
#ifdef CONFIG_MODULE_AUTH
int module_auth_check(struct load_info *info, int flags);
--
2.54.0
^ permalink raw reply related
* [PATCH v5 07/14] module: Make module authentication usable without MODULE_SIG
From: Thomas Weißschuh @ 2026-05-05 9:05 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Nathan Chancellor,
Nicolas Schier, Arnd Bergmann, Luis Chamberlain, Petr Pavlu,
Sami Tolvanen, Daniel Gomez, Paul Moore, James Morris,
Serge E. Hallyn, Jonathan Corbet, Madhavan Srinivasan,
Michael Ellerman, Nicholas Piggin, Naveen N Rao, Mimi Zohar,
Roberto Sassu, Dmitry Kasatkin, Eric Snowberg, Nicolas Schier,
Daniel Gomez, Aaron Tomlin, Christophe Leroy (CS GROUP),
Nicolas Bouchinet, Xiu Jianfeng, Christophe Leroy
Cc: Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa, bpf,
Fabian Grünbichler, Arnout Engelen, Mattia Rizzolo, kpcyrd,
Christian Heusel, Câju Mihai-Drosi, Eric Biggers,
Sebastian Andrzej Siewior, linux-kbuild, linux-kernel, linux-arch,
linux-modules, linux-security-module, linux-doc, linuxppc-dev,
linux-integrity, debian-kernel, Thomas Weißschuh
In-Reply-To: <20260505-module-hashes-v5-0-e174a5a49fce@weissschuh.net>
The module authentication functionality will also be used by the
hash-based module authentication. Split it out from CONFIG_MODULE_SIG
so it is usable by both.
Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
---
crypto/algapi.c | 4 ++--
include/linux/module.h | 18 +++++++++---------
kernel/module/Kconfig | 5 ++++-
kernel/module/Makefile | 1 +
kernel/module/auth.c | 32 ++++++++++++++++++++++++++++++++
kernel/module/internal.h | 2 +-
kernel/module/main.c | 8 ++++----
kernel/module/signing.c | 23 +----------------------
8 files changed, 54 insertions(+), 39 deletions(-)
diff --git a/crypto/algapi.c b/crypto/algapi.c
index 37de377719ae..14252b780266 100644
--- a/crypto/algapi.c
+++ b/crypto/algapi.c
@@ -24,8 +24,8 @@ static LIST_HEAD(crypto_template_list);
static inline void crypto_check_module_sig(struct module *mod)
{
- if (fips_enabled && mod && !module_sig_ok(mod))
- panic("Module %s signature verification failed in FIPS mode\n",
+ if (fips_enabled && mod && !module_auth_ok(mod))
+ panic("Module %s authentication failed in FIPS mode\n",
module_name(mod));
}
diff --git a/include/linux/module.h b/include/linux/module.h
index 7566815fabbe..b4760777daad 100644
--- a/include/linux/module.h
+++ b/include/linux/module.h
@@ -437,9 +437,9 @@ struct module {
/* GPL-only exported symbols. */
bool using_gplonly_symbols;
-#ifdef CONFIG_MODULE_SIG
- /* Signature was verified. */
- bool sig_ok;
+#ifdef CONFIG_MODULE_AUTH
+ /* Module was authenticated. */
+ bool auth_ok;
#endif
bool async_probe_requested;
@@ -918,16 +918,16 @@ static inline bool retpoline_module_ok(bool has_retpoline)
}
#endif
-#ifdef CONFIG_MODULE_SIG
+#ifdef CONFIG_MODULE_AUTH
bool is_module_sig_enforced(void);
void set_module_sig_enforced(void);
-static inline bool module_sig_ok(struct module *module)
+static inline bool module_auth_ok(struct module *module)
{
- return module->sig_ok;
+ return module->auth_ok;
}
-#else /* !CONFIG_MODULE_SIG */
+#else /* !CONFIG_MODULE_AUTH */
static inline bool is_module_sig_enforced(void)
{
return false;
@@ -937,11 +937,11 @@ static inline void set_module_sig_enforced(void)
{
}
-static inline bool module_sig_ok(struct module *module)
+static inline bool module_auth_ok(struct module *module)
{
return true;
}
-#endif /* CONFIG_MODULE_SIG */
+#endif /* CONFIG_MODULE_AUTH */
#if defined(CONFIG_MODULES) && defined(CONFIG_KALLSYMS)
int module_kallsyms_on_each_symbol(const char *modname,
diff --git a/kernel/module/Kconfig b/kernel/module/Kconfig
index f535181e0d98..84297da666ff 100644
--- a/kernel/module/Kconfig
+++ b/kernel/module/Kconfig
@@ -271,9 +271,12 @@ config MODULE_SIG
debuginfo strip done by some packagers (such as rpmbuild) and
inclusion into an initramfs that wants the module size reduced.
+config MODULE_AUTH
+ def_bool MODULE_SIG
+
config MODULE_SIG_FORCE
bool "Require modules to be validly signed"
- depends on MODULE_SIG
+ depends on MODULE_AUTH
help
Reject unsigned modules or signed modules for which we don't have a
key. Without this, such modules will simply taint the kernel.
diff --git a/kernel/module/Makefile b/kernel/module/Makefile
index d9e8759a7b05..c7200e293d04 100644
--- a/kernel/module/Makefile
+++ b/kernel/module/Makefile
@@ -14,6 +14,7 @@ obj-y += strict_rwx.o
obj-y += kmod.o
obj-$(CONFIG_MODULE_DEBUG_AUTOLOAD_DUPS) += dups.o
obj-$(CONFIG_MODULE_DECOMPRESS) += decompress.o
+obj-$(CONFIG_MODULE_AUTH) += auth.o
obj-$(CONFIG_MODULE_SIG) += signing.o
obj-$(CONFIG_LIVEPATCH) += livepatch.o
obj-$(CONFIG_MODULES_TREE_LOOKUP) += tree_lookup.o
diff --git a/kernel/module/auth.c b/kernel/module/auth.c
new file mode 100644
index 000000000000..956ac63d9d33
--- /dev/null
+++ b/kernel/module/auth.c
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* Module authentication checker
+ *
+ * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ */
+
+#include <linux/export.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/types.h>
+
+#undef MODULE_PARAM_PREFIX
+#define MODULE_PARAM_PREFIX "module."
+
+static bool sig_enforce = IS_ENABLED(CONFIG_MODULE_SIG_FORCE);
+module_param(sig_enforce, bool_enable_only, 0644);
+
+/*
+ * Export sig_enforce kernel cmdline parameter to allow other subsystems rely
+ * on that instead of directly to CONFIG_MODULE_SIG_FORCE config.
+ */
+bool is_module_sig_enforced(void)
+{
+ return sig_enforce;
+}
+EXPORT_SYMBOL(is_module_sig_enforced);
+
+void set_module_sig_enforced(void)
+{
+ sig_enforce = true;
+}
diff --git a/kernel/module/internal.h b/kernel/module/internal.h
index 006ada7d4e6e..f8f425b167f1 100644
--- a/kernel/module/internal.h
+++ b/kernel/module/internal.h
@@ -68,7 +68,7 @@ struct load_info {
Elf_Shdr *sechdrs;
char *secstrings, *strtab;
unsigned long symoffs, stroffs, init_typeoffs, core_typeoffs;
- bool sig_ok;
+ bool auth_ok;
#ifdef CONFIG_KALLSYMS
unsigned long mod_kallsyms_init_off;
#endif
diff --git a/kernel/module/main.c b/kernel/module/main.c
index 17a352198016..cd8a74df117e 100644
--- a/kernel/module/main.c
+++ b/kernel/module/main.c
@@ -2601,10 +2601,10 @@ static void module_augment_kernel_taints(struct module *mod, struct load_info *i
mod->name);
add_taint_module(mod, TAINT_TEST, LOCKDEP_STILL_OK);
}
-#ifdef CONFIG_MODULE_SIG
- mod->sig_ok = info->sig_ok;
- if (!mod->sig_ok) {
- pr_notice_once("%s: module verification failed: signature "
+#ifdef CONFIG_MODULE_AUTH
+ mod->auth_ok = info->auth_ok;
+ if (!mod->auth_ok) {
+ pr_notice_once("%s: module authentication failed: signature "
"and/or required key missing - tainting "
"kernel\n", mod->name);
add_taint_module(mod, TAINT_UNSIGNED_MODULE, LOCKDEP_STILL_OK);
diff --git a/kernel/module/signing.c b/kernel/module/signing.c
index 69d4b1758540..07a786723221 100644
--- a/kernel/module/signing.c
+++ b/kernel/module/signing.c
@@ -16,27 +16,6 @@
#include <uapi/linux/module.h>
#include "internal.h"
-#undef MODULE_PARAM_PREFIX
-#define MODULE_PARAM_PREFIX "module."
-
-static bool sig_enforce = IS_ENABLED(CONFIG_MODULE_SIG_FORCE);
-module_param(sig_enforce, bool_enable_only, 0644);
-
-/*
- * Export sig_enforce kernel cmdline parameter to allow other subsystems rely
- * on that instead of directly to CONFIG_MODULE_SIG_FORCE config.
- */
-bool is_module_sig_enforced(void)
-{
- return sig_enforce;
-}
-EXPORT_SYMBOL(is_module_sig_enforced);
-
-void set_module_sig_enforced(void)
-{
- sig_enforce = true;
-}
-
/*
* Verify the signature on a module.
*/
@@ -84,7 +63,7 @@ int module_sig_check(struct load_info *info, int flags)
info->len -= markerlen;
err = mod_verify_sig(mod, info);
if (!err) {
- info->sig_ok = true;
+ info->auth_ok = true;
return 0;
}
}
--
2.54.0
^ permalink raw reply related
* [PATCH v5 06/14] module: Switch load_info::len to size_t
From: Thomas Weißschuh @ 2026-05-05 9:05 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Nathan Chancellor,
Nicolas Schier, Arnd Bergmann, Luis Chamberlain, Petr Pavlu,
Sami Tolvanen, Daniel Gomez, Paul Moore, James Morris,
Serge E. Hallyn, Jonathan Corbet, Madhavan Srinivasan,
Michael Ellerman, Nicholas Piggin, Naveen N Rao, Mimi Zohar,
Roberto Sassu, Dmitry Kasatkin, Eric Snowberg, Nicolas Schier,
Daniel Gomez, Aaron Tomlin, Christophe Leroy (CS GROUP),
Nicolas Bouchinet, Xiu Jianfeng, Christophe Leroy
Cc: Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa, bpf,
Fabian Grünbichler, Arnout Engelen, Mattia Rizzolo, kpcyrd,
Christian Heusel, Câju Mihai-Drosi, Eric Biggers,
Sebastian Andrzej Siewior, linux-kbuild, linux-kernel, linux-arch,
linux-modules, linux-security-module, linux-doc, linuxppc-dev,
linux-integrity, debian-kernel, Thomas Weißschuh
In-Reply-To: <20260505-module-hashes-v5-0-e174a5a49fce@weissschuh.net>
Switching the types will make some later changes cleaner.
size_t is also the semantically correct type for this field.
As both 'size_t' and 'unsigned long' are always the same size, this
should be risk-free.
Reviewed-by: Nicolas Schier <nsc@kernel.org>
Acked-by: Nicolas Schier <nsc@kernel.org>
Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
---
kernel/module/internal.h | 2 +-
kernel/module/main.c | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/kernel/module/internal.h b/kernel/module/internal.h
index 071999743341..006ada7d4e6e 100644
--- a/kernel/module/internal.h
+++ b/kernel/module/internal.h
@@ -64,7 +64,7 @@ struct load_info {
/* pointer to module in temporary copy, freed at end of load_module() */
struct module *mod;
Elf_Ehdr *hdr;
- unsigned long len;
+ size_t len;
Elf_Shdr *sechdrs;
char *secstrings, *strtab;
unsigned long symoffs, stroffs, init_typeoffs, core_typeoffs;
diff --git a/kernel/module/main.c b/kernel/module/main.c
index 46dd8d25a605..17a352198016 100644
--- a/kernel/module/main.c
+++ b/kernel/module/main.c
@@ -1898,7 +1898,7 @@ static int validate_section_offset(const struct load_info *info, Elf_Shdr *shdr)
static int elf_validity_ehdr(const struct load_info *info)
{
if (info->len < sizeof(*(info->hdr))) {
- pr_err("Invalid ELF header len %lu\n", info->len);
+ pr_err("Invalid ELF header len %zu\n", info->len);
return -ENOEXEC;
}
if (memcmp(info->hdr->e_ident, ELFMAG, SELFMAG) != 0) {
--
2.54.0
^ permalink raw reply related
* [PATCH v5 09/14] module: Move signature type check out of mod_check_sig()
From: Thomas Weißschuh @ 2026-05-05 9:05 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Nathan Chancellor,
Nicolas Schier, Arnd Bergmann, Luis Chamberlain, Petr Pavlu,
Sami Tolvanen, Daniel Gomez, Paul Moore, James Morris,
Serge E. Hallyn, Jonathan Corbet, Madhavan Srinivasan,
Michael Ellerman, Nicholas Piggin, Naveen N Rao, Mimi Zohar,
Roberto Sassu, Dmitry Kasatkin, Eric Snowberg, Nicolas Schier,
Daniel Gomez, Aaron Tomlin, Christophe Leroy (CS GROUP),
Nicolas Bouchinet, Xiu Jianfeng, Christophe Leroy
Cc: Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa, bpf,
Fabian Grünbichler, Arnout Engelen, Mattia Rizzolo, kpcyrd,
Christian Heusel, Câju Mihai-Drosi, Eric Biggers,
Sebastian Andrzej Siewior, linux-kbuild, linux-kernel, linux-arch,
linux-modules, linux-security-module, linux-doc, linuxppc-dev,
linux-integrity, debian-kernel, Thomas Weißschuh
In-Reply-To: <20260505-module-hashes-v5-0-e174a5a49fce@weissschuh.net>
Additional signature types are about to be added.
As each caller of mod_check_sig() can have different support for these,
move the type validation into the callers.
Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
---
kernel/module/auth.c | 5 +++++
kernel/module_signature.c | 8 +-------
security/integrity/ima/ima_modsig.c | 5 +++++
3 files changed, 11 insertions(+), 7 deletions(-)
diff --git a/kernel/module/auth.c b/kernel/module/auth.c
index 831a13eb0c9b..21e49eb4967c 100644
--- a/kernel/module/auth.c
+++ b/kernel/module/auth.c
@@ -48,6 +48,11 @@ static int mod_verify_sig(const void *mod, struct load_info *info)
memcpy(&ms, mod + (modlen - sizeof(ms)), sizeof(ms));
+ if (ms.id_type != MODULE_SIGNATURE_TYPE_PKCS7) {
+ pr_err("module: not signed with expected PKCS#7 message\n");
+ return -ENOPKG;
+ }
+
ret = mod_check_sig(&ms, modlen, "module");
if (ret)
return ret;
diff --git a/kernel/module_signature.c b/kernel/module_signature.c
index a0eee2fe4368..4d0476bcdb72 100644
--- a/kernel/module_signature.c
+++ b/kernel/module_signature.c
@@ -24,12 +24,6 @@ int mod_check_sig(const struct module_signature *ms, size_t file_len,
if (be32_to_cpu(ms->sig_len) >= file_len - sizeof(*ms))
return -EBADMSG;
- if (ms->id_type != MODULE_SIGNATURE_TYPE_PKCS7) {
- pr_err("%s: not signed with expected PKCS#7 message\n",
- name);
- return -ENOPKG;
- }
-
if (ms->algo != 0 ||
ms->hash != 0 ||
ms->signer_len != 0 ||
@@ -37,7 +31,7 @@ int mod_check_sig(const struct module_signature *ms, size_t file_len,
ms->__pad[0] != 0 ||
ms->__pad[1] != 0 ||
ms->__pad[2] != 0) {
- pr_err("%s: PKCS#7 signature info has unexpected non-zero params\n",
+ pr_err("%s: signature info has unexpected non-zero params\n",
name);
return -EBADMSG;
}
diff --git a/security/integrity/ima/ima_modsig.c b/security/integrity/ima/ima_modsig.c
index 632c746fd81e..ebfcdd368a2a 100644
--- a/security/integrity/ima/ima_modsig.c
+++ b/security/integrity/ima/ima_modsig.c
@@ -57,6 +57,11 @@ int ima_read_modsig(enum ima_hooks func, const void *buf, loff_t buf_len,
buf_len -= marker_len;
sig = (const struct module_signature *)(p - sizeof(*sig));
+ if (sig->id_type != MODULE_SIGNATURE_TYPE_PKCS7) {
+ pr_err("%s: not signed with expected PKCS#7 message\n", func_tokens[func]);
+ return -ENOPKG;
+ }
+
rc = mod_check_sig(sig, buf_len, func_tokens[func]);
if (rc)
return rc;
--
2.54.0
^ permalink raw reply related
* [PATCH v5 08/14] module: Move authentication logic into dedicated new file
From: Thomas Weißschuh @ 2026-05-05 9:05 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, Nathan Chancellor,
Nicolas Schier, Arnd Bergmann, Luis Chamberlain, Petr Pavlu,
Sami Tolvanen, Daniel Gomez, Paul Moore, James Morris,
Serge E. Hallyn, Jonathan Corbet, Madhavan Srinivasan,
Michael Ellerman, Nicholas Piggin, Naveen N Rao, Mimi Zohar,
Roberto Sassu, Dmitry Kasatkin, Eric Snowberg, Nicolas Schier,
Daniel Gomez, Aaron Tomlin, Christophe Leroy (CS GROUP),
Nicolas Bouchinet, Xiu Jianfeng, Christophe Leroy
Cc: Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa, bpf,
Fabian Grünbichler, Arnout Engelen, Mattia Rizzolo, kpcyrd,
Christian Heusel, Câju Mihai-Drosi, Eric Biggers,
Sebastian Andrzej Siewior, linux-kbuild, linux-kernel, linux-arch,
linux-modules, linux-security-module, linux-doc, linuxppc-dev,
linux-integrity, debian-kernel, Thomas Weißschuh
In-Reply-To: <20260505-module-hashes-v5-0-e174a5a49fce@weissschuh.net>
The module authentication functionality will also be used by the
hash-based module authentication. To make it usable even if
CONFIG_MODULE_SIG is disabled, move it to a new file.
Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
---
kernel/module/auth.c | 85 +++++++++++++++++++++++++++++++++++++++++++++
kernel/module/internal.h | 14 ++++++--
kernel/module/main.c | 6 ++--
kernel/module/signing.c | 90 ++----------------------------------------------
4 files changed, 103 insertions(+), 92 deletions(-)
diff --git a/kernel/module/auth.c b/kernel/module/auth.c
index 956ac63d9d33..831a13eb0c9b 100644
--- a/kernel/module/auth.c
+++ b/kernel/module/auth.c
@@ -5,10 +5,16 @@
* Written by David Howells (dhowells@redhat.com)
*/
+#include <linux/errno.h>
#include <linux/export.h>
#include <linux/module.h>
+#include <linux/module_signature.h>
#include <linux/moduleparam.h>
+#include <linux/security.h>
+#include <linux/string.h>
#include <linux/types.h>
+#include <uapi/linux/module.h>
+#include "internal.h"
#undef MODULE_PARAM_PREFIX
#define MODULE_PARAM_PREFIX "module."
@@ -30,3 +36,82 @@ void set_module_sig_enforced(void)
{
sig_enforce = true;
}
+
+static int mod_verify_sig(const void *mod, struct load_info *info)
+{
+ struct module_signature ms;
+ size_t sig_len, modlen = info->len;
+ int ret;
+
+ if (modlen <= sizeof(ms))
+ return -EBADMSG;
+
+ memcpy(&ms, mod + (modlen - sizeof(ms)), sizeof(ms));
+
+ ret = mod_check_sig(&ms, modlen, "module");
+ if (ret)
+ return ret;
+
+ sig_len = be32_to_cpu(ms.sig_len);
+ modlen -= sig_len + sizeof(ms);
+ info->len = modlen;
+
+ return module_sig_check(mod, modlen, mod + modlen, sig_len);
+}
+
+int module_auth_check(struct load_info *info, int flags)
+{
+ int err = -ENODATA;
+ const unsigned long markerlen = sizeof(MODULE_SIGNATURE_MARKER) - 1;
+ const char *reason;
+ const void *mod = info->hdr;
+ bool mangled_module = flags & (MODULE_INIT_IGNORE_MODVERSIONS |
+ MODULE_INIT_IGNORE_VERMAGIC);
+ /*
+ * Do not allow mangled modules as a module with version information
+ * removed is no longer the module that was signed.
+ */
+ if (!mangled_module &&
+ info->len > markerlen &&
+ memcmp(mod + info->len - markerlen, MODULE_SIGNATURE_MARKER, markerlen) == 0) {
+ /* We truncate the module to discard the signature */
+ info->len -= markerlen;
+ err = mod_verify_sig(mod, info);
+ if (!err) {
+ info->auth_ok = true;
+ return 0;
+ }
+ }
+
+ /*
+ * We don't permit modules to be loaded into the trusted kernels
+ * without a valid signature on them, but if we're not enforcing,
+ * certain errors are non-fatal.
+ */
+ switch (err) {
+ case -ENODATA:
+ reason = "unsigned module";
+ break;
+ case -ENOPKG:
+ reason = "module with unsupported crypto";
+ break;
+ case -ENOKEY:
+ reason = "module with unavailable key";
+ break;
+
+ default:
+ /*
+ * All other errors are fatal, including lack of memory,
+ * unparseable signatures, and signature check failures --
+ * even if signatures aren't required.
+ */
+ return err;
+ }
+
+ if (is_module_sig_enforced()) {
+ pr_notice("Loading of %s is rejected\n", reason);
+ return -EKEYREJECTED;
+ }
+
+ return security_locked_down(LOCKDOWN_MODULE_SIGNATURE);
+}
diff --git a/kernel/module/internal.h b/kernel/module/internal.h
index f8f425b167f1..d923e31a5d8e 100644
--- a/kernel/module/internal.h
+++ b/kernel/module/internal.h
@@ -336,14 +336,24 @@ void module_mark_ro_after_init(const Elf_Ehdr *hdr, Elf_Shdr *sechdrs,
const char *secstrings);
#ifdef CONFIG_MODULE_SIG
-int module_sig_check(struct load_info *info, int flags);
+int module_sig_check(const void *mod, size_t mod_len, const void *sig, size_t sig_len);
#else /* !CONFIG_MODULE_SIG */
-static inline int module_sig_check(struct load_info *info, int flags)
+static inline int module_sig_check(const void *mod, size_t mod_len,
+ const void *sig, size_t sig_len)
{
return 0;
}
#endif /* !CONFIG_MODULE_SIG */
+#ifdef CONFIG_MODULE_AUTH
+int module_auth_check(struct load_info *info, int flags);
+#else /* !CONFIG_MODULE_AUTH */
+static inline int module_auth_check(struct load_info *info, int flags)
+{
+ return 0;
+}
+#endif /* !CONFIG_MODULE_AUTH */
+
#ifdef CONFIG_DEBUG_KMEMLEAK
void kmemleak_load_module(const struct module *mod, const struct load_info *info);
#else /* !CONFIG_DEBUG_KMEMLEAK */
diff --git a/kernel/module/main.c b/kernel/module/main.c
index cd8a74df117e..55a010383a8d 100644
--- a/kernel/module/main.c
+++ b/kernel/module/main.c
@@ -3428,8 +3428,8 @@ static int load_module(struct load_info *info, const char __user *uargs,
char *after_dashes;
/*
- * Do the signature check (if any) first. All that
- * the signature check needs is info->len, it does
+ * Do the authentication checks (if any) first. All that
+ * the authentication checks need is info->len, it does
* not need any of the section info. That can be
* set up later. This will minimize the chances
* of a corrupt module causing problems before
@@ -3439,7 +3439,7 @@ static int load_module(struct load_info *info, const char __user *uargs,
* off the sig length at the end of the module, making
* checks against info->len more correct.
*/
- err = module_sig_check(info, flags);
+ err = module_auth_check(info, flags);
if (err)
goto free_copy;
diff --git a/kernel/module/signing.c b/kernel/module/signing.c
index 07a786723221..a49317e3c66f 100644
--- a/kernel/module/signing.c
+++ b/kernel/module/signing.c
@@ -5,98 +5,14 @@
* Written by David Howells (dhowells@redhat.com)
*/
-#include <linux/kernel.h>
-#include <linux/errno.h>
-#include <linux/module.h>
-#include <linux/module_signature.h>
-#include <linux/string.h>
+#include <linux/types.h>
#include <linux/verification.h>
-#include <linux/security.h>
-#include <crypto/public_key.h>
-#include <uapi/linux/module.h>
#include "internal.h"
-/*
- * Verify the signature on a module.
- */
-static int mod_verify_sig(const void *mod, struct load_info *info)
+int module_sig_check(const void *mod, size_t mod_len, const void *sig, size_t sig_len)
{
- struct module_signature ms;
- size_t sig_len, modlen = info->len;
- int ret;
-
- if (modlen <= sizeof(ms))
- return -EBADMSG;
-
- memcpy(&ms, mod + (modlen - sizeof(ms)), sizeof(ms));
-
- ret = mod_check_sig(&ms, modlen, "module");
- if (ret)
- return ret;
-
- sig_len = be32_to_cpu(ms.sig_len);
- modlen -= sig_len + sizeof(ms);
- info->len = modlen;
-
- return verify_pkcs7_signature(mod, modlen, mod + modlen, sig_len,
+ return verify_pkcs7_signature(mod, mod_len, sig, sig_len,
VERIFY_USE_SECONDARY_KEYRING,
VERIFYING_MODULE_SIGNATURE,
NULL, NULL);
}
-
-int module_sig_check(struct load_info *info, int flags)
-{
- int err = -ENODATA;
- const unsigned long markerlen = sizeof(MODULE_SIGNATURE_MARKER) - 1;
- const char *reason;
- const void *mod = info->hdr;
- bool mangled_module = flags & (MODULE_INIT_IGNORE_MODVERSIONS |
- MODULE_INIT_IGNORE_VERMAGIC);
- /*
- * Do not allow mangled modules as a module with version information
- * removed is no longer the module that was signed.
- */
- if (!mangled_module &&
- info->len > markerlen &&
- memcmp(mod + info->len - markerlen, MODULE_SIGNATURE_MARKER, markerlen) == 0) {
- /* We truncate the module to discard the signature */
- info->len -= markerlen;
- err = mod_verify_sig(mod, info);
- if (!err) {
- info->auth_ok = true;
- return 0;
- }
- }
-
- /*
- * We don't permit modules to be loaded into the trusted kernels
- * without a valid signature on them, but if we're not enforcing,
- * certain errors are non-fatal.
- */
- switch (err) {
- case -ENODATA:
- reason = "unsigned module";
- break;
- case -ENOPKG:
- reason = "module with unsupported crypto";
- break;
- case -ENOKEY:
- reason = "module with unavailable key";
- break;
-
- default:
- /*
- * All other errors are fatal, including lack of memory,
- * unparseable signatures, and signature check failures --
- * even if signatures aren't required.
- */
- return err;
- }
-
- if (is_module_sig_enforced()) {
- pr_notice("Loading of %s is rejected\n", reason);
- return -EKEYREJECTED;
- }
-
- return security_locked_down(LOCKDOWN_MODULE_SIGNATURE);
-}
--
2.54.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