Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/1.2/patches/2232772/?format=api
{ "id": 2232772, "url": "http://patchwork.ozlabs.org/api/1.2/patches/2232772/?format=api", "web_url": "http://patchwork.ozlabs.org/project/linuxppc-dev/patch/20260505-module-hashes-v5-12-e174a5a49fce@weissschuh.net/", "project": { "id": 2, "url": "http://patchwork.ozlabs.org/api/1.2/projects/2/?format=api", "name": "Linux PPC development", "link_name": "linuxppc-dev", "list_id": "linuxppc-dev.lists.ozlabs.org", "list_email": "linuxppc-dev@lists.ozlabs.org", "web_url": "https://github.com/linuxppc/wiki/wiki", "scm_url": "https://git.kernel.org/pub/scm/linux/kernel/git/powerpc/linux.git", "webscm_url": "https://git.kernel.org/pub/scm/linux/kernel/git/powerpc/linux.git/", "list_archive_url": "https://lore.kernel.org/linuxppc-dev/", "list_archive_url_format": "https://lore.kernel.org/linuxppc-dev/{}/", "commit_url_format": "https://git.kernel.org/pub/scm/linux/kernel/git/powerpc/linux.git/commit/?id={}" }, "msgid": "<20260505-module-hashes-v5-12-e174a5a49fce@weissschuh.net>", "list_archive_url": "https://lore.kernel.org/linuxppc-dev/20260505-module-hashes-v5-12-e174a5a49fce@weissschuh.net/", "date": "2026-05-05T09:05:16", "name": "[v5,12/14] module: Introduce hash-based integrity checking", "commit_ref": null, "pull_url": null, "state": "handled-elsewhere", "archived": false, "hash": "3ae37cd67f6fc0bf2186b78a198667fcff15be50", "submitter": { "id": 82751, "url": "http://patchwork.ozlabs.org/api/1.2/people/82751/?format=api", "name": "Thomas Weißschuh", "email": "linux@weissschuh.net" }, "delegate": null, "mbox": "http://patchwork.ozlabs.org/project/linuxppc-dev/patch/20260505-module-hashes-v5-12-e174a5a49fce@weissschuh.net/mbox/", "series": [ { "id": 502791, "url": "http://patchwork.ozlabs.org/api/1.2/series/502791/?format=api", "web_url": "http://patchwork.ozlabs.org/project/linuxppc-dev/list/?series=502791", "date": "2026-05-05T09:05:17", "name": "module: Introduce hash-based integrity checking", "version": 5, "mbox": "http://patchwork.ozlabs.org/series/502791/mbox/" } ], "comments": "http://patchwork.ozlabs.org/api/patches/2232772/comments/", "check": "pending", "checks": "http://patchwork.ozlabs.org/api/patches/2232772/checks/", "tags": {}, "related": [], "headers": { "Return-Path": "\n <linuxppc-dev+bounces-20458-incoming=patchwork.ozlabs.org@lists.ozlabs.org>", "X-Original-To": [ "incoming@patchwork.ozlabs.org", "linuxppc-dev@lists.ozlabs.org" ], "Delivered-To": "patchwork-incoming@legolas.ozlabs.org", "Authentication-Results": [ "legolas.ozlabs.org;\n\tdkim=pass (1024-bit key;\n unprotected) header.d=weissschuh.net header.i=@weissschuh.net\n header.a=rsa-sha256 header.s=mail header.b=peK6gKbp;\n\tdkim-atps=neutral", "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=lists.ozlabs.org\n (client-ip=112.213.38.117; helo=lists.ozlabs.org;\n envelope-from=linuxppc-dev+bounces-20458-incoming=patchwork.ozlabs.org@lists.ozlabs.org;\n receiver=patchwork.ozlabs.org)", "lists.ozlabs.org;\n arc=none smtp.remote-ip=159.69.126.157", "lists.ozlabs.org;\n dmarc=pass (p=quarantine dis=none) header.from=weissschuh.net", "lists.ozlabs.org;\n\tdkim=pass (1024-bit key;\n unprotected) header.d=weissschuh.net header.i=@weissschuh.net\n header.a=rsa-sha256 header.s=mail header.b=peK6gKbp;\n\tdkim-atps=neutral", "lists.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=weissschuh.net\n (client-ip=159.69.126.157; helo=todd.t-8ch.de;\n envelope-from=linux@weissschuh.net; receiver=lists.ozlabs.org)" ], "Received": [ "from lists.ozlabs.org (lists.ozlabs.org [112.213.38.117])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4g8t6G5Mjwz1yJV\n\tfor <incoming@patchwork.ozlabs.org>; Tue, 05 May 2026 19:11:02 +1000 (AEST)", "from boromir.ozlabs.org (localhost [127.0.0.1])\n\tby lists.ozlabs.org (Postfix) with ESMTP id 4g8t630Xg5z30V2;\n\tTue, 05 May 2026 19:10:51 +1000 (AEST)", "from todd.t-8ch.de (todd.t-8ch.de [159.69.126.157])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519 server-signature RSA-PSS (2048 bits) server-digest\n SHA256)\n\t(No client certificate requested)\n\tby lists.ozlabs.org (Postfix) with ESMTPS id 4g8t5x5cLfz2xMV\n\tfor <linuxppc-dev@lists.ozlabs.org>; Tue, 05 May 2026 19:10:45 +1000 (AEST)" ], "ARC-Seal": "i=1; a=rsa-sha256; d=lists.ozlabs.org; s=201707; t=1777972250;\n\tcv=none;\n b=JLZHHj9SV1WmeXb1neqntbEOap+DT2q+bNZUJtgRJaDrNmlCtcROEZA8jsSl5VopDTsFifySBFXfKaYahAQ8j7DYlYmnfDEG3ZuFoza3ZnQmBMbv8v2aUEcpzyUlnSnQCFo3vqUORrDfsbmB82OMwt6Hn7FTzxYwkcL9Dj8k0hOOC4wb+2uZSw1mokZ9WfpceXl1wUYDvpA5+LDxO9SrQ/Tlxs7NxN+h8GNIIBQllHyYCxzoXZlyxVl4pKgsuDrdbcuua/V3R7ADL0kNSl3HRrusSzghQw6xpeOnx7hXfS5Rez/NUttQk2jFD6qcr37/jCNSF4mBf+vE/cJxFVqjwA==", "ARC-Message-Signature": "i=1; a=rsa-sha256; d=lists.ozlabs.org; s=201707;\n\tt=1777972250; c=relaxed/relaxed;\n\tbh=Hqu5FBUAPLss2ycTnPtsAtk5gjVZPM8cKIAP2mYai14=;\n\th=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References:\n\t In-Reply-To:To:Cc;\n b=c5Y8AeSytdbKt7/2PtfeKupIjI/aMdFEXZwDtmY0fgJgx5p2OiQWxQLWGdO2/4RUZbbHZuu3P1fHF2qsbcD9BRW7lpqDKrEVUc56mx0cW2lVUVh6Kb3LF8djVYiHC2dvkV/tMCYGoNUv8OHG8Mi+3w2eUYq7g22rQ7wuJJNMbxiUmekn46TWkYzfLzvYyvitstvbYu93b9ls8tWp155Gum8WyB02wK6E168IjxqDqtwdfmrhZMSLOfI6Ysd0pk77sB9/SaO8+WYmCRFTLvw08lT6Cl0/9KBRk4uKk0f4JcoZ+n0GrLEyTq+MmukUFG8n9eLacNN5Li6+HezO6xgMXw==", "ARC-Authentication-Results": "i=1; lists.ozlabs.org;\n dmarc=pass (p=quarantine dis=none) header.from=weissschuh.net;\n dkim=pass (1024-bit key;\n unprotected) header.d=weissschuh.net header.i=@weissschuh.net\n header.a=rsa-sha256 header.s=mail header.b=peK6gKbp; dkim-atps=neutral;\n spf=pass (client-ip=159.69.126.157; helo=todd.t-8ch.de;\n envelope-from=linux@weissschuh.net;\n receiver=lists.ozlabs.org) smtp.mailfrom=weissschuh.net", "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=weissschuh.net;\n\ts=mail; t=1777972242;\n\tbh=yof0b38w6FUaPXaXJVuEYnR5HxHXG6atxby+tUyoxFE=;\n\th=From:Date:Subject:References:In-Reply-To:To:Cc:From;\n\tb=peK6gKbpZy9I2phipLr55Fcmd9gtrGuZOeNZtSVObfqt6DpO/b/TAPVcRP8FOdZs3\n\t +FzamGKC2tLwT1aeF7e76QEhToz/xMmPPR2LtOk0y4dB7c99N+h6iXpMIPM0P6+V5e\n\t 71yemcE5YnO7t0cR3Y7DlzDSYtFxX8DhHB/XYvps=", "From": "=?utf-8?q?Thomas_Wei=C3=9Fschuh?= <linux@weissschuh.net>", "Date": "Tue, 05 May 2026 11:05:16 +0200", "Subject": "[PATCH v5 12/14] module: Introduce hash-based integrity checking", "X-Mailing-List": "linuxppc-dev@lists.ozlabs.org", "List-Id": "<linuxppc-dev.lists.ozlabs.org>", "List-Help": "<mailto:linuxppc-dev+help@lists.ozlabs.org>", "List-Owner": "<mailto:linuxppc-dev+owner@lists.ozlabs.org>", "List-Post": "<mailto:linuxppc-dev@lists.ozlabs.org>", "List-Archive": "<https://lore.kernel.org/linuxppc-dev/>,\n <https://lists.ozlabs.org/pipermail/linuxppc-dev/>", "List-Subscribe": "<mailto:linuxppc-dev+subscribe@lists.ozlabs.org>,\n <mailto:linuxppc-dev+subscribe-digest@lists.ozlabs.org>,\n <mailto:linuxppc-dev+subscribe-nomail@lists.ozlabs.org>", "List-Unsubscribe": "<mailto:linuxppc-dev+unsubscribe@lists.ozlabs.org>", "Precedence": "list", "MIME-Version": "1.0", "Content-Type": "text/plain; charset=\"utf-8\"", "Content-Transfer-Encoding": "8bit", "Message-Id": "<20260505-module-hashes-v5-12-e174a5a49fce@weissschuh.net>", "References": "<20260505-module-hashes-v5-0-e174a5a49fce@weissschuh.net>", "In-Reply-To": "<20260505-module-hashes-v5-0-e174a5a49fce@weissschuh.net>", "To": "Alexei Starovoitov <ast@kernel.org>,\n Daniel Borkmann <daniel@iogearbox.net>, Andrii Nakryiko <andrii@kernel.org>,\n Eduard Zingerman <eddyz87@gmail.com>,\n Kumar Kartikeya Dwivedi <memxor@gmail.com>,\n Nathan Chancellor <nathan@kernel.org>, Nicolas Schier <nsc@kernel.org>,\n Arnd Bergmann <arnd@arndb.de>, Luis Chamberlain <mcgrof@kernel.org>,\n Petr Pavlu <petr.pavlu@suse.com>, Sami Tolvanen <samitolvanen@google.com>,\n Daniel Gomez <da.gomez@samsung.com>, Paul Moore <paul@paul-moore.com>,\n James Morris <jmorris@namei.org>, \"Serge E. Hallyn\" <serge@hallyn.com>,\n Jonathan Corbet <corbet@lwn.net>, Madhavan Srinivasan <maddy@linux.ibm.com>,\n Michael Ellerman <mpe@ellerman.id.au>, Nicholas Piggin <npiggin@gmail.com>,\n Naveen N Rao <naveen@kernel.org>, Mimi Zohar <zohar@linux.ibm.com>,\n Roberto Sassu <roberto.sassu@huawei.com>,\n Dmitry Kasatkin <dmitry.kasatkin@gmail.com>,\n Eric Snowberg <eric.snowberg@oracle.com>,\n Nicolas Schier <nicolas.schier@linux.dev>,\n Daniel Gomez <da.gomez@kernel.org>, Aaron Tomlin <atomlin@atomlin.com>,\n \"Christophe Leroy (CS GROUP)\" <chleroy@kernel.org>,\n Nicolas Bouchinet <nicolas.bouchinet@oss.cyber.gouv.fr>,\n Xiu Jianfeng <xiujianfeng@huawei.com>,\n Christophe Leroy <chleroy@kernel.org>", "Cc": "Martin KaFai Lau <martin.lau@linux.dev>, Song Liu <song@kernel.org>,\n Yonghong Song <yonghong.song@linux.dev>, Jiri Olsa <jolsa@kernel.org>,\n bpf@vger.kernel.org,\n =?utf-8?q?Fabian_Gr=C3=BCnbichler?= <f.gruenbichler@proxmox.com>,\n Arnout Engelen <arnout@bzzt.net>, Mattia Rizzolo <mattia@mapreri.org>,\n kpcyrd <kpcyrd@archlinux.org>, Christian Heusel <christian@heusel.eu>,\n\t=?utf-8?q?C=C3=A2ju_Mihai-Drosi?= <mcaju95@gmail.com>,\n Eric Biggers <ebiggers@kernel.org>,\n Sebastian Andrzej Siewior <bigeasy@linutronix.de>,\n linux-kbuild@vger.kernel.org, linux-kernel@vger.kernel.org,\n linux-arch@vger.kernel.org, linux-modules@vger.kernel.org,\n linux-security-module@vger.kernel.org, linux-doc@vger.kernel.org,\n linuxppc-dev@lists.ozlabs.org, linux-integrity@vger.kernel.org,\n debian-kernel@lists.debian.org,\n =?utf-8?q?Thomas_Wei=C3=9Fschuh?= <linux@weissschuh.net>", "X-Mailer": "b4 0.15.2", "X-Developer-Signature": "v=1; a=ed25519-sha256; t=1777971921; l=31710;\n i=linux@weissschuh.net; s=20221212; h=from:subject:message-id;\n bh=yof0b38w6FUaPXaXJVuEYnR5HxHXG6atxby+tUyoxFE=;\n b=siIepplh1FilcLPn8putesLGCzUZGqtVhKp+bmtp42MnyR8HeJmS+FNxtZift5z7h6LEcmcCf\n IP6vBhYlk0YCllMcWb/mTsdHcfZS3SpufWRnpAfpah+MUlMt99oibUB", "X-Developer-Key": "i=linux@weissschuh.net; a=ed25519;\n pk=KcycQgFPX2wGR5azS7RhpBqedglOZVgRPfdFSPB1LNw=", "X-Spam-Status": "No, score=-0.2 required=3.0 tests=DKIM_SIGNED,DKIM_VALID,\n\tDKIM_VALID_AU,DKIM_VALID_EF,SPF_HELO_NONE,SPF_PASS autolearn=disabled\n\tversion=4.0.1 OzLabs 8", "X-Spam-Checker-Version": "SpamAssassin 4.0.1 (2024-03-25) on lists.ozlabs.org" }, "content": "The current signature-based module integrity checking has some drawbacks\nin combination with reproducible builds. Either the module signing key\nis generated at build time, which makes the build unreproducible, or a\nstatic signing key is used, which precludes rebuilds by third parties\nand makes the whole build and packaging process much more complicated.\n\nThe goal is to reach bit-for-bit reproducibility. Excluding certain\nparts of the build output from the reproducibility analysis would be\nerror-prone and force each downstream consumer to introduce new tooling.\n\nIntroduce a new mechanism to ensure only well-known modules are loaded\nby embedding a merkle tree root of all modules built as part of the full\nkernel build into vmlinux.\n\nOut-of-tree modules can be validated as before through signatures.\n\nNormally the .ko module files depend on a fully built vmlinux to be\navailable for modpost validation and BTF generation. With\nCONFIG_MODULE_HASHES, vmlinux now depends on the modules\nto build a merkle tree. This introduces a dependency cycle which is\nimpossible to satisfy. Work around this by building the modules during\nlink-vmlinux.sh, after vmlinux is complete enough for modpost and BTF\nbut before the final module hashes are\n\nThe PKCS7 format which is used for regular module signatures can not\nrepresent Merkle proofs, so a new kind of module signature is\nintroduced. As this signature type is only ever used for builtin\nmodules, no compatibility issues can arise.\n\nSigned-off-by: Thomas Weißschuh <linux@weissschuh.net>\n---\n .gitignore | 1 +\n Documentation/kbuild/reproducible-builds.rst | 5 +-\n Makefile | 7 +-\n include/asm-generic/vmlinux.lds.h | 11 +\n include/linux/module_hashes.h | 29 ++\n include/uapi/linux/module_signature.h | 1 +\n kernel/module/Kconfig | 21 +-\n kernel/module/Makefile | 1 +\n kernel/module/auth.c | 6 +\n kernel/module/hashes.c | 95 ++++++\n kernel/module/hashes_root.c | 6 +\n kernel/module/internal.h | 1 +\n scripts/.gitignore | 1 +\n scripts/Makefile | 4 +\n scripts/Makefile.modinst | 11 +\n scripts/Makefile.vmlinux | 32 +++\n scripts/include/xalloc.h | 29 ++\n scripts/link-vmlinux.sh | 3 +-\n scripts/modules-merkle-tree.c | 416 +++++++++++++++++++++++++++\n security/lockdown/Kconfig | 2 +-\n tools/include/uapi/linux/module_signature.h | 1 +\n 21 files changed, 677 insertions(+), 6 deletions(-)", "diff": "diff --git a/.gitignore b/.gitignore\nindex 3044b9590f05..78cf799401e6 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -36,6 +36,7 @@\n *.lz4\n *.lzma\n *.lzo\n+*.merkle\n *.mod\n *.mod.c\n *.o\ndiff --git a/Documentation/kbuild/reproducible-builds.rst b/Documentation/kbuild/reproducible-builds.rst\nindex bc1eb82211df..b15019678aae 100644\n--- a/Documentation/kbuild/reproducible-builds.rst\n+++ b/Documentation/kbuild/reproducible-builds.rst\n@@ -84,7 +84,10 @@ generate a different temporary key for each build, resulting in the\n modules being unreproducible. However, including a signing key with\n your source would presumably defeat the purpose of signing modules.\n \n-One approach to this is to divide up the build process so that the\n+Instead ``CONFIG_MODULE_HASHES`` can be used to embed a static list\n+of valid modules to load.\n+\n+Another approach to this is to divide up the build process so that the\n unreproducible parts can be treated as sources:\n \n 1. Generate a persistent signing key. Add the certificate for the key\ndiff --git a/Makefile b/Makefile\nindex e27c91ea56fc..def4a2413c43 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1650,7 +1650,9 @@ ifdef CONFIG_MODULES\n \n # By default, build modules as well\n \n+ifndef CONFIG_MODULE_HASHES\n all: modules\n+endif\n \n # When we're building modules with modversions, we need to consider\n # the built-in objects during the descend as well, in order to\n@@ -1666,8 +1668,10 @@ endif\n # is an exception.\n ifdef CONFIG_DEBUG_INFO_BTF_MODULES\n KBUILD_BUILTIN := y\n+ifndef CONFIG_MODULE_HASHES\n modules: vmlinux\n endif\n+endif\n \n modules: modules_prepare\n \n@@ -2068,7 +2072,7 @@ modules.order: $(build-dir)\n # KBUILD_MODPOST_NOFINAL can be set to skip the final link of modules.\n # This is solely useful to speed up test compiles.\n modules: modpost\n-ifneq ($(KBUILD_MODPOST_NOFINAL),1)\n+ifneq ($(CONFIG_MODULE_HASHES)|$(KBUILD_MODPOST_NOFINAL),|1)\n \t$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modfinal\n endif\n \n@@ -2162,6 +2166,7 @@ clean: $(clean-dirs)\n \t\t-o -name '*.c.[012]*.*' \\\n \t\t-o -name '*.ll' \\\n \t\t-o -name '*.gcno' \\\n+\t\t-o -name '*.merkle' \\\n \t\t\\) -type f -print \\\n \t\t-o -name '.tmp_*' -print \\\n \t\t| xargs rm -rf\ndiff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h\nindex 60c8c22fd3e4..661881e5ef96 100644\n--- a/include/asm-generic/vmlinux.lds.h\n+++ b/include/asm-generic/vmlinux.lds.h\n@@ -508,6 +508,8 @@\n \t\t\t\t\t\t\t\t\t\\\n \tPRINTK_INDEX\t\t\t\t\t\t\t\\\n \t\t\t\t\t\t\t\t\t\\\n+\tMODULE_HASHES\t\t\t\t\t\t\t\\\n+\t\t\t\t\t\t\t\t\t\\\n \t/* Kernel symbol table */\t\t\t\t\t\\\n \t__ksymtab : AT(ADDR(__ksymtab) - LOAD_OFFSET) {\t\t\\\n \t\t__start___ksymtab = .;\t\t\t\t\t\\\n@@ -913,6 +915,15 @@\n #define PRINTK_INDEX\n #endif\n \n+#ifdef CONFIG_MODULE_HASHES\n+#define MODULE_HASHES\t\t\t\t\t\t\t\\\n+\t.module_hashes : AT(ADDR(.module_hashes) - LOAD_OFFSET) {\t\\\n+\t\tKEEP(*(SORT(.module_hashes)))\t\t\t\t\\\n+\t}\n+#else\n+#define MODULE_HASHES\n+#endif\n+\n /*\n * Discard .note.GNU-stack, which is emitted as PROGBITS by the compiler.\n * Otherwise, the type of .notes section would become PROGBITS instead of NOTES.\ndiff --git a/include/linux/module_hashes.h b/include/linux/module_hashes.h\nnew file mode 100644\nindex 000000000000..53b34fa12f2d\n--- /dev/null\n+++ b/include/linux/module_hashes.h\n@@ -0,0 +1,29 @@\n+/* SPDX-License-Identifier: GPL-2.0-or-later */\n+\n+#ifndef _LINUX_MODULE_HASHES_H\n+#define _LINUX_MODULE_HASHES_H\n+\n+#include <linux/compiler_attributes.h>\n+#include <linux/types.h>\n+#include <crypto/sha2.h>\n+\n+#define __module_hashes_section __section(\".module_hashes\")\n+#define MODULE_HASHES_HASH_SIZE SHA256_DIGEST_SIZE\n+\n+struct module_hash {\n+\tu8 h[MODULE_HASHES_HASH_SIZE];\n+};\n+\n+struct module_hashes_proof {\n+\t__be32 pos;\n+\tstruct module_hash hash_sigs[];\n+} __packed;\n+\n+struct module_hashes_root {\n+\tu32 levels;\n+\tstruct module_hash hash;\n+};\n+\n+extern const struct module_hashes_root module_hashes_root;\n+\n+#endif /* _LINUX_MODULE_HASHES_H */\ndiff --git a/include/uapi/linux/module_signature.h b/include/uapi/linux/module_signature.h\nindex 634c9f1c8fc2..78e206996eed 100644\n--- a/include/uapi/linux/module_signature.h\n+++ b/include/uapi/linux/module_signature.h\n@@ -16,6 +16,7 @@\n \n enum module_signature_type {\n \tMODULE_SIGNATURE_TYPE_PKCS7 = 2,\t/* Signature in PKCS#7 message */\n+\tMODULE_SIGNATURE_TYPE_MERKLE = 3,\t/* Merkle proof for modules, opaque structure */\n };\n \n /*\ndiff --git a/kernel/module/Kconfig b/kernel/module/Kconfig\nindex 84297da666ff..acbbda58e7c8 100644\n--- a/kernel/module/Kconfig\n+++ b/kernel/module/Kconfig\n@@ -272,7 +272,7 @@ config MODULE_SIG\n \t inclusion into an initramfs that wants the module size reduced.\n \n config MODULE_AUTH\n-\tdef_bool MODULE_SIG\n+\tdef_bool MODULE_SIG || MODULE_HASHES\n \n config MODULE_SIG_FORCE\n \tbool \"Require modules to be validly signed\"\n@@ -291,7 +291,7 @@ config MODULE_SIG_ALL\n \t modules must be signed manually, using the scripts/sign-file tool.\n \n comment \"Do not forget to sign required modules with scripts/sign-file\"\n-\tdepends on MODULE_SIG_FORCE && !MODULE_SIG_ALL\n+\tdepends on MODULE_SIG_FORCE && !MODULE_SIG_ALL && !MODULE_HASHES\n \n choice\n \tprompt \"Hash algorithm to sign modules\"\n@@ -406,6 +406,23 @@ config MODULE_DECOMPRESS\n \n endif # MODULE_COMPRESS\n \n+config MODULE_HASHES\n+\tbool \"Hash-based module authentication\"\n+\tdepends on !MODULE_SIG_ALL\n+\tdepends on !IMA_APPRAISE_MODSIG\n+\tselect MODULE_SIG_FORMAT\n+\tselect CRYPTO_LIB_SHA256\n+\thelp\n+\t Validate modules by their hashes.\n+\t Only modules built together with the main kernel image can be\n+\t validated that way.\n+\n+\t This is a reproducible-build compatible alternative to a build-time\n+\t generated module keyring, as enabled by\n+\t CONFIG_MODULE_SIG_KEY=certs/signing_key.pem.\n+\n+\t Also see the warning in MODULE_SIG about stripping modules.\n+\n config MODULE_ALLOW_MISSING_NAMESPACE_IMPORTS\n \tbool \"Allow loading of modules with missing namespace imports\"\n \thelp\ndiff --git a/kernel/module/Makefile b/kernel/module/Makefile\nindex c7200e293d04..da9420f140e9 100644\n--- a/kernel/module/Makefile\n+++ b/kernel/module/Makefile\n@@ -26,3 +26,4 @@ obj-$(CONFIG_KGDB_KDB) += kdb.o\n obj-$(CONFIG_MODVERSIONS) += version.o\n obj-$(CONFIG_MODULE_UNLOAD_TAINT_TRACKING) += tracking.o\n obj-$(CONFIG_MODULE_STATS) += stats.o\n+obj-$(CONFIG_MODULE_HASHES) += hashes.o hashes_root.o\ndiff --git a/kernel/module/auth.c b/kernel/module/auth.c\nindex 2ee512d26790..cf3fe3f8bd89 100644\n--- a/kernel/module/auth.c\n+++ b/kernel/module/auth.c\n@@ -42,6 +42,9 @@ static __always_inline bool mod_sig_type_valid(enum module_signature_type id_typ\n \tif (id_type == MODULE_SIGNATURE_TYPE_PKCS7 && IS_ENABLED(CONFIG_MODULE_SIG))\n \t\treturn true;\n \n+\tif (id_type == MODULE_SIGNATURE_TYPE_MERKLE && IS_ENABLED(CONFIG_MODULE_HASHES))\n+\t\treturn true;\n+\n \treturn false;\n }\n \n@@ -72,6 +75,9 @@ static int mod_verify_sig(const void *mod, struct load_info *info)\n \tif (ms.id_type == MODULE_SIGNATURE_TYPE_PKCS7 && IS_ENABLED(CONFIG_MODULE_SIG))\n \t\treturn module_sig_check(mod, modlen, mod + modlen, sig_len);\n \n+\tif (ms.id_type == MODULE_SIGNATURE_TYPE_MERKLE && IS_ENABLED(CONFIG_MODULE_HASHES))\n+\t\treturn module_hash_check(mod, modlen, mod + modlen, sig_len);\n+\n \treturn 0;\n }\n \ndiff --git a/kernel/module/hashes.c b/kernel/module/hashes.c\nnew file mode 100644\nindex 000000000000..3d3cf0366f75\n--- /dev/null\n+++ b/kernel/module/hashes.c\n@@ -0,0 +1,95 @@\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+/* Module hash-based integrity checker\n+ *\n+ * Copyright (C) 2025 Thomas Weißschuh <linux@weissschuh.net>\n+ * Copyright (C) 2025 Sebastian Andrzej Siewior <sebastian@breakpoint.cc>\n+ *\n+ * The structure of the Merkle tree is documented in scripts/modules-merkle-tree.c.\n+ */\n+\n+#define pr_fmt(fmt) \"module/hash: \" fmt\n+\n+#include <linux/module_hashes.h>\n+#include <linux/module.h>\n+#include <linux/unaligned.h>\n+\n+#include <crypto/sha2.h>\n+\n+#include \"internal.h\"\n+\n+static __init __maybe_unused int module_hashes_init(void)\n+{\n+\tpr_debug(\"root: levels=%u hash=%*phN\\n\",\n+\t\t module_hashes_root.levels,\n+\t\t (int)sizeof(module_hashes_root.hash), &module_hashes_root.hash);\n+\n+\treturn 0;\n+}\n+\n+#if IS_ENABLED(CONFIG_MODULE_DEBUG)\n+early_initcall(module_hashes_init);\n+#endif\n+\n+static void hash_entry(const struct module_hash *left, const struct module_hash *right,\n+\t\t struct module_hash *out)\n+{\n+\tstruct sha256_ctx ctx;\n+\tu8 magic = 0x02;\n+\n+\tsha256_init(&ctx);\n+\tsha256_update(&ctx, &magic, sizeof(magic));\n+\tsha256_update(&ctx, left->h, sizeof(left->h));\n+\tsha256_update(&ctx, right->h, sizeof(right->h));\n+\tsha256_final(&ctx, out->h);\n+}\n+\n+static void hash_data(const u8 *d, size_t len, unsigned int pos, struct module_hash *out)\n+{\n+\tstruct sha256_ctx ctx;\n+\tu8 magic = 0x01;\n+\t__be32 pos_be;\n+\n+\tpos_be = cpu_to_be32(pos);\n+\n+\tsha256_init(&ctx);\n+\tsha256_update(&ctx, &magic, sizeof(magic));\n+\tsha256_update(&ctx, (const u8 *)&pos_be, sizeof(pos_be));\n+\tsha256_update(&ctx, d, len);\n+\tsha256_final(&ctx, out->h);\n+}\n+\n+static bool module_hashes_verify_proof(u32 pos, const struct module_hash *hash_sigs,\n+\t\t\t\t struct module_hash *cur)\n+{\n+\tfor (unsigned int i = 0; i < module_hashes_root.levels; i++, pos >>= 1) {\n+\t\tif ((pos & 1) == 0)\n+\t\t\thash_entry(cur, &hash_sigs[i], cur);\n+\t\telse\n+\t\t\thash_entry(&hash_sigs[i], cur, cur);\n+\t}\n+\n+\treturn !memcmp(cur, &module_hashes_root.hash, sizeof(module_hashes_root.hash));\n+}\n+\n+int module_hash_check(const void *mod, size_t mod_len, const void *sig, size_t sig_len)\n+{\n+\tconst struct module_hashes_proof *proof;\n+\tstruct module_hash modhash;\n+\tsize_t proof_size;\n+\tu32 pos;\n+\n+\tproof_size = struct_size(proof, hash_sigs, module_hashes_root.levels);\n+\n+\tif (sig_len != proof_size)\n+\t\treturn -ENOPKG;\n+\n+\tproof = (const struct module_hashes_proof *)sig;\n+\tpos = get_unaligned_be32(&proof->pos);\n+\n+\thash_data(mod, mod_len, pos, &modhash);\n+\n+\tif (!module_hashes_verify_proof(pos, proof->hash_sigs, &modhash))\n+\t\treturn -ENOKEY;\n+\n+\treturn 0;\n+}\ndiff --git a/kernel/module/hashes_root.c b/kernel/module/hashes_root.c\nnew file mode 100644\nindex 000000000000..ffb6adfc2193\n--- /dev/null\n+++ b/kernel/module/hashes_root.c\n@@ -0,0 +1,6 @@\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+\n+#include <linux/module_hashes.h>\n+\n+/* Blank dummy data. Will be replaced by the read data during the build */\n+const struct module_hashes_root module_hashes_root __module_hashes_section = {};\ndiff --git a/kernel/module/internal.h b/kernel/module/internal.h\nindex aabe7f8e1af4..259e8ca5cb25 100644\n--- a/kernel/module/internal.h\n+++ b/kernel/module/internal.h\n@@ -336,6 +336,7 @@ void module_mark_ro_after_init(const Elf_Ehdr *hdr, Elf_Shdr *sechdrs,\n \t\t\t const char *secstrings);\n \n int module_sig_check(const void *mod, size_t mod_len, const void *sig, size_t sig_len);\n+int module_hash_check(const void *mod, size_t mod_len, const void *sig, size_t sig_len);\n \n #ifdef CONFIG_MODULE_AUTH\n int module_auth_check(struct load_info *info, int flags);\ndiff --git a/scripts/.gitignore b/scripts/.gitignore\nindex 4215c2208f7e..8dad9b0d3b2d 100644\n--- a/scripts/.gitignore\n+++ b/scripts/.gitignore\n@@ -5,6 +5,7 @@\n /insert-sys-cert\n /kallsyms\n /module.lds\n+/modules-merkle-tree\n /recordmcount\n /rustdoc_test_builder\n /rustdoc_test_gen\ndiff --git a/scripts/Makefile b/scripts/Makefile\nindex c983e09be78c..b6291595d9e8 100644\n--- a/scripts/Makefile\n+++ b/scripts/Makefile\n@@ -11,6 +11,7 @@ hostprogs-always-y\t\t\t\t\t+= sign-file\n hostprogs-always-$(CONFIG_SYSTEM_EXTRA_CERTIFICATE)\t+= insert-sys-cert\n hostprogs-always-$(CONFIG_RUST_KERNEL_DOCTESTS)\t\t+= rustdoc_test_builder\n hostprogs-always-$(CONFIG_RUST_KERNEL_DOCTESTS)\t\t+= rustdoc_test_gen\n+hostprogs-always-$(CONFIG_MODULE_HASHES)\t\t+= modules-merkle-tree\n hostprogs-always-$(CONFIG_TRACEPOINTS)\t\t\t+= tracepoint-update\n \n sorttable-objs := sorttable.o elf-parse.o\n@@ -37,6 +38,9 @@ HOSTCFLAGS_asn1_compiler.o = -I$(srctree)/include\n HOSTCFLAGS_sign-file.o = $(shell $(HOSTPKG_CONFIG) --cflags libcrypto 2> /dev/null)\n HOSTCFLAGS_sign-file.o += -I$(srctree)/tools/include/uapi/\n HOSTLDLIBS_sign-file = $(shell $(HOSTPKG_CONFIG) --libs libcrypto 2> /dev/null || echo -lcrypto)\n+HOSTCFLAGS_modules-merkle-tree.o = $(shell $(HOSTPKG_CONFIG) --cflags libcrypto 2> /dev/null)\n+HOSTCFLAGS_modules-merkle-tree.o += -I$(srctree)/tools/include/uapi/\n+HOSTLDLIBS_modules-merkle-tree = $(shell $(HOSTPKG_CONFIG) --libs libcrypto 2> /dev/null || echo -lcrypto)\n \n ifdef CONFIG_UNWINDER_ORC\n ifeq ($(ARCH),x86_64)\ndiff --git a/scripts/Makefile.modinst b/scripts/Makefile.modinst\nindex 9ba45e5b32b1..68708a039a62 100644\n--- a/scripts/Makefile.modinst\n+++ b/scripts/Makefile.modinst\n@@ -79,6 +79,12 @@ quiet_cmd_install = INSTALL $@\n # as the options to the strip command.\n ifdef INSTALL_MOD_STRIP\n \n+ifdef CONFIG_MODULE_HASHES\n+ifeq ($(KBUILD_EXTMOD),)\n+$(error CONFIG_MODULE_HASHES and INSTALL_MOD_STRIP are mutually exclusive)\n+endif\n+endif\n+\n ifeq ($(INSTALL_MOD_STRIP),1)\n strip-option := --strip-debug\n else\n@@ -116,6 +122,11 @@ quiet_cmd_sign :=\n cmd_sign := :\n endif\n \n+ifeq ($(KBUILD_EXTMOD)|$(CONFIG_MODULE_HASHES),|y)\n+quiet_cmd_sign = MERKLE [M] $@\n+ cmd_sign = cat $(objtree)/$*.merkle >> $@\n+endif\n+\n # Create necessary directories\n $(foreach dir, $(sort $(dir $(install-y))), $(shell mkdir -p $(dir)))\n \ndiff --git a/scripts/Makefile.vmlinux b/scripts/Makefile.vmlinux\nindex 6cc661e5292b..a0332c06bde1 100644\n--- a/scripts/Makefile.vmlinux\n+++ b/scripts/Makefile.vmlinux\n@@ -78,6 +78,33 @@ ifdef CONFIG_BUILDTIME_TABLE_SORT\n vmlinux.unstripped: scripts/sorttable\n endif\n \n+ifdef CONFIG_MODULE_HASHES\n+targets += .tmp_module_hashes.c\n+\n+modules.order: vmlinux.unstripped FORCE\n+\t$(Q)echo \" MAKE modules\"\n+\t$(Q)$(MAKE) -f $(srctree)/Makefile modules\n+\n+quiet_cmd_modules_merkle_tree = MERKLE $@\n+ cmd_modules_merkle_tree = $< $@ .ko\n+\n+targets += .tmp_module_hashes.c\n+.tmp_module_hashes.c: $(objtree)/scripts/modules-merkle-tree modules.order FORCE\n+\t$(call if_changed,modules_merkle_tree)\n+\n+targets += .tmp_module_hashes.o\n+.tmp_module_hashes.o: .tmp_module_hashes.c FORCE\n+\n+quiet_cmd_modules_merkle_tree_root = GEN $@\n+ cmd_modules_merkle_tree_root = $(OBJCOPY) --dump-section .module_hashes=$@ $<\n+\n+targets += .tmp_module_hashes.bin\n+.tmp_module_hashes.bin: .tmp_module_hashes.o FORCE\n+\t$(call if_changed,modules_merkle_tree_root)\n+\n+vmlinux: .tmp_module_hashes.bin\n+endif\n+\n # vmlinux\n # ---------------------------------------------------------------------------\n \n@@ -95,6 +122,11 @@ quiet_cmd_objcopy_vmlinux = OBJCOPY $@\n cmd_objcopy_vmlinux = $(OBJCOPY) $(patsubst %,--set-section-flags %=noload,$(remove-section-y)) $< $@; \\\n $(OBJCOPY) $(addprefix --remove-section=,$(remove-section-y)) $(remove-symbols) $@\n \n+ifdef CONFIG_MODULE_HASHES\n+# Patch module hashes root into vmlinux after modules have been built.\n+ cmd_objcopy_vmlinux += ; $(OBJCOPY) --update-section .module_hashes=.tmp_module_hashes.bin $@\n+endif\n+\n targets += vmlinux\n vmlinux: vmlinux.unstripped FORCE\n \t$(call if_changed,objcopy_vmlinux)\ndiff --git a/scripts/include/xalloc.h b/scripts/include/xalloc.h\nindex cdadb07d0592..8294bc0b836f 100644\n--- a/scripts/include/xalloc.h\n+++ b/scripts/include/xalloc.h\n@@ -3,6 +3,7 @@\n #ifndef XALLOC_H\n #define XALLOC_H\n \n+#include <stdarg.h>\n #include <stdlib.h>\n #include <string.h>\n \n@@ -50,4 +51,32 @@ static inline char *xstrndup(const char *s, size_t n)\n \treturn p;\n }\n \n+static inline void *xreallocarray(void *oldp, size_t n, size_t size)\n+{\n+\tvoid *p;\n+\n+\tp = reallocarray(oldp, n, size);\n+\tif (!p)\n+\t\texit(1);\n+\n+\treturn p;\n+}\n+\n+#ifdef _GNU_SOURCE\n+static inline char *xasprintf(const char *fmt, ...)\n+{\n+\tva_list ap;\n+\tchar *strp;\n+\tint ret;\n+\n+\tva_start(ap, fmt);\n+\tret = vasprintf(&strp, fmt, ap);\n+\tva_end(ap);\n+\tif (ret == -1)\n+\t\texit(1);\n+\n+\treturn strp;\n+}\n+#endif /* _GNU_SOURCE */\n+\n #endif /* XALLOC_H */\ndiff --git a/scripts/link-vmlinux.sh b/scripts/link-vmlinux.sh\nindex f99e196abeea..0edec5ee34dc 100755\n--- a/scripts/link-vmlinux.sh\n+++ b/scripts/link-vmlinux.sh\n@@ -103,7 +103,7 @@ vmlinux_link()\n \t${ld} ${ldflags} -o ${output}\t\t\t\t\t\\\n \t\t${wl}--whole-archive ${objs} ${wl}--no-whole-archive\t\\\n \t\t${wl}--start-group ${libs} ${wl}--end-group\t\t\\\n-\t\t${kallsymso} ${btf_vmlinux_bin_o} ${arch_vmlinux_o} ${ldlibs}\n+\t\t${kallsymso} ${btf_vmlinux_bin_o} ${module_hashes_o} ${arch_vmlinux_o} ${ldlibs}\n }\n \n # Create ${2}.o file with all symbols from the ${1} object file\n@@ -183,6 +183,7 @@ fi\n btf_vmlinux_bin_o=\n btfids_vmlinux=\n kallsymso=\n+module_hashes_o=\n strip_debug=\n generate_map=\n \ndiff --git a/scripts/modules-merkle-tree.c b/scripts/modules-merkle-tree.c\nnew file mode 100644\nindex 000000000000..10e3455d5d7a\n--- /dev/null\n+++ b/scripts/modules-merkle-tree.c\n@@ -0,0 +1,416 @@\n+// SPDX-License-Identifier: GPL-2.0-or-later\n+/*\n+ * Compute hashes for modules files and build a merkle tree.\n+ *\n+ * Copyright (C) 2025 Sebastian Andrzej Siewior <sebastian@breakpoint.cc>\n+ * Copyright (C) 2025 Thomas Weißschuh <linux@weissschuh.net>\n+ *\n+ * Structure of the Merkle tree:\n+ *\n+ * The full built modules are leaf nodes. They are hashed pairwise in the order\n+ * of modules.order to create internal nodes. These in turn are also hashed\n+ * pairwise to create the next higher level of internal nodes. This is repeated\n+ * up to a single root node. In case of an uneven amount of node on a level, the\n+ * last node is paired with itself.\n+ *\n+ * The single root node can then be embedded into vmlinux to validate all modules.\n+ */\n+\n+#define _GNU_SOURCE 1\n+#include <endian.h>\n+#include <err.h>\n+#include <unistd.h>\n+#include <fcntl.h>\n+#include <limits.h>\n+#include <stdarg.h>\n+#include <stdio.h>\n+#include <string.h>\n+#include <stdbool.h>\n+#include <stdlib.h>\n+\n+#include <sys/stat.h>\n+#include <sys/mman.h>\n+\n+#include <openssl/evp.h>\n+#include <openssl/err.h>\n+\n+#include \"ssl-common.h\"\n+\n+#include <linux/module_signature.h>\n+#include <xalloc.h>\n+\n+static int hash_size;\n+static EVP_MD_CTX *ctx;\n+\n+struct hash {\n+\tuint8_t h[32]; /* For sha256 */\n+};\n+\n+struct file_entry {\n+\tchar *name;\n+\tunsigned int pos;\n+\tstruct hash hash;\n+};\n+\n+static struct file_entry *fh_list;\n+static size_t num_files;\n+\n+struct mtree {\n+\tstruct hash **level_hashes;\n+\tunsigned int *entries;\n+\tunsigned int num_levels;\n+};\n+\n+static unsigned int log2_roundup(uint32_t val)\n+{\n+\tif (val <= 1)\n+\t\treturn 1;\n+\treturn 32 - __builtin_clz(val - 1);\n+}\n+\n+static void hash_data(unsigned int pos, unsigned char *data, size_t size, struct hash *ret_hash)\n+{\n+\tuint8_t magic = 0x01; /* domain separation prefix */\n+\tuint32_t pos_be;\n+\n+\tpos_be = htobe32(pos);\n+\n+\tERR(EVP_DigestInit_ex(ctx, NULL, NULL) != 1, \"EVP_DigestInit_ex()\");\n+\tERR(EVP_DigestUpdate(ctx, &magic, sizeof(magic)) != 1, \"EVP_DigestUpdate(magic)\");\n+\tERR(EVP_DigestUpdate(ctx, &pos_be, sizeof(pos_be)) != 1, \"EVP_DigestUpdate(pos)\");\n+\tERR(EVP_DigestUpdate(ctx, data, size) != 1, \"EVP_DigestUpdate(data)\");\n+\tERR(EVP_DigestFinal_ex(ctx, ret_hash->h, NULL) != 1, \"EVP_DigestFinal_ex()\");\n+}\n+\n+static void hash_entry(const struct hash *left, const struct hash *right, struct hash *ret_hash)\n+{\n+\tuint8_t magic = 0x02; /* domain separation prefix */\n+\n+\tERR(EVP_DigestInit_ex(ctx, NULL, NULL) != 1, \"EVP_DigestInit_ex()\");\n+\tERR(EVP_DigestUpdate(ctx, &magic, sizeof(magic)) != 1, \"EVP_DigestUpdate(magic)\");\n+\tERR(EVP_DigestUpdate(ctx, left, hash_size) != 1, \"EVP_DigestUpdate(left)\");\n+\tERR(EVP_DigestUpdate(ctx, right, hash_size) != 1, \"EVP_DigestUpdate(right)\");\n+\tERR(EVP_DigestFinal_ex(ctx, ret_hash->h, NULL) != 1, \"EVP_DigestFinal_ex()\");\n+}\n+\n+static void hash_file(struct file_entry *fe)\n+{\n+\tunsigned char *mem;\n+\tstruct stat sb;\n+\tint fd, ret;\n+\n+\tfd = open(fe->name, O_RDONLY);\n+\tif (fd < 0)\n+\t\terr(1, \"Failed to open %s\", fe->name);\n+\n+\tret = fstat(fd, &sb);\n+\tif (ret)\n+\t\terr(1, \"Failed to stat %s\", fe->name);\n+\n+\tmem = mmap(NULL, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);\n+\tif (mem == MAP_FAILED)\n+\t\terr(1, \"Failed to mmap %s\", fe->name);\n+\n+\thash_data(fe->pos, mem, sb.st_size, &fe->hash);\n+\n+\tmunmap(mem, sb.st_size);\n+\tclose(fd);\n+}\n+\n+static struct mtree *build_merkle(struct file_entry *files, size_t num_files)\n+{\n+\tunsigned int num_cur_le, num_prev_le;\n+\tstruct mtree *mt;\n+\n+\tif (!num_files)\n+\t\treturn NULL;\n+\n+\tmt = xmalloc(sizeof(*mt));\n+\tmt->num_levels = log2_roundup(num_files);\n+\n+\tmt->level_hashes = xcalloc(sizeof(*mt->level_hashes), mt->num_levels);\n+\n+\tmt->entries = xcalloc(sizeof(*mt->entries), mt->num_levels);\n+\tnum_cur_le = (num_files + 1) / 2;\n+\tmt->entries[0] = num_cur_le;\n+\tmt->level_hashes[0] = xcalloc(sizeof(**mt->level_hashes), num_cur_le);\n+\n+\t/* First level of pairs */\n+\tfor (size_t i = 0; i < num_files; i += 2) {\n+\t\t/* Hash the pair, or the last file with itself if it's odd. */\n+\t\tconst struct hash *right = i + 1 < num_files ? &files[i + 1].hash : &files[i].hash;\n+\n+\t\thash_entry(&files[i].hash, right, &mt->level_hashes[0][i / 2]);\n+\t}\n+\n+\tfor (unsigned int i = 1; i < mt->num_levels; i++) {\n+\t\tnum_prev_le = num_cur_le;\n+\n+\t\tnum_cur_le = (num_prev_le + 1) / 2;\n+\t\tmt->entries[i] = num_cur_le;\n+\t\tmt->level_hashes[i] = xcalloc(sizeof(**mt->level_hashes), num_cur_le);\n+\n+\t\tfor (unsigned int n = 0; n < num_prev_le; n += 2) {\n+\t\t\t/* Hash the pair, or the last with itself if it's odd. */\n+\t\t\tconst struct hash *right = n + 1 < num_prev_le ?\n+\t\t\t\t\t\t &mt->level_hashes[i - 1][n + 1] :\n+\t\t\t\t\t\t &mt->level_hashes[i - 1][n];\n+\t\t\thash_entry(&mt->level_hashes[i - 1][n], right,\n+\t\t\t\t &mt->level_hashes[i][n / 2]);\n+\t\t}\n+\t}\n+\n+\t/* FIXME assert single hash in root */\n+\n+\treturn mt;\n+}\n+\n+static void free_mtree(struct mtree *mt)\n+{\n+\tif (!mt)\n+\t\treturn;\n+\n+\tfor (unsigned int i = 0; i < mt->num_levels; i++)\n+\t\tfree(mt->level_hashes[i]);\n+\n+\tfree(mt->level_hashes);\n+\tfree(mt->entries);\n+\tfree(mt);\n+}\n+\n+static void write_be_int(int fd, unsigned int v)\n+{\n+\tunsigned int be_val = htobe32(v);\n+\n+\tif (write(fd, &be_val, sizeof(be_val)) != sizeof(be_val))\n+\t\terr(1, \"Failed writing to file\");\n+}\n+\n+static void write_hash(int fd, const struct hash *hash)\n+{\n+\tif (write(fd, hash->h, hash_size) != hash_size)\n+\t\terr(1, \"Failed writing to file\");\n+}\n+\n+static void build_proof(struct mtree *mt, unsigned int n, int fd)\n+{\n+\tstruct file_entry *fe, *fe_sib;\n+\n+\tfe = &fh_list[n];\n+\n+\tif ((n & 1) == 0) {\n+\t\t/* No pair, hash with itself */\n+\t\tif (n + 1 == num_files)\n+\t\t\tfe_sib = fe;\n+\t\telse\n+\t\t\tfe_sib = &fh_list[n + 1];\n+\t} else {\n+\t\tfe_sib = &fh_list[n - 1];\n+\t}\n+\t/* First comes the node position into the file */\n+\twrite_be_int(fd, n);\n+\n+\t/* Next is the sibling hash, followed by hashes in the tree */\n+\twrite_hash(fd, &fe_sib->hash);\n+\n+\tfor (unsigned int i = 0; i < mt->num_levels - 1; i++) {\n+\t\tn >>= 1;\n+\t\tif ((n & 1) == 0) {\n+\t\t\tconst struct hash *h;\n+\n+\t\t\t/* No pair, hash with itself */\n+\t\t\tif (n + 1 == mt->entries[i])\n+\t\t\t\th = &mt->level_hashes[i][n];\n+\t\t\telse\n+\t\t\t\th = &mt->level_hashes[i][n + 1];\n+\n+\t\t\twrite_hash(fd, h);\n+\t\t} else {\n+\t\t\twrite_hash(fd, &mt->level_hashes[i][n - 1]);\n+\t\t}\n+\t}\n+}\n+\n+static void append_module_signature_magic(int fd, unsigned int sig_len)\n+{\n+\tconst struct module_signature sig_info = {\n+\t\t.id_type\t= MODULE_SIGNATURE_TYPE_MERKLE,\n+\t\t.sig_len\t= htobe32(sig_len),\n+\t};\n+\tconst size_t sig_str_len = sizeof(MODULE_SIGNATURE_MARKER) - 1;\n+\tconst char *sig_str = MODULE_SIGNATURE_MARKER;\n+\n+\tif (write(fd, &sig_info, sizeof(sig_info)) != sizeof(sig_info))\n+\t\terr(1, \"write(sig_info) failed\");\n+\n+\tif (write(fd, sig_str, sig_str_len) != sig_str_len)\n+\t\terr(1, \"write(magic_number) failed\");\n+}\n+\n+static void write_merkle_root(struct mtree *mt, const char *filename)\n+{\n+\tunsigned int num_levels;\n+\tstruct hash *h;\n+\tFILE *f;\n+\n+\tif (mt) {\n+\t\tnum_levels = mt->num_levels;\n+\t\th = &mt->level_hashes[mt->num_levels - 1][0];\n+\t} else {\n+\t\tnum_levels = 0;\n+\t\th = xcalloc(1, hash_size);\n+\t}\n+\n+\tf = fopen(filename, \"w\");\n+\tif (!f)\n+\t\terr(1, \"Failed to create %s\", filename);\n+\n+\tfprintf(f, \"#include <linux/module_hashes.h>\\n\\n\");\n+\tfprintf(f, \"const struct\\n\");\n+\tfprintf(f, \"module_hashes_root module_hashes_root __module_hashes_section = {\\n\");\n+\n+\tfprintf(f, \"\\t.levels = %u,\\n\", num_levels);\n+\tfprintf(f, \"\\t.hash = {{\");\n+\tfor (unsigned int i = 0; i < hash_size; i++) {\n+\t\tchar *space = \"\";\n+\n+\t\tif (!(i % 8))\n+\t\t\tfprintf(f, \"\\n\\t\\t\");\n+\n+\t\tif ((i + 1) % 8)\n+\t\t\tspace = \" \";\n+\n+\t\tfprintf(f, \"0x%02x,%s\", h->h[i], space);\n+\t}\n+\tfprintf(f, \"\\n\\t}},\");\n+\n+\tfprintf(f, \"\\n};\\n\");\n+\n+\tif (fclose(f))\n+\t\terr(1, \"Failed to write %s\", filename);\n+\n+\tif (!mt)\n+\t\tfree(h);\n+}\n+\n+static char *xstrdup_replace_suffix(const char *str, const char *old_suffix, const char *new_suffix)\n+{\n+\tsize_t str_len, old_suffix_len, base_len;\n+\n+\tstr_len = strlen(str);\n+\told_suffix_len = strlen(old_suffix);\n+\tbase_len = str_len - old_suffix_len;\n+\n+\tif (old_suffix_len > str_len || memcmp(str + base_len, old_suffix, old_suffix_len) != 0)\n+\t\terrx(1, \"'%s' does not end in '%s'\", str, old_suffix);\n+\n+\treturn xasprintf(\"%.*s%s\", (int)base_len, str, new_suffix);\n+}\n+\n+static void trim_newline(char *line)\n+{\n+\tsize_t len;\n+\n+\tif (!line)\n+\t\treturn;\n+\n+\tlen = strlen(line);\n+\tif (!len)\n+\t\treturn;\n+\n+\tif (line[len - 1] == '\\n')\n+\t\tline[len - 1] = '\\0';\n+}\n+\n+static void read_modules_order(const char *fname, const char *suffix)\n+{\n+\tchar line[PATH_MAX];\n+\tFILE *in;\n+\n+\tin = fopen(fname, \"r\");\n+\tif (!in)\n+\t\terr(1, \"Failed to open %s\", fname);\n+\n+\twhile (fgets(line, PATH_MAX, in)) {\n+\t\tstruct file_entry *entry;\n+\n+\t\ttrim_newline(line);\n+\n+\t\tfh_list = xreallocarray(fh_list, num_files + 1, sizeof(*fh_list));\n+\t\tentry = &fh_list[num_files];\n+\n+\t\tentry->pos = num_files;\n+\t\tentry->name = xstrdup_replace_suffix(line, \".o\", suffix);\n+\t\thash_file(entry);\n+\n+\t\tnum_files++;\n+\t}\n+\n+\tif (ferror(in))\n+\t\terrx(1, \"Failed to read %s\", fname);\n+\n+\tfclose(in);\n+}\n+\n+static __attribute__((noreturn))\n+void usage(void)\n+{\n+\tfprintf(stderr,\n+\t\t\"Usage: scripts/modules-merkle-tree <kmod suffix> <root definition>\\n\");\n+\texit(2);\n+}\n+\n+int main(int argc, char *argv[])\n+{\n+\tconst char *kmod_suffix;\n+\tconst EVP_MD *hash_evp;\n+\tstruct mtree *mt;\n+\n+\tif (argc != 3)\n+\t\tusage();\n+\n+\tkmod_suffix = argv[2];\n+\n+\thash_evp = EVP_sha256();\n+\tERR(!hash_evp, \"EVP_sha256()\");\n+\n+\tctx = EVP_MD_CTX_new();\n+\tERR(!ctx, \"EVP_MD_CTX_new()\");\n+\n+\thash_size = EVP_MD_get_size(hash_evp);\n+\tERR(hash_size <= 0, \"EVP_get_digestbyname\");\n+\n+\tif (hash_size != sizeof(struct hash))\n+\t\terrx(1, \"Invalid hash size\");\n+\n+\tif (EVP_DigestInit_ex(ctx, hash_evp, NULL) != 1)\n+\t\tERR(1, \"EVP_DigestInit_ex()\");\n+\n+\tread_modules_order(\"modules.order\", kmod_suffix);\n+\n+\tmt = build_merkle(fh_list, num_files);\n+\twrite_merkle_root(mt, argv[1]);\n+\tfor (size_t i = 0; i < num_files; i++) {\n+\t\tchar *signame;\n+\t\tint fd;\n+\n+\t\tsigname = xstrdup_replace_suffix(fh_list[i].name, kmod_suffix, \".merkle\");\n+\n+\t\tfd = open(signame, O_WRONLY | O_CREAT | O_TRUNC, 0644);\n+\t\tif (fd < 0)\n+\t\t\terr(1, \"Can't create %s\", signame);\n+\n+\t\tbuild_proof(mt, i, fd);\n+\t\tappend_module_signature_magic(fd, lseek(fd, 0, SEEK_CUR));\n+\t\tif (close(fd))\n+\t\t\terr(1, \"Can't write %s\", signame);\n+\t}\n+\n+\tfree_mtree(mt);\n+\tfor (size_t i = 0; i < num_files; i++)\n+\t\tfree(fh_list[i].name);\n+\tfree(fh_list);\n+\n+\tEVP_MD_CTX_free(ctx);\n+\treturn 0;\n+}\ndiff --git a/security/lockdown/Kconfig b/security/lockdown/Kconfig\nindex 155959205b8e..60b240e3ef1f 100644\n--- a/security/lockdown/Kconfig\n+++ b/security/lockdown/Kconfig\n@@ -1,7 +1,7 @@\n config SECURITY_LOCKDOWN_LSM\n \tbool \"Basic module for enforcing kernel lockdown\"\n \tdepends on SECURITY\n-\tdepends on !MODULES || MODULE_SIG\n+\tdepends on !MODULES || MODULE_SIG || MODULE_HASHES\n \thelp\n \t Build support for an LSM that enforces a coarse kernel lockdown\n \t behaviour.\ndiff --git a/tools/include/uapi/linux/module_signature.h b/tools/include/uapi/linux/module_signature.h\nindex 634c9f1c8fc2..78e206996eed 100644\n--- a/tools/include/uapi/linux/module_signature.h\n+++ b/tools/include/uapi/linux/module_signature.h\n@@ -16,6 +16,7 @@\n \n enum module_signature_type {\n \tMODULE_SIGNATURE_TYPE_PKCS7 = 2,\t/* Signature in PKCS#7 message */\n+\tMODULE_SIGNATURE_TYPE_MERKLE = 3,\t/* Merkle proof for modules, opaque structure */\n };\n \n /*\n", "prefixes": [ "v5", "12/14" ] }