Skip to content

Commit 6525fb3

Browse files
authored
Add opt-in QUIC_OPENSSL_SYMBOL_PREFIX to namespace bundled OpenSSL symbols (Linux) (#6031)
## Description This PR adds an **opt-in** CMake cache variable, `QUIC_OPENSSL_SYMBOL_PREFIX`, that namespace-prefixes every globally-visible symbol in the bundled OpenSSL static archives (and rewrites MsQuic's own undefined references to match). When `QUIC_OPENSSL_SYMBOL_PREFIX` is left empty (the default), no new code runs and the build is byte-for-byte identical to today. ### Motivation When MsQuic is statically linked into a process that also pulls in another copy of OpenSSL — for example, a system `libcrypto.so.3` brought in transitively by an unrelated dependency (a logging library, a database client, any C++ library that itself uses OpenSSL) — the two OpenSSL copies share the same global C symbols (`SSL_CTX_new`, `EVP_*`, `BN_*`, `ERR_*`, the per-module init constructors, …). The dynamic linker resolves every reference to the **first definition loaded**, so all callers — including MsQuic — silently end up sharing one OpenSSL's state machine while their headers and ABI assumptions came from the other. Typical symptoms: - Crashes in `OPENSSL_init_crypto` / `RAND_load_file` when one OpenSSL's per-module init runs against the other's global registries. - Spurious SSL handshake failures when callbacks installed against one `SSL_CTX` see the other's vtable layout. - ABI mismatches when one OpenSSL is `3.0.x` (system `libcrypto.so.3` on Ubuntu 22.04 / RHEL 9) and MsQuic's bundled OpenSSL is `3.5.x` (required for `SSL_set_quic_tls_cbs`). Note that MsQuic's bundled-OpenSSL path always links `libssl.a` / `libcrypto.a` **statically** into MsQuic, regardless of `QUIC_BUILD_SHARED` — `SHARED` just controls whether the result is `libmsquic.so` or `libmsquic.a`. Building `SHARED` with `--exclude-libs=ALL` therefore strips the (statically linked) OpenSSL symbols from `libmsquic.so`'s dynamic export table, which is sufficient when the consumer `dlopen`s/links against `libmsquic.so` itself. It does **not** help: - Static consumers that link `libmsquic.a` into a final binary — symbols flow straight into the executable's global table. - Consumers that bundle `libmsquic.a` into their own `.so` — same exposure unless they also `--exclude-libs` it. - Either case at runtime — even with exports hidden, both `libcrypto`s coexist in one address space and OpenSSL's process-global init/registries (`OPENSSL_init_crypto`, error/atexit tables, `RAND` state) can still collide. ### How it works When `QUIC_OPENSSL_SYMBOL_PREFIX=<prefix>` is passed at CMake configure time, the build: 1. Builds the bundled OpenSSL submodule normally to produce `libssl.a` and `libcrypto.a`. 2. Extracts every globally-defined external symbol from those archives via `nm --defined-only --extern-only` and writes a redefine-syms file mapping each `<sym>` to `<prefix><sym>`. 3. Produces prefixed copies via `objcopy --redefine-syms=<file>` (touches both definitions and undefined references inside each member object). 4. Applies the same `--redefine-syms` step as a `POST_BUILD` action on `libmsquic_platform.a` so MsQuic's own undefined references to OpenSSL (from `tls_openssl.c`, `tls_quictls.c`, `crypt_openssl.c`, `selfsign_openssl.c`) get rewritten to match. 5. Routes the existing `OpenSSL` interface target at the prefixed archives, so the rest of the build is unchanged. The result is a `libmsquic.{a,so}` whose only externally-visible OpenSSL symbols are the prefixed ones. The dynamic linker has no reason to resolve them against any other OpenSSL copy present in the same process. ### Constraints | Constraint | Why | | --- | --- | | Linux only (`CX_PLATFORM=linux`) | Uses GNU binutils `objcopy --redefine-syms`. macOS would need `llvm-objcopy` >= 13 (untested); PE/COFF lacks a flat-namespace symbol table. Rejected with `FATAL_ERROR` on other platforms. | | Bundled OpenSSL only | External/system OpenSSL is owned by the caller and cannot be renamed. Rejected with `FATAL_ERROR` if combined with `QUIC_USE_EXTERNAL_OPENSSL`, `QUIC_OPENSSL_INCLUDE_DIR`, `QUIC_OPENSSL_LIB_DIR`, `QUIC_OPENSSL_ROOT_DIR`, or `QUIC_USE_SYSTEM_LIBCRYPTO`. | | Cross-compile aware | `${CMAKE_NM}` / `${CMAKE_OBJCOPY}` are forwarded to the helper script, so `aarch64-linux-gnu-objcopy` etc. are used when configured. | ### Files - `cmake/openssl-prefix-rename.sh` — helper script (`gen-syms` / `apply` modes; honors `NM` / `OBJCOPY` env vars). - `cmake/PrefixOpenSSLArchives.cmake` — helper function `prefix_openssl_archives(PREFIX … INPUT_TARGET … OUTPUT_TARGET …)`. - `CMakeLists.txt` — new option + validation + plumbing in the bundled-OpenSSL branch. - `src/platform/CMakeLists.txt` — `POST_BUILD` rename on `libmsquic_platform.a`. - `docs/OpenSSLSymbolPrefix.md` — motivation, usage, caveats, verification recipe. ### Future direction The cleanest long-term solution is for OpenSSL itself to expose a configure-time `--symbol-prefix=` option that compiles every public symbol with the prefix baked in (an analog of BoringSSL's `BSSL_NAMESPACE` or LibreSSL's recurring discussion). I plan to file that issue upstream with this PR linked as concrete prior art demonstrating consumer demand. Until that lands, this CMake helper provides an equivalent at link time without requiring an OpenSSL fork. ## Testing ### Manual verification (local, this PR's branch) Built on Linux x86_64 with `-G Ninja -DQUIC_TLS_LIB=quictls -DQUIC_BUILD_SHARED=OFF`: **With `-DQUIC_OPENSSL_SYMBOL_PREFIX=msqtest_`:** ```bash # 0 unprefixed defined globals in the renamed archives: nm --defined-only --extern-only build/openssl-prefixed/msqtest_/libssl.a \ | awk '$2 ~ /^[TDRBWVC]$/ {print $3}' | grep -vc '^msqtest_' # → 0 nm --defined-only --extern-only build/openssl-prefixed/msqtest_/libcrypto.a \ | awk '$2 ~ /^[TDRBWVC]$/ {print $3}' | grep -vc '^msqtest_' # → 0 # 0 unprefixed OpenSSL undefs in msquic_platform.a, 161 prefixed undefs: nm -u build/obj/Release/libmsquic_platform.a | awk '$1=="U"{print $2}' \ | grep -E '^(SSL_|EVP_|BN_|ERR_|X509_|OPENSSL_|RAND_|RSA_|EC_|BIO_|ASN1_|PEM_|CRYPTO_)' \ | wc -l # → 0 nm -u build/obj/Release/libmsquic_platform.a | awk '$1=="U"{print $2}' \ | grep -c '^msqtest_' # → 161 ``` **Without the option (default build):** - 138 normal unprefixed OpenSSL undefs in `libmsquic_platform.a`. - `build/openssl-prefixed/` directory not created. - Build time identical to baseline (no new compilation; the rename rules simply don't fire). ### Production validation The same prefix-rename technique has been deployed in Microsoft's [meru](https://github.com/microsoft/meru-common) codebase (a consumer of MsQuic). It is currently validated across this matrix: - `Release` x86_64 - `Release_ASAN`, `Debug_ASAN`, `Debug_UBSAN`, `Debug_TSAN` x86_64 - `arm_debug_crosscompile` (aarch64) - `clang_tidy` See [microsoft/meru-common#4011](microsoft/meru-common#4011) for the consumer-side integration that ports the same helper script + CMake module pattern that this PR upstreams. ### MsQuic CI coverage The default (option-empty) path is unchanged, so existing CI should be unaffected. The option-set path is Linux-only and opt-in, so it does not need to enter the default matrix. Happy to add a single Linux CI leg that exercises `-DQUIC_OPENSSL_SYMBOL_PREFIX=msquic_` if maintainers think that's worthwhile. ## Documentation New file: `docs/OpenSSLSymbolPrefix.md` documents motivation, usage, constraints, and a verification recipe.
1 parent af14a2c commit 6525fb3

5 files changed

Lines changed: 560 additions & 4 deletions

File tree

CMakeLists.txt

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ endif()
4141

4242
project(msquic)
4343

44+
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
45+
4446
# Set a default build type if none was specified
4547
set(default_build_type "Release")
4648

@@ -113,7 +115,28 @@ option(QUIC_USE_SYSTEM_LIBCRYPTO "Use system libcrypto if quictls TLS" OFF)
113115
option(QUIC_USE_EXTERNAL_OPENSSL "Use external OpenSSL instead of building from submodules" OFF)
114116
set(QUIC_OPENSSL_INCLUDE_DIR "" CACHE PATH "Path to OpenSSL include directory")
115117
set(QUIC_OPENSSL_LIB_DIR "" CACHE PATH "Path to OpenSSL library directory")
118+
set(QUIC_OPENSSL_SYMBOL_PREFIX "" CACHE STRING "If non-empty, namespace-prefix every symbol in the bundled OpenSSL static archives (Linux only) so the resulting binary can coexist in the same process with another OpenSSL copy. See docs/OpenSSLSymbolPrefix.md")
116119
set(QUIC_OPENSSL_ROOT_DIR "" CACHE PATH "Path to OpenSSL root directory")
120+
121+
# Outer-most validation for QUIC_OPENSSL_SYMBOL_PREFIX so a value combined with
122+
# a non-quictls/openssl TLS_LIB (e.g. schannel) errors loudly instead of being
123+
# silently ignored, and so the prefix value is rejected before it is
124+
# interpolated into objcopy --redefine-syms map lines or build-tree paths.
125+
if(NOT "${QUIC_OPENSSL_SYMBOL_PREFIX}" STREQUAL "")
126+
if(NOT (QUIC_TLS_LIB STREQUAL "quictls" OR QUIC_TLS_LIB STREQUAL "openssl"))
127+
message(FATAL_ERROR
128+
"QUIC_OPENSSL_SYMBOL_PREFIX requires QUIC_TLS_LIB=quictls or openssl "
129+
"(got QUIC_TLS_LIB='${QUIC_TLS_LIB}'). The prefix only applies to the "
130+
"bundled OpenSSL build path.")
131+
endif()
132+
if(NOT QUIC_OPENSSL_SYMBOL_PREFIX MATCHES "^[A-Za-z_][A-Za-z0-9_]*$")
133+
message(FATAL_ERROR
134+
"QUIC_OPENSSL_SYMBOL_PREFIX must match ^[A-Za-z_][A-Za-z0-9_]*$ "
135+
"(got '${QUIC_OPENSSL_SYMBOL_PREFIX}'). The prefix is interpolated into "
136+
"objcopy --redefine-syms map lines and into the build-tree path; "
137+
"whitespace, '#', '/', leading digits, and shell metacharacters are unsafe.")
138+
endif()
139+
endif()
117140
option(QUIC_HIGH_RES_TIMERS "Configure the system to use high resolution timers" OFF)
118141
option(QUIC_OFFICIAL_RELEASE "Configured the build for an official release" OFF)
119142
set(QUIC_FOLDER_PREFIX "" CACHE STRING "Optional prefix for source group folders when using an IDE generator")
@@ -811,10 +834,46 @@ if(QUIC_TLS_LIB STREQUAL "quictls" OR QUIC_TLS_LIB STREQUAL "openssl")
811834
FetchContent_MakeAvailable(OpenSSLQuic)
812835
endif()
813836

814-
target_link_libraries(OpenSSL
815-
INTERFACE
816-
MsQuic::OpenSSL
817-
)
837+
# Optional: namespace-prefix the bundled OpenSSL symbols so the resulting
838+
# binary can coexist with another OpenSSL copy loaded into the same
839+
# process. See docs/OpenSSLSymbolPrefix.md for motivation and caveats.
840+
if(NOT "${QUIC_OPENSSL_SYMBOL_PREFIX}" STREQUAL "")
841+
if(NOT CX_PLATFORM STREQUAL "linux")
842+
message(FATAL_ERROR "QUIC_OPENSSL_SYMBOL_PREFIX is only supported on Linux today (got CX_PLATFORM=${CX_PLATFORM}).")
843+
endif()
844+
if(QUIC_USE_EXTERNAL_OPENSSL OR QUIC_OPENSSL_INCLUDE_DIR OR QUIC_OPENSSL_LIB_DIR OR QUIC_OPENSSL_ROOT_DIR)
845+
message(FATAL_ERROR "QUIC_OPENSSL_SYMBOL_PREFIX requires the bundled OpenSSL build; do not combine with QUIC_USE_EXTERNAL_OPENSSL / QUIC_OPENSSL_INCLUDE_DIR / QUIC_OPENSSL_LIB_DIR / QUIC_OPENSSL_ROOT_DIR.")
846+
endif()
847+
if(QUIC_USE_SYSTEM_LIBCRYPTO)
848+
message(FATAL_ERROR "QUIC_OPENSSL_SYMBOL_PREFIX is incompatible with QUIC_USE_SYSTEM_LIBCRYPTO; the system libcrypto cannot be renamed.")
849+
endif()
850+
# Block the shared-lib install path until the prefixed archives are
851+
# wired into install(EXPORT). The prefixed target is INTERFACE IMPORTED
852+
# GLOBAL and cannot be installed via install(TARGETS); a shared-lib
853+
# install would either fail or produce a package that references
854+
# build-tree-only paths under ${CMAKE_BINARY_DIR}/openssl-prefixed/.
855+
# Static-lib consumers (the primary use case) are unaffected.
856+
if(BUILD_SHARED_LIBS)
857+
message(FATAL_ERROR "QUIC_OPENSSL_SYMBOL_PREFIX is currently only supported with BUILD_SHARED_LIBS=OFF (the shared-lib install path does not yet handle the prefixed imported target).")
858+
endif()
859+
860+
include(PrefixOpenSSLArchives)
861+
prefix_openssl_archives(
862+
PREFIX "${QUIC_OPENSSL_SYMBOL_PREFIX}"
863+
INPUT_TARGET OpenSSLQuic
864+
OUTPUT_TARGET OpenSSLQuicPrefixed
865+
)
866+
867+
target_link_libraries(OpenSSL
868+
INTERFACE
869+
OpenSSLQuicPrefixed
870+
)
871+
else()
872+
target_link_libraries(OpenSSL
873+
INTERFACE
874+
MsQuic::OpenSSL
875+
)
876+
endif()
818877
endif()
819878

820879
if (QUIC_USE_SYSTEM_LIBCRYPTO)

cmake/PrefixOpenSSLArchives.cmake

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License.
3+
4+
#
5+
# PrefixOpenSSLArchives
6+
#
7+
# Produce namespace-prefixed copies of the bundled OpenSSL static archives so
8+
# the resulting MsQuic binary can coexist in the same process with a different
9+
# copy of OpenSSL (e.g. a system `libcrypto.so.3` pulled in by an unrelated
10+
# transitive dependency) without colliding on global C symbols.
11+
#
12+
# Mechanism: extract every globally-defined external symbol from the input
13+
# archives and use `objcopy --redefine-syms` to rewrite the symbol table so
14+
# both definitions and undefined references carry a `${QUIC_OPENSSL_SYMBOL_PREFIX}`
15+
# prefix. The rename is applied to:
16+
# - The bundled `libssl.a` and `libcrypto.a` (their *defined* symbols).
17+
# - Any consumer's static archive that references OpenSSL (the *undefined*
18+
# references inside, so they resolve against the prefixed archives at the
19+
# final link step). For MsQuic this is `libmsquic_platform.a`, handled in
20+
# `src/platform/CMakeLists.txt`.
21+
#
22+
# This is a Linux-only feature today. Honoring `${CMAKE_NM}` / `${CMAKE_OBJCOPY}`
23+
# makes the rename step cross-compile-safe. macOS support would require
24+
# `llvm-objcopy` >= 13 (untested); PE/COFF lacks a flat-namespace symbol table
25+
# and would need a fundamentally different approach.
26+
#
27+
# Function:
28+
# prefix_openssl_archives(
29+
# PREFIX <symbol_prefix> # e.g. "msquic_"
30+
# INPUT_TARGET <bundled-openssl-target> # INTERFACE target whose
31+
# # INTERFACE_LINK_LIBRARIES
32+
# # contains the .a paths
33+
# OUTPUT_TARGET <new-interface-target> # name of the renamed target
34+
# # to create
35+
# )
36+
#
37+
# After the call:
38+
# - INTERFACE IMPORTED target `<OUTPUT_TARGET>` exists with include dirs
39+
# copied from `<INPUT_TARGET>` and link libs pointing at the renamed
40+
# archives.
41+
# - Two target properties carry the artifacts a consumer needs to apply the
42+
# same rename to its own archive (e.g. `libmsquic_platform.a`):
43+
# `PREFIX_RENAME_SYMS_FILE` absolute path to the redefine-syms file
44+
# `PREFIX_RENAME_SCRIPT` absolute path to
45+
# `cmake/openssl-prefix-rename.sh`
46+
# Read them at the consumer site with `get_target_property()`.
47+
#
48+
49+
# Capture this module's directory at parse time so the script-path lookup is
50+
# independent of the parent build's `CMAKE_SOURCE_DIR` (which may not be
51+
# msquic's own root when msquic is consumed via `add_subdirectory`).
52+
set(_PREFIX_OPENSSL_SCRIPT "${CMAKE_CURRENT_LIST_DIR}/openssl-prefix-rename.sh")
53+
54+
function(prefix_openssl_archives)
55+
set(options)
56+
set(oneValueArgs PREFIX INPUT_TARGET OUTPUT_TARGET)
57+
set(multiValueArgs)
58+
cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
59+
60+
if(NOT ARG_PREFIX OR NOT ARG_INPUT_TARGET OR NOT ARG_OUTPUT_TARGET)
61+
message(FATAL_ERROR "prefix_openssl_archives requires PREFIX, INPUT_TARGET, and OUTPUT_TARGET")
62+
endif()
63+
64+
if(NOT TARGET ${ARG_INPUT_TARGET})
65+
message(FATAL_ERROR "prefix_openssl_archives: INPUT_TARGET '${ARG_INPUT_TARGET}' does not exist")
66+
endif()
67+
68+
# Pull the original archive paths off the INPUT_TARGET INTERFACE properties.
69+
get_target_property(_openssl_libs ${ARG_INPUT_TARGET} INTERFACE_LINK_LIBRARIES)
70+
get_target_property(_openssl_inc ${ARG_INPUT_TARGET} INTERFACE_INCLUDE_DIRECTORIES)
71+
72+
set(_orig_libssl)
73+
set(_orig_libcrypto)
74+
foreach(_lib IN LISTS _openssl_libs)
75+
if(_lib MATCHES "libssl\\.a$")
76+
set(_orig_libssl "${_lib}")
77+
elseif(_lib MATCHES "libcrypto\\.a$")
78+
set(_orig_libcrypto "${_lib}")
79+
endif()
80+
endforeach()
81+
82+
if(NOT _orig_libssl OR NOT _orig_libcrypto)
83+
message(FATAL_ERROR
84+
"prefix_openssl_archives: ${ARG_INPUT_TARGET} does not expose both libssl.a and libcrypto.a as plain archive paths on its INTERFACE_LINK_LIBRARIES. "
85+
"Got INTERFACE_LINK_LIBRARIES='${_openssl_libs}', found libssl.a='${_orig_libssl}' libcrypto.a='${_orig_libcrypto}'. "
86+
"This helper only handles bundled OpenSSL configurations whose interface points directly at the two static archives; "
87+
"generator-expression-wrapped or target-name link items are not supported.")
88+
endif()
89+
90+
set(_out_dir "${CMAKE_BINARY_DIR}/openssl-prefixed/${ARG_PREFIX}")
91+
set(_syms_file "${_out_dir}/redefine.syms")
92+
set(_renamed_libssl "${_out_dir}/libssl.a")
93+
set(_renamed_libcrypto "${_out_dir}/libcrypto.a")
94+
set(_script "${_PREFIX_OPENSSL_SCRIPT}")
95+
96+
file(MAKE_DIRECTORY "${_out_dir}")
97+
98+
# Forward the cross-compile-aware nm/objcopy to the helper script. If
99+
# ${CMAKE_NM} / ${CMAKE_OBJCOPY} are unset, the script falls back to its
100+
# own defaults (plain `nm` / `objcopy`).
101+
set(_env_args)
102+
if(CMAKE_NM)
103+
list(APPEND _env_args "NM=${CMAKE_NM}")
104+
endif()
105+
if(CMAKE_OBJCOPY)
106+
list(APPEND _env_args "OBJCOPY=${CMAKE_OBJCOPY}")
107+
endif()
108+
109+
add_custom_command(
110+
OUTPUT "${_syms_file}"
111+
DEPENDS "${_orig_libssl}" "${_orig_libcrypto}" "${_script}"
112+
COMMAND ${CMAKE_COMMAND} -E env ${_env_args}
113+
"${_script}" gen-syms "${ARG_PREFIX}" "${_syms_file}"
114+
"${_orig_libssl}" "${_orig_libcrypto}"
115+
COMMENT "Generating OpenSSL symbol prefix-map (prefix=${ARG_PREFIX})"
116+
VERBATIM
117+
)
118+
119+
add_custom_command(
120+
OUTPUT "${_renamed_libssl}"
121+
DEPENDS "${_orig_libssl}" "${_syms_file}" "${_script}"
122+
COMMAND ${CMAKE_COMMAND} -E env ${_env_args}
123+
"${_script}" apply "${_syms_file}" "${_orig_libssl}" "${_renamed_libssl}"
124+
COMMENT "Prefix-renaming libssl.a (prefix=${ARG_PREFIX})"
125+
VERBATIM
126+
)
127+
128+
add_custom_command(
129+
OUTPUT "${_renamed_libcrypto}"
130+
DEPENDS "${_orig_libcrypto}" "${_syms_file}" "${_script}"
131+
COMMAND ${CMAKE_COMMAND} -E env ${_env_args}
132+
"${_script}" apply "${_syms_file}" "${_orig_libcrypto}" "${_renamed_libcrypto}"
133+
COMMENT "Prefix-renaming libcrypto.a (prefix=${ARG_PREFIX})"
134+
VERBATIM
135+
)
136+
137+
add_custom_target(
138+
${ARG_OUTPUT_TARGET}_Build
139+
DEPENDS "${_renamed_libssl}" "${_renamed_libcrypto}" "${_syms_file}"
140+
)
141+
142+
add_library(${ARG_OUTPUT_TARGET} INTERFACE IMPORTED GLOBAL)
143+
add_dependencies(${ARG_OUTPUT_TARGET} ${ARG_OUTPUT_TARGET}_Build)
144+
set_target_properties(
145+
${ARG_OUTPUT_TARGET}
146+
PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${_openssl_inc}"
147+
)
148+
target_link_libraries(
149+
${ARG_OUTPUT_TARGET}
150+
INTERFACE "${_renamed_libssl}" "${_renamed_libcrypto}"
151+
)
152+
153+
# Carry the syms file and helper-script paths on the output target itself
154+
# (rather than as cache variables) so consumers retrieve them via
155+
# get_target_property() at the call site, which keeps the dependency
156+
# explicit and avoids polluting the cache.
157+
set_property(TARGET ${ARG_OUTPUT_TARGET} PROPERTY PREFIX_RENAME_SYMS_FILE "${_syms_file}")
158+
set_property(TARGET ${ARG_OUTPUT_TARGET} PROPERTY PREFIX_RENAME_SCRIPT "${_script}")
159+
160+
message(STATUS "Configured prefixed OpenSSL archives (${ARG_OUTPUT_TARGET}):")
161+
message(STATUS " Prefix: ${ARG_PREFIX}")
162+
message(STATUS " Syms file: ${_syms_file}")
163+
message(STATUS " libssl.a: ${_renamed_libssl}")
164+
message(STATUS " libcrypto.a: ${_renamed_libcrypto}")
165+
endfunction()

cmake/openssl-prefix-rename.sh

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
#!/bin/bash
2+
# Copyright (c) Microsoft Corporation.
3+
# Licensed under the MIT License.
4+
5+
# openssl-prefix-rename.sh
6+
#
7+
# Generate a symbol-prefix map from one or more static archives and apply it
8+
# via `objcopy --redefine-syms` to produce renamed copies.
9+
#
10+
# Used by `cmake/PrefixOpenSSLArchives.cmake` to namespace-prefix the bundled
11+
# OpenSSL static archives (and any consuming archive's undefined references)
12+
# when `QUIC_OPENSSL_SYMBOL_PREFIX` is set. This lets the resulting binary
13+
# coexist with another copy of OpenSSL loaded into the same process (e.g. a
14+
# system `libcrypto.so.3` pulled in by a transitive dependency) without
15+
# global-symbol collisions.
16+
#
17+
# Modes:
18+
# gen-syms PREFIX OUT_SYMS_FILE INPUT_AR [INPUT_AR ...]
19+
# Extract all defined external globals (nm types T/D/R/B/W/V/C) from the
20+
# input archives, sort+unique, and emit `<sym> <PREFIX><sym>` lines.
21+
#
22+
# apply SYMS_FILE INPUT_AR OUTPUT_AR
23+
# Copy INPUT_AR to OUTPUT_AR (skipped if same path) and run
24+
# `objcopy --redefine-syms=SYMS_FILE` on the copy. The rename touches both
25+
# definitions and undefined references inside the archive's member objects.
26+
#
27+
# Environment overrides (honored for cross-compilation):
28+
# NM path to the binutils `nm` matching the target architecture
29+
# (defaults to `nm`; host BFD multi-target usually works but
30+
# `${CMAKE_NM}` is the safe choice)
31+
# OBJCOPY path to the binutils `objcopy` matching the target architecture
32+
# (defaults to `objcopy`)
33+
34+
set -euo pipefail
35+
36+
NM=${NM:-nm}
37+
OBJCOPY=${OBJCOPY:-objcopy}
38+
39+
# Surface unusable tools as a clear configuration error instead of a generic
40+
# "no such file or directory" from the underlying exec.
41+
command -v "$NM" >/dev/null 2>&1 || { echo "$0: NM='$NM' not found" >&2; exit 1; }
42+
command -v "$OBJCOPY" >/dev/null 2>&1 || { echo "$0: OBJCOPY='$OBJCOPY' not found" >&2; exit 1; }
43+
44+
usage() {
45+
cat >&2 <<EOF
46+
usage:
47+
$0 gen-syms PREFIX OUT_SYMS_FILE INPUT_AR [INPUT_AR ...]
48+
$0 apply SYMS_FILE INPUT_AR OUTPUT_AR
49+
50+
env (optional, for cross-compile):
51+
NM (default: nm)
52+
OBJCOPY (default: objcopy)
53+
EOF
54+
exit 2
55+
}
56+
57+
cmd=${1:-}
58+
shift || usage
59+
60+
case "$cmd" in
61+
gen-syms)
62+
[[ $# -ge 3 ]] || usage
63+
prefix=$1
64+
out_syms=$2
65+
shift 2
66+
# Defined-extern symbol-type filter:
67+
# T/D/R/B = text / data / read-only / bss globals
68+
# W/V/C = weak (func / object) and common
69+
# I = STT_GNU_IFUNC (e.g. AES-NI/SHA-NI dispatch resolvers OpenSSL
70+
# 3.x emits on x86_64); these are externally-visible defined
71+
# symbols, so they must end up in the rename map or MsQuic's
72+
# undefs to them won't get prefixed and unprefixed OpenSSL
73+
# symbols will still leak through the consumer archive.
74+
# Do NOT pipe nm's stderr to /dev/null: an unreadable archive, a wrong
75+
# cross-compile nm, or a corrupt input file should surface its real error
76+
# message in the custom-command log.
77+
"$NM" --defined-only --extern-only "$@" \
78+
| awk 'NF==3 && $2 ~ /^[TDRBWVCI]$/ {print $3}' \
79+
| LC_ALL=C sort -u \
80+
| awk -v p="$prefix" '{print $1 " " p $1}' \
81+
> "$out_syms"
82+
# A 0-line syms file would make `objcopy --redefine-syms` a no-op, leaving
83+
# the build green with unprefixed symbols. Fail loudly instead.
84+
[[ -s "$out_syms" ]] || {
85+
echo "$0: gen-syms produced an empty syms file from: $*" >&2
86+
echo "$0: (check that nm '$NM' supports the input archives and emits defined-extern globals)" >&2
87+
exit 1
88+
}
89+
;;
90+
91+
apply)
92+
[[ $# -eq 3 ]] || usage
93+
syms=$1
94+
in_ar=$2
95+
out_ar=$3
96+
# Always go through a temp file + atomic rename, even when in_ar and out_ar
97+
# resolve to the same path (the POST_BUILD step on libmsquic_platform.a).
98+
# POSIX rename(2) over an existing file is atomic, so an interrupted run
99+
# cannot leave out_ar in a partially-rewritten (or un-renamed but
100+
# fresh-mtime) state that the next build would mistake for up-to-date.
101+
tmp="${out_ar}.tmp.$$"
102+
trap 'rm -f -- "$tmp"' EXIT
103+
cp -- "$in_ar" "$tmp"
104+
"$OBJCOPY" --redefine-syms="$syms" "$tmp"
105+
mv -- "$tmp" "$out_ar"
106+
trap - EXIT
107+
;;
108+
109+
*)
110+
usage
111+
;;
112+
esac

0 commit comments

Comments
 (0)