/* linuxkm_memory.c
 *
 * 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
 */

/* included by wolfcrypt/src/memory.c */

#if defined(WOLFSSL_LINUXKM) && defined(WC_SYM_RELOC_TABLES) && defined(CONFIG_FORTIFY_SOURCE)
/* needed because FORTIFY_SOURCE inline implementations call fortify_panic(). */
void __my_fortify_panic(const char *name) {
    pr_emerg("__my_fortify_panic in %s\n", name);
    BUG();
}
#endif

#ifdef DEBUG_LINUXKM_PIE_SUPPORT
    #define RELOC_DEBUG_PRINTF(fmt, ...) WOLFSSL_DEBUG_PRINTF("%s L %d: " fmt, __FILE__, __LINE__, ## __VA_ARGS__)
#else
    #define RELOC_DEBUG_PRINTF(...) WC_DO_NOTHING
#endif

#if defined(WC_SYM_RELOC_TABLES) || defined(WC_SYM_RELOC_TABLES_SUPPORT)

static const struct reloc_layout_ent {
    const char *name;
    word64 mask;
    word64 width;
    word64 is_signed:1;
    word64 is_relative:1;
    word64 is_pages:1;
    word64 is_pair_lo:1;
    word64 is_pair_hi:1;
} reloc_layouts[] = {
    [WC_R_X86_64_32]                  = { "R_X86_64_32",                                                ~0UL, 32, .is_signed = 0, .is_relative = 0 },
    [WC_R_X86_64_32S]                 = { "R_X86_64_32S",                                               ~0UL, 32, .is_signed = 1, .is_relative = 0 },
    [WC_R_X86_64_64]                  = { "R_X86_64_64",                                                ~0UL, 64, .is_signed = 0, .is_relative = 0 },
    [WC_R_X86_64_PC32]                = { "R_X86_64_PC32",                                              ~0UL, 32, .is_signed = 1, .is_relative = 1 },
    [WC_R_X86_64_PLT32]               = { "R_X86_64_PLT32",                                             ~0UL, 32, .is_signed = 1, .is_relative = 1 },
    [WC_R_AARCH64_ABS32]              = { "R_AARCH64_ABS32",                                            ~0UL, 32, .is_signed = 1, .is_relative = 0, .is_pages = 0, .is_pair_lo = 0, .is_pair_hi = 0 },
    [WC_R_AARCH64_ABS64]              = { "R_AARCH64_ABS64",                                            ~0UL, 64, .is_signed = 1, .is_relative = 0, .is_pages = 0, .is_pair_lo = 0, .is_pair_hi = 0 },
    [WC_R_AARCH64_ADD_ABS_LO12_NC]    = { "R_AARCH64_ADD_ABS_LO12_NC",    0b00000000001111111111110000000000, 32, .is_signed = 0, .is_relative = 0, .is_pages = 0, .is_pair_lo = 1, .is_pair_hi = 0 },
    [WC_R_AARCH64_ADR_PREL_PG_HI21]   = { "R_AARCH64_ADR_PREL_PG_HI21",   0b01100000111111111111111111100000, 32, .is_signed = 1, .is_relative = 1, .is_pages = 1, .is_pair_lo = 0, .is_pair_hi = 1 },
    [WC_R_AARCH64_CALL26]             = { "R_AARCH64_CALL26",             0b00000011111111111111111111111111, 32, .is_signed = 1, .is_relative = 1, .is_pages = 0, .is_pair_lo = 0, .is_pair_hi = 0 },
    [WC_R_AARCH64_JUMP26]             = { "R_AARCH64_JUMP26",             0b00000011111111111111111111111111, 32, .is_signed = 1, .is_relative = 1, .is_pages = 0, .is_pair_lo = 0, .is_pair_hi = 0 },
    [WC_R_AARCH64_LDST8_ABS_LO12_NC]  = { "R_AARCH64_LDST8_ABS_LO12_NC",  0b00000000001111111111110000000000, 32, .is_signed = 0, .is_relative = 0, .is_pages = 0, .is_pair_lo = 1, .is_pair_hi = 0 },
    [WC_R_AARCH64_LDST16_ABS_LO12_NC] = { "R_AARCH64_LDST16_ABS_LO12_NC", 0b00000000001111111111110000000000, 32, .is_signed = 0, .is_relative = 0, .is_pages = 0, .is_pair_lo = 1, .is_pair_hi = 0 },
    [WC_R_AARCH64_LDST32_ABS_LO12_NC] = { "R_AARCH64_LDST32_ABS_LO12_NC", 0b00000000001111111111110000000000, 32, .is_signed = 0, .is_relative = 0, .is_pages = 0, .is_pair_lo = 1, .is_pair_hi = 0 },
    [WC_R_AARCH64_LDST64_ABS_LO12_NC] = { "R_AARCH64_LDST64_ABS_LO12_NC", 0b00000000001111111111110000000000, 32, .is_signed = 0, .is_relative = 0, .is_pages = 0, .is_pair_lo = 1, .is_pair_hi = 0 },
    [WC_R_AARCH64_PREL32]             = { "R_AARCH64_PREL32",                                           ~0UL, 32, .is_signed = 1, .is_relative = 1, .is_pages = 0, .is_pair_lo = 0, .is_pair_hi = 0 },
    [WC_R_ARM_ABS32]                  = { "R_ARM_ABS32",                                                ~0UL, 32, .is_signed = 0, .is_relative = 0, .is_pages = 0, .is_pair_lo = 0, .is_pair_hi = 0 },
    [WC_R_ARM_PREL31]                 = { "R_ARM_PREL31",                 0b01111111111111111111111111111111, 32, .is_signed = 1, .is_relative = 1, .is_pages = 0, .is_pair_lo = 0, .is_pair_hi = 0 },
    [WC_R_ARM_REL32]                  = { "R_ARM_REL32",                                                ~0UL, 32, .is_signed = 1, .is_relative = 1, .is_pages = 0, .is_pair_lo = 0, .is_pair_hi = 0 },
    [WC_R_ARM_THM_CALL]               = { "R_ARM_THM_CALL",               0b00000111111111110010111111111111, 32, .is_signed = 1, .is_relative = 1, .is_pages = 0, .is_pair_lo = 0, .is_pair_hi = 0 },
    [WC_R_ARM_THM_JUMP24]             = { "R_ARM_THM_JUMP24",             0b00000111111111110010111111111111, 32, .is_signed = 1, .is_relative = 1, .is_pages = 0, .is_pair_lo = 0, .is_pair_hi = 0 },
    [WC_R_ARM_THM_JUMP11]             = { "R_ARM_THM_JUMP11",             0b00000000000000000000011111111111, 16, .is_signed = 1, .is_relative = 1, .is_pages = 0, .is_pair_lo = 0, .is_pair_hi = 0 },
    [WC_R_ARM_THM_MOVT_ABS]           = { "R_ARM_THM_MOVT_ABS",           0b00000100000011110111000011111111, 32, .is_signed = 0, .is_relative = 0, .is_pages = 0, .is_pair_lo = 0, .is_pair_hi = 1 },
    [WC_R_ARM_THM_MOVW_ABS_NC]        = { "R_ARM_THM_MOVW_ABS_NC",        0b00000100000011110111000011111111, 32, .is_signed = 0, .is_relative = 0, .is_pages = 0, .is_pair_lo = 1, .is_pair_hi = 0 }
    };

static inline long find_reloc_tab_offset(
    const struct wc_reloc_table_ent reloc_tab[],
    word32 reloc_tab_len,
    size_t seg_in_offset)
{
    long ret;
    unsigned long hop;

    if (seg_in_offset >= (size_t)reloc_tab[reloc_tab_len - 1].offset) {
        RELOC_DEBUG_PRINTF("ERROR: %s failed.\n", __FUNCTION__);
        return BAD_FUNC_ARG;
    }

    if (reloc_tab_len <= 1) {
        /* empty relocation table. */
        return 0;
    }
    for (ret = 0,
             hop = reloc_tab_len >> 1;
         hop;
         hop >>= 1)
    {
        if (seg_in_offset == (size_t)reloc_tab[ret].offset)
            break;
        else if (seg_in_offset > (size_t)reloc_tab[ret].offset)
            ret += hop;
        else if (ret)
            ret -= hop;
    }

    while ((ret < (long)reloc_tab_len - 1) &&
           ((size_t)reloc_tab[ret].offset < seg_in_offset))
        ++ret;

    while ((ret > 0) &&
           ((size_t)reloc_tab[ret - 1].offset >= seg_in_offset))
        --ret;

#ifdef DEBUG_LINUXKM_PIE_SUPPORT
    if (ret < 0)
        RELOC_DEBUG_PRINTF("ERROR: %s returning %ld.\n", __FUNCTION__, ret);
#endif
    return ret;
}

/* Note we are not currently accommodating endianness conflicts between the
 * build and target host, but if we were, these macros would byte swap.
 * Currently, we detect and fail early on endianness conflicts.
 */
#define wc_get_unaligned(v) ({ typeof(*(v)) _v_aligned; XMEMCPY((void *)&_v_aligned, (void *)(v), sizeof _v_aligned); _v_aligned; })
#define wc_put_unaligned(v, v_out) do { typeof(v) _v = (v); XMEMCPY((void *)(v_out), (void *)&_v, sizeof(typeof(*(v_out)))); } while (0)

ssize_t wc_reloc_normalize_segment(
    const byte *seg_in,
    size_t *seg_in_out_len,
    byte *seg_out,
    ssize_t *cur_index_p,
    const struct wc_reloc_table_segments *seg_map,
    struct wc_reloc_counts *reloc_counts)
{
    ssize_t i;
    size_t seg_in_offset;
    const struct wc_reloc_table_ent *last_reloc; /* for error-checking order in reloc_tab[] */
    int n_text_r = 0, n_rodata_r = 0, n_rwdata_r = 0, n_bss_r = 0, n_other_r = 0, n_oob_r = 0;
    const struct wc_reloc_table_ent *reloc_tab;
    word32 reloc_tab_len;
    uintptr_t src_seg_start;
#ifdef DEBUG_LINUXKM_PIE_SUPPORT
    uintptr_t src_seg_end;
    const char *src_seg_name;
#endif

    if (*seg_in_out_len == 0)
        return BAD_FUNC_ARG;

    if (((uintptr_t)seg_in >= seg_map->text_start) &&
        ((uintptr_t)(seg_in + *seg_in_out_len) <= seg_map->text_end))
    {
        reloc_tab = (const struct wc_reloc_table_ent *)seg_map->text_reloc_tab.start;
        reloc_tab_len = *(const word32 *)seg_map->text_reloc_tab.len_start;
        src_seg_start = seg_map->text_start;
#ifdef DEBUG_LINUXKM_PIE_SUPPORT
        src_seg_end = seg_map->text_end;
        src_seg_name = "text";
#endif
    }
    else if (((uintptr_t)seg_in >= seg_map->rodata_start) &&
        ((uintptr_t)(seg_in + *seg_in_out_len) <= seg_map->rodata_end))
    {
        reloc_tab = (const struct wc_reloc_table_ent *)seg_map->rodata_reloc_tab.start;
        reloc_tab_len = *(const word32 *)seg_map->rodata_reloc_tab.len_start;
        src_seg_start = seg_map->rodata_start;
#ifdef DEBUG_LINUXKM_PIE_SUPPORT
        src_seg_end = seg_map->rodata_end;
        src_seg_name = "rodata";
#endif
    }
    else
    {
        RELOC_DEBUG_PRINTF("ERROR: %s returning BAD_FUNC_ARG with span %llx-%llx versus text %llx-%llx and rodata %llx-%llx.\n",
               __FUNCTION__,
               (unsigned long long)(uintptr_t)seg_in,
               (unsigned long long)(uintptr_t)(seg_in + *seg_in_out_len),
               (unsigned long long)seg_map->text_start,
               (unsigned long long)seg_map->text_end,
               (unsigned long long)seg_map->rodata_start,
               (unsigned long long)seg_map->rodata_end);
        return BAD_FUNC_ARG;
    }

    seg_in_offset = (uintptr_t)seg_in - src_seg_start;

    if (cur_index_p)
        i = *cur_index_p;
    else
        i = -1;

    if (i == -1)
        i = find_reloc_tab_offset(reloc_tab, reloc_tab_len, seg_in_offset);

    if (i < 0)
        return i;

    WC_SANITIZE_DISABLE();
    memcpy(seg_out, seg_in, *seg_in_out_len);
    WC_SANITIZE_ENABLE();

    /* note, if there are no relocations in the src seg, the loop isn't entered
     * at all, and we return without further ado.
     */
    for (last_reloc = &reloc_tab[i > 0 ? i-1 : 0];
         (size_t)i < reloc_tab_len - 1;
         ++i)
    {
        const struct wc_reloc_table_ent *next_reloc = &reloc_tab[i];
        enum wc_reloc_dest_segment dest_seg;
        uintptr_t dest_seg_start;
#ifdef DEBUG_LINUXKM_PIE_SUPPORT
        uintptr_t dest_seg_end;
        const char *dest_seg_name;
#endif
        word64 reloc_buf = 0;
        const struct reloc_layout_ent *layout;
        unsigned int next_reloc_rel;

        if (next_reloc->dest_segment == WC_R_SEG_NONE) {
            RELOC_DEBUG_PRINTF("BUG: missing dest segment for relocation at reloc_tab[%zd]\n", i);
            continue;
        }

        if (last_reloc->offset > next_reloc->offset) {
            RELOC_DEBUG_PRINTF("BUG: out-of-order offset found at reloc_tab[%zd]: %u > %u\n",
                   i, last_reloc->offset, next_reloc->offset);
            return BAD_FUNC_ARG;
        }

        last_reloc = next_reloc;

        if (next_reloc->reloc_type >= (sizeof reloc_layouts / sizeof reloc_layouts[0])) {
            RELOC_DEBUG_PRINTF("BUG: unknown relocation type %u found at reloc_tab[%zd]\n",
                   next_reloc->reloc_type, i);
                return BAD_FUNC_ARG;
        }

        layout = &reloc_layouts[next_reloc->reloc_type];

        switch (layout->width) {
        case 32:
        case 64:
        case 16:
            break;
        default:
            RELOC_DEBUG_PRINTF("BUG: unexpected relocation width %llu found at reloc_tab[%lld], reloc type %u\n",
                   (unsigned long long)layout->width, (long long)i, next_reloc->reloc_type);
            return BAD_FUNC_ARG;
        }

        /* provisionally assign the destination segment from the reloc record --
         * it may wind up getting overridden later if things don't go as
         * expected.
         */
        dest_seg = next_reloc->dest_segment;

        /* next_reloc_rel is the offset of the relocation relative to the start
         * of the current text chunk (seg_in).  i.e., seg_in + next_reloc_rel
         * is the start of the relocation.
         */
        next_reloc_rel = next_reloc->offset - seg_in_offset;

        if (next_reloc_rel >= *seg_in_out_len) {
            /* no more relocations in this buffer. */
            break;
        }

        if ((*seg_in_out_len < WC_BITS_TO_BYTES(layout->width)) ||
            (next_reloc_rel > *seg_in_out_len - WC_BITS_TO_BYTES(layout->width)))
        {
            /* relocation straddles buffer at end -- caller will try again with
             * that relocation at the start.
             */
            *seg_in_out_len = next_reloc_rel;
            break;
        }

        /* set reloc_buf to the address bits from the live text segment.  on
         * ARM, this will often also pull in some opcode bits, which we mask out.
         */
        if (layout->is_signed) {
            /* Note, the intermediate cast to sword64 is necessary to
             * sign-extend the value to 64 bits before unsigned
             * reinterpretation.  Normalization later relies on this.
             */
            switch (layout->width) {
            case 32:
                reloc_buf = (word64)(sword64)(wc_get_unaligned((sword32 *)&seg_out[next_reloc_rel]) & (sword32)layout->mask);
                break;
            case 64:
                reloc_buf = (word64)(sword64)(wc_get_unaligned((sword64 *)&seg_out[next_reloc_rel]) & (sword64)layout->mask);
                break;
            case 16:
                reloc_buf = (word64)(sword64)(wc_get_unaligned((sword16 *)&seg_out[next_reloc_rel]) & (sword16)layout->mask);
                break;
            }
        }
        else {
            switch (layout->width) {
            case 32:
                reloc_buf = (word64)(wc_get_unaligned((word32 *)&seg_out[next_reloc_rel]) & (word32)layout->mask);
                break;
            case 64:
                reloc_buf = (word64)(wc_get_unaligned((word64 *)&seg_out[next_reloc_rel]) & layout->mask);
                break;
            case 16:
                reloc_buf = (word64)(wc_get_unaligned((word16 *)&seg_out[next_reloc_rel]) & (word16)layout->mask);
                break;
            }
        }

        switch (dest_seg) {
        case WC_R_SEG_TEXT:
            dest_seg_start = seg_map->text_start;
            ++n_text_r;
            #ifdef DEBUG_LINUXKM_PIE_SUPPORT
            dest_seg_end = seg_map->text_end;
            dest_seg_name = "text";
            #endif
            break;
        case WC_R_SEG_RODATA:
            dest_seg_start = seg_map->rodata_start;
            ++n_rodata_r;
            #ifdef DEBUG_LINUXKM_PIE_SUPPORT
            dest_seg_end = seg_map->rodata_end;
            dest_seg_name = "rodata";
            #endif
            break;
        case WC_R_SEG_RWDATA:
            dest_seg_start = seg_map->data_start;
            ++n_rwdata_r;
            #ifdef DEBUG_LINUXKM_PIE_SUPPORT
            dest_seg_end = seg_map->data_end;
            dest_seg_name = "data";
            #endif
            break;
        case WC_R_SEG_BSS:
            dest_seg_start = seg_map->bss_start;
            ++n_bss_r;
            #ifdef DEBUG_LINUXKM_PIE_SUPPORT
            dest_seg_end = seg_map->bss_end;
            dest_seg_name = "bss";
            #endif
            break;
        default:
        case WC_R_SEG_NONE:
            dest_seg = WC_R_SEG_OTHER;
            FALL_THROUGH;
        case WC_R_SEG_OTHER:
            dest_seg_start = 0;
            #ifdef DEBUG_LINUXKM_PIE_SUPPORT
            dest_seg_end = 0;
            dest_seg_name = "other";
            #endif
            break;
        }

        switch (next_reloc->reloc_type) {
        case WC_R_X86_64_PC32:
        case WC_R_X86_64_PLT32:
        case WC_R_X86_64_32:
        case WC_R_X86_64_32S:
        case WC_R_X86_64_64:

            if (dest_seg != WC_R_SEG_OTHER) {
#ifdef DEBUG_LINUXKM_PIE_SUPPORT
                word64 raw_dest_addr = reloc_buf;
#endif
                if (seg_map->text_is_live) {
                    /* note these normalize to the base address of the
                     * destination symbol, S, and removes the addend A, which is
                     * baked into the reloc_tab for each relocation.
                     */
                    if (layout->is_relative)
                        reloc_buf = reloc_buf + (uintptr_t)next_reloc->offset - (uintptr_t)next_reloc->dest_addend - (dest_seg_start - src_seg_start);
                    else
                        reloc_buf = reloc_buf - dest_seg_start - (uintptr_t)next_reloc->dest_addend;
                }
                else {
                    reloc_buf = (word64)next_reloc->dest_offset;
                }
#ifdef DEBUG_LINUXKM_PIE_SUPPORT
                if (reloc_buf >= dest_seg_end - dest_seg_start) {
                    ++n_oob_r;
                    RELOC_DEBUG_PRINTF("WARNING: normalized value is out of bounds (%s0x%llx) at index %lld, %s offset 0x%x, reloc type %s, "
                                         "src seg .%s_wolfcrypt, dest seg .%s_wolfcrypt, offset from src to dest segment %s0x%llx, raw dest addr %s0x%llx, "
                                         "seg span 0x%llx - 0x%llx, seg size 0x%llx, src seg 0x%llx-0x%llx\n",
                                         (long long)reloc_buf < 0 ? "-" : "",
                                         (long long)reloc_buf < 0 ? -(long long)reloc_buf : (long long)reloc_buf,
                                         (long long)i,
                                         src_seg_name,
                                         next_reloc->offset,
                                         layout->name,
                                         src_seg_name,
                                         dest_seg_name,
                                         dest_seg_start < src_seg_start ? "-" : "+",
                                         dest_seg_start < src_seg_start ? (unsigned long long)src_seg_start - dest_seg_start : dest_seg_start - (unsigned long long)src_seg_start,
                                         (layout->is_signed && ((long long)raw_dest_addr < 0)) ? "-" : "",
                                         (layout->is_signed && ((long long)raw_dest_addr < 0)) ? (unsigned long long)-(long long)raw_dest_addr : raw_dest_addr,
                                         (unsigned long long)dest_seg_start,
                                         (unsigned long long)dest_seg_end,
                                         (unsigned long long)(dest_seg_end - dest_seg_start),
                                       (unsigned long long)src_seg_start, (unsigned long long)src_seg_end);
                }
#endif
            }

            break;

        case WC_R_ARM_ABS32:
        case WC_R_ARM_PREL31:
        case WC_R_ARM_REL32:
        case WC_R_ARM_THM_CALL:
        case WC_R_ARM_THM_JUMP11:
        case WC_R_ARM_THM_JUMP24:
        case WC_R_ARM_THM_MOVT_ABS:
        case WC_R_ARM_THM_MOVW_ABS_NC:
            /* Don't attempt to reconstruct ARM destination addresses -- just
             * normalize to zero.  They can be reconstructed using the
             * parameters in reloc_layouts[] and reloc_tab[] but it's very
             * fidgety.
             */
            reloc_buf = 0;
            break;

        case WC_R_AARCH64_ABS32:
        case WC_R_AARCH64_ABS64:
        case WC_R_AARCH64_ADD_ABS_LO12_NC:
        case WC_R_AARCH64_ADR_PREL_PG_HI21:
        case WC_R_AARCH64_CALL26:
        case WC_R_AARCH64_JUMP26:
        case WC_R_AARCH64_LDST16_ABS_LO12_NC:
        case WC_R_AARCH64_LDST32_ABS_LO12_NC:
        case WC_R_AARCH64_LDST64_ABS_LO12_NC:
        case WC_R_AARCH64_LDST8_ABS_LO12_NC:
        case WC_R_AARCH64_PREL32:

            /* Don't attempt to reconstruct ARM destination addresses -- just
             * normalize to zero.  They can be reconstructed using the
             * parameters in reloc_layouts[] and reloc_tab[] but it's very
             * fidgety.
             */
            reloc_buf = 0;
            break;

        default:
            RELOC_DEBUG_PRINTF("BUG: unrecognized relocation type %u in reloc record %zu, %s offset 0x%x\n",
                               (unsigned)next_reloc->reloc_type, i, src_seg_name, reloc_tab[i].offset);
            ++n_oob_r;
            dest_seg = WC_R_SEG_OTHER;
        }

        if (dest_seg == WC_R_SEG_OTHER) {
            /* relocation referring to non-wolfcrypt segment -- these can only
             * be stabilized by zeroing them.
             */
            reloc_buf = 0;

            ++n_other_r;
            RELOC_DEBUG_PRINTF("found non-wolfcrypt relocation at index %lld, %s offset 0x%x.\n",
                               (long long)i, src_seg_name, reloc_tab[i].offset);
        }

        /* xor in a label identifying the dest segment and reloc type. */
        reloc_buf ^= dest_seg << (layout->width - WC_RELOC_DEST_SEGMENT_BITS);
        reloc_buf ^= next_reloc->reloc_type << (layout->width - (WC_RELOC_DEST_SEGMENT_BITS + WC_RELOC_TYPE_BITS));

        /* write the modified reloc_buf to the destination buffer. */
        switch (layout->width) {
        case 32:
            wc_put_unaligned((word32)reloc_buf, (word32 *)&seg_out[next_reloc_rel]);
            break;
        case 64:
            wc_put_unaligned(reloc_buf, (word64 *)&seg_out[next_reloc_rel]);
            break;
        case 16:
            wc_put_unaligned((word16)reloc_buf, (word16 *)&seg_out[next_reloc_rel]);
            break;
        }
    }

    if (reloc_counts) {
        reloc_counts->text += n_text_r;
        reloc_counts->rodata += n_rodata_r;
        reloc_counts->rwdata += n_rwdata_r;
        reloc_counts->bss += n_bss_r;
        reloc_counts->other += n_other_r;
    }

    if ((n_other_r > 0) || (n_oob_r > 0))
        RELOC_DEBUG_PRINTF("seg_in=%llx relocs=%d/%d/%d/%d/%d/%d ret = %llu\n",
                  (unsigned long long)(uintptr_t)seg_in, n_text_r, n_rodata_r,
                  n_rwdata_r, n_bss_r, n_other_r, n_oob_r,
                  (unsigned long long)*seg_in_out_len);

    if (cur_index_p)
        *cur_index_p = i;

    return (ssize_t)*seg_in_out_len;
}

#endif /* WC_SYM_RELOC_TABLES || WC_SYM_RELOC_TABLES_SUPPORT */

#ifdef HAVE_FIPS

#if defined(WOLFCRYPT_FIPS_CORE_DYNAMIC_HASH_VALUE) || \
    defined(WOLFCRYPT_FIPS_CORE_DYNAMIC_HASH_VALUE_SUPPORT)

#include <wolfssl/wolfcrypt/fips_test.h>
#ifndef MAX_FIPS_DATA_SZ
    #define MAX_FIPS_DATA_SZ 10000000
#endif
#ifndef MAX_FIPS_CODE_SZ
    #define MAX_FIPS_CODE_SZ 10000000
#endif

#include <wolfssl/wolfcrypt/coding.h>

#ifndef NO_SHA256
    #include <wolfssl/wolfcrypt/sha256.h>
#endif
#if defined(WOLFSSL_SHA384) || defined(WOLFSSL_SHA512)
    #include <wolfssl/wolfcrypt/sha512.h>
#endif

/* 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
    #elif defined(WOLFSSL_SHA512)
        #define FIPS_IN_CORE_DIGEST_SIZE WC_SHA512_DIGEST_SIZE
        #define FIPS_IN_CORE_HASH_TYPE   WC_SHA512
    #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

/* wc_fips_generate_hash() is the high level entry point to the supplementary
 * FIPS integrity hash calculation facility, used for offline hash calculation
 * (particularly for kernel module builds), and for the
 * WOLFCRYPT_FIPS_CORE_DYNAMIC_HASH_VALUE mechanism in the kernel module.
 *
 * The seg_map describes the layout of the module, including its precomputed
 * relocation table and its FIPS fenceposts.  For
 * WOLFCRYPT_FIPS_CORE_DYNAMIC_HASH_VALUE, seg_map is a static const in
 * module_hooks.c, but for offline calculation, readelf is used in
 * linuxkm-fips-hash-wrapper.sh to extract the values to pass to
 * linuxkm-fips-hash.
 *
 * The HMAC callback pointers are generic, but have wolfCrypt-like argument
 * structure -- for live WOLFCRYPT_FIPS_CORE_DYNAMIC_HASH_VALUE calls, they
 * point to wrappers around native Linux kernel implementations, but for
 * linuxkm-fips-hash, they point to wrappers around native wolfCrypt
 * implementations.
 */

int wc_fips_generate_hash(
    const struct wc_reloc_table_segments *seg_map,
    word32 digest_size,
    const char *hmac_key_base16,
    void *hmac_ctx,
    wc_fips_verifyCore_hmac_setkey_fn hmac_setkey,
    wc_fips_verifyCore_hmac_update_fn hmac_update,
    wc_fips_verifyCore_hmac_final_fn hmac_final,
    char *out,
    word32 *out_size,
    struct wc_reloc_counts *reloc_counts)
{
    word32 binCoreSz  = FIPS_IN_CORE_KEY_SZ;
    int ret;
    byte *hash = NULL;
    byte *binCoreKey = NULL;

#if defined(WC_SYM_RELOC_TABLES) || defined(WC_SYM_RELOC_TABLES_SUPPORT)
    if (seg_map->text_is_live) {
        if ((seg_map->text_reloc_tab.start == 0) ||
            (seg_map->text_reloc_tab.len_start == 0))
        {
            RELOC_DEBUG_PRINTF("assert failed.\n");
            return BAD_FUNC_ARG;
        }
        if ((seg_map->rodata_reloc_tab.start == 0) ||
            (seg_map->rodata_reloc_tab.len_start == 0))
        {
            RELOC_DEBUG_PRINTF("assert failed.\n");
            return BAD_FUNC_ARG;
        }
    }
    else {
        if ((seg_map->text_reloc_tab.end == 0) ||
            (seg_map->text_reloc_tab.len_end == 0))
        {
            RELOC_DEBUG_PRINTF("assert failed.\n");
            return BAD_FUNC_ARG;
        }
        if ((seg_map->rodata_reloc_tab.end == 0) ||
            (seg_map->rodata_reloc_tab.len_end == 0))
        {
            RELOC_DEBUG_PRINTF("assert failed.\n");
            return BAD_FUNC_ARG;
        }
    }
#endif

    if (((seg_map->end > 0) && (seg_map->start >= seg_map->end)) ||
        (seg_map->fips_text_start >= seg_map->fips_text_end) ||
        (seg_map->fips_rodata_start >= seg_map->fips_rodata_end)
#if defined(WC_SYM_RELOC_TABLES) || defined(WC_SYM_RELOC_TABLES_SUPPORT)
        ||
        ((seg_map->text_reloc_tab.end != 0) && (seg_map->text_reloc_tab.start >= seg_map->text_reloc_tab.end)) ||
        ((seg_map->text_reloc_tab.len_end != 0) && (seg_map->text_reloc_tab.len_start >= seg_map->text_reloc_tab.len_end)) ||
        ((seg_map->rodata_reloc_tab.end != 0) && (seg_map->rodata_reloc_tab.start >= seg_map->rodata_reloc_tab.end)) ||
        ((seg_map->rodata_reloc_tab.len_end != 0) && (seg_map->rodata_reloc_tab.len_start >= seg_map->rodata_reloc_tab.len_end)) ||
        (seg_map->text_start >= seg_map->text_end) ||
        (seg_map->rodata_start >= seg_map->rodata_end) ||
        (seg_map->data_start >= seg_map->data_end) ||
        (seg_map->bss_start >= seg_map->bss_end)
#endif
            )
    {
        RELOC_DEBUG_PRINTF("assert failed.\n");
        return BAD_FUNC_ARG;
    }

    if (seg_map->start > 0) {
        if ((seg_map->fips_text_start < seg_map->start) ||
            (seg_map->fips_rodata_start < seg_map->start) ||
            (seg_map->verifyCore_start < seg_map->start)
#if defined(WC_SYM_RELOC_TABLES) || defined(WC_SYM_RELOC_TABLES_SUPPORT)
            ||
            (seg_map->text_reloc_tab.start < seg_map->start) ||
            (seg_map->text_reloc_tab.len_start < seg_map->start) ||
            (seg_map->rodata_reloc_tab.start < seg_map->start) ||
            (seg_map->rodata_reloc_tab.len_start < seg_map->start) ||
            (seg_map->text_start < seg_map->start) ||
            (seg_map->rodata_start < seg_map->start) ||
            (seg_map->data_start < seg_map->start) ||
            (seg_map->bss_start < seg_map->start)
#endif
            )
        {
            RELOC_DEBUG_PRINTF("assert failed.\n");
            return BUFFER_E;
        }
    }

    if (seg_map->end > 0) {
        if ((seg_map->fips_text_end > seg_map->end) ||
            (seg_map->fips_rodata_end > seg_map->end) ||
            (seg_map->verifyCore_end > seg_map->end)
#if defined(WC_SYM_RELOC_TABLES) || defined(WC_SYM_RELOC_TABLES_SUPPORT)
            ||
            ((seg_map->text_reloc_tab.end != 0) &&
             (seg_map->text_reloc_tab.end > seg_map->end)) ||
            ((seg_map->text_reloc_tab.len_end != 0) &&
             (seg_map->text_reloc_tab.len_end > seg_map->end)) ||
            ((seg_map->rodata_reloc_tab.end != 0) &&
             (seg_map->rodata_reloc_tab.end > seg_map->end)) ||
            ((seg_map->rodata_reloc_tab.len_end != 0) &&
             (seg_map->rodata_reloc_tab.len_end > seg_map->end)) ||
            (seg_map->text_end > seg_map->end) ||
            (seg_map->rodata_end > seg_map->end) ||
            (seg_map->data_end > seg_map->end) ||
            (seg_map->bss_end > seg_map->end)
#endif
            )
        {
            RELOC_DEBUG_PRINTF("assert failed.\n");
            return BUFFER_E;
        }
    }

#if defined(WC_SYM_RELOC_TABLES) || defined(WC_SYM_RELOC_TABLES_SUPPORT)
    if ((seg_map->text_reloc_tab.len_end != 0) &&
        (seg_map->text_reloc_tab.len_end - seg_map->text_reloc_tab.len_start != sizeof(word32)))
    {
        RELOC_DEBUG_PRINTF("assert failed.\n");
        return BAD_FUNC_ARG;
    }
    else if (seg_map->text_reloc_tab.len_start & (sizeof(word32) - 1)) {
        /* fprintf(stderr, "%s: seg_map->text_reloc_tab.len_start isn't properly aligned: 0x%llx.\n", progname, (
           unsigned long long)seg_map->text_reloc_tab.len_start); */
        RELOC_DEBUG_PRINTF("assert failed.\n");
        return BAD_ALIGN_E;
    }
    else {
        /* Note we don't currently handle modules that are endian-conflicted
         * with the build host -- that'll be caught here, when reloc_tab_len is
         * a nonsense byte-swapped value, or the final reloc_tab ent has
         * nonsense flags.
         */
        word32 reloc_tab_len = *(const word32 *)seg_map->text_reloc_tab.len_start;
        const struct wc_reloc_table_ent *reloc_tab = (const struct wc_reloc_table_ent *)seg_map->text_reloc_tab.start;
        if (reloc_tab_len == 0) {
            RELOC_DEBUG_PRINTF("assert failed.\n");
            return BAD_FUNC_ARG;
        }
        else if ((seg_map->end != 0) &&
            ((unsigned long)(reloc_tab + reloc_tab_len) > seg_map->end))
        {
            RELOC_DEBUG_PRINTF("assert failed.\n");
            return BAD_FUNC_ARG;
        }
        else if ((reloc_tab[reloc_tab_len - 1].dest_segment != WC_R_SEG_NONE) ||
                 (reloc_tab[reloc_tab_len - 1].reloc_type != WC_R_NONE))
        {
            RELOC_DEBUG_PRINTF("assert failed.\n");
            return BAD_FUNC_ARG;
        }
        else if ((seg_map->text_reloc_tab.end != 0) &&
            (seg_map->text_reloc_tab.end - seg_map->text_reloc_tab.start != sizeof(struct wc_reloc_table_ent) * *(const word32 *)seg_map->text_reloc_tab.len_start))
        {
            /*
              fprintf(stderr, "%s: wc_linuxkm_pie_text_reloc_tab_length from module (%u) is inconsistent with actual text_reloc_tab size %llu.\n",
              progname,
              *(const word32 *)seg_map->text_reloc_tab.len_start,
              (unsigned long long)(seg_map->text_reloc_tab.end - seg_map->text_reloc_tab.start));
            */
            RELOC_DEBUG_PRINTF("assert failed.\n");
            return BAD_FUNC_ARG;
        }
    }

    if ((seg_map->rodata_reloc_tab.len_end != 0) &&
        (seg_map->rodata_reloc_tab.len_end - seg_map->rodata_reloc_tab.len_start != sizeof(word32)))
    {
        RELOC_DEBUG_PRINTF("assert failed.\n");
        return BAD_FUNC_ARG;
    }
    else if (seg_map->rodata_reloc_tab.len_start & (sizeof(word32) - 1)) {
        /* fprintf(stderr, "%s: seg_map->rodata_reloc_tab.len_start isn't properly aligned: 0x%llx.\n", progname, (
           unsigned long long)seg_map->rodata_reloc_tab.len_start); */
        RELOC_DEBUG_PRINTF("assert failed.\n");
        return BAD_ALIGN_E;
    }
    else {
        /* Note we don't currently handle modules that are endian-conflicted
         * with the build host -- that'll be caught here, when reloc_tab_len is
         * a nonsense byte-swapped value, or the final reloc_tab ent has
         * nonsense flags.
         */
        word32 reloc_tab_len = *(const word32 *)seg_map->rodata_reloc_tab.len_start;
        const struct wc_reloc_table_ent *reloc_tab = (const struct wc_reloc_table_ent *)seg_map->rodata_reloc_tab.start;
        if (reloc_tab_len == 0) {
            RELOC_DEBUG_PRINTF("assert failed.\n");
            return BAD_FUNC_ARG;
        }
        else if ((seg_map->end != 0) &&
            ((unsigned long)(reloc_tab + reloc_tab_len) > seg_map->end))
        {
            RELOC_DEBUG_PRINTF("assert failed.\n");
            return BAD_FUNC_ARG;
        }
        else if ((reloc_tab[reloc_tab_len - 1].dest_segment != WC_R_SEG_NONE) ||
                 (reloc_tab[reloc_tab_len - 1].reloc_type != WC_R_NONE))
        {
            RELOC_DEBUG_PRINTF("assert failed.\n");
            return BAD_FUNC_ARG;
        }
        else if ((seg_map->rodata_reloc_tab.end != 0) &&
            (seg_map->rodata_reloc_tab.end - seg_map->rodata_reloc_tab.start != sizeof(struct wc_reloc_table_ent) * *(const word32 *)seg_map->rodata_reloc_tab.len_start))
        {
            /*
              fprintf(stderr, "%s: wc_linuxkm_pie_rodata_reloc_tab_length from module (%u) is inconsistent with actual rodata_reloc_tab size %llu.\n",
              progname,
              *(const word32 *)seg_map->rodata_reloc_tab.len_start,
              (unsigned long long)(seg_map->rodata_reloc_tab.end - seg_map->rodata_reloc_tab.start));
            */
            RELOC_DEBUG_PRINTF("assert failed.\n");
            return BAD_FUNC_ARG;
        }
    }
#endif

    if (out_size == NULL) {
        RELOC_DEBUG_PRINTF("assert failed.\n");
        return BAD_FUNC_ARG;
    }

    if (*out_size < (digest_size * 2) + 1) {
        RELOC_DEBUG_PRINTF("assert failed.\n");
        return BUFFER_E;
    }

    hash = XMALLOC(digest_size, 0, DYNAMIC_TYPE_TMP_BUFFER);
    if (hash == NULL) {
        ret = MEMORY_E;
        RELOC_DEBUG_PRINTF("XMALLOC() failed.\n");
        goto out;
    }

    binCoreKey = XMALLOC(binCoreSz, 0, DYNAMIC_TYPE_TMP_BUFFER);
    if (binCoreKey == NULL) {
        ret = MEMORY_E;
        RELOC_DEBUG_PRINTF("XMALLOC() failed.\n");
        goto out;
    }

    {
        word32 base16_out_len = binCoreSz;
        ret = Base16_Decode((const byte *)hmac_key_base16, strlen(hmac_key_base16), binCoreKey, &base16_out_len);
        if (ret != 0) {
            RELOC_DEBUG_PRINTF("Base16_Decode() failed.\n");
            goto out;
        }
        if (base16_out_len != binCoreSz) {
            ret = BAD_FUNC_ARG;
            RELOC_DEBUG_PRINTF("assert failed.\n");
            goto out;
        }
    }

    ret = hmac_setkey(hmac_ctx, binCoreKey, binCoreSz);
    if (ret) {
        RELOC_DEBUG_PRINTF("hmac_setkey() failed.\n");
        goto out;
    }

#if defined(WC_SYM_RELOC_TABLES) || defined(WC_SYM_RELOC_TABLES_SUPPORT)
    {
        ssize_t cur_reloc_index;
        const byte *text_p = (const byte *)seg_map->fips_text_start;
        const byte *rodata_p = (const byte *)seg_map->fips_rodata_start;
        byte *buf = XMALLOC(WOLFSSL_SEGMENT_CANONICALIZER_BUFSIZ, NULL, DYNAMIC_TYPE_TMP_BUFFER);

        if (! buf) {
            ret = MEMORY_E;
            RELOC_DEBUG_PRINTF("XMALLOC() failed.\n");
            goto out;
        }

        cur_reloc_index = -1;
        while (text_p < (const byte *)seg_map->fips_text_end) {
            size_t text_in_out_len = min(WOLFSSL_SEGMENT_CANONICALIZER_BUFSIZ,
                                         (size_t)((const byte *)seg_map->fips_text_end - text_p));
            ssize_t progress = wc_reloc_normalize_segment(
                text_p,
                &text_in_out_len,
                buf,
                &cur_reloc_index,
                seg_map,
                reloc_counts);
            if (progress <= 0) {
                RELOC_DEBUG_PRINTF("wc_reloc_normalize_segment() for text failed: %zd.\n", progress);
                ret = progress ? (int)progress : IN_CORE_FIPS_E;
                break;
            }
            ret = hmac_update(hmac_ctx, buf, (word32)text_in_out_len);
            if (ret) {
                RELOC_DEBUG_PRINTF("hmac_update() failed.\n");
                break;
            }
            text_p += progress;
        }

        if (ret) {
            XFREE(buf, NULL, DYNAMIC_TYPE_TMP_BUFFER);
            goto out;
        }

        cur_reloc_index = -1;
        while (rodata_p < (const byte *)seg_map->fips_rodata_end) {
            size_t rodata_in_out_len = min(WOLFSSL_SEGMENT_CANONICALIZER_BUFSIZ,
                                           (size_t)((const byte *)seg_map->fips_rodata_end - rodata_p));
            /* don't hash verifyCore or changing verifyCore will change hash */
            if ((rodata_p < (const byte *)seg_map->verifyCore_end) &&
                (rodata_p + rodata_in_out_len >= (const byte *)seg_map->verifyCore_start))
            {
                rodata_in_out_len = (size_t)((const byte *)seg_map->verifyCore_start - rodata_p);
                if (rodata_in_out_len == 0) {
                    rodata_p = (const byte *)seg_map->verifyCore_end;
                    /* force recomputation of relocation offset when skipping
                     * a span (not processed by wc_reloc_normalize_segment()).
                     */
                    cur_reloc_index = -1;
                    continue;
                }
            }

            ssize_t progress = wc_reloc_normalize_segment(
                rodata_p,
                &rodata_in_out_len,
                buf,
                &cur_reloc_index,
                seg_map,
                reloc_counts);
            if (progress <= 0) {
                RELOC_DEBUG_PRINTF("wc_reloc_normalize_segment() for rodata failed: %zd.\n", progress);
                ret = progress ? (int)progress : IN_CORE_FIPS_E;
                break;
            }
            ret = hmac_update(hmac_ctx, buf, (word32)rodata_in_out_len);
            if (ret) {
                RELOC_DEBUG_PRINTF("hmac_update() failed.\n");
                break;
            }
            rodata_p += progress;
        }

        XFREE(buf, NULL, DYNAMIC_TYPE_TMP_BUFFER);

        if (ret)
            goto out;
    }

#else /* ! (WC_SYM_RELOC_TABLES || WC_SYM_RELOC_TABLES_SUPPORT) */

    (void)reloc_counts;
    WC_SANITIZE_DISABLE();
    ret = hmac_update(hmac_ctx, (byte *)(wc_ptr_t)seg_map->fips_text_start, (word32)(seg_map->fips_text_end - seg_map->fips_text_start));

    if (ret) {
        RELOC_DEBUG_PRINTF("ERROR: hmac_update failed: err %d\n", ret);
        ret = BAD_STATE_E;
        WC_SANITIZE_ENABLE();
        goto out;
    }

    /* don't hash verifyCore or changing verifyCore will change hash */
    if (seg_map->verifyCore_start >= seg_map->fips_rodata_start && seg_map->verifyCore_start < seg_map->fips_rodata_end) {
        ret = hmac_update(hmac_ctx, (byte*)seg_map->fips_rodata_start, (word32)(seg_map->verifyCore_start - (unsigned long)seg_map->fips_rodata_start));
        if (ret) {
            RELOC_DEBUG_PRINTF("ERROR: hmac_update failed: err %d\n", ret);
            ret = BAD_STATE_E;
            goto out;
        }
        ret = hmac_update(hmac_ctx, (const byte *)seg_map->verifyCore_end, (word32)(seg_map->fips_rodata_end - (unsigned long)seg_map->verifyCore_end));
    }
    else {
        ret = hmac_update(hmac_ctx, (byte*)seg_map->fips_rodata_start, (word32)(seg_map->fips_rodata_end - (unsigned long)seg_map->fips_rodata_start));
    }

    WC_SANITIZE_ENABLE();

    if (ret) {
        RELOC_DEBUG_PRINTF("ERROR: hmac_update failed: err %d\n", ret);
        ret = BAD_STATE_E;
        goto out;
    }

#endif /* ! (WC_SYM_RELOC_TABLES || WC_SYM_RELOC_TABLES_SUPPORT) */

    ret = hmac_final(hmac_ctx, hash, digest_size);
    if (ret) {
        RELOC_DEBUG_PRINTF("ERROR: hmac_final failed: err %d\n", ret);
        ret = BAD_STATE_E;
        goto out;
    }

    ret = Base16_Encode(hash, digest_size, (byte *)out, out_size);

  out:

    XFREE(hash, NULL, DYNAMIC_TYPE_TMP_BUFFER);
    XFREE(binCoreKey, NULL, DYNAMIC_TYPE_TMP_BUFFER);

    return ret;
}

#endif /* WOLFCRYPT_FIPS_CORE_DYNAMIC_HASH_VALUE ||      */
       /* WOLFCRYPT_FIPS_CORE_DYNAMIC_HASH_VALUE_SUPPORT */

#endif /* HAVE_FIPS */
