diff --git a/arch/arm64/Makefile.postlink b/arch/arm64/Makefile.postlink index eedcf35f8d94..a13789148bfb 100644 --- a/arch/arm64/Makefile.postlink +++ b/arch/arm64/Makefile.postlink @@ -3,8 +3,11 @@ # # This file is included by the generic Kbuild makefile to permit the # architecture to perform postlink actions on vmlinux and any .ko module file. -# In this case, we only need it for fips140.ko, which needs a HMAC digest to be -# injected into it. All other targets are NOPs. +# In this case, we only need it for fips140.ko, which needs some postprocessing +# for the integrity check mandated by FIPS. This involves making copies of the +# relocation sections so that the module will have access to them at +# initialization time, and calculating and injecting a HMAC digest into the +# module. All other targets are NOPs. # PHONY := __archpost @@ -15,7 +18,14 @@ include scripts/Kbuild.include CMD_FIPS140_GEN_HMAC = crypto/fips140_gen_hmac quiet_cmd_gen_hmac = HMAC $@ - cmd_gen_hmac = $(CMD_FIPS140_GEN_HMAC) $@ + cmd_gen_hmac = $(OBJCOPY) $@ \ + --dump-section=$(shell $(READELF) -SW $@|grep -Eo '\.rela\.text\S*')=$@.rela.text \ + --dump-section=$(shell $(READELF) -SW $@|grep -Eo '\.rela\.rodata\S*')=$@.rela.rodata \ + --add-section=.init.rela.text=$@.rela.text \ + --add-section=.init.rela.rodata=$@.rela.rodata \ + --set-section-flags=.init.rela.text=alloc,readonly \ + --set-section-flags=.init.rela.rodata=alloc,readonly && \ + $(CMD_FIPS140_GEN_HMAC) $@ # `@true` prevents complaints when there is nothing to be done @@ -29,7 +39,7 @@ $(objtree)/crypto/fips140.ko: FORCE @true clean: - @true + rm -f $(objtree)/crypto/fips140.ko.rela.* PHONY += FORCE clean diff --git a/crypto/Makefile b/crypto/Makefile index bf5e547afc76..bee803d812d2 100644 --- a/crypto/Makefile +++ b/crypto/Makefile @@ -233,7 +233,8 @@ $(obj)/lib-crypto-%-fips.o: $(srctree)/lib/crypto/%.c FORCE $(obj)/crypto-fips.a: $(addprefix $(obj)/,$(crypto-fips-objs)) FORCE $(call if_changed,ar_and_symver) -fips140-objs := fips140-module.o fips140-selftests.o crypto-fips.a +fips140-objs := fips140-module.o fips140-selftests.o crypto-fips.a \ + fips140-refs.o obj-m += fips140.o CFLAGS_fips140-module.o += $(FIPS140_CFLAGS) diff --git a/crypto/fips140-module.c b/crypto/fips140-module.c index 0be7642773e2..704b367ec562 100644 --- a/crypto/fips140-module.c +++ b/crypto/fips140-module.c @@ -279,6 +279,11 @@ static void __init unapply_rodata_relocations(void *section, int section_size, } } +extern struct { + u32 offset; + u32 count; +} fips140_rela_text, fips140_rela_rodata; + static bool __init check_fips140_module_hmac(void) { SHASH_DESC_ON_STACK(desc, dontcare); @@ -306,15 +311,12 @@ static bool __init check_fips140_module_hmac(void) // apply the relocations in reverse on the copies of .text and .rodata unapply_text_relocations(textcopy, textsize, - __this_module.arch.text_relocations, - __this_module.arch.num_text_relocations); + offset_to_ptr(&fips140_rela_text.offset), + fips140_rela_text.count); unapply_rodata_relocations(rodatacopy, rodatasize, - __this_module.arch.rodata_relocations, - __this_module.arch.num_rodata_relocations); - - kfree(__this_module.arch.text_relocations); - kfree(__this_module.arch.rodata_relocations); + offset_to_ptr(&fips140_rela_rodata.offset), + fips140_rela_rodata.count); desc->tfm = crypto_alloc_shash("hmac(sha256)", 0, 0); if (IS_ERR(desc->tfm)) { diff --git a/crypto/fips140-refs.S b/crypto/fips140-refs.S new file mode 100644 index 000000000000..fcbd52776323 --- /dev/null +++ b/crypto/fips140-refs.S @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright 2021 Google LLC + * Author: Ard Biesheuvel + * + * This file contains the variable definitions that will be used by the FIPS140 + * s/w module to access the RELA sections in the ELF image. These are used to + * apply the relocations applied by the module loader in reverse, so that we + * can reconstruct the image that was used to derive the HMAC used by the + * integrity check. + * + * The first .long of each entry will be populated by the module loader based + * on the actual placement of the respective RELA section in memory. The second + * .long carries the RELA entry count, and is populated by the host tool that + * also generates the HMAC of the contents of .text and .rodata. + */ + +#include +#include + + .section ".init.rodata", "a" + + .align 2 + .globl fips140_rela_text +fips140_rela_text: + .weak __sec_rela_text + .long __sec_rela_text - . + .long 0 + + .globl fips140_rela_rodata +fips140_rela_rodata: + .weak __sec_rela_rodata + .long __sec_rela_rodata - . + .long 0 diff --git a/crypto/fips140_gen_hmac.c b/crypto/fips140_gen_hmac.c index eebdc729ca99..69f754d38a1d 100644 --- a/crypto/fips140_gen_hmac.c +++ b/crypto/fips140_gen_hmac.c @@ -28,7 +28,7 @@ static Elf64_Ehdr *ehdr; static Elf64_Shdr *shdr; static int num_shdr; -static const char *strtab; +static const char *strtab, *shstrtab; static Elf64_Sym *syms; static int num_syms; @@ -42,17 +42,78 @@ static Elf64_Shdr *find_symtab_section(void) return NULL; } -static void *get_sym_addr(const char *sym_name) +static int get_section_idx(const char *name) +{ + int i; + + for (i = 0; i < num_shdr; i++) + if (!strcmp(shstrtab + shdr[i].sh_name, name)) + return i; + return -1; +} + +static int get_sym_idx(const char *sym_name) { int i; for (i = 0; i < num_syms; i++) if (!strcmp(strtab + syms[i].st_name, sym_name)) - return (void *)ehdr + shdr[syms[i].st_shndx].sh_offset + - syms[i].st_value; + return i; + return -1; +} + +static void *get_sym_addr(const char *sym_name) +{ + int i = get_sym_idx(sym_name); + + if (i >= 0) + return (void *)ehdr + shdr[syms[i].st_shndx].sh_offset + + syms[i].st_value; return NULL; } +static int update_rela_ref(const char *name) +{ + /* + * We need to do a couple of things to ensure that the copied RELA data + * is accessible to the module itself at module init time: + * - the associated entry in the symbol table needs to refer to the + * correct section index, and have SECTION type and GLOBAL linkage. + * - the 'count' global variable in the module need to be set to the + * right value based on the size of the RELA section. + */ + unsigned int *size_var; + int sec_idx, sym_idx; + char str[32]; + + sprintf(str, "fips140_rela_%s", name); + size_var = get_sym_addr(str); + if (!size_var) { + printf("variable '%s' not found, disregarding .%s section\n", + str, name); + return 1; + } + + sprintf(str, "__sec_rela_%s", name); + sym_idx = get_sym_idx(str); + + sprintf(str, ".init.rela.%s", name); + sec_idx = get_section_idx(str); + + if (sec_idx < 0 || sym_idx < 0) { + fprintf(stderr, "failed to locate metadata for .%s section in binary\n", + name); + return 0; + } + + syms[sym_idx].st_shndx = sec_idx; + syms[sym_idx].st_info = (STB_GLOBAL << 4) | STT_SECTION; + + size_var[1] = shdr[sec_idx].sh_size / sizeof(Elf64_Rela); + + return 1; +} + static void hmac_section(HMAC_CTX *hmac, const char *start, const char *end) { void *start_addr = get_sym_addr(start); @@ -103,6 +164,10 @@ int main(int argc, char **argv) num_syms = symtab_shdr->sh_size / sizeof(Elf64_Sym); strtab = (void *)ehdr + shdr[symtab_shdr->sh_link].sh_offset; + shstrtab = (void *)ehdr + shdr[ehdr->e_shstrndx].sh_offset; + + if (!update_rela_ref("text") || !update_rela_ref("rodata")) + exit(EXIT_FAILURE); hmac_key = get_sym_addr("fips140_integ_hmac_key"); if (!hmac_key) {