mirror of
https://github.com/wolfSSL/wolfssl.git
synced 2026-07-05 16:30:49 +02:00
29c5b02046
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.
444 lines
15 KiB
C
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);
|
|
}
|