Skip to content

Commit df58ad9

Browse files
authored
Upgrade custom libc++ to LLVM 19 and add sanitizer support to build_and_test.sh (#3131)
### Description of changes: Currently, the custom libc++ used for MSAN/TSAN-instrumented builds is pinned to LLVM 15, and running sanitizer builds requires manually assembling the correct CMake flags. Additionally, the no-ASM code paths are not tested under sanitizers in CI. This change upgrades the custom libc++ from LLVM 15 to LLVM 19.1.7, adds first-class `--sanitizer` support to `util/build_and_test.sh`, and introduces new CI jobs that test the no-ASM code paths under ASan and MSan. **LLVM 19 upgrade:** - Update `Dockerfile.al2023` to clone LLVM 19.1.7 (from 15.0.6) - Bump `CXX_STANDARD` from 20 to 23 for the custom libc++ build (LLVM 19 added `expected.cpp` which requires C++23) - Expand `util/bot/libcxx-config/__config_site` with required LLVM 19+ configuration macros (`_LIBCPP_HARDENING_MODE`, positive-sense feature flags, thread API selection, ASan instrumentation flag) - Add `util/bot/libcxx-config/__assertion_handler` (required by LLVM 19+ which no longer ships a default) - Update `linux-multi-arch-omnibus.yml` to use the unversioned `setup-clang.sh` **Sanitizer support in `build_and_test.sh`:** - Add `--sanitizer <name>` flag supporting `asan`, `msan`, `tsan`, `ubsan`, and `cfi` - Add `--test <binary>` flag to build and run a single test binary directly - Auto-clone LLVM (sparse, cached) when MSAN/TSAN need a custom libc++ and `LLVM_PROJECT_HOME` is not set - Clean the build directory on sanitizer runs to avoid stale configuration artifacts **Bug fix:** - Zero-initialize `res` and `tmp` arrays in `ec_nistp_scalar_mul` to eliminate MSAN false positives on unused limbs ### Call-outs: The new `sanitizer-tests` job in `actions-ci.yml` runs ASan and MSan with `-DOPENSSL_NO_ASM=1` on x86-64 and aarch64. These jobs do **not** overlap with the existing `sanitizers` job in `linux-multi-arch-omnibus.yml`, which runs all five sanitizers (ASan, MSan, TSan, UBSan, CFI) with ASM enabled inside Docker containers on CodeBuild via `run_posix_sanitizers.sh`. The new jobs specifically cover the no-ASM code paths under sanitizers, which were previously untested in CI. ### Testing: - New `sanitizer-tests` CI matrix covers ASan + MSan (no-ASM) × {x86-64, aarch64} - Existing Docker-based omnibus sanitizer jobs updated to use LLVM 19 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.
1 parent 863ed59 commit df58ad9

9 files changed

Lines changed: 217 additions & 11 deletions

File tree

.github/docker_images/aws-lc/amazonlinux/Dockerfile.al2023

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ RUN <<EOF
8888
set -ex
8989
mkdir -p ${DEPENDENCIES_DIR}
9090
cd ${DEPENDENCIES_DIR}
91-
git clone https://github.com/llvm/llvm-project.git --branch llvmorg-15.0.6 --depth 1
91+
git clone https://github.com/llvm/llvm-project.git --branch llvmorg-19.1.7 --depth 1
9292
cd llvm-project
9393
rm -rf $(ls -A | grep -Ev "(libcxx|libcxxabi)")
9494
EOF

.github/workflows/actions-ci.yml

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,74 @@ jobs:
205205
- name: Run tests
206206
run: cmake --build ./build --target run_tests
207207

208+
sanitizer-tests:
209+
name: sanitizer - ${{ matrix.arch.name }} - ${{ matrix.config.name }} [${{ matrix.shard_index }}/${{ matrix.config.total_shards }}]
210+
needs: [sanity-test-run]
211+
if: github.repository_owner == 'aws'
212+
runs-on: ${{ matrix.arch.runner }}
213+
env:
214+
GOFLAGS: "-buildvcs=false"
215+
strategy:
216+
fail-fast: false
217+
matrix:
218+
arch:
219+
- name: x86-64
220+
runner: ubuntu-latest
221+
- name: aarch64
222+
runner: ubuntu-24.04-arm
223+
config:
224+
- name: asan-noasm
225+
sanitizer: asan
226+
extra_cmake_args: "-DOPENSSL_NO_ASM=1"
227+
total_shards: 1
228+
- name: msan-noasm
229+
sanitizer: msan
230+
extra_cmake_args: "-DOPENSSL_NO_ASM=1"
231+
total_shards: 4
232+
shard_index: [0, 1, 2, 3]
233+
exclude:
234+
- config:
235+
name: asan-noasm
236+
include:
237+
- arch:
238+
name: x86-64
239+
runner: ubuntu-latest
240+
config:
241+
name: asan-noasm
242+
sanitizer: asan
243+
extra_cmake_args: "-DOPENSSL_NO_ASM=1"
244+
total_shards: 1
245+
shard_index: 0
246+
- arch:
247+
name: aarch64
248+
runner: ubuntu-24.04-arm
249+
config:
250+
name: asan-noasm
251+
sanitizer: asan
252+
extra_cmake_args: "-DOPENSSL_NO_ASM=1"
253+
total_shards: 1
254+
shard_index: 0
255+
steps:
256+
- uses: actions/checkout@v4
257+
- uses: actions/setup-go@v4
258+
with:
259+
go-version: ">=1.18"
260+
- name: Install dependencies
261+
run: |
262+
sudo apt-get update
263+
sudo apt-get install -y ninja-build
264+
- name: Build and Test crypto_test
265+
env:
266+
GTEST_TOTAL_SHARDS: ${{ matrix.config.total_shards }}
267+
GTEST_SHARD_INDEX: ${{ matrix.shard_index }}
268+
run: |
269+
./util/build_and_test.sh --sanitizer ${{ matrix.config.sanitizer }} \
270+
--test crypto_test --build-target all_tests \
271+
-DCMAKE_BUILD_TYPE=Release ${{ matrix.config.extra_cmake_args }}
272+
- name: Run ssl_test
273+
if: matrix.shard_index == 0
274+
run: ./build/ssl/ssl_test
275+
208276
musl-tests:
209277
name: musl - ${{ matrix.config.arch }}
210278
needs: [sanity-test-run]

.github/workflows/linux-multi-arch-omnibus.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,7 @@ jobs:
512512
env: |
513513
AWS_LC_GO_TEST_TIMEOUT
514514
run: |
515-
source /opt/compiler-env/setup-clang-15.sh
515+
source /opt/compiler-env/setup-clang.sh
516516
./tests/ci/run_posix_sanitizers.sh
517517
518518
benchmark:

CMakeLists.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1174,8 +1174,9 @@ if(USE_CUSTOM_LIBCXX)
11741174
set_target_properties(
11751175
libcxx libcxxabi PROPERTIES
11761176
COMPILE_FLAGS "-Wno-missing-prototypes -Wno-implicit-fallthrough"
1177-
# libc++ and libc++abi must be built in C++20 mode.
1178-
CXX_STANDARD 20
1177+
# libc++ and libc++abi must be built in C++23 mode.
1178+
# LLVM 19+ added expected.cpp which requires C++23.
1179+
CXX_STANDARD 23
11791180
CXX_STANDARD_REQUIRED TRUE
11801181
)
11811182
# libc++abi depends on libc++ internal headers.

crypto/fips_callback_test.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,8 @@ TEST(FIPSCallback, PowerOnSelfTests) {
106106
uint8_t private_key[ED25519_PRIVATE_KEY_LEN];
107107
ED25519_keypair(public_key, private_key);
108108

109-
uint8_t message[2];
110-
uint8_t context[2];
109+
uint8_t message[2] = {0};
110+
uint8_t context[2] = {0};
111111
uint8_t signature[ED25519_SIGNATURE_LEN];
112112
EXPECT_TRUE(ED25519ph_sign(signature, message, sizeof(message), private_key, context, sizeof(context)));
113113

crypto/fipsmodule/ec/ec_nistp.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -440,8 +440,8 @@ void ec_nistp_scalar_mul(const ec_nistp_meth *ctx,
440440
// to avoid allocation, and just take pointers to individual coordinates.
441441
// (This cruft will dissapear when we refactor point_add/dbl to work with
442442
// whole points instead of individual coordinates).
443-
ec_nistp_felem_limb res[3 * FELEM_MAX_NUM_OF_LIMBS];
444-
ec_nistp_felem_limb tmp[3 * FELEM_MAX_NUM_OF_LIMBS];
443+
ec_nistp_felem_limb res[3 * FELEM_MAX_NUM_OF_LIMBS] = {0};
444+
ec_nistp_felem_limb tmp[3 * FELEM_MAX_NUM_OF_LIMBS] = {0};
445445
ec_nistp_felem_limb *x_res = &res[0];
446446
ec_nistp_felem_limb *y_res = &res[ctx->felem_num_limbs];
447447
ec_nistp_felem_limb *z_res = &res[ctx->felem_num_limbs * 2];
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#ifndef BORINGSSL_LIBCXX_ASSERTION_HANDLER_
2+
#define BORINGSSL_LIBCXX_ASSERTION_HANDLER_
3+
4+
#include <__verbose_abort>
5+
6+
// We only use our custom libc++ for sanitizer testing, so we can use verbose
7+
// aborts for assertion failures.
8+
#define _LIBCPP_ASSERTION_HANDLER(message) _LIBCPP_VERBOSE_ABORT("%s", message)
9+
10+
#endif // BORINGSSL_LIBCXX_ASSERTION_HANDLER_
Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,47 @@
11
#ifndef BORINGSSL_LIBCXX_CONFIG_SITE_
22
#define BORINGSSL_LIBCXX_CONFIG_SITE_
33

4+
// We only use our custom libc++ for sanitizer testing, so enable all hardening
5+
// checks. This is required for LLVM 16+ which mandates that
6+
// _LIBCPP_HARDENING_MODE is set to a valid value.
7+
#define _LIBCPP_HARDENING_MODE _LIBCPP_HARDENING_MODE_DEBUG
8+
9+
// libc++ headers disable std::string ASan annotations if this is not defined.
10+
// This is to avoid false positives when libc++'s runtime components are
11+
// uninstrumented. When building our custom libc++, libc++ will be as
12+
// instrumented as the caller, so we can safely enable this.
13+
#define _LIBCPP_INSTRUMENTED_WITH_ASAN 1
14+
15+
// New-style feature macros (LLVM 19+). Older versions use
16+
// _LIBCPP_HAS_NO_<feature> negated macros which are auto-detected, so these
17+
// definitions are only consumed by the versions that require them.
18+
#define _LIBCPP_HAS_FILESYSTEM 1
19+
#define _LIBCPP_HAS_LOCALIZATION 1
20+
#define _LIBCPP_HAS_MONOTONIC_CLOCK 1
21+
#define _LIBCPP_HAS_RANDOM_DEVICE 1
22+
#define _LIBCPP_HAS_THREADS 1
23+
#define _LIBCPP_HAS_UNICODE 1
24+
#define _LIBCPP_HAS_WIDE_CHARACTERS 1
25+
26+
// LLVM 19+ replaced _LIBCPP_HAS_NO_VENDOR_AVAILABILITY_ANNOTATIONS with a
27+
// positive-sense macro.
28+
#define _LIBCPP_HAS_VENDOR_AVAILABILITY_ANNOTATIONS 0
29+
// Keep the old macro for LLVM <= 18 compatibility.
430
#define _LIBCPP_HAS_NO_VENDOR_AVAILABILITY_ANNOTATIONS
531

6-
#endif // BORINGSSL_LIBCXX_CONFIG_SITE_
32+
#if defined(__APPLE__)
33+
#define _LIBCPP_PSTL_BACKEND_LIBDISPATCH 1
34+
#else
35+
#define _LIBCPP_PSTL_BACKEND_STD_THREAD 1
36+
#endif
37+
38+
// Thread API macros use defined() checks in LLVM 19+, so only define the one
39+
// that applies. Defining the others to 0 would still satisfy defined() and
40+
// break the include logic.
41+
#ifdef _WIN32
42+
#define _LIBCPP_HAS_THREAD_API_WIN32 1
43+
#else
44+
#define _LIBCPP_HAS_THREAD_API_PTHREAD 1
45+
#endif
46+
47+
#endif // BORINGSSL_LIBCXX_CONFIG_SITE_

util/build_and_test.sh

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,38 @@ DEFAULT_CMAKE_FLAGS=("-DCMAKE_BUILD_TYPE=Debug" "-GNinja")
1313
BUILD_TARGET="all_tests"
1414
RUN_TARGET="run_tests"
1515
BUILD_ONLY=false
16+
SANITIZER=""
17+
TEST_BINARY=""
1618
CMAKE_ARGS=()
1719

20+
# Ensures LLVM_PROJECT_HOME is set, cloning if necessary.
21+
# MSAN and TSAN require an MSAN-instrumented libc++ (USE_CUSTOM_LIBCXX), which
22+
# is built from LLVM source. If LLVM_PROJECT_HOME is not already set, this
23+
# function performs a minimal sparse clone of the LLVM project (libcxx and
24+
# libcxxabi only) and caches it under /tmp for subsequent runs.
25+
setup_llvm_project() {
26+
if [[ -n "${LLVM_PROJECT_HOME:-}" ]]; then
27+
echo "Using LLVM_PROJECT_HOME=${LLVM_PROJECT_HOME}"
28+
return
29+
fi
30+
31+
local llvm_cache_dir="/tmp/aws-lc-llvm-project"
32+
if [[ -d "${llvm_cache_dir}/libcxx" && -d "${llvm_cache_dir}/libcxxabi" ]]; then
33+
echo "Using cached LLVM project at ${llvm_cache_dir}"
34+
else
35+
echo "LLVM_PROJECT_HOME not set. Cloning llvm-project to ${llvm_cache_dir}..."
36+
rm -rf "${llvm_cache_dir}"
37+
git clone https://github.com/llvm/llvm-project.git \
38+
--branch llvmorg-19.1.7 --depth 1 \
39+
--filter=blob:none --sparse \
40+
"${llvm_cache_dir}"
41+
pushd "${llvm_cache_dir}"
42+
git sparse-checkout set libcxx libcxxabi
43+
popd
44+
fi
45+
export LLVM_PROJECT_HOME="${llvm_cache_dir}"
46+
}
47+
1848
while [[ $# -gt 0 ]]; do
1949
case "$1" in
2050
--build-only)
@@ -29,6 +59,15 @@ while [[ $# -gt 0 ]]; do
2959
RUN_TARGET="$2"
3060
shift 2
3161
;;
62+
--sanitizer)
63+
SANITIZER="$2"
64+
shift 2
65+
;;
66+
--test)
67+
TEST_BINARY="$2"
68+
BUILD_TARGET="$2"
69+
shift 2
70+
;;
3271
*)
3372
# Everything else gets passed to CMake
3473
CMAKE_ARGS+=("$1")
@@ -37,13 +76,60 @@ while [[ $# -gt 0 ]]; do
3776
esac
3877
done
3978

79+
# Handle sanitizer configuration
80+
if [[ -n "${SANITIZER}" ]]; then
81+
# Sanitizer builds require a clean build directory to avoid stale artifacts
82+
# from a different configuration (e.g., switching from ASAN to MSAN).
83+
rm -rf "${AWS_LC_BUILD}"
84+
export AWS_LC_GO_TEST_TIMEOUT="${AWS_LC_GO_TEST_TIMEOUT:-90m}"
85+
86+
# All sanitizers require Clang.
87+
export CC=${CC:-clang}
88+
export CXX=${CXX:-clang++}
89+
90+
case "${SANITIZER}" in
91+
asan)
92+
CMAKE_ARGS+=("-DASAN=1")
93+
;;
94+
msan)
95+
CMAKE_ARGS+=("-DMSAN=1" "-DUSE_CUSTOM_LIBCXX=1")
96+
setup_llvm_project
97+
CMAKE_ARGS+=("-DLLVM_PROJECT_HOME=${LLVM_PROJECT_HOME}")
98+
;;
99+
tsan)
100+
CMAKE_ARGS+=("-DTSAN=1" "-DUSE_CUSTOM_LIBCXX=1")
101+
setup_llvm_project
102+
CMAKE_ARGS+=("-DLLVM_PROJECT_HOME=${LLVM_PROJECT_HOME}")
103+
;;
104+
ubsan)
105+
CMAKE_ARGS+=("-DUBSAN=1")
106+
;;
107+
cfi)
108+
CMAKE_ARGS+=("-DCFI=1")
109+
;;
110+
*)
111+
echo "Error: Unknown sanitizer '${SANITIZER}'. Supported: asan, msan, tsan, ubsan, cfi"
112+
exit 1
113+
;;
114+
esac
115+
fi
116+
40117
mkdir -p "${AWS_LC_BUILD}"
41118

42119
cmake "${BASE_DIR}" -B "${AWS_LC_BUILD}" "${DEFAULT_CMAKE_FLAGS[@]}" "${CMAKE_ARGS[@]}"
43120

44121
cmake --build "${AWS_LC_BUILD}" -j --target ${BUILD_TARGET}
45122

46-
# Only run tests if --build-only was not specified
47-
if [ "$BUILD_ONLY" = false ]; then
123+
if [[ -n "${TEST_BINARY}" ]]; then
124+
# --test was specified: find and run the test binary directly instead of
125+
# going through the heavyweight run_tests CMake target.
126+
TEST_PATH=$(find "${AWS_LC_BUILD}" -name "${TEST_BINARY}" -type f -executable | head -1)
127+
if [[ -z "${TEST_PATH}" ]]; then
128+
echo "Error: Could not find test binary '${TEST_BINARY}' in ${AWS_LC_BUILD}"
129+
exit 1
130+
fi
131+
"${TEST_PATH}"
132+
elif [ "$BUILD_ONLY" = false ]; then
133+
# Default: run via the CMake run_tests target (Go tests + SSL runner + unit tests).
48134
cmake --build "${AWS_LC_BUILD}" --target ${RUN_TARGET}
49135
fi

0 commit comments

Comments
 (0)