Skip to content

Add FIPS shared library support for MinGW#3263

Open
Will-Low wants to merge 19 commits into
aws:mainfrom
Will-Low:main
Open

Add FIPS shared library support for MinGW#3263
Will-Low wants to merge 19 commits into
aws:mainfrom
Will-Low:main

Conversation

@Will-Low

@Will-Low Will-Low commented May 21, 2026

Copy link
Copy Markdown

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-fips CI 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 .refptr indirection cells and genuine imports (e.g. the CRT's free) into .rdata inside 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 .refptr cells), 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_region reconstructs 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 bytes inject_hash.go measured on disk. The load delta is actual_base − preferred_base, where preferred_base is read from the injected BORINGSSL_bcm_preferred_base constant — not the in-memory PE header, since the loader overwrites OptionalHeader.ImageBase with 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.startup sections don't land outside the markers after the plain ld -r partial link), and check_fips_mingw_sections.cmake fails the build if any split-out section remains — the MinGW analogue of the ELF /DISCARD/ net.

Testing:

cross-mingw-fips CI 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 for x86_64-pc-windows-gnu from 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.

@justsmth justsmth left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will also need to add CI coverage for this.

Comment thread CMakeLists.txt Outdated
Comment thread CMakeLists.txt Outdated
Comment thread crypto/fipsmodule/CMakeLists.txt Outdated
Comment thread util/fipstools/inject_hash/inject_hash.go Outdated
Comment thread util/fipstools/inject_hash/inject_hash.go Outdated
Comment thread crypto/fipsmodule/CMakeLists.txt
@justsmth justsmth requested a review from torben-hansen May 27, 2026 13:02
@codecov-commenter

codecov-commenter commented May 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 78.17%. Comparing base (864b432) to head (95b8f68).

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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Will-Low and others added 18 commits June 22, 2026 14:51
- 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.
Comment thread CMakeLists.txt
Comment thread CMakeLists.txt
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants