Thread (77 messages) 77 messages, 10 authors, 2026-03-11

Re: [PATCH v4 15/17] module: Introduce hash-based integrity checking

From: Thomas Weißschuh <linux@weissschuh.net>
Date: 2026-02-23 21:43:33
Also in: linux-arch, linux-doc, linux-integrity, linux-kbuild, linux-modules, linux-security-module, lkml

On 2026-02-23 19:41:52+0100, Nicolas Schier wrote:
On Mon, Feb 23, 2026 at 08:53:29AM +0100, Thomas Weißschuh wrote:
quoted
On 2026-02-21 22:38:29+0100, Nicolas Schier wrote:
quoted
On Tue, Jan 13, 2026 at 01:28:59PM +0100, Thomas Weißschuh wrote:
quoted
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 <linux@weissschuh.net>
---
 .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(-)
[...]
quoted
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.
I did not want to suggest changing the behaviour, that would make things
more complicated to prevent needless rebuilds.  I just wanted to mention
it here to prevent others from burning time.
Understood.
quoted
quoted
[...]
quoted
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 <sebastian@breakpoint.cc>
+ * Copyright (C) 2025 Thomas Weißschuh <linux@weissschuh.net>
+ *
+ */
+#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.
quoted
(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.
Yeah, ack.
What exactly is the 'ack' for?
* Avoiding to include internal headers?
* Moving the definition to UAPI headers?

(...)
quoted
quoted
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.
I'd like to see some (rough) description in Documentation or in a commit
message at least, otherwise future me will have to ask that again.
Ack in general. I'd prefer to document it in a source code comment,
though. That feels like the best fit to me.


Thomas
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help