Files
wolfssl/linuxkm/linuxkm-fips-hash.c
T
Daniel Pouzzner 29c5b02046 linuxkm/: finish support for stabilization of .rodata_wolfcrypt segment in WC_SYM_RELOC_TABLES (FIPS) kernel module builds:
linuxkm/Makefile: update the GENERATE_RELOC_TAB recipe to generate both wc_linuxkm_pie_text_reloc_tab[] and wc_linuxkm_pie_rodata_reloc_tab.

linuxkm/linuxkm-fips-hash-wrapper.sh: add handling for wc_linuxkm_pie_rodata_reloc_tab.

linuxkm/linuxkm-fips-hash.c: add handling for rodata_reloc_tab.*.

linuxkm/linuxkm_memory.c:
* refactor find_reloc_tab_offset() to be segment-agnostic and tolerate empty reloc tabs.
* refactor wc_reloc_normalize_segment():
  * to be segment-agnostic,
  * identify the src segment dynamically,
  * return BAD_FUNC_ARG where previously returning literal -1,
  * use seg_in_out_len arg to accommodate size skew between input and output (not currently used), and
  * rename working vars for better mnemonicitude.
* update wc_fips_generate_hash() to
  * handle seg_map->rodata_reloc_tab,
  * use new calling convention for wc_reloc_normalize_segment(), and
  * add wc_reloc_normalize_segment() loop for .rodata_wolfcrypt.

linuxkm/linuxkm_memory.h and linuxkm/linuxkm_wc_port.h: rename WOLFSSL_TEXT_SEGMENT_CANONICALIZER* to WOLFSSL_SEGMENT_CANONICALIZER*, with backward-compat provisions.

linuxkm/module_hooks.c:
* add wc_linuxkm_normalize_relocations_noresize() backward-compat wrapper.
* wolfssl_init(): add .rodata_wolfcrypt relocation handling alongside existing .text_wolfcrypt handling, and update for new wc_reloc_normalize_segment() calling convention.
* add seg_map.rodata_reloc_tab initialization.
* update wc_linuxkm_normalize_relocations() to be segment-agnostic and use new wc_reloc_normalize_segment() calling convention.
2026-04-28 12:58:32 -05:00

444 lines
15 KiB
C

/* 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 <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <sys/mman.h>
#include <getopt.h>
#include <wolfssl/options.h>
#include <wolfssl/wolfcrypt/types.h>
#include <wolfssl/wolfcrypt/error-crypt.h>
#include <wolfssl/wolfcrypt/hmac.h>
#include <wolfssl/wolfcrypt/memory.h>
/* 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 <offset>", opt->name);
else
printf(" --%s <arg>", 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);
}