On 2026-02-21 22:38:29+0100, Nicolas Schier wrote:
> On Tue, Jan 13, 2026 at 01:28:59PM +0100, Thomas Weißschuh wrote:
> > 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.
> > 
> > Non-builtin 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 <[email protected]>
> > ---
> >  .gitignore                                   |   1 +
> >  Documentation/kbuild/reproducible-builds.rst |   5 +-
> >  Makefile                                     |   8 +-
> >  include/asm-generic/vmlinux.lds.h            |  11 +
> >  include/linux/module_hashes.h                |  25 ++
> >  include/linux/module_signature.h             |   1 +
> >  kernel/module/Kconfig                        |  21 +-
> >  kernel/module/Makefile                       |   1 +
> >  kernel/module/hashes.c                       |  92 ++++++
> >  kernel/module/hashes_root.c                  |   6 +
> >  kernel/module/internal.h                     |   1 +
> >  kernel/module/main.c                         |   4 +-
> >  scripts/.gitignore                           |   1 +
> >  scripts/Makefile                             |   3 +
> >  scripts/Makefile.modfinal                    |  11 +
> >  scripts/Makefile.modinst                     |  13 +
> >  scripts/Makefile.vmlinux                     |   5 +
> >  scripts/link-vmlinux.sh                      |  14 +-
> >  scripts/modules-merkle-tree.c                | 467 
> > +++++++++++++++++++++++++++
> >  security/lockdown/Kconfig                    |   2 +-
> >  20 files changed, 685 insertions(+), 7 deletions(-)
> > 
> [...]
> 
> > diff --git a/kernel/module/hashes_root.c b/kernel/module/hashes_root.c
> > new file mode 100644
> > index 000000000000..1abfcd3aa679
> > --- /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 overridden by link-vmlinux.sh */
> > +const struct module_hashes_root module_hashes_root __module_hashes_section 
> > = {};
> > diff --git a/kernel/module/internal.h b/kernel/module/internal.h
> > index e2d49122c2a1..e22837d3ac76 100644
> > --- a/kernel/module/internal.h
> > +++ b/kernel/module/internal.h
> > @@ -338,6 +338,7 @@ void module_mark_ro_after_init(const Elf_Ehdr *hdr, 
> > Elf_Shdr *sechdrs,
> >                            const char *secstrings);
> >  
> >  int module_sig_check(struct load_info *info, const u8 *sig, size_t 
> > sig_len);
> > +int module_hash_check(struct load_info *info, const u8 *sig, size_t 
> > sig_len);
> >  
> >  #ifdef CONFIG_DEBUG_KMEMLEAK
> >  void kmemleak_load_module(const struct module *mod, const struct load_info 
> > *info);
> > diff --git a/kernel/module/main.c b/kernel/module/main.c
> > index 2a28a0ece809..fa30b6387936 100644
> > --- a/kernel/module/main.c
> > +++ b/kernel/module/main.c
> > @@ -3362,8 +3362,10 @@ static int module_integrity_check(struct load_info 
> > *info, int flags)
> >  
> >     if (IS_ENABLED(CONFIG_MODULE_SIG) && sig_type == PKEY_ID_PKCS7) {
> >             err = module_sig_check(info, sig, sig_len);
> > +   } else if (IS_ENABLED(CONFIG_MODULE_HASHES) && sig_type == 
> > PKEY_ID_MERKLE) {
> > +           err = module_hash_check(info, sig, sig_len);
> >     } else {
> > -           pr_err("module: not signed with expected PKCS#7 message\n");
> > +           pr_err("module: not signed with signature mechanism\n");
> >             err = -ENOPKG;
> 
> To prevent others from running into the same issue:
> 
> My first test got stuck here, as I tested with virtme-ng, which symlinks
> modules from build tree to /lib/modules/$(uname -r)/..., resulting in
> 
>     [   15.956855] module: not signed with signature mechanism
>     modprobe: ERROR: could not insert 'efivarfs': Package not installed
> 
> As the modules_install step was missing, modules were not being signed.

Currently the signing is deferred to installation time to keep in sync
with regular module signing and to keep the logic simpler by not having
to gracefully handle previously-signed files.
But this could be changed.

> [...]
> > diff --git a/scripts/modules-merkle-tree.c b/scripts/modules-merkle-tree.c
> > new file mode 100644
> > index 000000000000..a6ec0e21213b
> > --- /dev/null
> > +++ b/scripts/modules-merkle-tree.c
> > @@ -0,0 +1,467 @@
> > +// SPDX-License-Identifier: GPL-2.0-or-later
> > +/*
> > + * Compute hashes for modules files and build a merkle tree.
> > + *
> > + * Copyright (C) 2025 Sebastian Andrzej Siewior <[email protected]>
> > + * Copyright (C) 2025 Thomas Weißschuh <[email protected]>
> > + *
> > + */
> > +#define _GNU_SOURCE 1
> > +#include <arpa/inet.h>
> > +#include <err.h>
> > +#include <unistd.h>
> > +#include <fcntl.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"
> > +
> > +static int hash_size;
> > +static EVP_MD_CTX *ctx;
> > +
> > +struct module_signature {
> > +   uint8_t         algo;           /* Public-key crypto algorithm [0] */
> > +   uint8_t         hash;           /* Digest algorithm [0] */
> > +   uint8_t         id_type;        /* Key identifier type [PKEY_ID_PKCS7] 
> > */
> > +   uint8_t         signer_len;     /* Length of signer's name [0] */
> > +   uint8_t         key_id_len;     /* Length of key identifier [0] */
> > +   uint8_t         __pad[3];
> > +   uint32_t        sig_len;        /* Length of signature data */
> > +};
> > +
> > +#define PKEY_ID_MERKLE 3
> > +
> > +static const char magic_number[] = "~Module signature appended~\n";
> 
> This here will be the forth definition of struct module_signature,
> increasing the risk of unwanted diversion.  I second Petr's suggestion
> to reuse a _common_ definition instead.

Ack.

> (Here, even include/linux/module_signature.h could be included itself.)

I'd like to avoid including internal headers from other components.
We could move it to an UAPI header. Various other subsystems use those
for not-really-UAPI but still ABI definitions.

(...)

> > +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)
> > +           err(1, "Memory allocation failed");
> > +
> > +   return strp;
> > +}
> 
> Please consider moving these x* functions into scripts/include/xalloc.h
> for reuse.  (I am sure someone else wrote this already, but I can't find
> it...)

Petr suggested it somewhere, it is done for the next revision.

> thanks for all your efforts for reproducibility!
> 
> As I have no clue about that:  Is the patent for merkle trees [1] a
> problem when integrating that here?

That should have expired a long time ago [2].
And fs-verity is also using merkle trees.

> Can you verify if I get the mechanics roughly correct?
> 
>   * Modules are merkle tree leaves.  Modules are built and logically
>     paired by the order from modules.order; a single left-over module is
>     paired with itself.
> 
>   * Hashes of paired modules are hashed again (branch node hash);
>     hashes of pairs of branch nodes' hashes are hashed again;
>     repeat until we reach the single merkle tree root hash
> 
>   * The final merkle tree root hash (and the count of tree levels) is
>     included in vmlinux

The merkle tree code was written by Sebastian so he will have the best
knowledge about it. But this is also my understanding.

> 'make && find . -name '*.ko' -exec rm {} \; && make' does not rebuild
> the in-tree modules.  Shifting the module-hashes support from
> scripts/link-vmlinux.sh to scripts/Makefile.vmlinux might (make it
> easier) to fix this again.

I'll take a look at it.

> [1]: 
> https://worldwide.espacenet.com/patent/search/family/022107098/publication/US4309569A?q=pn%3DUS4309569

[2] 
https://patents.stackexchange.com/questions/17901/validity-of-patent-on-merkle-trees


Thomas

Reply via email to