LinuxPPC-Dev Archive on lore.kernel.org
 help / color / mirror / Atom feed
* Re: [PATCH 5/6] KVM: PPC: Book3S HV: Add support for compat CPU capabilities for KVM on PowerNV
From: Harsh Prateek Bora @ 2026-05-05  9:49 UTC (permalink / raw)
  To: Amit Machhiwal, linuxppc-dev, Madhavan Srinivasan
  Cc: Vaibhav Jain, Nicholas Piggin, Michael Ellerman,
	Christophe Leroy (CS GROUP), kvm, linux-kernel
In-Reply-To: <20260430054906.94431-6-amachhiw@linux.ibm.com>



On 30/04/26 11:19 am, Amit Machhiwal wrote:
> Currently, when booting a compatibility-mode KVM guest (L1) on a PowerNV
> hypervisor (L0), the guest runs with the expected processor
> compatibility level. However, when booting a nested KVM guest (L2)
> inside the L1, QEMU derives the CPU model from the raw host PVR and
> attempts to run the nested guest at that level, instead of honoring the
> compatibility mode of the L1.
> 
> Extend host CPU compatibility capability reporting to support nested
> virtualization on PowerNV systems (PAPR nested API v1).
> 
> For nested API v2 (PowerVM), compatibility capabilities are obtained
> from the hypervisor via the H_GUEST_GET_CAPABILITIES hcall. This
> information is not available on PowerNV systems.
> 
> For nested API v1, derive the compatibility capabilities from the L1
> guest by reading the "cpu-version" property from the device tree, which
> reflects the effective (logical) processor compatibility level. Map this
> value to the corresponding compatibility capability bitmap.
> 
> Introduce a helper to translate CPU version values into compatibility
> capability bits and integrate it into kvmppc_get_compat_cpu_caps().
> 
> This allows userspace to query host CPU compatibility modes on both
> PowerVM and PowerNV platforms via the KVM_PPC_GET_COMPAT_CAPS ioctl.
> 
> Signed-off-by: Amit Machhiwal <amachhiw@linux.ibm.com>
> ---
>   arch/powerpc/kvm/book3s_hv.c | 37 +++++++++++++++++++++++++++++++++++-
>   1 file changed, 36 insertions(+), 1 deletion(-)
> 
> diff --git a/arch/powerpc/kvm/book3s_hv.c b/arch/powerpc/kvm/book3s_hv.c
> index d602d90111d1..25d05f1ccb72 100644
> --- a/arch/powerpc/kvm/book3s_hv.c
> +++ b/arch/powerpc/kvm/book3s_hv.c
> @@ -6516,16 +6516,51 @@ static bool kvmppc_hash_v3_possible(void)
>   	return true;
>   }
>   
> +static int kvmppc_map_compat_capabilities(const __be32 cpu_version,
> +				      unsigned long *capabilities)
> +{
> +	switch (cpu_version) {
> +	case PVR_ARCH_31_P11:
> +		*capabilities |= H_GUEST_CAP_POWER11;

Should this be:

+               *capabilities |= H_GUEST_CAP_POWER11 |
+                                H_GUEST_CAP_POWER10 |
+                                H_GUEST_CAP_POWER9;

Likewise, the remaining as applicable?

> +		break;
> +	case PVR_ARCH_31:
> +		*capabilities |= H_GUEST_CAP_POWER10;
> +		break;
> +	case PVR_ARCH_300:
> +		*capabilities |= H_GUEST_CAP_POWER9;
> +		break;
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
>   
>   static int kvmppc_get_compat_cpu_caps(struct kvm_ppc_compat_caps *host_caps)
>   {
>   
> +	struct device_node *np;
>   	unsigned long capabilities = 0;
> +	const __be32 *prop = NULL;
>   	long rc = -EINVAL;
> +	u32 cpu_version;
>   
>   	if (kvmhv_on_pseries()) {

Commit title says PowerNV, but here were are doing only for PowerVM?

> -		if (kvmhv_is_nestedv2())
> +		if (kvmhv_is_nestedv2()) {
>   			rc = plpar_guest_get_capabilities(0, &capabilities);
> +		} else {
> +			for_each_node_by_type(np, "cpu") {
> +				prop = of_get_property(np, "cpu-version", NULL);
> +				if (prop) {
> +					cpu_version = be32_to_cpup(prop);
> +					break;
> +				}
> +			}
> +			if (!prop)
> +				return -EINVAL;
> +			rc = kvmppc_map_compat_capabilities(cpu_version,
> +								&capabilities);
> +		}
>   		host_caps->compat_capabilities = capabilities;
>   	}
>   



^ permalink raw reply

* Re: [PATCH v5 net-next 15/15] net: dsa: netc: add support for ethtool private statistics
From: Paolo Abeni @ 2026-05-05  9:43 UTC (permalink / raw)
  To: Wei Fang, claudiu.manoil, vladimir.oltean, xiaoning.wang,
	andrew+netdev, davem, edumazet, kuba, robh, krzk+dt, conor+dt,
	f.fainelli, frank.li, chleroy, horms, linux
  Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
	imx
In-Reply-To: <20260430024945.3413973-16-wei.fang@nxp.com>

On 4/30/26 4:49 AM, Wei Fang wrote:
> Implement the ethtool private statistics interface to expose additional
> port-level and MAC-level counters that are not covered by the standard
> IEEE 802.3 statistics. The pMAC counters are only reported when the port
> supports Frame Preemption (802.1Qbu/802.3br).
> 
> Signed-off-by: Wei Fang <wei.fang@nxp.com>
> ---
>  drivers/net/dsa/netc/netc_ethtool.c   | 107 ++++++++++++++++++++++++++
>  drivers/net/dsa/netc/netc_main.c      |   3 +
>  drivers/net/dsa/netc/netc_switch.h    |   9 +++
>  drivers/net/dsa/netc/netc_switch_hw.h |  58 ++++++++++++++
>  4 files changed, 177 insertions(+)
> 
> diff --git a/drivers/net/dsa/netc/netc_ethtool.c b/drivers/net/dsa/netc/netc_ethtool.c
> index ac8940b5a85c..8d04db534347 100644
> --- a/drivers/net/dsa/netc/netc_ethtool.c
> +++ b/drivers/net/dsa/netc/netc_ethtool.c
> @@ -19,6 +19,56 @@ static const struct ethtool_rmon_hist_range netc_rmon_ranges[] = {
>  	{ }
>  };
>  
> +static const struct netc_port_stat netc_port_counters[] = {
> +	{ NETC_PTGSLACR,	"port gate late arrival frames" },
> +	{ NETC_PSDFTCR,	"port SDF transmit frames" },
> +	{ NETC_PSDFDDCR,	"port SDF drop duplicate frames" },
> +	{ NETC_PRXDCR,		"port rx discard frames" },
> +	{ NETC_PRXDCRRR,	"port rx discard read-reset" },
> +	{ NETC_PRXDCRR0,	"port rx discard reason 0" },
> +	{ NETC_PRXDCRR1,	"port rx discard reason 1" },
> +	{ NETC_PTXDCR,		"port tx discard frames" },
> +	{ NETC_PTXDCRRR,	"port tx discard read-reset" },
> +	{ NETC_PTXDCRR0,	"port tx discard reason 0" },
> +	{ NETC_PTXDCRR1,	"port tx discard reason 1" },
> +	{ NETC_BPDCR,		"bridge port discard frames" },
> +	{ NETC_BPDCRRR,	"bridge port discard read-reset" },
> +	{ NETC_BPDCRR0,	"bridge port discard reason 0" },
> +	{ NETC_BPDCRR1,	"bridge port discard reason 1" },
> +};
> +
> +static const struct netc_port_stat netc_emac_counters[] = {
> +	{ NETC_PM_ROCT(0),	"eMAC rx octets" },
> +	{ NETC_PM_RVLAN(0),	"eMAC rx VLAN frames" },
> +	{ NETC_PM_RERR(0),	"eMAC rx frame errors" },
> +	{ NETC_PM_RUCA(0),	"eMAC rx unicast frames" },
> +	{ NETC_PM_RDRP(0),	"eMAC rx dropped packets" },
> +	{ NETC_PM_RPKT(0),	"eMAC rx packets" },
> +	{ NETC_PM_TOCT(0),	"eMAC tx octets" },
> +	{ NETC_PM_TVLAN(0),	"eMAC tx VLAN frames" },
> +	{ NETC_PM_TFCS(0),	"eMAC tx FCS errors" },
> +	{ NETC_PM_TUCA(0),	"eMAC tx unicast frames" },
> +	{ NETC_PM_TPKT(0),	"eMAC tx packets" },
> +	{ NETC_PM_TUND(0),	"eMAC tx undersized packets" },
> +	{ NETC_PM_TIOCT(0),	"eMAC tx invalid octets" },
> +};
> +
> +static const struct netc_port_stat netc_pmac_counters[] = {
> +	{ NETC_PM_ROCT(1),	"pMAC rx octets" },
> +	{ NETC_PM_RVLAN(1),	"pMAC rx VLAN frames" },
> +	{ NETC_PM_RERR(1),	"pMAC rx frame errors" },
> +	{ NETC_PM_RUCA(1),	"pMAC rx unicast frames" },
> +	{ NETC_PM_RDRP(1),	"pMAC rx dropped packets" },
> +	{ NETC_PM_RPKT(1),	"pMAC rx packets" },
> +	{ NETC_PM_TOCT(1),	"pMAC tx octets" },
> +	{ NETC_PM_TVLAN(1),	"pMAC tx VLAN frames" },
> +	{ NETC_PM_TFCS(1),	"pMAC tx FCS errors" },
> +	{ NETC_PM_TUCA(1),	"pMAC tx unicast frames" },
> +	{ NETC_PM_TPKT(1),	"pMAC tx packets" },
> +	{ NETC_PM_TUND(1),	"pMAC tx undersized packets" },
> +	{ NETC_PM_TIOCT(1),	"pMAC tx invalid octets" },
> +};

Sashiko says:

Are these standard MAC and interface statistics being duplicated in the
driver-private ethtool strings?
The networking subsystem guidelines indicate that statistics which have
a standard uAPI must not be duplicated in ethtool -S. Standard
interfaces like struct rtnl_link_stats64 or struct ethtool_eth_mac_stats
already exist for counters such as rx octets, rx packets, dropped
packets, and FCS errors.
Could these metrics be exposed through the standardized interfaces
rather than adding them to the private string sets?

Other comments are AFAICS false positives.

/P



^ permalink raw reply

* Re: [PATCH 4/6] KVM: PPC: Book3S HV: Implement compat CPU capability retrieval for KVM on PowerVM
From: Harsh Prateek Bora @ 2026-05-05  9:25 UTC (permalink / raw)
  To: Amit Machhiwal, linuxppc-dev, Madhavan Srinivasan
  Cc: Vaibhav Jain, Nicholas Piggin, Michael Ellerman,
	Christophe Leroy (CS GROUP), kvm, linux-kernel
In-Reply-To: <20260430054906.94431-5-amachhiw@linux.ibm.com>



On 30/04/26 11:19 am, Amit Machhiwal wrote:
> On POWER systems, the host CPU may run in a compatibility mode (e.g., a
> Power11 processor operating in Power10 compatibility mode). In such
> cases, the effective CPU level exposed to guests differs from the
> physical processor generation.
> 
> When running nested KVM guests, QEMU derives the host CPU type using
> mfpvr(), which reflects the physical processor version. This can result
> in a mismatch between the CPU model selected by QEMU and the
> compatibility mode enforced by the host, leading to guest boot failures.
> 
> For example, booting a nested guest on a Power11 LPAR configured in
> Power10 compatibility mode fails with:
> 
>    KVM-NESTEDv2: couldn't set guest wide elements
>    [..KVM reg dump..]
> 
> This occurs because QEMU selects a CPU model corresponding to the
> physical processor (via mfpvr()), while the host operates in a lower
> compatibility mode. As a result, KVM rejects the requested compatibility
> level during guest initialization.
> 
> Add support for retrieving host CPU compatibility capabilities for
> nested guests on PowerVM (PAPR nested API v2). The hypervisor provides
> the effective compatibility levels via the H_GUEST_GET_CAPABILITIES
> hcall, which reflects the processor modes negotiated between the Power
> hypervisor (L0) and the host partition (L1).
> 
> On pseries systems, obtain the capability bitmap using
> plpar_guest_get_capabilities() and return it via struct
> kvm_ppc_compat_caps. This information is then exposed to userspace
> through the KVM_PPC_GET_COMPAT_CAPS ioctl.
> 
> Hook the implementation into the Book3S HV kvmppc_ops so that it can be
> invoked by the generic KVM ioctl handling code.
> 
> Signed-off-by: Amit Machhiwal <amachhiw@linux.ibm.com>
> ---
>   arch/powerpc/kvm/book3s_hv.c | 17 +++++++++++++++++
>   1 file changed, 17 insertions(+)
> 
> diff --git a/arch/powerpc/kvm/book3s_hv.c b/arch/powerpc/kvm/book3s_hv.c
> index 948c6b099a29..d602d90111d1 100644
> --- a/arch/powerpc/kvm/book3s_hv.c
> +++ b/arch/powerpc/kvm/book3s_hv.c
> @@ -6516,6 +6516,22 @@ static bool kvmppc_hash_v3_possible(void)
>   	return true;
>   }
>   
> +
> +static int kvmppc_get_compat_cpu_caps(struct kvm_ppc_compat_caps *host_caps)
> +{
> +
> +	unsigned long capabilities = 0;
> +	long rc = -EINVAL;
> +
> +	if (kvmhv_on_pseries()) {
> +		if (kvmhv_is_nestedv2())

If this is only intended for nested APIv2, it should reflect in commit 
log/title, otherwise, we need to return success with capabilities set to 
0 for non-nestedv2 case.

Also, better to have combined check for pseries and nestedv2 if we are 
not handling other cases.

> +			rc = plpar_guest_get_capabilities(0, &capabilities);
> +		host_caps->compat_capabilities = capabilities;

Do we need to take care of PowerNV case ?

> +	}
> +
> +	return rc;
> +}
> +
>   static struct kvmppc_ops kvm_ops_hv = {
>   	.get_sregs = kvm_arch_vcpu_ioctl_get_sregs_hv,
>   	.set_sregs = kvm_arch_vcpu_ioctl_set_sregs_hv,
> @@ -6558,6 +6574,7 @@ static struct kvmppc_ops kvm_ops_hv = {
>   	.hash_v3_possible = kvmppc_hash_v3_possible,
>   	.create_vcpu_debugfs = kvmppc_arch_create_vcpu_debugfs_hv,
>   	.create_vm_debugfs = kvmppc_arch_create_vm_debugfs_hv,
> +	.get_compat_cpu_ver = kvmppc_get_compat_cpu_caps,
>   };
>   
>   static int kvm_init_subcore_bitmap(void)



^ permalink raw reply

* [PATCH v5 00/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

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.

Interest has been proclaimed by Arch Linux, Debian, Proxmox, SUSE, NixOS
and the general reproducible builds community.

Compatibility with IMA modsig is not provided yet. It is still unclear
to me if it should be hooked up transparently without any changes to the
policy or it should require new policy options.

BPF/BTF folks, please take a look at patch 1.

Further improvements:
* Use MODULE_SIG_HASH for configuration
* UAPI for discovery?

To: Nathan Chancellor <nathan@kernel.org>
To: Nicolas Schier <nsc@kernel.org>
To: Arnd Bergmann <arnd@arndb.de>
To: Luis Chamberlain <mcgrof@kernel.org>
To: Petr Pavlu <petr.pavlu@suse.com>
To: Sami Tolvanen <samitolvanen@google.com>
To: Daniel Gomez <da.gomez@samsung.com>
To: Paul Moore <paul@paul-moore.com>
To: James Morris <jmorris@namei.org>
To: Serge E. Hallyn <serge@hallyn.com>
To: Jonathan Corbet <corbet@lwn.net>
To: Madhavan Srinivasan <maddy@linux.ibm.com>
To: Michael Ellerman <mpe@ellerman.id.au>
To: Nicholas Piggin <npiggin@gmail.com>
To: Christophe Leroy <christophe.leroy@csgroup.eu>
To: Naveen N Rao <naveen@kernel.org>
To: Mimi Zohar <zohar@linux.ibm.com>
To: Roberto Sassu <roberto.sassu@huawei.com>
To: Dmitry Kasatkin <dmitry.kasatkin@gmail.com>
To: Eric Snowberg <eric.snowberg@oracle.com>
To: Nicolas Schier <nicolas.schier@linux.dev>
To: Daniel Gomez <da.gomez@kernel.org>
To: Aaron Tomlin <atomlin@atomlin.com>
To: Christophe Leroy (CS GROUP) <chleroy@kernel.org>
To: Nicolas Schier <nsc@kernel.org>
To: Nicolas Bouchinet <nicolas.bouchinet@oss.cyber.gouv.fr>
To: Xiu Jianfeng <xiujianfeng@huawei.com>
Cc: Fabian Grünbichler <f.gruenbichler@proxmox.com>
Cc: Arnout Engelen <arnout@bzzt.net>
Cc: Mattia Rizzolo <mattia@mapreri.org>
Cc: kpcyrd <kpcyrd@archlinux.org>
Cc: Christian Heusel <christian@heusel.eu>
Cc: Câju Mihai-Drosi <mcaju95@gmail.com>
Cc: Eric Biggers <ebiggers@kernel.org>
Cc: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
Cc: linux-kbuild@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-arch@vger.kernel.org
Cc: linux-modules@vger.kernel.org
Cc: linux-security-module@vger.kernel.org
Cc: linux-doc@vger.kernel.org
Cc: linuxppc-dev@lists.ozlabs.org
Cc: linux-integrity@vger.kernel.org
Cc: debian-kernel@lists.debian.org
Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>

---
Changes in v5:
- Document tree layout.
- Make scripts/module-merkle-tree more robust.
- Remove all changes to link-vmlinux.sh, use vmlinux.unstripped instead.
- Clean up types and logic in modules-merkle-tree.c.
- Use "auth" over "integrity" naming scheme.
- Reduce the changes to the existing authentication flow.
- Explicitly send the series to BTF folks for review of BTF changes.
- Link to v4: https://patch.msgid.link/20260113-module-hashes-v4-0-0b932db9b56b@weissschuh.net

Changes in v4:
- Use as Merkle tree over a linera list of hashes.
- Provide compatibilith with INSTALL_MOD_STRIP
- Rework commit messages.
- Use vmlinux.unstripped over plain "vmlinux".
- Link to v3: https://lore.kernel.org/r/20250429-module-hashes-v3-0-00e9258def9e@weissschuh.net

Changes in v3:
- Rebase on v6.15-rc1
- Use openssl to calculate hash
- Avoid warning if no modules are built
- Simplify module_integrity_check() a bit
- Make incompatibility with INSTALL_MOD_STRIP explicit
- Update docs
- Add IMA cleanups
- Link to v2: https://lore.kernel.org/r/20250120-module-hashes-v2-0-ba1184e27b7f@weissschuh.net

Changes in v2:
- Drop RFC state
- Mention interested parties in cover letter
- Expand Kconfig description
- Add compatibility with CONFIG_MODULE_SIG
- Parallelize module-hashes.sh
- Update Documentation/kbuild/reproducible-builds.rst
- Link to v1: https://lore.kernel.org/r/20241225-module-hashes-v1-0-d710ce7a3fd1@weissschuh.net

---
Thomas Weißschuh (14):
      kbuild: generate module BTF based on vmlinux.unstripped
      lockdown: Make the relationship to MODULE_SIG a dependency
      kbuild: rename the strip_relocs command
      module: Drop pointless debugging message
      module: Make mod_verify_sig() static
      module: Switch load_info::len to size_t
      module: Make module authentication usable without MODULE_SIG
      module: Move authentication logic into dedicated new file
      module: Move signature type check out of mod_check_sig()
      module: Prepare for additional module authentication mechanisms
      module: update timestamp of modules.order after modules are built
      module: Introduce hash-based integrity checking
      kbuild: move handling of module stripping to Makefile.lib
      kbuild: make CONFIG_MODULE_HASHES compatible with module stripping

 .gitignore                                   |   2 +
 Documentation/kbuild/reproducible-builds.rst |   5 +-
 Makefile                                     |   7 +-
 crypto/algapi.c                              |   4 +-
 include/asm-generic/vmlinux.lds.h            |  11 +
 include/linux/module.h                       |  18 +-
 include/linux/module_hashes.h                |  29 ++
 include/uapi/linux/module_signature.h        |   1 +
 kernel/module/Kconfig                        |  29 +-
 kernel/module/Makefile                       |   2 +
 kernel/module/auth.c                         | 139 +++++++++
 kernel/module/hashes.c                       |  95 ++++++
 kernel/module/hashes_root.c                  |   6 +
 kernel/module/internal.h                     |  18 +-
 kernel/module/main.c                         |  16 +-
 kernel/module/signing.c                      | 113 +-------
 kernel/module_signature.c                    |   8 +-
 scripts/.gitignore                           |   1 +
 scripts/Makefile                             |   4 +
 scripts/Makefile.lib                         |  32 +++
 scripts/Makefile.modfinal                    |  28 +-
 scripts/Makefile.modinst                     |  44 +--
 scripts/Makefile.vmlinux                     |  40 ++-
 scripts/include/xalloc.h                     |  29 ++
 scripts/link-vmlinux.sh                      |   3 +-
 scripts/modules-merkle-tree.c                | 416 +++++++++++++++++++++++++++
 security/integrity/ima/ima_modsig.c          |   5 +
 security/lockdown/Kconfig                    |   2 +-
 tools/include/uapi/linux/module_signature.h  |   1 +
 29 files changed, 919 insertions(+), 189 deletions(-)
---
base-commit: 585c2e775b12ef45bdf9cef5f679dcb1220e0d65
change-id: 20241225-module-hashes-7a50a7cc2a30

Best regards,
--  
Thomas Weißschuh <linux@weissschuh.net>



^ permalink raw reply

* [PATCH v5 04/14] module: Drop pointless debugging message
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>

This is the only instance of pr_devel() in the whole module subsystem
and essentially useless.

Drop it.

Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
---
 kernel/module/signing.c | 2 --
 1 file changed, 2 deletions(-)

diff --git a/kernel/module/signing.c b/kernel/module/signing.c
index 590ba29c85ab..4a5e4eef250d 100644
--- a/kernel/module/signing.c
+++ b/kernel/module/signing.c
@@ -46,8 +46,6 @@ int mod_verify_sig(const void *mod, struct load_info *info)
 	size_t sig_len, modlen = info->len;
 	int ret;
 
-	pr_devel("==>%s(,%zu)\n", __func__, modlen);
-
 	if (modlen <= sizeof(ms))
 		return -EBADMSG;
 

-- 
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 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 05/14] module: Make mod_verify_sig() static
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>

It is not used outside of signing.c.

Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
Reviewed-by: Aaron Tomlin <atomlin@atomlin.com>
Reviewed-by: Nicolas Schier <nsc@kernel.org>
Reviewed-by: Eric Biggers <ebiggers@kernel.org>
---
 kernel/module/internal.h | 1 -
 kernel/module/signing.c  | 2 +-
 2 files changed, 1 insertion(+), 2 deletions(-)

diff --git a/kernel/module/internal.h b/kernel/module/internal.h
index 061161cc79d9..071999743341 100644
--- a/kernel/module/internal.h
+++ b/kernel/module/internal.h
@@ -117,7 +117,6 @@ struct module_use {
 	struct module *source, *target;
 };
 
-int mod_verify_sig(const void *mod, struct load_info *info);
 int try_to_force_load(struct module *mod, const char *reason);
 bool find_symbol(struct find_symbol_arg *fsa);
 struct module *find_module_all(const char *name, size_t len, bool even_unformed);
diff --git a/kernel/module/signing.c b/kernel/module/signing.c
index 4a5e4eef250d..69d4b1758540 100644
--- a/kernel/module/signing.c
+++ b/kernel/module/signing.c
@@ -40,7 +40,7 @@ void set_module_sig_enforced(void)
 /*
  * Verify the signature on a module.
  */
-int mod_verify_sig(const void *mod, struct load_info *info)
+static int mod_verify_sig(const void *mod, struct load_info *info)
 {
 	struct module_signature ms;
 	size_t sig_len, modlen = info->len;

-- 
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

* [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 01/14] kbuild: generate module BTF based on vmlinux.unstripped
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 upcoming module hashes functionality will build the modules in
between the generation of the BTF data and the final link of vmlinux.
At this point vmlinux is not yet built and therefore can't be used for
module BTF generation. vmlinux.unstripped however is usable and
sufficient for BTF generation.

Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
Reviewed-by: Nicolas Schier <nsc@kernel.org>
---
 scripts/Makefile.modfinal | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/scripts/Makefile.modfinal b/scripts/Makefile.modfinal
index adcbcde16a07..b09040ccddd2 100644
--- a/scripts/Makefile.modfinal
+++ b/scripts/Makefile.modfinal
@@ -38,12 +38,14 @@ quiet_cmd_ld_ko_o = LD [M]  $@
 		$(KBUILD_LDFLAGS_MODULE) $(LDFLAGS_MODULE)		\
 		-T $(objtree)/scripts/module.lds -o $@ $(filter %.o, $^)
 
+btf-vmlinux := $(if $(KBUILD_EXTMOD),vmlinux,vmlinux.unstripped)
+
 quiet_cmd_btf_ko = BTF [M] $@
       cmd_btf_ko = 							\
-	if [ ! -f $(objtree)/vmlinux ]; then				\
-		printf "Skipping BTF generation for %s due to unavailability of vmlinux\n" $@ 1>&2; \
+	if [ ! -f $(objtree)/$(btf-vmlinux) ]; then				\
+		printf "Skipping BTF generation for %s due to unavailability of $(btf-vmlinux)\n" $@ 1>&2; \
 	else	\
-		$(CONFIG_SHELL) $(srctree)/scripts/gen-btf.sh --btf_base $(objtree)/vmlinux $@; \
+		$(CONFIG_SHELL) $(srctree)/scripts/gen-btf.sh --btf_base $(objtree)/$(btf-vmlinux) $@; \
 	fi;
 
 # Same as newer-prereqs, but allows to exclude specified extra dependencies
@@ -55,8 +57,8 @@ if_changed_except = $(if $(call newer_prereqs_except,$(2))$(cmd-check),      \
 	printf '%s\n' 'savedcmd_$@ := $(make-cmd)' > $(dot-target).cmd, @:)
 
 # Re-generate module BTFs if either module's .ko or vmlinux changed
-%.ko: %.o %.mod.o .module-common.o $(objtree)/scripts/module.lds $(and $(CONFIG_DEBUG_INFO_BTF_MODULES),$(KBUILD_BUILTIN),$(objtree)/vmlinux) FORCE
-	+$(call if_changed_except,ld_ko_o,$(objtree)/vmlinux)
+%.ko: %.o %.mod.o .module-common.o $(objtree)/scripts/module.lds $(and $(CONFIG_DEBUG_INFO_BTF_MODULES),$(KBUILD_BUILTIN),$(objtree)/$(btf-vmlinux)) FORCE
+	+$(call if_changed_except,ld_ko_o,$(objtree)/$(btf-vmlinux))
 ifdef CONFIG_DEBUG_INFO_BTF_MODULES
 	+$(if $(newer-prereqs),$(call cmd,btf_ko))
 endif

-- 
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 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 02/14] lockdown: Make the relationship to MODULE_SIG a dependency
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 new hash-based module integrity checking will also be able to
satisfy the requirements of lockdown.
Such an alternative is not representable with "select", so use
"depends on" instead.

Acked-by: Paul Moore <paul@paul-moore.com>
Reviewed-by: Nicolas Schier <nsc@kernel.org>
Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
---
 security/lockdown/Kconfig | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/security/lockdown/Kconfig b/security/lockdown/Kconfig
index e84ddf484010..155959205b8e 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
-	select MODULE_SIG if MODULES
+	depends on !MODULES || MODULE_SIG
 	help
 	  Build support for an LSM that enforces a coarse kernel lockdown
 	  behaviour.

-- 
2.54.0



^ permalink raw reply related

* [PATCH v5 03/14] kbuild: rename the strip_relocs command
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>

This command is doing more than just stripping relocations.
With the introduction of CONFIG_MODULE_HASHES it will do even more.

Use a more generic name for the variable.

Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
---
 scripts/Makefile.vmlinux | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/scripts/Makefile.vmlinux b/scripts/Makefile.vmlinux
index fcae1e432d9a..6cc661e5292b 100644
--- a/scripts/Makefile.vmlinux
+++ b/scripts/Makefile.vmlinux
@@ -91,13 +91,13 @@ remove-symbols := -w --strip-unneeded-symbol='__mod_device_table__*'
 
 # To avoid warnings: "empty loadable segment detected at ..." from GNU objcopy,
 # it is necessary to remove the PT_LOAD flag from the segment.
-quiet_cmd_strip_relocs = OBJCOPY $@
-      cmd_strip_relocs = $(OBJCOPY) $(patsubst %,--set-section-flags %=noload,$(remove-section-y)) $< $@; \
-                         $(OBJCOPY) $(addprefix --remove-section=,$(remove-section-y)) $(remove-symbols) $@
+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) $@
 
 targets += vmlinux
 vmlinux: vmlinux.unstripped FORCE
-	$(call if_changed,strip_relocs)
+	$(call if_changed,objcopy_vmlinux)
 
 # modules.builtin.modinfo
 # ---------------------------------------------------------------------------

-- 
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 11/14] module: update timestamp of modules.order after modules are built
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>

Make sure that modules.order is always newer than the module .ko files.
Other rules can then use modules.order as a prerequisite and rerun if
any module is (re-)built.

Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
---
 scripts/Makefile.modfinal | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/scripts/Makefile.modfinal b/scripts/Makefile.modfinal
index b09040ccddd2..44a382689a5a 100644
--- a/scripts/Makefile.modfinal
+++ b/scripts/Makefile.modfinal
@@ -66,6 +66,13 @@ endif
 
 targets += $(modules:%.o=%.ko) $(modules:%.o=%.mod.o) .module-common.o
 
+# Update modules.order when a module is (re-)built.
+# Allow using it as target dependency.
+targets += modules.order
+__modfinal: modules.order
+modules.order: $(modules:%.o=%.ko)
+	@touch $@
+
 # Add FORCE to the prerequisites of a target to force it to be always rebuilt.
 # ---------------------------------------------------------------------------
 

-- 
2.54.0



^ permalink raw reply related

* [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

* Re: [PATCH v1 0/6] objtool: Fixup alternate feature relative addresses
From: Christophe Leroy (CS GROUP) @ 2026-05-05  9:05 UTC (permalink / raw)
  To: Sathvika Vasireddy, nathan, nsc, maddy, mpe, npiggin, jpoimboe,
	peterz, ojeda, masahiroy, lossin, tamird, thomas.weissschuh,
	rostedt, ihor.solodrai, thuth, pmladek, aliceryhl, elver, kees,
	legion, ardb, yuxuan.zuo, alexghiti, alexandre.chartre, bp,
	linux-kbuild, linux-kernel, linuxppc-dev
In-Reply-To: <20260505084628.17940-1-sv@linux.ibm.com>



Le 05/05/2026 à 10:46, Sathvika Vasireddy a écrit :
> This patch series implements build-time fixup of alternate feature
> relative addresses for powerpc.
> 
> Previously, Nicholas Piggin proposed a build-time solution using a
> custom PowerPC tool [1], which provided the foundation for this approach.
> The current implementation leverages objtool's existing ELF parsing
> infrastructure to do the same.

What are the changes since last RFC 
https://lore.kernel.org/linuxppc-dev/20260316062237.30948-1-sv@linux.ibm.com/

> 
> This patchset applies atop powerpc/merge branch.
> 
> [1] Original PowerPC tool approach:
>      http://patchwork.ozlabs.org/project/linuxppc-dev/patch/20170521010130.13552-1-npiggin@gmail.com/
> 
> Testing:
> Build and Boot tested on ppc64le, ppc64be, and ppc32be configs.
> 
> Sathvika Vasireddy (6):
>    objtool/powerpc: Add build-time fixup of alternate feature branch
>      targets
>    objtool: Set ELF_F_LAYOUT flag to preserve vmlinux segment layout
>    objtool: Fix "can't find starting instruction" warnings on vmlinux
>    objtool/powerpc: Skip jump destination analysis and unnanotated
>      intra-function call warnings for --ftr-fixup
>    kbuild: Add objtool integration for PowerPC feature fixups
>    powerpc: Enable build-time feature fixup processing by default
> 
>   Makefile                                  |   7 +
>   arch/powerpc/Kconfig                      |   3 +
>   arch/powerpc/Makefile                     |   5 +
>   arch/powerpc/include/asm/feature-fixups.h |   2 +-
>   arch/powerpc/kernel/vmlinux.lds.S         |   8 +-
>   arch/powerpc/lib/feature-fixups.c         |  12 -
>   scripts/Makefile.lib                      |   4 +-
>   scripts/Makefile.vmlinux                  |  11 +-
>   tools/objtool/arch/powerpc/decode.c       |  15 +-
>   tools/objtool/arch/powerpc/special.c      | 451 ++++++++++++++++++++++
>   tools/objtool/builtin-check.c             |   2 +
>   tools/objtool/check.c                     |  57 ++-
>   tools/objtool/elf.c                       |   4 +
>   tools/objtool/include/objtool/builtin.h   |   1 +
>   tools/objtool/include/objtool/special.h   |  56 +++
>   tools/objtool/special.c                   |  29 ++
>   16 files changed, 637 insertions(+), 30 deletions(-)
> 



^ permalink raw reply

* Re: [PATCH v5 net-next 04/15] net: enetc: add basic operations to the FDB table
From: Paolo Abeni @ 2026-05-05  8:59 UTC (permalink / raw)
  To: Wei Fang, claudiu.manoil, vladimir.oltean, xiaoning.wang,
	andrew+netdev, davem, edumazet, kuba, robh, krzk+dt, conor+dt,
	f.fainelli, frank.li, chleroy, horms, linux
  Cc: netdev, linux-kernel, devicetree, linuxppc-dev, linux-arm-kernel,
	imx
In-Reply-To: <20260430024945.3413973-5-wei.fang@nxp.com>

On 4/30/26 4:49 AM, Wei Fang wrote:
> The FDB table is used for MAC learning lookups and MAC forwarding lookups.
> Each table entry includes information such as a FID and MAC address that
> may be unicast or multicast and a forwarding destination field containing
> a port bitmap identifying the associated port(s) with the MAC address.
> FDB table entries can be static or dynamic. Static entries are added from
> software whereby dynamic entries are added either by software or by the
> hardware as MAC addresses are learned in the datapath.
> 
> The FDB table can only be managed by the command BD ring using table
> management protocol version 2.0. Table management command operations Add,
> Delete, Update and Query are supported. And the FDB table supports three
> access methods: Entry ID, Exact Match Key Element and Search. This patch
> adds the following basic supports to the FDB table.
> 
> ntmp_fdbt_update_entry() - update the configuration element data of a
> specified FDB entry
> 
> ntmp_fdbt_delete_entry() - delete a specified FDB entry
> 
> ntmp_fdbt_add_entry() - add an entry into the FDB table
> 
> ntmp_fdbt_search_port_entry() - Search the FDB entry on the specified
> port based on RESUME_ENTRY_ID.
> 
> Signed-off-by: Wei Fang <wei.fang@nxp.com>
> ---
>  drivers/net/ethernet/freescale/enetc/ntmp.c   | 203 +++++++++++++++++-
>  .../ethernet/freescale/enetc/ntmp_private.h   |  61 +++++-
>  include/linux/fsl/ntmp.h                      |  44 +++-
>  3 files changed, 305 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c b/drivers/net/ethernet/freescale/enetc/ntmp.c
> index c94a928622fd..4ed8d783a9a2 100644
> --- a/drivers/net/ethernet/freescale/enetc/ntmp.c
> +++ b/drivers/net/ethernet/freescale/enetc/ntmp.c
> @@ -1,7 +1,7 @@
>  // SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
>  /*
>   * NETC NTMP (NETC Table Management Protocol) 2.0 Library
> - * Copyright 2025 NXP
> + * Copyright 2025-2026 NXP
>   */
>  
>  #include <linux/dma-mapping.h>
> @@ -21,11 +21,15 @@
>  /* Define NTMP Table ID */
>  #define NTMP_MAFT_ID			1
>  #define NTMP_RSST_ID			3
> +#define NTMP_FDBT_ID			15
>  
>  /* Generic Update Actions for most tables */
>  #define NTMP_GEN_UA_CFGEU		BIT(0)
>  #define NTMP_GEN_UA_STSEU		BIT(1)
>  
> +/* Query Action: 0: Full query, 1: Only query entry ID */
> +#define NTMP_QA_ENTRY_ID		1

Sashiko noted that the above comments looks inconsistent with the update
code, where NTMP_QA_ENTRY_ID apparently uses a full query, and 0 just
the entry ID.

If you have to repost for other reasons, please fix this. Note that you
should reply on the ML to sashiko reviews ruling out invalid comments.

Thanks,

Paolo



^ permalink raw reply

* [PATCH v1 4/6] objtool/powerpc: Skip jump destination analysis and unnanotated intra-function call warnings for --ftr-fixup
From: Sathvika Vasireddy @ 2026-05-05  8:46 UTC (permalink / raw)
  To: nathan, nsc, maddy, mpe, npiggin, chleroy, jpoimboe, peterz,
	ojeda, masahiroy, lossin, tamird, thomas.weissschuh, rostedt,
	ihor.solodrai, thuth, pmladek, aliceryhl, elver, kees, legion,
	ardb, yuxuan.zuo, alexghiti, alexandre.chartre, bp, linux-kbuild,
	linux-kernel, linuxppc-dev, sv
In-Reply-To: <20260505084628.17940-1-sv@linux.ibm.com>

Objtool is throwing unannotated intra-function call warnings
when run on vmlinux with --ftr-fixup option.

One such example:

vmlinux: warning: objtool: .text+0x3d94:
                        unannotated intra-function call

.text + 0x3d94 = c000000000008000 + 3d94 = c0000000000081d4

c0000000000081d4: 45 24 02 48  bl c00000000002a618
<system_reset_exception+0x8>

c00000000002a610 <system_reset_exception>:
c00000000002a610:       0e 01 4c 3c     addis   r2,r12,270
                        c00000000002a610: R_PPC64_REL16_HA    .TOC.
c00000000002a614:       f0 6c 42 38     addi    r2,r2,27888
                        c00000000002a614: R_PPC64_REL16_LO    .TOC.+0x4
c00000000002a618:       a6 02 08 7c     mflr    r0

This is happening because we should be looking for destination
symbols that are at absolute offsets instead of relative offsets.
After fixing dest_off to point to absolute offset, there are still
a lot of these warnings shown.

In the above example, objtool is computing the destination
offset to be c00000000002a618, which points to a completely
different instruction. find_call_destination() is looking for this
offset and failing. Instead, we should be looking for destination
offset c00000000002a610 which points to system_reset_exception
function.

Even after fixing the way destination offset is computed, and
after looking for dest_off - 0x8 in cases where the original offset
is not found, there are still a lot of unannotated intra-function
call warnings generated. This is due to symbols that are not
properly annotated.

So, for now, as a hack to curb these warnings, do not emit
unannotated intra-function call warnings when objtool is run
with --ftr-fixup option.

Skip add_jump_destinations() and suppress intra-function call
errors in --ftr-fixup mode. The feature fixup pass only needs fixup
entry tables, relocation entries from .__ftr_alternates.text, and
elf_write_insn() to patch branch offsets.

Signed-off-by: Sathvika Vasireddy <sv@linux.ibm.com>
---
 tools/objtool/check.c | 14 +++++++++-----
 1 file changed, 9 insertions(+), 5 deletions(-)

diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index f3501b149829..14644ca36d73 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -1644,7 +1644,6 @@ static int add_jump_destinations(struct objtool_file *file)
 			    dest_off == func->offset + func->len)
 				continue;
 
-
 			ERROR_INSN(insn, "can't find jump dest instruction at %s",
 				offstr(dest_sec, dest_off));
 			return -1;
@@ -1727,8 +1726,11 @@ static int add_call_destinations(struct objtool_file *file)
 				continue;
 
 			if (!insn_call_dest(insn)) {
-				ERROR_INSN(insn, "unannotated intra-function call");
-				return -1;
+				if (!opts.ftr_fixup) {
+					ERROR_INSN(insn, "unannotated intra-function call");
+					return -1;
+				}
+				continue;
 			}
 
 			if (func && !is_func_sym(insn_call_dest(insn))) {
@@ -2681,8 +2683,10 @@ static int decode_sections(struct objtool_file *file)
 			return -1;
 	}
 
-	if (add_jump_destinations(file))
-		return -1;
+	if (!opts.ftr_fixup) {
+		if (add_jump_destinations(file))
+			return -1;
+	}
 
 	/*
 	 * Must be before add_call_destination(); it changes INSN_CALL to
-- 
2.43.0



^ permalink raw reply related

* [PATCH v1 6/6] powerpc: Enable build-time feature fixup processing by default
From: Sathvika Vasireddy @ 2026-05-05  8:46 UTC (permalink / raw)
  To: nathan, nsc, maddy, mpe, npiggin, chleroy, jpoimboe, peterz,
	ojeda, masahiroy, lossin, tamird, thomas.weissschuh, rostedt,
	ihor.solodrai, thuth, pmladek, aliceryhl, elver, kees, legion,
	ardb, yuxuan.zuo, alexghiti, alexandre.chartre, bp, linux-kbuild,
	linux-kernel, linuxppc-dev, sv
In-Reply-To: <20260505084628.17940-1-sv@linux.ibm.com>

Enable HAVE_OBJTOOL_FTR_FIXUP by default on PowerPC architecture.

- Remove runtime branch translation logic from patch_alt_instruction()
- Add --emit-relocs linker flags for post-link fixup processing
- Update ftr_alt section attributes to include executable flag
- Strip the --emit-relocs relocation sections (.rel*) from the final
  vmlinux after processing

Co-developed-by: Nicholas Piggin <npiggin@gmail.com>
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
Signed-off-by: Sathvika Vasireddy <sv@linux.ibm.com>
---
 arch/powerpc/Kconfig                      |  3 +++
 arch/powerpc/Makefile                     |  5 +++++
 arch/powerpc/include/asm/feature-fixups.h |  2 +-
 arch/powerpc/kernel/vmlinux.lds.S         |  8 ++++++--
 arch/powerpc/lib/feature-fixups.c         | 12 ------------
 scripts/Makefile.vmlinux                  |  8 ++++++--
 6 files changed, 21 insertions(+), 17 deletions(-)

diff --git a/arch/powerpc/Kconfig b/arch/powerpc/Kconfig
index 10240cb80904..6cc10927730d 100644
--- a/arch/powerpc/Kconfig
+++ b/arch/powerpc/Kconfig
@@ -23,6 +23,9 @@ config 64BIT
 	bool
 	default y if PPC64
 
+config HAVE_OBJTOOL_FTR_FIXUP
+        def_bool y
+
 config LIVEPATCH_64
 	def_bool PPC64
 	depends on LIVEPATCH
diff --git a/arch/powerpc/Makefile b/arch/powerpc/Makefile
index a58b1029592c..8e1dab5f3c9a 100644
--- a/arch/powerpc/Makefile
+++ b/arch/powerpc/Makefile
@@ -105,6 +105,11 @@ LDFLAGS_vmlinux-$(CONFIG_RELOCATABLE) := -pie --no-dynamic-linker
 LDFLAGS_vmlinux-$(CONFIG_RELOCATABLE) += -z notext
 LDFLAGS_vmlinux	:= $(LDFLAGS_vmlinux-y)
 
+# --emit-relocs required for post-link fixup of alternate feature
+# text section relocations.
+LDFLAGS_vmlinux        += --emit-relocs
+KBUILD_LDFLAGS_MODULE += --emit-relocs
+
 ifdef CONFIG_PPC64
 ifndef CONFIG_PPC_KERNEL_PCREL
 	# -mcmodel=medium breaks modules because it uses 32bit offsets from
diff --git a/arch/powerpc/include/asm/feature-fixups.h b/arch/powerpc/include/asm/feature-fixups.h
index 756a6c694018..d6ae92a292ec 100644
--- a/arch/powerpc/include/asm/feature-fixups.h
+++ b/arch/powerpc/include/asm/feature-fixups.h
@@ -32,7 +32,7 @@
 
 #define FTR_SECTION_ELSE_NESTED(label)			\
 label##2:						\
-	.pushsection __ftr_alt_##label,"a";		\
+	.pushsection __ftr_alt_##label, "ax";		\
 	.align 2;					\
 label##3:
 
diff --git a/arch/powerpc/kernel/vmlinux.lds.S b/arch/powerpc/kernel/vmlinux.lds.S
index 8fc11d6565bf..1a2d7c2d32f1 100644
--- a/arch/powerpc/kernel/vmlinux.lds.S
+++ b/arch/powerpc/kernel/vmlinux.lds.S
@@ -99,8 +99,8 @@ SECTIONS
 	.text : AT(ADDR(.text) - LOAD_OFFSET) {
 		ALIGN_FUNCTION();
 #endif
-		/* careful! __ftr_alt_* sections need to be close to .text */
-		*(.text.hot .text.hot.* TEXT_MAIN .text.fixup .text.unlikely .text.unlikely.* .fixup __ftr_alt_* .ref.text);
+		*(.text.hot .text.hot.* TEXT_MAIN .text.fixup .text.unlikely
+			.text.unlikely.* .fixup .ref.text);
 		*(.tramp.ftrace.text);
 		NOINSTR_TEXT
 		SCHED_TEXT
@@ -267,6 +267,10 @@ SECTIONS
 		_einittext = .;
 	} :text
 
+	.__ftr_alternates.text : AT(ADDR(.__ftr_alternates.text) - LOAD_OFFSET) {
+		*(__ftr_alt*);
+	}
+
 	/* .exit.text is discarded at runtime, not link time,
 	 * to deal with references from __bug_table
 	 */
diff --git a/arch/powerpc/lib/feature-fixups.c b/arch/powerpc/lib/feature-fixups.c
index 587c8cf1230f..269e992b1631 100644
--- a/arch/powerpc/lib/feature-fixups.c
+++ b/arch/powerpc/lib/feature-fixups.c
@@ -53,22 +53,10 @@ static u32 *calc_addr(struct fixup_entry *fcur, long offset)
 
 static int patch_alt_instruction(u32 *src, u32 *dest, u32 *alt_start, u32 *alt_end)
 {
-	int err;
 	ppc_inst_t instr;
 
 	instr = ppc_inst_read(src);
 
-	if (instr_is_relative_branch(ppc_inst_read(src))) {
-		u32 *target = (u32 *)branch_target(src);
-
-		/* Branch within the section doesn't need translating */
-		if (target < alt_start || target > alt_end) {
-			err = translate_branch(&instr, dest, src);
-			if (err)
-				return 1;
-		}
-	}
-
 	raw_patch_instruction(dest, instr);
 
 	return 0;
diff --git a/scripts/Makefile.vmlinux b/scripts/Makefile.vmlinux
index a1bef0638ecb..66e5d58a6ce8 100644
--- a/scripts/Makefile.vmlinux
+++ b/scripts/Makefile.vmlinux
@@ -82,11 +82,15 @@ endif
 # vmlinux
 # ---------------------------------------------------------------------------
 
+# These configurations require vmlinux.unstripped to be linked with
+# '--emit-relocs', which need to be stripped from the final vmlinux.
+uses-emit-relocs := $(or $(CONFIG_ARCH_VMLINUX_NEEDS_RELOCS),$(CONFIG_HAVE_OBJTOOL_FTR_FIXUP))
+
 remove-section-y                                   := .modinfo
-remove-section-$(CONFIG_ARCH_VMLINUX_NEEDS_RELOCS) += '.rel*' '!.rel*.dyn'
+remove-section-$(uses-emit-relocs)                 += '.rel*' '!.rel*.dyn'
 # for compatibility with binutils < 2.32
 # https://sourceware.org/git/?p=binutils-gdb.git;a=commit;h=c12d9fa2afe7abcbe407a00e15719e1a1350c2a7
-remove-section-$(CONFIG_ARCH_VMLINUX_NEEDS_RELOCS) += '.rel.*'
+remove-section-$(uses-emit-relocs)                 += '.rel.*'
 
 remove-symbols := -w --strip-unneeded-symbol='__mod_device_table__*'
 
-- 
2.43.0



^ permalink raw reply related

* [PATCH v1 5/6] kbuild: Add objtool integration for PowerPC feature fixups
From: Sathvika Vasireddy @ 2026-05-05  8:46 UTC (permalink / raw)
  To: nathan, nsc, maddy, mpe, npiggin, chleroy, jpoimboe, peterz,
	ojeda, masahiroy, lossin, tamird, thomas.weissschuh, rostedt,
	ihor.solodrai, thuth, pmladek, aliceryhl, elver, kees, legion,
	ardb, yuxuan.zuo, alexghiti, alexandre.chartre, bp, linux-kbuild,
	linux-kernel, linuxppc-dev, sv
In-Reply-To: <20260505084628.17940-1-sv@linux.ibm.com>

Add build system support for PowerPC feature fixup processing:

- Add HAVE_OBJTOOL_FTR_FIXUP config option for architectures that support
  build-time feature fixup processing
- Integrate objtool feature fixup processing into vmlinux build

Suggested-by: Masahiro Yamada <masahiroy@kernel.org>
Signed-off-by: Sathvika Vasireddy <sv@linux.ibm.com>
Acked-by: Nicolas Schier <nsc@kernel.org>  # kbuild
---
 Makefile                 | 7 +++++++
 scripts/Makefile.lib     | 4 ++--
 scripts/Makefile.vmlinux | 3 ++-
 3 files changed, 11 insertions(+), 3 deletions(-)

diff --git a/Makefile b/Makefile
index 6b1d9fb1a6b4..f9a990036f3f 100644
--- a/Makefile
+++ b/Makefile
@@ -1478,6 +1478,13 @@ ifdef CONFIG_OBJTOOL
 prepare: tools/objtool
 endif
 
+# CONFIG_OBJTOOL and CONFIG_HAVE_OBJTOOL_FTR_FIXUP are unrelated, separate
+# options. It was integrated in objtool in order to borrow the elf parser,
+# but this is different from how the other objtool commands are used.
+ifdef CONFIG_HAVE_OBJTOOL_FTR_FIXUP
+prepare: tools/objtool
+endif
+
 ifdef CONFIG_BPF
 ifdef CONFIG_DEBUG_INFO_BTF
 prepare: tools/bpf/resolve_btfids
diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib
index 0718e39cedda..fa72619e5bb0 100644
--- a/scripts/Makefile.lib
+++ b/scripts/Makefile.lib
@@ -179,10 +179,10 @@ cpp_flags      = -Wp,-MMD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE)     \
 
 ld_flags       = $(KBUILD_LDFLAGS) $(ldflags-y) $(LDFLAGS_$(@F))
 
-ifdef CONFIG_OBJTOOL
-
 objtool := $(objtree)/tools/objtool/objtool
 
+ifdef CONFIG_OBJTOOL
+
 objtool-args-$(CONFIG_HAVE_JUMP_LABEL_HACK)		+= --hacks=jump_label
 objtool-args-$(CONFIG_HAVE_NOINSTR_HACK)		+= --hacks=noinstr
 objtool-args-$(CONFIG_MITIGATION_CALL_DEPTH_TRACKING)	+= --hacks=skylake
diff --git a/scripts/Makefile.vmlinux b/scripts/Makefile.vmlinux
index fcae1e432d9a..a1bef0638ecb 100644
--- a/scripts/Makefile.vmlinux
+++ b/scripts/Makefile.vmlinux
@@ -65,7 +65,8 @@ ARCH_POSTLINK := $(wildcard $(srctree)/arch/$(SRCARCH)/Makefile.postlink)
 # Final link of vmlinux with optional arch pass after final link
 cmd_link_vmlinux =							\
 	$< "$(LD)" "$(KBUILD_LDFLAGS)" "$(LDFLAGS_vmlinux)" "$@";	\
-	$(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) $@, true)
+	$(if $(ARCH_POSTLINK), $(MAKE) -f $(ARCH_POSTLINK) $@, true);	\
+	$(if $(CONFIG_HAVE_OBJTOOL_FTR_FIXUP), $(objtool) --ftr-fixup --link $@, true)
 
 targets += vmlinux.unstripped .vmlinux.export.o
 vmlinux.unstripped: scripts/link-vmlinux.sh vmlinux.o .vmlinux.export.o $(KBUILD_LDS) FORCE
-- 
2.43.0



^ permalink raw reply related

* [PATCH v1 1/6] objtool/powerpc: Add build-time fixup of alternate feature branch targets
From: Sathvika Vasireddy @ 2026-05-05  8:46 UTC (permalink / raw)
  To: nathan, nsc, maddy, mpe, npiggin, chleroy, jpoimboe, peterz,
	ojeda, masahiroy, lossin, tamird, thomas.weissschuh, rostedt,
	ihor.solodrai, thuth, pmladek, aliceryhl, elver, kees, legion,
	ardb, yuxuan.zuo, alexghiti, alexandre.chartre, bp, linux-kbuild,
	linux-kernel, linuxppc-dev, sv
In-Reply-To: <20260505084628.17940-1-sv@linux.ibm.com>

PowerPC __ftr_alt* sections must currently be placed near .text because
they lack the executable attribute, preventing the linker from inserting
stubs. Branches in the alternate code must reach their targets directly.
This can cause build failures as the kernel grows.

Fix this by processing alternate feature relocations at build time with
objtool. Link with --emit-relocs to preserve relocation information,
mark __ftr_alt* sections executable ("ax") so the linker can insert
stubs, then use objtool --ftr-fixup to recompute branch offsets based
on their runtime locations.

objtool already has ELF parsing and instruction patching infrastructure,
so this avoids introducing a separate tool as was originally proposed [1].

[1] https://lore.kernel.org/linuxppc-dev/20170521010130.13552-1-npiggin@gmail.com/

Co-developed-by: Nicholas Piggin <npiggin@gmail.com>
Signed-off-by: Nicholas Piggin <npiggin@gmail.com>
Signed-off-by: Sathvika Vasireddy <sv@linux.ibm.com>
---
 tools/objtool/arch/powerpc/decode.c     |  15 +-
 tools/objtool/arch/powerpc/special.c    | 451 ++++++++++++++++++++++++
 tools/objtool/builtin-check.c           |   2 +
 tools/objtool/check.c                   |  29 +-
 tools/objtool/elf.c                     |   1 +
 tools/objtool/include/objtool/builtin.h |   1 +
 tools/objtool/include/objtool/special.h |  56 +++
 tools/objtool/special.c                 |  29 ++
 8 files changed, 582 insertions(+), 2 deletions(-)

diff --git a/tools/objtool/arch/powerpc/decode.c b/tools/objtool/arch/powerpc/decode.c
index e534ac1123b3..f6e69d074f28 100644
--- a/tools/objtool/arch/powerpc/decode.c
+++ b/tools/objtool/arch/powerpc/decode.c
@@ -59,13 +59,26 @@ int arch_decode_instruction(struct objtool_file *file, const struct section *sec
 	enum insn_type typ;
 	unsigned long imm;
 	u32 ins;
+	unsigned int aa;
 
 	ins = bswap_if_needed(file->elf, *(u32 *)(sec->data->d_buf + offset));
 	opcode = ins >> 26;
 	typ = INSN_OTHER;
 	imm = 0;
+	aa = ins & 2;
 
 	switch (opcode) {
+	case 16:
+		if (ins & 1)
+			typ = INSN_OTHER;
+		else
+			typ = INSN_JUMP_CONDITIONAL;
+		imm = ins & 0xfffc;
+		if (imm & 0x8000)
+			imm -= 0x10000;
+		insn->immediate = imm | aa;
+		break;
+
 	case 18: /* b[l][a] */
 		if (ins == 0x48000005)	/* bl .+4 */
 			typ = INSN_OTHER;
@@ -77,7 +90,7 @@ int arch_decode_instruction(struct objtool_file *file, const struct section *sec
 		imm = ins & 0x3fffffc;
 		if (imm & 0x2000000)
 			imm -= 0x4000000;
-		imm |= ins & 2;	/* AA flag */
+		insn->immediate = imm | aa;
 		break;
 	}
 
diff --git a/tools/objtool/arch/powerpc/special.c b/tools/objtool/arch/powerpc/special.c
index 8f9bf61ca089..deb248c63bca 100644
--- a/tools/objtool/arch/powerpc/special.c
+++ b/tools/objtool/arch/powerpc/special.c
@@ -3,7 +3,17 @@
 #include <stdlib.h>
 #include <objtool/special.h>
 #include <objtool/builtin.h>
+#include <objtool/warn.h>
+#include <asm/byteorder.h>
+#include <errno.h>
 
+struct section *ftr_alt;
+
+struct fixup_entry *fes;
+unsigned int nr_fes;
+
+uint64_t fe_alt_start = -1;
+uint64_t fe_alt_end;
 
 bool arch_support_alt_relocation(struct special_alt *special_alt,
 				 struct instruction *insn,
@@ -23,3 +33,444 @@ const char *arch_cpu_feature_name(int feature_number)
 {
 	return NULL;
 }
+
+
+int process_alt_data(struct objtool_file *file)
+{
+	struct section *section;
+
+	section = find_section_by_name(file->elf, ".__ftr_alternates.text");
+	ftr_alt = section;
+
+	if (!ftr_alt) {
+		if (opts.link)
+			WARN(".__ftr_alternates.text section not found in vmlinux\n");
+		return opts.link ? -1 : 0;
+	}
+
+	fe_alt_start = ftr_alt->sh.sh_addr;
+	fe_alt_end = ftr_alt->sh.sh_addr + ftr_alt->sh.sh_size;
+
+	return 0;
+
+}
+
+static int is_le(struct objtool_file *file)
+{
+	return file->elf->ehdr.e_ident[EI_DATA] == ELFDATA2LSB;
+}
+
+static int is_64bit(struct objtool_file *file)
+{
+	return file->elf->ehdr.e_ident[EI_CLASS] == ELFCLASS64;
+}
+
+static uint32_t f32_to_cpu(struct objtool_file *file, uint32_t val)
+{
+	if (is_le(file))
+		return __le32_to_cpu(val);
+	else
+		return __be32_to_cpu(val);
+}
+
+static uint64_t f64_to_cpu(struct objtool_file *file, uint64_t val)
+{
+	if (is_le(file))
+		return __le64_to_cpu(val);
+	else
+		return __be64_to_cpu(val);
+}
+
+static uint32_t cpu_to_f32(struct objtool_file *file, uint32_t val)
+{
+	if (is_le(file))
+		return __cpu_to_le32(val);
+	else
+		return __cpu_to_be32(val);
+}
+
+int process_fixup_entries(struct objtool_file *file)
+{
+	struct section *sec;
+	int i;
+
+	for_each_sec(file->elf, sec) {
+		Elf_Data *data;
+		unsigned int nr;
+
+		if (strstr(sec->name, "_ftr_fixup") == NULL)
+			continue;
+
+		if (strstr(sec->name, ".rela") != NULL)
+			continue;
+
+		data = sec->data;
+		if (!data || data->d_size == 0)
+			continue;
+
+		if (is_64bit(file))
+			nr = data->d_size / sizeof(struct fixup_entry_64);
+		else
+			nr = data->d_size / sizeof(struct fixup_entry_32);
+
+		for (i = 0; i < nr; i++) {
+			unsigned long idx;
+			unsigned long long off;
+			struct fixup_entry *dst;
+
+			if (is_64bit(file)) {
+				struct fixup_entry_64 *src;
+
+				idx = i * sizeof(struct fixup_entry_64);
+				off = sec->sh.sh_addr + data->d_off + idx;
+				src = data->d_buf + idx;
+
+				if (src->alt_start_off == src->alt_end_off)
+					continue;
+
+				struct fixup_entry *tmp = realloc(fes, (nr_fes + 1)
+					* sizeof(struct fixup_entry));
+
+				if (!tmp) {
+					free(fes);
+					fes = NULL;
+					return -1;
+				}
+				fes = tmp;
+				dst = &fes[nr_fes++];
+
+				dst->mask = f64_to_cpu(file, src->mask);
+				dst->value = f64_to_cpu(file, src->value);
+				dst->start_off = f64_to_cpu(file, src->start_off) + off;
+				dst->end_off = f64_to_cpu(file, src->end_off) + off;
+				dst->alt_start_off = f64_to_cpu(file, src->alt_start_off) + off;
+				dst->alt_end_off = f64_to_cpu(file, src->alt_end_off) + off;
+			} else {
+				struct fixup_entry_32 *src;
+
+				idx = i * sizeof(struct fixup_entry_32);
+				off = sec->sh.sh_addr + data->d_off + idx;
+				src = data->d_buf + idx;
+
+				if (src->alt_start_off == src->alt_end_off)
+					continue;
+
+				struct fixup_entry *tmp = realloc(fes, (nr_fes + 1)
+					* sizeof(struct fixup_entry));
+
+				if (!tmp) {
+					free(fes);
+					fes = NULL;
+					return -1;
+				}
+				fes = tmp;
+				dst = &fes[nr_fes++];
+
+				dst->mask = f32_to_cpu(file, src->mask);
+				dst->value = f32_to_cpu(file, src->value);
+				dst->start_off = (int32_t)f32_to_cpu(file, src->start_off) + off;
+				dst->end_off = (int32_t)f32_to_cpu(file, src->end_off) + off;
+				dst->alt_start_off = (int32_t)f32_to_cpu(file,
+								src->alt_start_off) + off;
+				dst->alt_end_off = (int32_t)f32_to_cpu(file,
+								src->alt_end_off) + off;
+			}
+		}
+	}
+	return 0;
+}
+
+struct fixup_entry *find_fe_altaddr(uint64_t addr)
+{
+	unsigned int i;
+
+	if (addr < fe_alt_start)
+		return NULL;
+	if (addr >= fe_alt_end)
+		return NULL;
+
+	for (i = 0; i < nr_fes; i++) {
+		if (addr >= fes[i].alt_start_off && addr < fes[i].alt_end_off)
+			return &fes[i];
+	}
+	return NULL;
+}
+
+int set_uncond_branch_target(uint32_t *insn,
+		const uint64_t addr, uint64_t target)
+{
+	uint32_t i = *insn;
+	int64_t offset;
+
+	offset = target;
+	if (!(i & BRANCH_ABSOLUTE))
+		offset = offset - addr;
+
+	/* Check we can represent the target in the instruction format */
+	if (offset < -0x2000000 || offset > 0x1fffffc || offset & 0x3)
+		return -EOVERFLOW;
+
+	/* Mask out the flags and target, so they don't step on each other. */
+	*insn = 0x48000000 | (i & 0x3) | (offset & 0x03FFFFFC);
+
+	return 0;
+}
+
+int set_cond_branch_target(uint32_t *insn,
+		const uint64_t addr, uint64_t target)
+{
+	uint32_t i = *insn;
+	int64_t offset;
+
+	offset = target;
+
+	if (!(i & BRANCH_ABSOLUTE))
+		offset = offset - addr;
+
+	/* Check we can represent the target in the instruction format */
+	if (offset < -0x8000 || offset > 0x7FFF || offset & 0x3)
+		return -EOVERFLOW;
+
+	/* Mask out the flags and target, so they don't step on each other. */
+	*insn = 0x40000000 | (i & 0x3FF0003) | (offset & 0xFFFC);
+
+	return 0;
+}
+
+void check_and_flatten_fixup_entries(void)
+{
+	static struct fixup_entry *fe;
+	unsigned int i;
+
+	for (i = 0; i < nr_fes; i++) {
+		struct fixup_entry *parent;
+		uint64_t nested_off;
+		uint64_t size;
+
+		fe = &fes[i];
+
+		parent = find_fe_altaddr(fe->start_off);
+		if (!parent)
+			continue;
+
+		size = fe->end_off - fe->start_off;
+		nested_off = fe->start_off - parent->alt_start_off;
+
+		fe->start_off = parent->start_off + nested_off;
+		fe->end_off = fe->start_off + size;
+	}
+}
+
+
+static struct symbol *find_symbol_at_address_within_section(struct section *sec,
+				unsigned long address)
+{
+	struct symbol *sym;
+
+	sec_for_each_sym(sec, sym) {
+		if (sym->sym.st_value <= address && address < sym->sym.st_value + sym->len)
+			return sym;
+	}
+
+	return NULL;
+}
+
+static int is_local_symbol(uint8_t st_other)
+{
+	/* STO_PPC64_LOCAL_MASK: bits [7:5] encode the local entry point offset */
+	return (st_other & (7 << 5)) == 0;
+}
+
+static struct symbol *find_symbol_at_address(struct objtool_file *file,
+					unsigned long address)
+{
+	struct section *sec;
+	struct symbol *sym;
+
+	list_for_each_entry(sec, &file->elf->sections, list) {
+		sym = find_symbol_at_address_within_section(sec, address);
+		if (sym)
+			return sym;
+		}
+	return NULL;
+}
+
+int process_alt_relocations(struct objtool_file *file)
+{
+	struct section *section;
+	size_t n = 0;
+	struct reloc *relocation;
+	struct symbol *sym;
+	struct fixup_entry *fe;
+	uint64_t addr;
+	uint64_t scn_delta;
+	uint64_t dst_addr;
+	const char *insn_ptr;
+	unsigned long target;
+	struct symbol *symbol;
+	int is_local;
+	int j;
+	uint32_t new_insn;
+	uint32_t file_insn;
+	struct instruction decoded_insn = {0};
+	uint32_t *insn_ptr_raw;
+	uint32_t insn;
+
+	section = find_section_by_name(file->elf, ".rela.__ftr_alternates.text");
+	if (!section) {
+		printf(".rela.__ftr_alternates.text section not found.\n");
+		return 0;
+	}
+
+	for (j = 0; j < sec_num_entries(section); j++) {
+
+		relocation = &section->relocs[j];
+		sym = relocation->sym;
+
+		addr = reloc_offset(relocation);
+		target = sym->sym.st_value + reloc_addend(relocation);
+		symbol = find_symbol_at_address(file, target);
+
+		if (symbol) {
+			is_local = is_local_symbol(symbol->sym.st_other);
+			if (!is_local)
+				target = target + 0x8;
+		}
+
+		n++;
+		fe = find_fe_altaddr(addr);
+		if (!fe)
+			continue;
+
+		if (target >= fe->alt_start_off && target < fe->alt_end_off)
+			continue;
+
+		if (target >= ftr_alt->sh.sh_addr &&
+			target < ftr_alt->sh.sh_addr + ftr_alt->sh.sh_size)
+			return -1;
+
+		scn_delta = addr - ftr_alt->sh.sh_addr;
+		dst_addr = addr - fe->alt_start_off + fe->start_off;
+
+		if (!ftr_alt->data || !ftr_alt->data->d_buf)
+			continue;
+
+		if (arch_decode_instruction(file, ftr_alt, scn_delta, 4, &decoded_insn) < 0)
+			continue;
+
+		insn_ptr_raw = (uint32_t *)(ftr_alt->data->d_buf + scn_delta);
+
+		insn = f32_to_cpu(file, *insn_ptr_raw);
+		new_insn = insn;
+
+		switch (decoded_insn.type) {
+		case INSN_JUMP_CONDITIONAL:
+			if (set_cond_branch_target(&new_insn, dst_addr, target) != 0)
+				continue;
+			break;
+
+		case INSN_JUMP_UNCONDITIONAL:
+			if (set_uncond_branch_target(&new_insn, dst_addr, target) != 0)
+				continue;
+			break;
+
+		case INSN_CALL:
+			if (set_uncond_branch_target(&new_insn, dst_addr, target) != 0)
+				continue;
+			break;
+		default:
+			continue;
+		}
+
+		if (new_insn == insn)
+			continue;
+
+		file_insn = cpu_to_f32(file, new_insn);
+		insn_ptr = (const char *)&file_insn;
+		elf_write_insn(file->elf, ftr_alt, scn_delta, sizeof(file_insn), insn_ptr);
+	}
+	return 0;
+}
+
+int process_exception_entries(struct objtool_file *file)
+{
+	struct section *section;
+	Elf_Data *data;
+	unsigned int nr, i;
+
+	section = find_section_by_name(file->elf, "__ex_table");
+	if (!section) {
+		printf("__ex_table section not found\n");
+		return 0;
+	}
+
+	data = section->data;
+	if (!data || data->d_size == 0)
+		return 0;
+
+	nr = data->d_size / sizeof(struct exception_entry);
+
+	for (i = 0; i < nr; i++) {
+		struct exception_entry *ex;
+		unsigned long idx;
+		uint64_t exaddr;
+		unsigned long long off;
+
+		idx = i * sizeof(struct exception_entry);
+		off = section->sh.sh_addr + data->d_off + idx;
+		ex = data->d_buf + idx;
+
+		exaddr = off + (int32_t)f32_to_cpu(file, ex->insn);
+
+		if (exaddr < fe_alt_start)
+			continue;
+		if (exaddr >= fe_alt_end)
+			continue;
+
+		return -1;
+	}
+	return 0;
+}
+
+int process_bug_entries(struct objtool_file *file)
+{
+	struct section *section;
+	Elf_Data *data;
+	unsigned int nr, i;
+
+	section = find_section_by_name(file->elf, "__bug_table");
+	if (!section) {
+		printf("__bug_table section not found\n");
+		return 0;
+	}
+
+	data = section->data;
+	if (!data || data->d_size == 0)
+		return 0;
+
+	if (data->d_size % sizeof(struct bug_entry) != 0)
+		return -1;
+
+	nr = data->d_size / sizeof(struct bug_entry);
+	for (i = 0; i < nr; i++) {
+		struct bug_entry *bug;
+		unsigned long idx;
+		uint64_t entry_addr;
+		uint64_t bugaddr;
+		int32_t bug_disp;
+
+		idx = i * sizeof(struct bug_entry);
+		entry_addr = section->sh.sh_addr + data->d_off + idx;
+		bug = (struct bug_entry *)(data->d_buf + idx);
+		bug_disp = f32_to_cpu(file, bug->bug_addr_disp);
+		bugaddr = entry_addr + bug_disp;
+
+		if (bugaddr < fe_alt_start)
+			continue;
+		if (bugaddr >= fe_alt_end)
+			continue;
+
+		return -1;
+	}
+	return 0;
+}
diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c
index b780df513715..5ee0880baec5 100644
--- a/tools/objtool/builtin-check.c
+++ b/tools/objtool/builtin-check.c
@@ -78,6 +78,7 @@ static const struct option check_options[] = {
 	OPT_STRING_OPTARG('d',	 "disas", &opts.disas, "function-pattern", "disassemble functions", "*"),
 	OPT_CALLBACK_OPTARG('h', "hacks", NULL, NULL, "jump_label,noinstr,skylake", "patch toolchain bugs/limitations", parse_hacks),
 	OPT_BOOLEAN('i',	 "ibt", &opts.ibt, "validate and annotate IBT"),
+	OPT_BOOLEAN('f',	 "ftr-fixup", &opts.ftr_fixup, "feature fixup"),
 	OPT_BOOLEAN('m',	 "mcount", &opts.mcount, "annotate mcount/fentry calls for ftrace"),
 	OPT_BOOLEAN(0,		 "noabs", &opts.noabs, "reject absolute references in allocatable sections"),
 	OPT_BOOLEAN('n',	 "noinstr", &opts.noinstr, "validate noinstr rules"),
@@ -181,6 +182,7 @@ static bool opts_valid(void)
 	    opts.disas			||
 	    opts.hack_jump_label	||
 	    opts.hack_noinstr		||
+	    opts.ftr_fixup		||
 	    opts.ibt			||
 	    opts.mcount			||
 	    opts.noabs			||
diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index b6765e876507..8cb55fe6e1dc 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -1636,9 +1636,11 @@ static int add_jump_destinations(struct objtool_file *file)
 			    dest_off == func->offset + func->len)
 				continue;
 
+
 			ERROR_INSN(insn, "can't find jump dest instruction at %s",
-				   offstr(dest_sec, dest_off));
+				offstr(dest_sec, dest_off));
 			return -1;
+
 		}
 
 		if (!dest_sym || is_sec_sym(dest_sym)) {
@@ -5021,6 +5023,31 @@ int check(struct objtool_file *file)
 	if (!nr_insns)
 		goto out;
 
+	if (opts.ftr_fixup) {
+		ret = process_alt_data(file);
+		if (ret < 0)
+			return ret;
+
+		ret = process_fixup_entries(file);
+		if (ret < 0)
+			return ret;
+
+		check_and_flatten_fixup_entries();
+
+		ret = process_exception_entries(file);
+		if (ret < 0)
+			return ret;
+
+		ret = process_bug_entries(file);
+		if (ret < 0)
+			return ret;
+
+		ret = process_alt_relocations(file);
+		if (ret < 0)
+			return ret;
+	}
+
+
 	if (opts.retpoline)
 		warnings += validate_retpoline(file);
 
diff --git a/tools/objtool/elf.c b/tools/objtool/elf.c
index 2ffe3ebfbe37..2ca2a4e4b92e 100644
--- a/tools/objtool/elf.c
+++ b/tools/objtool/elf.c
@@ -1156,6 +1156,7 @@ struct elf *elf_open_read(const char *name, int flags)
 		cmd = ELF_C_WRITE;
 
 	elf->elf = elf_begin(elf->fd, cmd, NULL);
+
 	if (!elf->elf) {
 		ERROR_ELF("elf_begin");
 		goto err;
diff --git a/tools/objtool/include/objtool/builtin.h b/tools/objtool/include/objtool/builtin.h
index b9e229ed4dc0..3578ec174a59 100644
--- a/tools/objtool/include/objtool/builtin.h
+++ b/tools/objtool/include/objtool/builtin.h
@@ -13,6 +13,7 @@ struct opts {
 	bool checksum;
 	bool dump_orc;
 	bool hack_jump_label;
+	bool ftr_fixup;
 	bool hack_noinstr;
 	bool hack_skylake;
 	bool ibt;
diff --git a/tools/objtool/include/objtool/special.h b/tools/objtool/include/objtool/special.h
index 121c3761899c..0a5efbfa29d0 100644
--- a/tools/objtool/include/objtool/special.h
+++ b/tools/objtool/include/objtool/special.h
@@ -12,6 +12,41 @@
 
 #define C_JUMP_TABLE_SECTION ".data.rel.ro.c_jump_table"
 
+#define BRANCH_SET_LINK 0x1
+#define BRANCH_ABSOLUTE 0x2
+
+struct bug_entry {
+	int32_t  bug_addr_disp;
+	int32_t  file_disp;
+	uint16_t line;
+	uint16_t flags;
+};
+
+struct exception_entry {
+	int32_t insn;
+	int32_t fixup;
+};
+
+struct fixup_entry_64 {
+	uint64_t mask;
+	uint64_t value;
+	uint64_t start_off;
+	uint64_t end_off;
+	uint64_t alt_start_off;
+	uint64_t alt_end_off;
+};
+
+#define fixup_entry fixup_entry_64
+
+struct fixup_entry_32 {
+	uint32_t mask;
+	uint32_t value;
+	uint32_t start_off;
+	uint32_t end_off;
+	uint32_t alt_start_off;
+	uint32_t alt_end_off;
+};
+
 struct special_alt {
 	struct list_head list;
 
@@ -28,6 +63,8 @@ struct special_alt {
 	unsigned int orig_len, new_len, feature; /* group only */
 };
 
+int process_alt_data(struct objtool_file *file);
+
 int special_get_alts(struct elf *elf, struct list_head *alts);
 
 void arch_handle_alternative(struct special_alt *alt);
@@ -40,4 +77,23 @@ struct reloc *arch_find_switch_table(struct objtool_file *file,
 				     unsigned long *table_size);
 const char *arch_cpu_feature_name(int feature_number);
 
+int process_fixup_entries(struct objtool_file *file);
+
+void check_and_flatten_fixup_entries(void);
+
+int process_exception_entries(struct objtool_file *file);
+
+int process_bug_entries(struct objtool_file *file);
+
+int process_alt_relocations(struct objtool_file *file);
+
+struct fixup_entry *find_fe_altaddr(uint64_t addr);
+
+int set_uncond_branch_target(uint32_t *insn,
+		const uint64_t addr, uint64_t target);
+
+int set_cond_branch_target(uint32_t *insn,
+		const uint64_t addr, uint64_t target);
+
+
 #endif /* _SPECIAL_H */
diff --git a/tools/objtool/special.c b/tools/objtool/special.c
index 2a533afbc69a..43b60bf789dd 100644
--- a/tools/objtool/special.c
+++ b/tools/objtool/special.c
@@ -167,3 +167,32 @@ int special_get_alts(struct elf *elf, struct list_head *alts)
 
 	return 0;
 }
+
+int __weak process_alt_data(struct objtool_file *file)
+{
+	return 0;
+}
+
+int __weak process_fixup_entries(struct objtool_file *file)
+{
+	return 0;
+}
+
+void __weak check_and_flatten_fixup_entries(void)
+{
+}
+
+int __weak process_exception_entries(struct objtool_file *file)
+{
+	return 0;
+}
+
+int __weak process_bug_entries(struct objtool_file *file)
+{
+	return 0;
+}
+
+int __weak process_alt_relocations(struct objtool_file *file)
+{
+	return 0;
+}
-- 
2.43.0



^ permalink raw reply related

* [PATCH v1 3/6] objtool: Fix "can't find starting instruction" warnings on vmlinux
From: Sathvika Vasireddy @ 2026-05-05  8:46 UTC (permalink / raw)
  To: nathan, nsc, maddy, mpe, npiggin, chleroy, jpoimboe, peterz,
	ojeda, masahiroy, lossin, tamird, thomas.weissschuh, rostedt,
	ihor.solodrai, thuth, pmladek, aliceryhl, elver, kees, legion,
	ardb, yuxuan.zuo, alexghiti, alexandre.chartre, bp, linux-kbuild,
	linux-kernel, linuxppc-dev, sv
In-Reply-To: <20260505084628.17940-1-sv@linux.ibm.com>

Objtool throws a lot of can't find starting instruction warnings
when run on vmlinux with --ftr-fixup option.

These warnings are seen because find_insn() function looks for
instructions at offsets that are relative to the start of the section.
In case of individual object files (.o), there are no can't find
starting instruction warnings seen because the actual offset
associated with an instruction is itself a relative offset since the
sections start at offset 0x0.

However, in case of vmlinux, find_insn() function fails to find
instructions at the actual offset associated with an instruction
since the sections in vmlinux do not start at offset 0x0. Due to
this, find_insn() will look for absolute offset and not the relative
offset. This is resulting in a lot of can't find starting instruction
warnings when objtool is run on vmlinux.

To fix this, pass offset that is relative to the start of the section
to find_insn().

find_insn() is also looking for symbols of size 0. But, objtool does
not store empty STT_NOTYPE symbols in the rbtree. Due to this,
for empty symbols, objtool is throwing can't find starting
instruction warnings. Fix this by ignoring symbols that are of
size 0 since objtool does not add them to the rbtree.

Signed-off-by: Sathvika Vasireddy <sv@linux.ibm.com>
---
 tools/objtool/check.c | 16 ++++++++++++----
 1 file changed, 12 insertions(+), 4 deletions(-)

diff --git a/tools/objtool/check.c b/tools/objtool/check.c
index 8cb55fe6e1dc..f3501b149829 100644
--- a/tools/objtool/check.c
+++ b/tools/objtool/check.c
@@ -412,7 +412,7 @@ static int decode_instructions(struct objtool_file *file)
 {
 	struct section *sec;
 	struct symbol *func;
-	unsigned long offset;
+	unsigned long offset, func_off;
 	struct instruction *insn;
 
 	for_each_sec(file->elf, sec) {
@@ -494,17 +494,25 @@ static int decode_instructions(struct objtool_file *file)
 			if (func->embedded_insn || func->alias != func)
 				continue;
 
-			if (!find_insn(file, sec, func->offset)) {
+			if (func->len == 0 && is_notype_sym(func))
+				continue;
+
+			func_off = opts.ftr_fixup ?
+				func->offset - sec->sh.sh_addr : func->offset;
+
+			if (!find_insn(file, sec, func_off)) {
 				ERROR("%s(): can't find starting instruction", func->name);
 				return -1;
 			}
 
-			sym_for_each_insn(file, func, insn) {
+			for (insn = find_insn(file, sec, func_off);
+				insn && insn->offset < func_off + func->len;
+				insn = next_insn_same_sec(file, insn)) {
 				insn->sym = func;
 				if (is_func_sym(func) &&
 				    insn->type == INSN_ENDBR &&
 				    list_empty(&insn->call_node)) {
-					if (insn->offset == func->offset) {
+					if (insn->offset == func_off) {
 						list_add_tail(&insn->call_node, &file->endbr_list);
 						file->nr_endbr++;
 					} else {
-- 
2.43.0



^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox