Skip to content

Commit 69f7463

Browse files
committed
Fix Windows/ARM FIPS build
1 parent 167015c commit 69f7463

File tree

5 files changed

+180
-37
lines changed

5 files changed

+180
-37
lines changed

crypto/CMakeLists.txt

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -628,9 +628,34 @@ endfunction()
628628
if(FIPS_SHARED)
629629
# Rewrite libcrypto.so, libcrypto.dylib, or crypto.dll to inject the correct module
630630
# hash value. For now we support the FIPS build only on Linux, macOS, iOS, and Windows.
631-
if(MSVC)
632-
# On Windows we use capture_hash.go to capture the computed integrity value that bcm.o prints to generate the
633-
# correct value in generated_fips_shared_support.c. See FIPS.md for a full explanation of the process
631+
if(MSVC AND ARCH STREQUAL "aarch64")
632+
# On ARM64 Windows, ASLR is mandatory and cannot be disabled. The linker
633+
# resolves ADRP immediates differently when building two separate DLLs
634+
# (precrypto.dll vs crypto.dll), even when the FIPS module code comes from
635+
# the same bcm.lib. This makes the two-DLL capture_hash approach produce a
636+
# hash mismatch. Instead we use a single-DLL approach: build crypto.dll
637+
# once with a placeholder hash, run fips_empty_main.exe (which triggers the
638+
# integrity check, computes the real hash, and prints it), then binary-patch
639+
# the placeholder in crypto.dll with the captured hash.
640+
build_libcrypto(NAME crypto MODULE_SOURCE $<TARGET_OBJECTS:fipsmodule> SET_OUTPUT_NAME)
641+
642+
add_executable(fips_empty_main fipsmodule/fips_empty_main.c)
643+
target_link_libraries(fips_empty_main PUBLIC crypto)
644+
target_add_awslc_include_paths(TARGET fips_empty_main SCOPE PRIVATE)
645+
646+
add_custom_command(
647+
TARGET fips_empty_main POST_BUILD
648+
COMMAND ${GO_EXECUTABLE} run
649+
${AWSLC_SOURCE_DIR}/util/fipstools/capture_hash/capture_hash.go
650+
-in-executable $<TARGET_FILE:fips_empty_main>
651+
-patch-dll $<TARGET_FILE:crypto>
652+
WORKING_DIRECTORY ${AWSLC_SOURCE_DIR}
653+
)
654+
elseif(MSVC)
655+
# On x64 Windows we use capture_hash.go to capture the computed integrity
656+
# value that the module prints on its first (failing) run, then embed that
657+
# value in generated_fips_shared_support.c for the final crypto.dll.
658+
# See FIPS.md for a full explanation of the process.
634659
build_libcrypto(NAME precrypto MODULE_SOURCE $<TARGET_OBJECTS:fipsmodule>)
635660
add_executable(fips_empty_main fipsmodule/fips_empty_main.c)
636661
target_link_libraries(fips_empty_main PUBLIC precrypto)
@@ -650,6 +675,7 @@ if(FIPS_SHARED)
650675
generated_fips_shared_support.c
651676
${AWSLC_SOURCE_DIR}/crypto/fipsmodule/cpucap/cpucap.c
652677
)
678+
target_compile_definitions(generated_fipsmodule PRIVATE BORINGSSL_IMPLEMENTATION S2N_BN_HIDE_SYMBOLS)
653679
target_add_awslc_include_paths(TARGET generated_fipsmodule SCOPE PRIVATE)
654680

655681
build_libcrypto(NAME crypto MODULE_SOURCE $<TARGET_OBJECTS:generated_fipsmodule> SET_OUTPUT_NAME)

crypto/fipsmodule/bcm.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@
3232
#pragma data_seg(".fipsda$b")
3333
#pragma const_seg(".fipsco$b")
3434
#pragma bss_seg(".fipsbs$b")
35+
// Explicitly declare the FIPS rodata section with correct attributes. This
36+
// ensures the section is known to the compiler/linker even if #pragma const_seg
37+
// is not fully supported (e.g. clang-cl on ARM64).
38+
#pragma section(".fipsco$b", read)
3539
#endif
3640

3741
#include <openssl/chacha.h>

crypto/fipsmodule/fips_shared_library_marker.c

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,35 +23,48 @@
2323
#pragma data_seg(".fipsda$a")
2424
#pragma const_seg(".fipsco$a")
2525
#pragma bss_seg(".fipsbs$a")
26+
// Declare the FIPS rodata section so that __declspec(allocate()) can reference
27+
// it. This provides an explicit fallback in case #pragma const_seg is not fully
28+
// supported by the compiler (e.g. clang-cl on ARM64).
29+
#pragma section(".fipsco$a", read)
2630
#endif
2731

2832
// Dummy but not empty function and array to avoid the compiler completely
2933
// optimizing out the symbols.
3034
const uint8_t *BORINGSSL_bcm_text_start(void) {
3135
return NULL;
3236
}
37+
#if defined(_MSC_VER)
38+
__declspec(allocate(".fipsco$a"))
39+
#endif
3340
const uint8_t BORINGSSL_bcm_rodata_start[16] =
34-
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
41+
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
3542

3643
#elif defined(AWSLC_FIPS_SHARED_END)
3744
#if defined(_MSC_VER)
3845
#pragma code_seg(".fipstx$z")
3946
#pragma data_seg(".fipsda$z")
4047
#pragma const_seg(".fipsco$z")
4148
#pragma bss_seg(".fipsbs$z")
49+
// Declare the FIPS rodata section so that __declspec(allocate()) can reference
50+
// it. This provides an explicit fallback in case #pragma const_seg is not fully
51+
// supported by the compiler (e.g. clang-cl on ARM64).
52+
#pragma section(".fipsco$z", read)
4253
#endif
4354

4455
// Dummy but not empty function and array to avoid the compiler completely
4556
// optimizing out the symbols.
4657
const uint8_t *BORINGSSL_bcm_text_end(void){
4758
return NULL;
4859
}
60+
#if defined(_MSC_VER)
61+
__declspec(allocate(".fipsco$z"))
62+
#endif
4963
const uint8_t BORINGSSL_bcm_rodata_end[16] =
50-
{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
64+
{16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
5165

5266
#else
5367

5468
#error "This file should be compiled only as part of the Shared FIPS build on macOS/iOS/Windows."
5569

5670
#endif
57-

crypto/fipsmodule/fips_shared_support.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@
1414

1515
#include <stdint.h>
1616

17+
// Ensure that no ambient #pragma const_seg is active. BORINGSSL_bcm_text_hash
18+
// MUST be placed in the default .rdata section, outside the FIPS module rodata
19+
// boundary (.fipsco). If it were placed inside the FIPS rodata boundary, the
20+
// integrity check would hash the expected value itself, creating a circular
21+
// dependency that can never be satisfied.
22+
#if defined(_MSC_VER)
23+
#pragma const_seg()
24+
#endif
1725

1826
#if defined(BORINGSSL_FIPS) && defined(BORINGSSL_SHARED_LIBRARY)
1927
// BORINGSSL_bcm_text_hash is is default hash value for the FIPS integrity check
Lines changed: 123 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,165 @@
11
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0 OR ISC
33

4-
// capture_hash runs another executable that has been linked with libcrypto. It expects the libcrypto to run the
5-
// power-on self-tests and fail due to a fingerprint mismatch. capture_hash parses the output, takes the correct
6-
// fingerprint value, and generates a new C file that contains the correct fingerprint which is used to build the
7-
// final libcrypto.
4+
// capture_hash runs another executable that has been linked with libcrypto. It
5+
// expects the libcrypto to run the power-on self-tests and fail due to a
6+
// fingerprint mismatch. capture_hash parses the output to extract the correct
7+
// fingerprint value.
8+
//
9+
// By default it generates a C source file on stdout containing the correct
10+
// hash, which is used to build the final libcrypto (the two-DLL approach used
11+
// on Windows x64).
12+
//
13+
// When -patch-dll is specified, it instead reads the given DLL, finds the
14+
// placeholder hash value, replaces it with the captured hash, and writes the
15+
// patched DLL back. This single-DLL approach avoids building two separate
16+
// DLLs whose linker output might differ (as happens on ARM64 Windows where
17+
// mandatory ASLR causes ADRP immediate differences between DLLs).
818

919
package main
1020

1121
import (
22+
"bytes"
23+
"encoding/hex"
1224
"flag"
1325
"fmt"
1426
"os"
1527
"os/exec"
1628
"strings"
29+
30+
"github.com/aws/aws-lc/util/fipstools/fipscommon"
1731
)
1832

19-
const line0 = "AWS-LC FIPS failure caused by:"
20-
const line1 = "FIPS integrity test failed."
33+
const expectedFailureMsg = "FIPS integrity test failed."
2134

2235
// This must match what is in crypto/fipsmodule/fips_shared_support.c
23-
const line2 = "Expected: ae2cea2abda6f3ec977f9bf6949afc836827cba0a09f6b6fde52cde2cdff3180"
24-
const hash_len = 64
36+
const expectedHashLine = "Expected: ae2cea2abda6f3ec977f9bf6949afc836827cba0a09f6b6fde52cde2cdff3180"
37+
const calculatedPrefix = "Calculated: "
38+
const hashHexLen = 64
2539

2640
func main() {
2741
executable := flag.String("in-executable", "", "Path to the executable file")
42+
patchDll := flag.String("patch-dll", "", "Path to a DLL to binary-patch with the captured hash (single-DLL mode)")
2843
flag.Parse()
44+
45+
if *executable == "" {
46+
fmt.Fprintf(os.Stderr, "capture_hash: -in-executable is required\n")
47+
os.Exit(1)
48+
}
49+
2950
cmd := exec.Command(*executable)
3051
out, err := cmd.CombinedOutput()
3152
if err == nil {
32-
fmt.Fprintf(os.Stderr, string(out))
33-
panic("Executable did not fail as expected")
53+
fmt.Fprintf(os.Stderr, "%s", out)
54+
fmt.Fprintf(os.Stderr, "capture_hash: executable did not fail as expected\n")
55+
os.Exit(1)
3456
}
57+
58+
// Search for the expected lines by content rather than by strict line
59+
// numbers. This makes the parser tolerant of additional diagnostic output
60+
// that may be printed before or between the FIPS integrity test messages.
3561
lines := strings.Split(string(out), "\r\n")
36-
if len(lines) != 6 {
37-
fmt.Fprintf(os.Stderr, string(out))
38-
panic(fmt.Sprintf("Expected 6 lines in output but got %d", len(lines)))
62+
63+
foundFailureMsg := false
64+
foundExpectedHash := false
65+
hashHex := ""
66+
67+
for _, line := range lines {
68+
line = strings.TrimSpace(line)
69+
if line == expectedFailureMsg {
70+
foundFailureMsg = true
71+
}
72+
if line == expectedHashLine {
73+
foundExpectedHash = true
74+
}
75+
if strings.HasPrefix(line, calculatedPrefix) {
76+
parts := strings.Fields(line)
77+
if len(parts) >= 2 {
78+
hashHex = parts[1]
79+
}
80+
}
3981
}
4082

41-
if lines[0] != line0 {
42-
fmt.Fprintf(os.Stderr, string(out))
43-
panic(fmt.Sprintf("Expected \"%s\" got \"%s\"", line0, lines[0]))
83+
if !foundFailureMsg {
84+
fmt.Fprintf(os.Stderr, "%s", out)
85+
fmt.Fprintf(os.Stderr, "capture_hash: did not find %q in output\n", expectedFailureMsg)
86+
os.Exit(1)
4487
}
4588

46-
if lines[1] != line1 {
47-
fmt.Fprintf(os.Stderr, string(out))
48-
panic(fmt.Sprintf("Expected \"%s\" got \"%s\"", line1, lines[1]))
89+
if !foundExpectedHash {
90+
fmt.Fprintf(os.Stderr, "%s", out)
91+
fmt.Fprintf(os.Stderr, "capture_hash: did not find %q in output\n", expectedHashLine)
92+
os.Exit(1)
4993
}
50-
if lines[2] != line2 {
51-
fmt.Fprintf(os.Stderr, string(out))
52-
panic(fmt.Sprintf("Expected \"%s\" got \"%s\"", line1, lines[1]))
94+
95+
if hashHex == "" {
96+
fmt.Fprintf(os.Stderr, "%s", out)
97+
fmt.Fprintf(os.Stderr, "capture_hash: did not find %q line in output\n", calculatedPrefix)
98+
os.Exit(1)
5399
}
54-
hash := strings.Split(lines[3], " ")[1]
55100

56-
if len(hash) != hash_len {
57-
fmt.Fprintf(os.Stderr, string(out))
58-
panic(fmt.Sprintf("Hash \"%s\" is %d long, expected %d", hash, len(hash), hash_len))
101+
if len(hashHex) != hashHexLen {
102+
fmt.Fprintf(os.Stderr, "%s", out)
103+
fmt.Fprintf(os.Stderr, "capture_hash: hash %q is %d chars, expected %d\n", hashHex, len(hashHex), hashHexLen)
104+
os.Exit(1)
59105
}
60106

61-
fmt.Printf(`// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
107+
fmt.Fprintf(os.Stderr, "capture_hash: captured hash = %s\n", hashHex)
108+
109+
if *patchDll != "" {
110+
// Single-DLL mode: binary-patch the placeholder hash in the DLL.
111+
hashBytes, err := hex.DecodeString(hashHex)
112+
if err != nil {
113+
fmt.Fprintf(os.Stderr, "capture_hash: failed to decode hash hex: %v\n", err)
114+
os.Exit(1)
115+
}
116+
117+
fi, err := os.Stat(*patchDll)
118+
if err != nil {
119+
fmt.Fprintf(os.Stderr, "capture_hash: %v\n", err)
120+
os.Exit(1)
121+
}
122+
perm := fi.Mode() & 0777
123+
124+
dllBytes, err := os.ReadFile(*patchDll)
125+
if err != nil {
126+
fmt.Fprintf(os.Stderr, "capture_hash: failed to read DLL: %v\n", err)
127+
os.Exit(1)
128+
}
129+
130+
offset := bytes.Index(dllBytes, fipscommon.UninitHashValue[:])
131+
if offset < 0 {
132+
fmt.Fprintf(os.Stderr, "capture_hash: placeholder hash not found in %s\n", *patchDll)
133+
os.Exit(1)
134+
}
135+
136+
// Verify uniqueness — the placeholder must appear exactly once.
137+
if bytes.Index(dllBytes[offset+len(fipscommon.UninitHashValue):], fipscommon.UninitHashValue[:]) >= 0 {
138+
fmt.Fprintf(os.Stderr, "capture_hash: found multiple occurrences of placeholder hash in %s\n", *patchDll)
139+
os.Exit(1)
140+
}
141+
142+
copy(dllBytes[offset:], hashBytes)
143+
144+
if err := os.WriteFile(*patchDll, dllBytes, perm); err != nil {
145+
fmt.Fprintf(os.Stderr, "capture_hash: failed to write patched DLL: %v\n", err)
146+
os.Exit(1)
147+
}
148+
149+
fmt.Fprintf(os.Stderr, "capture_hash: patched %s at offset 0x%x\n", *patchDll, offset)
150+
} else {
151+
// Two-DLL mode: generate a C source file with the correct hash on stdout.
152+
fmt.Printf(`// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
62153
// SPDX-License-Identifier: Apache-2.0 OR ISC
63154
// This file is generated by: 'go run util/fipstools/capture_hash/capture_hash.go -in-executable %s'
64155
#include <stdint.h>
65156
const uint8_t BORINGSSL_bcm_text_hash[32] = {
66157
`, *executable)
67-
for i := 0; i < len(hash); i += 2 {
68-
fmt.Printf("0x%s, ", hash[i:i+2])
69-
}
70-
fmt.Printf(`
158+
for i := 0; i < len(hashHex); i += 2 {
159+
fmt.Printf("0x%s, ", hashHex[i:i+2])
160+
}
161+
fmt.Printf(`
71162
};
72163
`)
164+
}
73165
}

0 commit comments

Comments
 (0)