Add a mechanism for loading a FIPS module from a byte array compiled into vmlinux. fips140-module.o and fips140-digest.o are files generated during the build of the FIPS cryptographic module; fips140-module.o provides the following symbols: - _binary_fips140_ko_start - _binary_fips140_ko_end while fips140-digest.o provides the following symbol: - _binary_fips140_hmac_start fips140-loader.c then uses the _binary_fips140_ko_start/_end byte array with load_module_mem() in arch_initcall_sync call to load the precompiled FIPS 140 module. This code is partly based on the existing crypto/fips.c code and partly on the Android FIPS 140 module [1]. [1]: https://android.googlesource.com/kernel/common/+/1ca1130ec62d/crypto/fips140-module.c Only arm64 and x86-64 are explicitly supported for now. Co-developed-by: Saeed Mirzamohammadi <saeed.mirzamohammadi@xxxxxxxxxx> Signed-off-by: Vegard Nossum <vegard.nossum@xxxxxxxxxx> --- arch/arm64/kernel/vmlinux.lds.S | 1 + crypto/Makefile | 24 ++++++++ crypto/fips140-loader.c | 96 +++++++++++++++++++++++++++++++ include/asm-generic/vmlinux.lds.h | 37 +++++++++++- include/linux/fips.h | 17 ++++++ 5 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 crypto/fips140-loader.c diff --git a/arch/arm64/kernel/vmlinux.lds.S b/arch/arm64/kernel/vmlinux.lds.S index ad6133b89e7a..58a99b2de237 100644 --- a/arch/arm64/kernel/vmlinux.lds.S +++ b/arch/arm64/kernel/vmlinux.lds.S @@ -271,6 +271,7 @@ SECTIONS INIT_RAM_FS *(.init.altinstructions .init.bss) /* from the EFI stub */ } + FIPS140 .exit.data : { EXIT_DATA } diff --git a/crypto/Makefile b/crypto/Makefile index 6c5d59369dac..e07e93b189fe 100644 --- a/crypto/Makefile +++ b/crypto/Makefile @@ -7,7 +7,9 @@ obj-$(CONFIG_CRYPTO) += crypto.o crypto-y := api.o cipher.o obj-$(CONFIG_CRYPTO_ENGINE) += crypto_engine.o +ifneq ($(CONFIG_CRYPTO_FIPS140_EXTMOD),y) obj-$(CONFIG_CRYPTO_FIPS) += fips.o +endif crypto_algapi-$(CONFIG_PROC_FS) += proc.o crypto_algapi-y := algapi.o scatterwalk.o $(crypto_algapi-y) @@ -211,3 +213,25 @@ obj-$(CONFIG_CRYPTO_SIMD) += crypto_simd.o obj-$(CONFIG_CRYPTO_KDF800108_CTR) += kdf_sp800108.o obj-$(CONFIG_CRYPTO_KRB5) += krb5/ + +# +# Loader for standalone FIPS 140 module +# + +obj-$(CONFIG_CRYPTO_FIPS140_EXTMOD) += fips140-loader.o + +# +# Standalone FIPS 140 module +# + +quiet_cmd_ld_bin = LD $@ + cmd_ld_bin = (cd "$(dir $<)" && \ + $(LD) -r -b binary -o $(abspath $@) $(notdir $<) && \ + $(OBJCOPY) --rename-section .data=$(2) $(abspath $@)) + +$(obj)/fips140-module.o: $(src)/fips140.ko FORCE + $(call if_changed,ld_bin,__fips140_module) +$(obj)/fips140-digest.o: $(src)/fips140.hmac FORCE + $(call if_changed,ld_bin,__fips140_digest) + +obj-$(CONFIG_CRYPTO_FIPS140_EXTMOD) += fips140-module.o fips140-digest.o fips140-api.o diff --git a/crypto/fips140-loader.c b/crypto/fips140-loader.c new file mode 100644 index 000000000000..865d45d46786 --- /dev/null +++ b/crypto/fips140-loader.c @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * FIPS 140-3 module loader. + * + * Copyright (c) 2008 Neil Horman <nhorman@xxxxxxxxxxxxx> + * Copyright 2021 Google LLC + * Copyright (c) 2025, Oracle and/or its affiliates. + */ + +#include <linux/fips.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/panic.h> +#include <linux/printk.h> +#include <linux/string.h> + +#include <crypto/api.h> +#include <crypto/hash.h> + +int fips_enabled; +EXPORT_SYMBOL_GPL(fips_enabled); + +ATOMIC_NOTIFIER_HEAD(fips_fail_notif_chain); +EXPORT_SYMBOL_GPL(fips_fail_notif_chain); + +void fips_fail_notify(void) +{ + if (fips_enabled) + atomic_notifier_call_chain(&fips_fail_notif_chain, 0, NULL); +} +EXPORT_SYMBOL_GPL(fips_fail_notify); + +/* defined in crypto/fips140-{module,digest}.o -OR- vmlinux.lds */ +EXPORT_SYMBOL_GPL(_binary_fips140_ko_start); +EXPORT_SYMBOL_GPL(_binary_fips140_ko_end); +EXPORT_SYMBOL_GPL(_binary_fips140_hmac_start); + +/* Process kernel command-line parameter at boot time. fips=0 or fips=1 */ +static int fips_enable(char *str) +{ + fips_enabled = !!simple_strtol(str, NULL, 0); + if (!fips_enabled) + pr_info("FIPS 140-3 module: disabled\n"); + + return 1; +} + +__setup("fips=", fips_enable); + +static struct ctl_table crypto_sysctl_table[] = { + { + .procname = "fips_enabled", + .data = &fips_enabled, + .maxlen = sizeof(int), + .mode = 0444, + .proc_handler = proc_dointvec, + }, +}; + +static int __init fips_loader_init(void) +{ + void *ko_mem; + int err; + struct ctl_table_header *crypto_sysctls; + + if (!fips_enabled) { + /* Add crypto sysctl for nonfips mode */ + crypto_sysctls = register_sysctl("crypto", crypto_sysctl_table); + if (!crypto_sysctls) + pr_err("fips 140: failed to register sysctl for nonfips mode"); + + return 0; + } + + /* + * Duplicate the memory as the kernel module loader will + * modify it and mess up the integrity check. + */ + ko_mem = kvmemdup(_binary_fips140_ko_start, _binary_fips140_ko_size, GFP_KERNEL); + if (!ko_mem) { + err = -ENOMEM; + goto out; + } + + err = load_module_mem(ko_mem, _binary_fips140_ko_size); + if (err) + goto out; + + kvfree(ko_mem); + +out: + if (err) + panic("FIPS 140-3 module: loading error\n"); + return err; +} +arch_initcall_sync(fips_loader_init); diff --git a/include/asm-generic/vmlinux.lds.h b/include/asm-generic/vmlinux.lds.h index 1881d9b6b3ae..b4aee570223c 100644 --- a/include/asm-generic/vmlinux.lds.h +++ b/include/asm-generic/vmlinux.lds.h @@ -454,6 +454,40 @@ defined(CONFIG_AUTOFDO_CLANG) || defined(CONFIG_PROPELLER_CLANG) #endif #endif +#ifdef CONFIG_CRYPTO_FIPS140_EXTMOD +/* + * We have an external module; include it and its digest in their own + * named sections so they are easy to extract from the vmlinux ELF file + * after the kernel has been built. + */ +#define FIPS140 \ + . = ALIGN(PAGE_SIZE); \ + __fips140_module : AT(ADDR(__fips140_module) - LOAD_OFFSET) { \ + *(__fips140_module) \ + } \ + . = ALIGN(PAGE_SIZE); \ + __fips140_digest : AT(ADDR(__fips140_digest) - LOAD_OFFSET) { \ + *(__fips140_digest) \ + } +#else +#ifdef CONFIG_CRYPTO_FIPS140_EXTMOD +/* + * We don't have an external module (yet) but the kernel has been built + * with the loader, so this just defines an empty byte array where the + * module will go eventually. + */ +#define FIPS140 \ + _binary_fips140_ko_start = .; \ + _binary_fips140_ko_end = .; \ + _binary_fips140_hmac_start = .; \ + _binary_fips140_hmac_end = .; +#else +#define FIPS140 +#endif +#endif + + + /* * Read only Data */ @@ -1145,7 +1179,8 @@ defined(CONFIG_AUTOFDO_CLANG) || defined(CONFIG_PROPELLER_CLANG) INIT_CALLS \ CON_INITCALL \ INIT_RAM_FS \ - } + } \ + FIPS140 #define BSS_SECTION(sbss_align, bss_align, stop_align) \ . = ALIGN(sbss_align); \ diff --git a/include/linux/fips.h b/include/linux/fips.h index c6961e932fef..81560dfc6cef 100644 --- a/include/linux/fips.h +++ b/include/linux/fips.h @@ -2,12 +2,29 @@ #ifndef _FIPS_H #define _FIPS_H +#include <linux/init.h> + +#include <crypto/sha2.h> /* SHA256_DIGEST_SIZE */ + #ifdef CONFIG_CRYPTO_FIPS +/* + * fips_enabled = FIPS mode was requested on the command line + * fips_operational = FIPS module has run self-tests etc. and is operational + */ extern int fips_enabled; +extern int fips_operational; + extern struct atomic_notifier_head fips_fail_notif_chain; void fips_fail_notify(void); +/* FIPS-certified module blob and digest */ +extern const u8 __initconst _binary_fips140_ko_start[]; +extern const u8 __initconst _binary_fips140_ko_end[]; +extern const u8 __initconst _binary_fips140_hmac_start[SHA256_DIGEST_SIZE]; + +#define _binary_fips140_ko_size (_binary_fips140_ko_end - _binary_fips140_ko_start) + #else #define fips_enabled 0 -- 2.39.3