Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
c48f32d
Add MinGW cross-compilation support for FIPS builds
Will-Low May 20, 2026
33689c2
Move MinGW thread static linking to top-level CMakeLists.txt
Will-Low May 20, 2026
e22aaf8
Fix jitterentropy MinGW shared build: add BORINGSSL_IMPLEMENTATION
Will-Low May 21, 2026
3def106
Fix inject_hash.go: error message and rodata section check
Will-Low May 21, 2026
a380e9d
Clarify jitterentropy BORINGSSL_IMPLEMENTATION comment
Will-Low May 21, 2026
c94856c
Remove comment from libwinpthread static link
Will-Low May 21, 2026
0c8dc44
Address PR feedback for MinGW FIPS support
justsmth Jun 9, 2026
a560080
Add cross-MinGW FIPS shared CI job
justsmth Jun 10, 2026
1f3d795
Fix MinGW FIPS shared build compilation under -Werror
justsmth Jun 18, 2026
e719f56
Keep MinGW FIPS module code within the integrity markers and enforce it
justsmth Jun 18, 2026
8dd6bcd
Make the MinGW FIPS shared integrity check correct under ASLR
justsmth Jun 19, 2026
b3f0893
Add a FIPS integrity negative test to the cross-mingw-fips CI job
justsmth Jun 19, 2026
785379d
Use Win32 threads on MinGW so FIPS static locks are zero-initialized
justsmth Jun 19, 2026
698d614
Clarify MinGW FIPS shared comments and fold duplicate guard
justsmth Jun 19, 2026
98521d2
Move cross-mingw-fips CI to Ubuntu 24.04 (mingw-w64 gcc-13)
justsmth Jun 19, 2026
337cd7f
Export FIPS module and jitterentropy via dllexport on MinGW
justsmth Jun 19, 2026
9caeb82
Install binfmt-support for cross-mingw-fips on Ubuntu 24.04
justsmth Jun 19, 2026
a75710e
Harden MinGW FIPS integrity check and inject_hash input validation
justsmth Jun 23, 2026
eaabe35
PR feedback
justsmth Jun 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .github/workflows/windows-alt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,32 @@ jobs:
- name: x86_64-w64-mingw32 Build/Test
run:
./tests/ci/run_cross_mingw_tests.sh x86_64 w64-mingw32 "-DCMAKE_BUILD_TYPE=Release"
cross-mingw-fips:
if: github.repository_owner == 'aws'
runs-on: ubuntu-24.04
steps:
- name: Install Tools
run: |
set -ex
sudo apt-get update -o Acquire::Languages=none -o Acquire::Translation=none
sudo apt-get install --assume-yes --no-install-recommends software-properties-common
sudo add-apt-repository --yes ppa:longsleep/golang-backports
sudo dpkg --add-architecture i386
sudo mkdir -pm755 /etc/apt/keyrings
sudo wget -O /etc/apt/keyrings/winehq-archive.key https://dl.winehq.org/wine-builds/winehq.key
sudo wget -NP /etc/apt/sources.list.d/ https://dl.winehq.org/wine-builds/ubuntu/dists/noble/winehq-noble.sources
sudo apt-get update -o Acquire::Languages=none -o Acquire::Translation=none
sudo apt-get install --assume-yes --no-install-recommends build-essential cmake golang-go nasm clang wget mingw-w64
sudo apt-get install --assume-yes --install-recommends winehq-stable wine-binfmt binfmt-support
sudo update-binfmts --display
sudo update-binfmts --disable
sudo update-binfmts --enable wine
sudo update-binfmts --display
sudo rm -rf /tmp/*
- uses: actions/checkout@v6
- name: x86_64-w64-mingw32 FIPS shared Build/Test
run:
./tests/ci/run_cross_mingw_fips_tests.sh x86_64 w64-mingw32 "-DCMAKE_BUILD_TYPE=Release"
cross-clang-cl-ninja:
# Cross-compile from Linux to x86_64-pc-windows-msvc with Ninja + clang-cl,
# mirroring the default aws-lc-rs setup. This catches command-line quoting
Expand Down
30 changes: 27 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1063,15 +1063,31 @@ if(FIPS)
add_definitions(-DBORINGSSL_FIPS)
endif()

# The Android CMake files set -ffunction-sections and -fdata-sections, which
# is incompatible with FIPS_SHARED.
if(FIPS_SHARED AND ANDROID)
# -ffunction-sections / -fdata-sections are incompatible with FIPS_SHARED.
# On ELF, gcc_fips_shared.lds already handles this: it gathers known split
# sections into .text and /DISCARD/s anything unexpected. Android and MinGW
# lack that linker-script safety net (Android's NDK toolchain injects these
# flags externally; MinGW/PE has no linker script support), so we override
# at the compiler level.
if(FIPS_SHARED AND (ANDROID OR MINGW))
Comment thread
justsmth marked this conversation as resolved.
set(CMAKE_C_FLAGS
"${CMAKE_C_FLAGS} -fno-function-sections -fno-data-sections")
set(CMAKE_CXX_FLAGS
"${CMAKE_CXX_FLAGS} -fno-function-sections -fno-data-sections")
endif()

# GCC (-O2+) splits cold code into separate .text.unlikely/.text.* sections.
# On ELF, gcc_fips_shared.lds gathers these between the bcm text markers, but
# the MinGW path partial-links with a plain `ld -r` (PE has no linker script
# support), so any split-out section would land outside the integrity-checked
# region. Keep all module code in one contiguous .text bracketed by the markers.
if(FIPS_SHARED AND MINGW AND GCC)
set(CMAKE_C_FLAGS
"${CMAKE_C_FLAGS} -fno-reorder-functions -fno-reorder-blocks-and-partition")
set(CMAKE_CXX_FLAGS
"${CMAKE_CXX_FLAGS} -fno-reorder-functions -fno-reorder-blocks-and-partition")
endif()

if(OPENSSL_SMALL)
add_definitions(-DOPENSSL_SMALL)
# OPENSSL_SMALL implies disabling AVX-512 optimizations on x86_64 for size.
Expand Down Expand Up @@ -1369,6 +1385,14 @@ endif()
# via Web Workers and SharedArrayBuffer, configured through compiler flags.
if(NOT CMAKE_SYSTEM_NAME MATCHES "^(Generic|Android|Emscripten)$")
find_package(Threads REQUIRED)
if(MINGW AND FIPS)
# Statically link winpthread so the FIPS module has no DLL dependency on it.
# This overwrites (not appends) INTERFACE_LINK_LIBRARIES because
# find_package(Threads) already set it to -lpthread (dynamic); appending
# would leave the dynamic entry on the link line.
set_property(TARGET Threads::Threads PROPERTY
INTERFACE_LINK_LIBRARIES "-Wl,-Bstatic,-lwinpthread;-Wl,-Bdynamic")
Comment thread
justsmth marked this conversation as resolved.
endif()
set(AWSLC_LINK_THREADS TRUE)
# CMAKE_THREAD_LIBS_INIT contains the actual linker flags (e.g., -lpthread)
# set by find_package(Threads). Use this for pkgconfig instead of the
Expand Down
5 changes: 4 additions & 1 deletion crypto/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -673,12 +673,15 @@ if(FIPS_SHARED)
if (APPLE)
set(INJECT_HASH_APPLE_FLAG "-apple")
endif()
if (MINGW)
set(INJECT_HASH_MINGW_FLAG "-mingw")
endif()

add_custom_command(
TARGET crypto POST_BUILD
COMMAND ${GO_EXECUTABLE} run
${AWSLC_SOURCE_DIR}/util/fipstools/inject_hash/inject_hash.go
-o $<TARGET_FILE:crypto> -in-object $<TARGET_FILE:crypto> ${INJECT_HASH_APPLE_FLAG}
-o $<TARGET_FILE:crypto> -in-object $<TARGET_FILE:crypto> ${INJECT_HASH_APPLE_FLAG} ${INJECT_HASH_MINGW_FLAG}
WORKING_DIRECTORY ${AWSLC_SOURCE_DIR}
)

Expand Down
66 changes: 66 additions & 0 deletions crypto/fipsmodule/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,72 @@ elseif(FIPS_SHARED)
DEPENDS fips_msvc_start.obj fips_msvc_end.obj bcm_library
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
)
elseif(MINGW)
# The PE linker does not support linker scripts. Use the same start/end
# marker object approach as macOS by compiling the marker file twice
# (start and end), then partial-link the three pieces in order so
# BORINGSSL_bcm_text_start/end bracket all BCM code.
set(BCM_NAME bcm.o)
# These custom commands invoke the compiler directly (outside CMake's normal
# target compilation), so we must forward CMAKE_C_FLAGS manually. This is
# necessary for cross-compilation where the flags contain --target and
# system include paths (e.g. clang targeting MinGW).
separate_arguments(FIPS_MARKER_C_FLAGS NATIVE_COMMAND "${CMAKE_C_FLAGS}")

# Resolve an objdump for the target toolchain. It is used below to verify
# that the assembled module object keeps all code/rodata in the single
# .text/.rdata sections bracketed by the integrity markers.
get_filename_component(FIPS_LD_DIR "${CMAKE_LINKER}" DIRECTORY)
get_filename_component(FIPS_LD_NAME "${CMAKE_LINKER}" NAME)

set(FIPS_MINGW_OBJDUMP "${CMAKE_OBJDUMP}")
if(NOT FIPS_MINGW_OBJDUMP)
# Derive from the linker (e.g. <triple>-ld -> <triple>-objdump).
string(REGEX REPLACE "ld(\\.exe)?$" "objdump" FIPS_OD_NAME "${FIPS_LD_NAME}")
find_program(FIPS_MINGW_OBJDUMP NAMES "${FIPS_OD_NAME}" objdump llvm-objdump
HINTS "${FIPS_LD_DIR}")
endif()
if(NOT FIPS_MINGW_OBJDUMP)
message(FATAL_ERROR
"FIPS MinGW build: could not find objdump to verify the module "
"integrity section layout. Set CMAKE_OBJDUMP to the target objdump.")
endif()

set(FIPS_MINGW_OBJCOPY "${CMAKE_OBJCOPY}")
if(NOT FIPS_MINGW_OBJCOPY)
# Derive from the linker (e.g. <triple>-ld -> <triple>-objcopy).
string(REGEX REPLACE "ld(\\.exe)?$" "objcopy" FIPS_OC_NAME "${FIPS_LD_NAME}")
find_program(FIPS_MINGW_OBJCOPY NAMES "${FIPS_OC_NAME}" objcopy llvm-objcopy
HINTS "${FIPS_LD_DIR}")
endif()
if(NOT FIPS_MINGW_OBJCOPY)
message(FATAL_ERROR
"FIPS MinGW build: could not find objcopy to merge the module "
"read-only data sections. Set CMAKE_OBJCOPY to the target objcopy.")
endif()

add_custom_command(
OUTPUT fips_mingw_start.o
COMMAND ${CMAKE_C_COMPILER} ${FIPS_MARKER_C_FLAGS} -w -c ${CMAKE_CURRENT_SOURCE_DIR}/fips_shared_library_marker.c -DAWSLC_FIPS_SHARED_START -o fips_mingw_start.o
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/fips_shared_library_marker.c
)
add_custom_command(
OUTPUT fips_mingw_end.o
COMMAND ${CMAKE_C_COMPILER} ${FIPS_MARKER_C_FLAGS} -w -c ${CMAKE_CURRENT_SOURCE_DIR}/fips_shared_library_marker.c -DAWSLC_FIPS_SHARED_END -o fips_mingw_end.o
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/fips_shared_library_marker.c
)
set(BCM_UNMERGED_NAME bcm.unmerged.o)
add_custom_command(
OUTPUT ${BCM_NAME}
COMMAND ${CMAKE_LINKER} -r fips_mingw_start.o --whole-archive $<TARGET_FILE:bcm_library> --no-whole-archive fips_mingw_end.o -o ${BCM_UNMERGED_NAME}
COMMAND ${CMAKE_COMMAND} -DOBJDUMP=${FIPS_MINGW_OBJDUMP} -DOBJCOPY=${FIPS_MINGW_OBJCOPY} -DLINKER=${CMAKE_LINKER} -DINPUT_OBJECT=${BCM_UNMERGED_NAME} -DOUTPUT_OBJECT=${BCM_NAME} -P ${CMAKE_CURRENT_SOURCE_DIR}/merge_fips_mingw_rodata.cmake
# Fail the build if GCC split any module code/rodata into sections that
# would fall outside the integrity markers in the final DLL.
COMMAND ${CMAKE_COMMAND} -DOBJDUMP=${FIPS_MINGW_OBJDUMP} -DBCM_OBJECT=${BCM_NAME} -P ${CMAKE_CURRENT_SOURCE_DIR}/check_fips_mingw_sections.cmake
DEPENDS fips_mingw_start.o fips_mingw_end.o bcm_library ${CMAKE_CURRENT_SOURCE_DIR}/check_fips_mingw_sections.cmake ${CMAKE_CURRENT_SOURCE_DIR}/merge_fips_mingw_rodata.cmake
BYPRODUCTS ${BCM_UNMERGED_NAME}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
)
Comment thread
justsmth marked this conversation as resolved.
else()
set(BCM_NAME bcm.o)
# fips_shared.lds does not have 'clang' prefix because we want to keep merging any changes from upstream.
Expand Down
164 changes: 155 additions & 9 deletions crypto/fipsmodule/bcm.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,20 @@
#include <openssl/crypto.h>

#include <stdlib.h>
#if defined(__MINGW32__) && defined(BORINGSSL_SHARED_LIBRARY)
#include <windows.h>
#endif
#if defined(BORINGSSL_FIPS) && !defined(OPENSSL_WINDOWS)
#include <sys/mman.h>
#include <unistd.h>
#endif

// On Windows place the bcm code in a specific section that uses Grouped Sections
// to control the order. $b section will place bcm in between the start/end markers
// which are in $a and $z.
#if defined(BORINGSSL_FIPS) && defined(OPENSSL_WINDOWS)
// which are in $a and $z. These #pragma directives are MSVC-specific, so this is
// gated on _MSC_VER (matches fips_shared_library_marker.c); the MinGW FIPS build
// instead brackets the module via marker objects partial-linked with `ld -r`.
#if defined(BORINGSSL_FIPS) && defined(_MSC_VER)
#pragma code_seg(".fipstx$b")
#pragma data_seg(".fipsda$b")
#pragma const_seg(".fipsco$b")
Expand Down Expand Up @@ -179,6 +184,136 @@ extern const uint8_t BORINGSSL_bcm_rodata_start[];
extern const uint8_t BORINGSSL_bcm_rodata_end[];
#endif

#if defined(__MINGW32__) && defined(BORINGSSL_SHARED_LIBRARY)
#if !defined(OPENSSL_64_BIT)
#error "FIPS shared MinGW builds are only supported on 64-bit (x86_64) targets."
#endif
// Defined in fips_shared_support.c and filled in by inject_hash.go with the
// link-time preferred PE image base used to normalize ASLR relocations.
extern const uint64_t BORINGSSL_bcm_preferred_base;
#define BORINGSSL_BCM_PREFERRED_BASE_UNSET UINT64_C(0xBADC0FFEE0DDF00D)

// MinGW is the only supported FIPS target whose hashed module region contains
// base relocations: GCC emits .refptr indirection cells (and genuine imports
// such as the CRT's free) into .rdata, inside the integrity boundary. Every
// other toolchain keeps the hashed region relocation-free by construction
// (ELF/Mach-O segregate relocated read-only pointers into separate sections;
// MSVC does not emit .refptr cells), so they hash the region directly via the
// plain definition below. Here we recompute the hash as it would appear at the
// preferred image base by un-applying the ASLR load delta to each relocation.
static int hmac_update_module_region(HMAC_CTX *hmac_ctx, const uint8_t *start,
size_t len) {
MEMORY_BASIC_INFORMATION mbi;
if (VirtualQuery(start, &mbi, sizeof(mbi)) == 0) {
return 0;
}

const uint8_t *const image_base = (const uint8_t *)mbi.AllocationBase;
const IMAGE_DOS_HEADER *const dos_header =
(const IMAGE_DOS_HEADER *)image_base;
if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) {
return 0;
}

const IMAGE_NT_HEADERS *const nt_headers =
(const IMAGE_NT_HEADERS *)(image_base + dos_header->e_lfanew);
if (nt_headers->Signature != IMAGE_NT_SIGNATURE ||
nt_headers->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
return 0;
}

// Use the same link-time preferred base that inject_hash.go used when it
// hashed the on-disk PE image. Keeping this value in .fipshash avoids relying
// on loader-specific treatment of PE headers that are outside the module
// boundary.
if (BORINGSSL_bcm_preferred_base == BORINGSSL_BCM_PREFERRED_BASE_UNSET ||
BORINGSSL_bcm_preferred_base == 0) {
return 0;
}
const uintptr_t preferred_base = (uintptr_t)BORINGSSL_bcm_preferred_base;
const uintptr_t load_delta = (uintptr_t)image_base - preferred_base;
const IMAGE_DATA_DIRECTORY *const reloc_dir =
&nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
if (load_delta == 0 || reloc_dir->VirtualAddress == 0 ||
reloc_dir->Size == 0) {
return HMAC_Update(hmac_ctx, start, len);
}

const uintptr_t region_start = (uintptr_t)start;
const uintptr_t region_end = region_start + len;
if (region_end < region_start) {
return 0;
}

const uint8_t *cursor = start;
const uint8_t *const end = start + len;
const uint8_t *reloc_block_ptr = image_base + reloc_dir->VirtualAddress;
const uint8_t *const reloc_end = reloc_block_ptr + reloc_dir->Size;
while (reloc_block_ptr + sizeof(IMAGE_BASE_RELOCATION) <= reloc_end) {
const IMAGE_BASE_RELOCATION *const reloc_block =
(const IMAGE_BASE_RELOCATION *)reloc_block_ptr;
if (reloc_block->SizeOfBlock < sizeof(IMAGE_BASE_RELOCATION) ||
reloc_block_ptr + reloc_block->SizeOfBlock > reloc_end) {
return 0;
}

const WORD *const entries =
(const WORD *)(reloc_block_ptr + sizeof(IMAGE_BASE_RELOCATION));
const size_t entry_count =
(reloc_block->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) /
sizeof(WORD);
for (size_t i = 0; i < entry_count; i++) {
const WORD entry = entries[i];
const WORD type = entry >> 12;
const WORD offset = entry & 0x0fff;

// IMAGE_REL_BASED_ABSOLUTE entries are block padding, not relocations.
if (type == IMAGE_REL_BASED_ABSOLUTE) {
continue;
}
const uintptr_t reloc_addr =
(uintptr_t)image_base + reloc_block->VirtualAddress + offset;
if (reloc_addr < region_start || reloc_addr >= region_end) {
continue;
}
// Inside the hashed region only 64-bit absolute relocations are expected.
// Anything else (or one that would run past the region end) cannot be
// reconciled, so fail closed rather than hash load-dependent bytes.
if (type != IMAGE_REL_BASED_DIR64 ||
(size_t)(region_end - reloc_addr) < sizeof(uint64_t)) {
return 0;
}

// PE base relocations are emitted in ascending address order; the cursor
// check enforces that and bounds the gap length below.
const uint8_t *const reloc_ptr = (const uint8_t *)reloc_addr;
if (reloc_ptr < cursor ||
!HMAC_Update(hmac_ctx, cursor, (size_t)(reloc_ptr - cursor))) {
return 0;
}

uint8_t adjusted[sizeof(uint64_t)];
const uint64_t value =
CRYPTO_load_u64_le(reloc_ptr) - (uint64_t)load_delta;
CRYPTO_store_u64_le(adjusted, value);
if (!HMAC_Update(hmac_ctx, adjusted, sizeof(uint64_t))) {
return 0;
}
cursor = reloc_ptr + sizeof(uint64_t);
}

reloc_block_ptr += reloc_block->SizeOfBlock;
}

return HMAC_Update(hmac_ctx, cursor, (size_t)(end - cursor));
}
#else
static int hmac_update_module_region(HMAC_CTX *hmac_ctx, const uint8_t *start,
size_t len) {
return HMAC_Update(hmac_ctx, start, len);
}
#endif

#define STRING_POINTER_LENGTH 18
#define MAX_FUNCTION_NAME 32
#define ASSERT_WITHIN_MSG "FIPS module doesn't span expected symbol (%s). Expected %p <= %p < %p\n"
Expand Down Expand Up @@ -348,18 +483,29 @@ int BORINGSSL_integrity_test(void) {
BORINGSSL_maybe_set_module_text_permissions(PROT_READ | PROT_EXEC);
#endif
#if defined(BORINGSSL_SHARED_LIBRARY)
uint64_t length = end - start;
size_t region_len = (size_t)(end - start);
uint64_t length = region_len;
uint8_t buffer[sizeof(length)];
CRYPTO_store_u64_le(buffer, length);
HMAC_Update(&hmac_ctx, buffer, sizeof(length));
HMAC_Update(&hmac_ctx, start, length);
if (!HMAC_Update(&hmac_ctx, buffer, sizeof(length)) ||
!hmac_update_module_region(&hmac_ctx, start, region_len)) {
fprintf(stderr, "HMAC failed.\n");
return 0;
}

length = rodata_end - rodata_start;
region_len = (size_t)(rodata_end - rodata_start);
length = region_len;
CRYPTO_store_u64_le(buffer, length);
HMAC_Update(&hmac_ctx, buffer, sizeof(length));
HMAC_Update(&hmac_ctx, rodata_start, length);
if (!HMAC_Update(&hmac_ctx, buffer, sizeof(length)) ||
!hmac_update_module_region(&hmac_ctx, rodata_start, region_len)) {
fprintf(stderr, "HMAC failed.\n");
return 0;
}
#else
HMAC_Update(&hmac_ctx, start, end - start);
if (!hmac_update_module_region(&hmac_ctx, start, (size_t)(end - start))) {
fprintf(stderr, "HMAC failed.\n");
return 0;
}
#endif
#if !defined(OPENSSL_WINDOWS)
BORINGSSL_maybe_set_module_text_permissions(PROT_EXEC);
Expand Down
Loading
Loading