Add FIPS shared library support for MinGW#3263
Open
Will-Low wants to merge 19 commits into
Open
Conversation
justsmth
reviewed
May 27, 2026
justsmth
left a comment
Contributor
There was a problem hiding this comment.
We will also need to add CI coverage for this.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3263 +/- ##
==========================================
- Coverage 78.40% 78.17% -0.23%
==========================================
Files 693 693
Lines 123896 123893 -3
Branches 17207 17204 -3
==========================================
- Hits 97138 96859 -279
- Misses 25841 26113 +272
- Partials 917 921 +4 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
- inject_hash.go: clarify COFF section filter (avoid fallthrough-looking switch) and make the rodata/.rdata consistency check bidirectional, matching doLinux; use idiomatic boolean test for the mingw flag.
- crypto/fipsmodule/CMakeLists.txt: rename FIPS marker objects to fips_mingw_{start,end}.o and forward CMAKE_C_FLAGS to the marker compile commands for cross-compilation.
- CMakeLists.txt: gate static winpthread linking on FIPS, and disable -ffunction-sections/-fdata-sections for MinGW FIPS_SHARED builds.
Adds a cross-mingw-fips job (Ubuntu -> x86_64-w64-mingw32) that builds a FIPS shared library and runs the FIPS integrity self-test under Wine, exercising the PE/COFF inject_hash path and the MinGW CMake plumbing. - tests/ci/run_cross_mingw_fips_tests.sh: new script modeled on run_cross_mingw_tests.sh; builds with -DFIPS=1 -DBUILD_SHARED_LIBS=1, sets WINEPATH for the crypto/ssl and mingw runtime DLLs, and runs bssl isfips, test_fips, crypto_test, and ssl_test. - .github/workflows/windows-alt.yml: add cross-mingw-fips job reusing the cross-mingw toolchain/Wine install block.
Two pre-existing latent issues break the FIPS shared build the moment it is compiled with the MinGW GCC toolchain: - bcm.c gated its MSVC section pragmas (#pragma code_seg/data_seg/const_seg/ bss_seg/section) on OPENSSL_WINDOWS, which is also defined for MinGW. These pragmas are MSVC-only, so MinGW GCC trips -Werror=unknown-pragmas. Gate them on _MSC_VER instead, matching fips_shared_library_marker.c; MinGW places the module via marker objects partial-linked with 'ld -r', so the pragmas are unnecessary there. - The MinGW marker objects are compiled by forwarding the full CMAKE_C_FLAGS, which includes -Werror -Wmissing-prototypes. The marker file intentionally defines BORINGSSL_bcm_text_start/end without prototypes, so compile with -w (the MSVC marker path already does this). Surfaced by the cross-mingw-fips CI job.
At -O2+ GCC partitions functions into hot/cold paths, emitting cold code into
separate .text.unlikely/.text.{exit,startup} sections. On ELF the FIPS linker
script (gcc_fips_shared.lds) gathers these between the BORINGSSL_bcm_text_start/
end markers, but the PE/COFF MinGW path partial-links with a plain 'ld -r' (PE
linkers do not support linker scripts), so any split-out section lands outside
the integrity-checked region in the final DLL. The self-test would still pass,
since injection and the runtime check measure the same window, so the gap is
silent.
- Disable the reordering/partitioning for FIPS_SHARED MinGW GCC builds so all
module code stays in one contiguous .text bracketed by the markers.
- Add a build-time guard (check_fips_mingw_sections.cmake) that inspects the
partial-linked module object and fails the build if any split-out .text/.rdata
section remains. This is the MinGW analogue of the /DISCARD/ net in the ELF
linker scripts and turns the otherwise-silent coverage gap into a hard error.
The FIPS integrity hash is injected over the crypto DLL's preferred-base on-disk bytes, but a PE DLL's hashed .text/.rdata windows contain DIR64 base relocations (the .refptr pointer table). Under ASLR the loader rewrites those pointers to the actual load address, so a naive in-memory hash no longer matches the injected value. To reconcile this, the runtime integrity check walks the PE base relocation table and, for every relocation inside the hashed windows, un-applies the ASLR load delta before hashing, recovering the preferred-base bytes that inject_hash measured. Computing that load delta requires the link-time preferred image base. It cannot be read from the in-memory PE header at runtime because the Windows loader overwrites OptionalHeader.ImageBase with the actual load address once the module is relocated. Instead, inject_hash.go records the preferred base in BORINGSSL_bcm_preferred_base, a plain integer (so it carries no relocation of its own) living in .fipshash outside the hashed module boundary. The runtime reads it to compute load_delta = actual_base - preferred_base. The expected-hash storage (BORINGSSL_bcm_text_hash) is likewise kept in its own .fipshash section so it is never part of the integrity input, which would otherwise create an unsatisfiable circular dependency.
The positive test_fips run alone cannot distinguish a working integrity check from a no-op, since injection and the runtime check measure the same window. Bring the MinGW job to parity with the native MSVC FIPS job by corrupting the module and asserting the check fails. - break-hash.go: add a -mingw mode that locates the module via the PE/COFF symbol table (no .map file, mirroring inject_hash.go's -mingw path) and flips a byte inside [BORINGSSL_bcm_text_start, BORINGSSL_bcm_text_end]. - run_cross_mingw_fips_tests.sh: after the positive test, corrupt the crypto DLL, confirm test_fips now fails, then restore the pristine DLL before the gtest runs.
MinGW-GCC was the only Windows toolchain still routed to winpthreads (crypto/internal.h). winpthreads defines PTHREAD_RWLOCK_INITIALIZER as (pthread_rwlock_t)-1, so CRYPTO_STATIC_MUTEX_INIT is all-ones rather than all zeros. The FIPS power-on self-test build requires these static initializers to be zero (BSS-placeable), so ThreadTest.InitZeros failed once the cross-mingw-fips job started running crypto_test in FIPS mode under MinGW. Route MinGW-GCC to the native Win32/SRWLOCK threading backend, matching MSVC and clang-MinGW (the latter was moved in aws#2381). SRWLOCK_INIT is {0}, satisfying the zero-init requirement. Zeroing the winpthreads initializer is not a valid alternative: (void*)-1 is its lazy-init sentinel. thread_win.c registers a thread-local-destructor callback in .CRT$XLC via the MSVC-only const_seg/data_seg pragmas, which MinGW-GCC rejects under -Werror=unknown-pragmas. Add a GCC branch that places the callback with __attribute__((section(".CRT$XLC"), used)) instead. Verified under Wine that ThreadTest.InitZeros and the TLS-destructor test (ThreadTest.ThreadLocal) both pass.
- Expand the Threads::Threads comment to note that statically linking winpthread by mutating the imported target's interface affects every threaded artifact in the build (libssl.dll, bssl.exe, tests), not just the integrity-checked crypto DLL, and is build-local only. - Fold two adjacent identical #if (__MINGW32__ && BORINGSSL_SHARED_LIBRARY) guards in bcm.c into a single block. No functional change.
The MinGW FIPS shared build relies on the linker preserving the dllexport directives (.drectve) for the FIPS module's symbols through the bcm.o partial link. binutils 2.38 (Ubuntu 22.04) drops them, leaving libcrypto.dll under-exported; binutils >= ~2.41 (Ubuntu 24.04 / mingw-w64 gcc-13) preserves them. Move the job to ubuntu-24.04 and target gcc-13 so the module can be exported via dllexport rather than a blanket -Wl,--export-all-symbols, and point WineHQ at the noble repo to match the new runner. MinGW FIPS shared is a new build target with no existing consumers, so setting this toolchain floor breaks nothing.
Drop -Wl,--export-all-symbols from the MinGW FIPS shared crypto DLL so
it exports the same OPENSSL_EXPORT surface as the MSVC and ELF builds
instead of every internal global symbol.
The FIPS module's public API is already OPENSSL_EXPORT (dllexport) and
exports correctly once the toolchain preserves .drectve through the
bcm.o partial link (see the Ubuntu 24.04 bump). jitterentropy's
JENT_PRIVATE_STATIC keyed its export decision on _MSC_VER, so MinGW-GCC
fell into the ELF visibility("default") branch -- a no-op on PE/COFF --
and jent_* (called directly by cpu_jitter_test) were never exported.
Key on _WIN32 so MinGW exports them via dllexport like MSVC does.
Verified with mingw-w64 gcc-13/binutils-2.41 under Wine: libcrypto.dll,
libssl.dll, bssl, crypto_test, ssl_test and test_fips all link, and the
FIPS positive and negative (break-hash -mingw) integrity tests pass.
The job registers Wine as the PE interpreter via `update-binfmts` so the test binaries (test_fips.exe, bssl.exe) can run directly. On Ubuntu 22.04 that tool came in transitively with wine-binfmt, but on 24.04 (noble) wine-binfmt no longer pulls in binfmt-support, so `update-binfmts` was missing and the install step failed with "command not found". Install binfmt-support explicitly.
- bcm.c: fail closed in hmac_update_module_region if BORINGSSL_bcm_preferred_base still holds the injection sentinel (or 0), so a DLL whose hash injection never ran cannot reach the relocation normalization path and compute a load delta against a bogus base. Add a BORINGSSL_BCM_PREFERRED_BASE_UNSET macro for the sentinel value. - inject_hash.go: make -apple/-windows/-mingw mutually exclusive, reject -map outside -windows, and reject static archives on -mingw, matching the existing -windows guards. - Comment clarifications in bcm.c, fips_shared_support.c, check_fips_mingw_sections.cmake, and CMakeLists.txt.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Issues:
Addressed: #3207, aws/aws-lc-rs#534
Description of changes:
AWS-LC's FIPS build does not currently support cross-compilation targeting Windows using MinGW. This PR adds that support: MinGW-specific BCM boundary markers, PE/COFF support in the integrity hash injection, statically linked libwinpthread in the FIPS DLL, Win32 threading on MinGW, and a
cross-mingw-fipsCI job (positive + negative integrity tests).Call-outs:
The most important thing to review is how the runtime integrity check handles relocations. MinGW is the only supported FIPS target whose hashed region contains base relocations: GCC emits
.refptrindirection cells and genuine imports (e.g. the CRT'sfree) into.rdatainside the integrity boundary. Every other toolchain keeps the hashed window relocation-free by construction (ELF/Mach-O segregate relocated pointers into separate sections via linker scripts + delocate; MSVC emits no.refptrcells), so they hash the region directly. PE linkers don't support linker scripts, so we can't use that approach here.Instead,
hmac_update_module_regionreconstructs the preferred-base image at runtime: it walks the PE base relocation table and subtracts the ASLR load delta from each DIR64 relocation inside the hashed window before hashing, recovering the bytesinject_hash.gomeasured on disk. The load delta isactual_base − preferred_base, wherepreferred_baseis read from the injectedBORINGSSL_bcm_preferred_baseconstant — not the in-memory PE header, since the loader overwritesOptionalHeader.ImageBasewith the actual load address. The delta is a single module-wide constant derived independently of the hashed bytes, so coverage stays complete and any genuine modification (including to a relocated pointer) is still detected; the check fails closed on anything unexpected (non-DIR64 reloc in the window, out-of-order relocs, malformed blocks).This is a deliberate departure from AWS-LC's usual "relocation-free hashed region" paradigm and is worth a FIPS/lab conversation before we commit to it — flagging so reviewers with validation context can weigh in on whether runtime normalization is acceptable versus pushing the
.refptr/import cells out of the boundary.Two supporting pieces are needed to make this correct and safe: GCC hot/cold partitioning is disabled for FIPS_SHARED MinGW builds (so split-out
.text.unlikely/.text.startupsections don't land outside the markers after the plainld -rpartial link), andcheck_fips_mingw_sections.cmakefails the build if any split-out section remains — the MinGW analogue of the ELF/DISCARD/net.Testing:
cross-mingw-fipsCI job runs the FIPS tests under Wine. The negative test (break-hash.go -mingw) corrupts a byte inside the module and asserts the integrity check fails, since the positive test alone can't distinguish a working check from a no-op (injection and the runtime check measure the same window). Also cross-compiled a FIPS-enabled Rust app forx86_64-pc-windows-gnufrom a Linux/aarch64 host and confirmed it starts on Windows Server 2022.By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license and the ISC license.