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
b049d57
Add MinGW cross-compilation support for FIPS builds
Will-Low May 20, 2026
9fe2498
Move MinGW thread static linking to top-level CMakeLists.txt
Will-Low May 20, 2026
6203c7e
Fix jitterentropy MinGW shared build: add BORINGSSL_IMPLEMENTATION
Will-Low May 21, 2026
0050197
Fix inject_hash.go: error message and rodata section check
Will-Low May 21, 2026
28af050
Clarify jitterentropy BORINGSSL_IMPLEMENTATION comment
Will-Low May 21, 2026
dc28a06
Remove comment from libwinpthread static link
Will-Low May 21, 2026
4d8ccb3
Address PR feedback for MinGW FIPS support
justsmth Jun 9, 2026
b23a863
Add cross-MinGW FIPS shared CI job
justsmth Jun 10, 2026
79cfca5
Fix MinGW FIPS shared build compilation under -Werror
justsmth Jun 18, 2026
a6d4d6e
Keep MinGW FIPS module code within the integrity markers and enforce it
justsmth Jun 18, 2026
e7b37a8
Make the MinGW FIPS shared integrity check correct under ASLR
justsmth Jun 19, 2026
9dd1464
Add a FIPS integrity negative test to the cross-mingw-fips CI job
justsmth Jun 19, 2026
b499a80
Use Win32 threads on MinGW so FIPS static locks are zero-initialized
justsmth Jun 19, 2026
e3a73bd
Clarify MinGW FIPS shared comments and fold duplicate guard
justsmth Jun 19, 2026
f808b90
Move cross-mingw-fips CI to Ubuntu 24.04 (mingw-w64 gcc-13)
justsmth Jun 19, 2026
c4046a1
Export FIPS module and jitterentropy via dllexport on MinGW
justsmth Jun 19, 2026
f5ef29d
Install binfmt-support for cross-mingw-fips on Ubuntu 24.04
justsmth Jun 19, 2026
775833b
Harden MinGW FIPS integrity check and inject_hash input validation
justsmth Jun 23, 2026
9832950
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 @@ -255,6 +255,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
26 changes: 25 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1163,14 +1163,30 @@ endif()

# FIPS_SHARED's linker script does not collect per-symbol sections, which causes
# `ld -r` to fail. Disable GNU-style section flags even when inherited from a
# toolchain or CFLAGS.
# toolchain or CFLAGS (e.g. Android CMake files, or a future MinGW default).
# On ELF, gcc_fips_shared.lds gathers known split sections into .text and
# /DISCARD/s anything unexpected, but Android and MinGW lack that safety net
# (Android's NDK toolchain injects these flags externally; MinGW/PE has no
# linker script support).
if(FIPS_SHARED AND (GCC OR CLANG) AND NOT MSVC)
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)
# Apply its x86_64 AVX-512 implication after ARCH is detected.
Expand Down Expand Up @@ -1481,6 +1497,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 @@ -680,12 +680,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 @@ -790,6 +790,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