/* linuxkm-fips-hash.c * * A utility to compute the correct FIPS integrity hash for a fully linked * libwolfssl.ko kernel module, and to optionally update the module in place. * * Copyright (C) 2006-2026 wolfSSL Inc. * * This file is part of wolfSSL. * * wolfSSL is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * wolfSSL is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ /* See linuxkm-fips-hash-wrapper.sh for argument setup example. */ #include #include #include #include #include #include #include #include #include #include #include #include /* failsafe definitions for FIPS <5.3 */ #ifndef FIPS_IN_CORE_DIGEST_SIZE #ifndef NO_SHA256 #define FIPS_IN_CORE_DIGEST_SIZE WC_SHA256_DIGEST_SIZE #define FIPS_IN_CORE_HASH_TYPE WC_SHA256 #elif defined(WOLFSSL_SHA384) #define FIPS_IN_CORE_DIGEST_SIZE WC_SHA384_DIGEST_SIZE #define FIPS_IN_CORE_HASH_TYPE WC_SHA384 #else #error Unsupported FIPS hash alg. #endif #endif #ifndef FIPS_IN_CORE_KEY_SZ #define FIPS_IN_CORE_KEY_SZ FIPS_IN_CORE_DIGEST_SIZE #endif #ifndef FIPS_IN_CORE_VERIFY_SZ #define FIPS_IN_CORE_VERIFY_SZ FIPS_IN_CORE_DIGEST_SIZE #endif #ifdef WOLFCRYPT_FIPS_CORE_DYNAMIC_HASH_VALUE extern const char coreKey[FIPS_IN_CORE_KEY_SZ*2 + 1]; #endif static int hmac_setkey_cb(Hmac *hmac, const byte *key, word32 key_len) { return wc_HmacSetKey(hmac, FIPS_IN_CORE_HASH_TYPE, key, key_len); } static int hmac_update_cb(Hmac *hmac, const byte *in, word32 in_len) { return wc_HmacUpdate_fips(hmac, in, in_len); } static int hmac_final_cb(Hmac *hmac, byte *out, word32 out_sz) { int actual_size = wc_HmacSizeByType(hmac->macType); if (actual_size < 0) return actual_size; if ((int)out_sz != actual_size) return BUFFER_E; return wc_HmacFinal(hmac, out); } int main(int argc, char **argv) { Hmac hmac; int ret; struct wc_reloc_table_segments seg_map = WC_RELOC_TABLE_SEGMENTS_INITIALIZER; word32 new_verifyCore_size = FIPS_IN_CORE_DIGEST_SIZE*2 + 1; char new_verifyCore[FIPS_IN_CORE_DIGEST_SIZE*2 + 1]; const char *progname = strchr(argv[0], '/') ? strrchr(argv[0], '/') + 1 : argv[0]; const char *mod_path = NULL; const char *user_coreKey = NULL; int mod_fd; struct stat st; byte *mod_map = MAP_FAILED; struct wc_reloc_counts reloc_counts; int inplace = 0; int quiet = 0; int verbose = 0; static const struct option long_options[] = { #define FENCEPOST_OPT_FLAG (1U << 30) #define FENCEPOST_OPT(x) { .name = #x, \ .has_arg = required_argument, \ .flag = NULL, \ .val = FENCEPOST_OPT_FLAG | offsetof(typeof(seg_map), x) } FENCEPOST_OPT(text_start), FENCEPOST_OPT(text_end), FENCEPOST_OPT(text_reloc_tab.start), FENCEPOST_OPT(text_reloc_tab.end), FENCEPOST_OPT(text_reloc_tab.len_start), FENCEPOST_OPT(text_reloc_tab.len_end), FENCEPOST_OPT(rodata_reloc_tab.start), FENCEPOST_OPT(rodata_reloc_tab.end), FENCEPOST_OPT(rodata_reloc_tab.len_start), FENCEPOST_OPT(rodata_reloc_tab.len_end), FENCEPOST_OPT(fips_text_start), FENCEPOST_OPT(fips_text_end), FENCEPOST_OPT(rodata_start), FENCEPOST_OPT(rodata_end), FENCEPOST_OPT(fips_rodata_start), FENCEPOST_OPT(fips_rodata_end), FENCEPOST_OPT(verifyCore_start), FENCEPOST_OPT(verifyCore_end), FENCEPOST_OPT(data_start), FENCEPOST_OPT(data_end), FENCEPOST_OPT(bss_start), FENCEPOST_OPT(bss_end), { "core-key", required_argument, NULL, 'k' }, { "mod-path", required_argument, NULL, 'f' }, { "in-place", no_argument, NULL, 'i' }, { "quiet", no_argument, NULL, 'q' }, { "verbose", no_argument, NULL, 'v' }, { "help", no_argument, NULL, 'h' }, { } }; for (;;) { int option_index = 0; int c = getopt_long(argc, argv, "f:ik:qvh", long_options, &option_index); if (c == -1) break; if (c & FENCEPOST_OPT_FLAG) { char *eptr; c &= ~FENCEPOST_OPT_FLAG; *((unsigned long *)((byte *)&seg_map + c)) = strtoul(optarg, &eptr, 0); if (*eptr != '\0') { fprintf(stderr, "%s: %s: supplied arg \"%s\" isn't a valid number.\n", progname, long_options[option_index].name, optarg); exit(1); } continue; } switch (c) { case 'f': mod_path = optarg; break; case 'i': inplace = 1; break; case 'k': user_coreKey = optarg; break; case 'q': quiet = 1; break; case 'v': verbose = 1; break; case 'h': printf("usage: %s \\\n", progname); for (int i=0; i < (int)(sizeof long_options / sizeof long_options[0]); ++i) { const struct option *opt = &long_options[i]; if (opt->name == NULL) { printf("\n"); continue; } if (i > 0) printf(" \\\n"); if (opt->has_arg == no_argument) printf(" --%s", opt->name); else if (opt->val & FENCEPOST_OPT_FLAG) printf(" --%s ", opt->name); else printf(" --%s ", opt->name); } exit(0); case ':': fprintf(stderr, "%s: Missing argument. Try --help.\n", progname); exit(1); __builtin_unreachable(); case '?': fprintf(stderr, "%s: Unrecognized option. Try --help.\n", progname); exit(1); __builtin_unreachable(); default: fprintf(stderr, "%s: Unexpected error. Try --help.\n", progname); exit(1); __builtin_unreachable(); } } if (optind < argc) { fprintf(stderr, "%s: unexpected trailing argument(s).\n", progname); exit(1); } if (user_coreKey == NULL) { #ifdef WOLFCRYPT_FIPS_CORE_DYNAMIC_HASH_VALUE user_coreKey = coreKey; #else fprintf(stderr, "%s: must supply --core-key.\n", progname); exit(1); #endif } #ifdef WC_USE_PIE_FENCEPOSTS_FOR_FIPS if ((seg_map.fips_text_start != ~0UL) || (seg_map.fips_text_end != ~0UL) || (seg_map.fips_rodata_start != ~0UL) || (seg_map.fips_rodata_end != ~0UL)) { fprintf(stderr, "%s: note, ignoring explicit FIPS fenceposts " "because WC_USE_PIE_FENCEPOSTS_FOR_FIPS.\n", progname); } seg_map.fips_text_start = seg_map.text_start; seg_map.fips_text_end = seg_map.text_end; seg_map.fips_rodata_start = seg_map.rodata_start; seg_map.fips_rodata_end = seg_map.rodata_end; #endif if ((seg_map.text_start == ~0UL) || (seg_map.text_end == ~0UL) || (seg_map.text_reloc_tab.start == ~0UL) || (seg_map.text_reloc_tab.end == ~0UL) || (seg_map.text_reloc_tab.len_start == ~0UL) || (seg_map.text_reloc_tab.len_end == ~0UL) || (seg_map.rodata_reloc_tab.start == ~0UL) || (seg_map.rodata_reloc_tab.end == ~0UL) || (seg_map.rodata_reloc_tab.len_start == ~0UL) || (seg_map.rodata_reloc_tab.len_end == ~0UL) || (seg_map.fips_text_start == ~0UL) || (seg_map.fips_text_end == ~0UL) || (seg_map.rodata_start == ~0UL) || (seg_map.rodata_end == ~0UL) || (seg_map.fips_rodata_start == ~0UL) || (seg_map.fips_rodata_end == ~0UL) || (seg_map.verifyCore_start == ~0UL) || (seg_map.verifyCore_end == ~0UL) || (seg_map.data_start == ~0UL) || (seg_map.data_end == ~0UL) || (seg_map.bss_start == ~0UL) || (seg_map.bss_end == ~0UL)) { fprintf(stderr, "%s: segment fencepost(s) missing. Try --help.\n", progname); exit(1); } if (mod_path == NULL) { fprintf(stderr, "%s: module path missing. Try --help.\n", progname); exit(1); } mod_fd = open(mod_path, inplace ? O_RDWR : O_RDONLY); if (mod_fd < 0) { fprintf(stderr, "%s: open %s: %m.\n", progname, mod_path); exit(1); } ret = fstat(mod_fd, &st); if (ret < 0) { fprintf(stderr, "%s: fstat %s: %m.\n", progname, mod_path); exit(1); } if ((seg_map.text_reloc_tab.start >= seg_map.text_reloc_tab.end) || (seg_map.text_reloc_tab.end >= (unsigned long)st.st_size) || (seg_map.text_reloc_tab.len_start >= seg_map.text_reloc_tab.len_end) || (seg_map.text_reloc_tab.len_end >= (unsigned long)st.st_size)) { fprintf(stderr, "%s: supplied text_reloc_tab fencepost(s) are out of bounds " "for supplied module %s with length %lu.\n", progname, mod_path, (unsigned long)st.st_size); exit(1); } if ((seg_map.rodata_reloc_tab.start >= seg_map.rodata_reloc_tab.end) || (seg_map.rodata_reloc_tab.end >= (unsigned long)st.st_size) || (seg_map.rodata_reloc_tab.len_start >= seg_map.rodata_reloc_tab.len_end) || (seg_map.rodata_reloc_tab.len_end >= (unsigned long)st.st_size)) { fprintf(stderr, "%s: supplied rodata_reloc_tab fencepost(s) are out of bounds " "for supplied module %s with length %lu.\n", progname, mod_path, (unsigned long)st.st_size); exit(1); } mod_map = (byte *)mmap(NULL, st.st_size, inplace ? PROT_READ | PROT_WRITE : PROT_READ, (inplace ? MAP_SHARED : MAP_PRIVATE) | MAP_POPULATE, mod_fd, 0); if (mod_map == MAP_FAILED) { fprintf(stderr, "%s: mmap() of %s, length %zu: %m.\n", progname, mod_path, st.st_size); exit(1); } seg_map.start = (unsigned long)mod_map; seg_map.end = (unsigned long)mod_map + st.st_size; seg_map.text_reloc_tab.start += (unsigned long)mod_map; seg_map.text_reloc_tab.end += (unsigned long)mod_map; seg_map.text_reloc_tab.len_start += (unsigned long)mod_map; seg_map.text_reloc_tab.len_end += (unsigned long)mod_map; seg_map.rodata_reloc_tab.start += (unsigned long)mod_map; seg_map.rodata_reloc_tab.end += (unsigned long)mod_map; seg_map.rodata_reloc_tab.len_start += (unsigned long)mod_map; seg_map.rodata_reloc_tab.len_end += (unsigned long)mod_map; seg_map.verifyCore_start += (unsigned long)mod_map; seg_map.verifyCore_end += (unsigned long)mod_map; seg_map.fips_text_start += (unsigned long)mod_map; seg_map.fips_text_end += (unsigned long)mod_map; seg_map.fips_rodata_start += (unsigned long)mod_map; seg_map.fips_rodata_end += (unsigned long)mod_map; seg_map.text_start += (unsigned long)mod_map; seg_map.text_end += (unsigned long)mod_map; seg_map.rodata_start += (unsigned long)mod_map; seg_map.rodata_end += (unsigned long)mod_map; seg_map.data_start += (unsigned long)mod_map; seg_map.data_end += (unsigned long)mod_map; seg_map.bss_start += (unsigned long)mod_map; seg_map.bss_end += (unsigned long)mod_map; ret = wolfCrypt_Init(); if (ret < 0) { fprintf(stderr, "%s: wolfCrypt_Init() failed: %s.\n", progname, wc_GetErrorString(ret)); exit(1); } ret = wc_HmacInit(&hmac, NULL, INVALID_DEVID); if (ret != 0) { fprintf(stderr, "%s: wc_HmacInit() failed: %s.\n", progname, wc_GetErrorString(ret)); exit(1); } if (seg_map.verifyCore_end - seg_map.verifyCore_start != new_verifyCore_size) { fprintf(stderr, "%s: unexpected verifyCore length %zu.\n", progname, (size_t)(seg_map.verifyCore_end - seg_map.verifyCore_start)); ret = -1; goto out; } XMEMSET(&reloc_counts, 0, sizeof(reloc_counts)); ret = wc_fips_generate_hash( &seg_map, FIPS_IN_CORE_DIGEST_SIZE, user_coreKey, &hmac, (wc_fips_verifyCore_hmac_setkey_fn)hmac_setkey_cb, (wc_fips_verifyCore_hmac_update_fn)hmac_update_cb, (wc_fips_verifyCore_hmac_final_fn)hmac_final_cb, new_verifyCore, &new_verifyCore_size, &reloc_counts); if (ret < 0) { fprintf(stderr, "%s: wc_fips_generate_hash() failed: %s.\n", progname, wc_GetErrorString(ret)); goto out; } if (verbose) fprintf(inplace ? stdout : stderr, "FIPS-bounded relocation normalizations: text=%d, rodata=%d, " "rwdata=%d, bss=%d, other=%d\n", reloc_counts.text, reloc_counts.rodata, reloc_counts.rwdata, reloc_counts.bss, reloc_counts.other); if (new_verifyCore_size < sizeof new_verifyCore) { fprintf(stderr, "%s: wc_fips_generate_hash() returned unexpected " "verifyCore length %u.\n", progname, new_verifyCore_size); ret = -1; goto out; } if ((! quiet) && (verbose || !inplace)) printf("%s\n", new_verifyCore); if (strcmp((char *)seg_map.verifyCore_start, new_verifyCore) == 0) { fprintf(stderr, "%s: note, verifyCore already matches.\n", progname); } else if (inplace) { XMEMCPY((void *)seg_map.verifyCore_start, new_verifyCore, new_verifyCore_size); ret = munmap(mod_map, st.st_size); if (ret < 0) { fprintf(stderr, "%s: munmap: %m\n", progname); exit(1); } mod_map = MAP_FAILED; ret = close(mod_fd); if (ret < 0) { fprintf(stderr, "%s: close: %m\n", progname); exit(1); } mod_fd = -1; printf("FIPS integrity hash updated successfully.\n"); } out: wc_HmacFree(&hmac); wolfCrypt_Cleanup(); if (mod_map != MAP_FAILED) { if (munmap(mod_map, st.st_size) < 0) { fprintf(stderr, "%s: munmap: %m\n", progname); if (ret == 0) ret = -1; } } if (mod_fd >= 0) { if (close(mod_fd) < 0) { fprintf(stderr, "%s: close: %m\n", progname); if (ret == 0) ret = -1; } } if (ret) exit(1); else exit(0); }