diff --git a/contrib/openssl-cmake/CMakeLists.txt b/contrib/openssl-cmake/CMakeLists.txt index 0527936648de..dc834176eae2 100644 --- a/contrib/openssl-cmake/CMakeLists.txt +++ b/contrib/openssl-cmake/CMakeLists.txt @@ -17,6 +17,17 @@ if(FIPS_CLICKHOUSE) set(AWSLC_BUILD_DIR "${CMAKE_BINARY_DIR}/awslc-build") set(AWSLC_BINARIES_DIR "${AWSLC_BUILD_DIR}/output") +# When the caller doesn't supply -DAWSLC_SRC_DIR=…, auto-set it to the +# source tree that the Docker build extracts alongside the compiled libs. +# The actual files arrive at ninja-time (the build-awslc target copies them +# out of the container), but CMake only needs the path to be DEFINED at +# configure-time so the ssl-shim/handshaker/acvp targets are created. +if(NOT DEFINED AWSLC_SRC_DIR) + set(AWSLC_SRC_DIR "${AWSLC_BUILD_DIR}/awslc-src" CACHE PATH + "Path to AWS-LC source tree (auto-populated from Docker build)") + message(STATUS "AWSLC_SRC_DIR not set; will extract source from Docker build to ${AWSLC_SRC_DIR}") +endif() + message("Will build AWS-LC FIPS 2.0.0 in FIPS mode according to security policy #4816.") # build AWS-LC in FIPS mode accoring to the Security Policy: # https://csrc.nist.gov/CSRC/media/projects/cryptographic-module-validation-program/documents/security-policies/140sp4816.pdf @@ -42,6 +53,16 @@ docker cp $id:$lib_dir/build/ssl/libssl.a $OUTPUT_DIR docker cp $id:$lib_dir/build/crypto/libcrypto.a $OUTPUT_DIR docker cp $id:$lib_dir/include $OUTPUT_DIR +# Extract source files needed by ssl-shim / ssl-handshaker / acvp-server. +# These targets compile AWS-LC test harness sources with the system compiler. +readonly src_dir=$OUTPUT_DIR/../awslc-src +mkdir -p $src_dir +docker cp $id:$lib_dir/ssl $src_dir/ssl +docker cp $id:$lib_dir/crypto $src_dir/crypto +docker cp $id:$lib_dir/util $src_dir/util +docker cp $id:$lib_dir/include $src_dir/include +docker cp $id:$lib_dir/third_party $src_dir/third_party 2>/dev/null || true + docker rm $id" ) diff --git a/programs/CMakeLists.txt b/programs/CMakeLists.txt index 59cf435fdf82..aeb8cdd21631 100644 --- a/programs/CMakeLists.txt +++ b/programs/CMakeLists.txt @@ -62,6 +62,14 @@ else() message(STATUS "ClickHouse keeper-client mode: OFF") endif() +if (FIPS_CLICKHOUSE AND DEFINED AWSLC_SRC_DIR) + set(ENABLE_CLICKHOUSE_SSL_SHIM 1) + if (CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(ENABLE_CLICKHOUSE_SSL_HANDSHAKER 1) + set(ENABLE_CLICKHOUSE_ACVP_SERVER 1) + endif() +endif() + configure_file (config_tools.h.in ${CONFIG_INCLUDE_PATH}/config_tools.h) macro(clickhouse_target_link_split_lib target name) @@ -127,6 +135,16 @@ if (ENABLE_CLICKHOUSE_KEEPER) add_subdirectory (keeper) endif() +if (ENABLE_CLICKHOUSE_SSL_SHIM) + add_subdirectory (ssl-shim) +endif() +if (ENABLE_CLICKHOUSE_SSL_HANDSHAKER) + add_subdirectory (ssl-handshaker) +endif() +if (ENABLE_CLICKHOUSE_ACVP_SERVER) + add_subdirectory (acvp-server) +endif() + if (ENABLE_CLICKHOUSE_SELF_EXTRACTING AND NOT ENABLE_DUMMY_LAUNCHERS) add_subdirectory (self-extracting) endif () @@ -225,6 +243,16 @@ if (ENABLE_CLICKHOUSE_KEEPER_CLIENT) list(APPEND CLICKHOUSE_BUNDLE clickhouse-keeper-client) endif () +if (ENABLE_CLICKHOUSE_SSL_SHIM) + clickhouse_program_install(clickhouse-ssl-shim ssl-shim) +endif() +if (ENABLE_CLICKHOUSE_SSL_HANDSHAKER) + clickhouse_program_install(clickhouse-ssl-handshaker ssl-handshaker) +endif() +if (ENABLE_CLICKHOUSE_ACVP_SERVER) + clickhouse_program_install(clickhouse-acvp-server acvp-server) +endif() + add_custom_target (clickhouse-bundle ALL DEPENDS ${CLICKHOUSE_BUNDLE}) if (USE_BINARY_HASH) diff --git a/programs/acvp-server/AcvpServer.cpp b/programs/acvp-server/AcvpServer.cpp new file mode 100644 index 000000000000..95209e62a0fc --- /dev/null +++ b/programs/acvp-server/AcvpServer.cpp @@ -0,0 +1,6 @@ +extern int acvp_modulewrapper_main(int argc, char ** argv); + +int mainEntryClickHouseAcvpServer(int argc, char ** argv) +{ + return acvp_modulewrapper_main(argc, argv); +} diff --git a/programs/acvp-server/CMakeLists.txt b/programs/acvp-server/CMakeLists.txt new file mode 100644 index 000000000000..54d53c5af306 --- /dev/null +++ b/programs/acvp-server/CMakeLists.txt @@ -0,0 +1,96 @@ +if(NOT FIPS_CLICKHOUSE OR NOT DEFINED AWSLC_SRC_DIR) + return() +endif() + +if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux") + return() +endif() + +set(AWSLC_ACVP_MODULEWRAPPER_DIR "${AWSLC_SRC_DIR}/util/fipstools/acvp/modulewrapper") +set(AWSLC_LIBCRYPTO "${CMAKE_BINARY_DIR}/awslc-build/output/libcrypto.a") + +set(ACVP_OBJ_DIR "${CMAKE_CURRENT_BINARY_DIR}/awslc_acvp_server_objs") + +set(AWSLC_ACVP_SERVER_CXX_SRCS + ${AWSLC_ACVP_MODULEWRAPPER_DIR}/main.cc + ${AWSLC_ACVP_MODULEWRAPPER_DIR}/modulewrapper.cc +) + +set(AWSLC_ACVP_SERVER_INCLUDE_FLAGS + -I${AWSLC_ACVP_MODULEWRAPPER_DIR} + -I${AWSLC_SRC_DIR}/include + -I${AWSLC_SRC_DIR} +) + +set(AWSLC_ACVP_SERVER_OBJS) +foreach(src ${AWSLC_ACVP_SERVER_CXX_SRCS}) + get_filename_component(base "${src}" NAME_WE) + get_filename_component(dir "${src}" DIRECTORY) + string(MD5 dir_hash "${dir}") + string(SUBSTRING "${dir_hash}" 0 8 dir_tag) + set(obj "${ACVP_OBJ_DIR}/${base}_${dir_tag}.o") + + set(extra_defs "") + if("${base}" STREQUAL "main") + set(extra_defs -Dmain=acvp_modulewrapper_main) + endif() + + add_custom_command( + OUTPUT "${obj}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${ACVP_OBJ_DIR}" + COMMAND /usr/bin/c++ + -std=c++17 -fPIC -g -O2 + ${AWSLC_ACVP_SERVER_INCLUDE_FLAGS} + ${extra_defs} + -w + -c "${src}" + -o "${obj}" + DEPENDS "${src}" build-awslc + COMMENT "Building aws-lc acvp-server object ${base}.cc (system C++ ABI)" + ) + list(APPEND AWSLC_ACVP_SERVER_OBJS "${obj}") +endforeach() + +set(GLIBC_COMPAT_ACVP_OBJ "${ACVP_OBJ_DIR}/glibc_compat.o") +add_custom_command( + OUTPUT "${GLIBC_COMPAT_ACVP_OBJ}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${ACVP_OBJ_DIR}" + COMMAND /usr/bin/cc -fPIC -O2 -w + -c "${CMAKE_CURRENT_SOURCE_DIR}/../ssl-common/glibc_compat.c" + -o "${GLIBC_COMPAT_ACVP_OBJ}" + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/../ssl-common/glibc_compat.c" + COMMENT "Building glibc C23 compat shim (acvp-server)" +) +list(APPEND AWSLC_ACVP_SERVER_OBJS "${GLIBC_COMPAT_ACVP_OBJ}") + +set(AWSLC_ACVP_SERVER_LIB "${ACVP_OBJ_DIR}/libawslc_acvp_server.a") +add_custom_command( + OUTPUT "${AWSLC_ACVP_SERVER_LIB}" + COMMAND ${CMAKE_AR} rcs "${AWSLC_ACVP_SERVER_LIB}" ${AWSLC_ACVP_SERVER_OBJS} + DEPENDS ${AWSLC_ACVP_SERVER_OBJS} + COMMENT "Archiving libawslc_acvp_server.a" +) +add_custom_target(awslc_acvp_server_lib DEPENDS "${AWSLC_ACVP_SERVER_LIB}") + +set(CLICKHOUSE_ACVP_SERVER_SOURCES AcvpServer.cpp) +find_file(LIBSTDCXX_STATIC_ACVP libstdc++.a + PATHS /usr/lib/gcc/x86_64-linux-gnu + PATH_SUFFIXES 13 12 11 + NO_DEFAULT_PATH +) +if(NOT LIBSTDCXX_STATIC_ACVP) + message(FATAL_ERROR "Could not find static libstdc++.a needed for acvp-server") +endif() + +set(CLICKHOUSE_ACVP_SERVER_LINK + PRIVATE + "${AWSLC_ACVP_SERVER_LIB}" + "${AWSLC_LIBCRYPTO}" + "${LIBSTDCXX_STATIC_ACVP}" +) +set(CLICKHOUSE_ACVP_SERVER_INCLUDE PRIVATE ${AWSLC_SRC_DIR}/include) + +clickhouse_program_add(acvp-server) +add_dependencies(clickhouse-acvp-server-lib awslc_acvp_server_lib build-awslc) +target_compile_options(clickhouse-acvp-server-lib PRIVATE -Wno-everything) +target_link_options(clickhouse-acvp-server-lib INTERFACE "LINKER:--allow-multiple-definition") diff --git a/programs/config_tools.h.in b/programs/config_tools.h.in index 11689cf346f9..c9e87a938f3c 100644 --- a/programs/config_tools.h.in +++ b/programs/config_tools.h.in @@ -5,3 +5,6 @@ #cmakedefine01 ENABLE_CLICKHOUSE_KEEPER #cmakedefine01 ENABLE_CLICKHOUSE_KEEPER_CLIENT #cmakedefine01 ENABLE_CLICKHOUSE_KEEPER_CONVERTER +#cmakedefine01 ENABLE_CLICKHOUSE_SSL_SHIM +#cmakedefine01 ENABLE_CLICKHOUSE_SSL_HANDSHAKER +#cmakedefine01 ENABLE_CLICKHOUSE_ACVP_SERVER diff --git a/programs/main.cpp b/programs/main.cpp index 4240778f768f..8db9ca3c50cf 100644 --- a/programs/main.cpp +++ b/programs/main.cpp @@ -64,6 +64,16 @@ int mainEntryClickHouseKeeperBench(int argc, char ** argv); int mainEntryClickHouseKeeperDataDumper(int argc, char ** argv); #endif +#if ENABLE_CLICKHOUSE_SSL_SHIM +int mainEntryClickHouseSslShim(int argc, char ** argv); +#endif +#if ENABLE_CLICKHOUSE_SSL_HANDSHAKER +int mainEntryClickHouseSslHandshaker(int argc, char ** argv); +#endif +#if ENABLE_CLICKHOUSE_ACVP_SERVER +int mainEntryClickHouseAcvpServer(int argc, char ** argv); +#endif + // install int mainEntryClickHouseInstall(int argc, char ** argv); int mainEntryClickHouseStart(int argc, char ** argv); @@ -115,6 +125,15 @@ std::pair clickhouse_applications[] = #endif #if USE_NURAFT {"keeper-data-dumper", mainEntryClickHouseKeeperDataDumper}, +#endif +#if ENABLE_CLICKHOUSE_SSL_SHIM + {"ssl-shim", mainEntryClickHouseSslShim}, +#endif +#if ENABLE_CLICKHOUSE_SSL_HANDSHAKER + {"ssl-handshaker", mainEntryClickHouseSslHandshaker}, +#endif +#if ENABLE_CLICKHOUSE_ACVP_SERVER + {"acvp-server", mainEntryClickHouseAcvpServer}, #endif // install {"install", mainEntryClickHouseInstall}, diff --git a/programs/ssl-common/README.md b/programs/ssl-common/README.md new file mode 100644 index 000000000000..804e51ecac6e --- /dev/null +++ b/programs/ssl-common/README.md @@ -0,0 +1,157 @@ +# Running SSL and ACVP Tests Manually for ClickHouse FIPS Build + +This guide explains how to run the AWS-LC SSL conformance tests and ACVP +cryptographic algorithm validation tests against the ClickHouse FIPS binary. + +## Prerequisites + +- ClickHouse built with `-DFIPS_CLICKHOUSE=1` and `-DAWSLC_SRC_DIR=` + (or the auto-populated source from the Docker build) +- The resulting binary must contain the `ssl-shim`, `ssl-handshaker`, and + `acvp-server` modes (verify with `./clickhouse ssl-shim --help`) +- **Go >= 1.13** (the AWS-LC test harness is written in Go) +- A checkout of the AWS-LC source matching the FIPS build version + (`AWS-LC-FIPS-2.0.0`), which is the same tree referenced by `AWSLC_SRC_DIR` + +Throughout this document, the following paths are used as examples: + +| Variable | Example Path | +|---|---| +| `CLICKHOUSE_BUILD` | `/path/to/ClickHouse/build` | +| `AWSLC_SRC` | `/path/to/aws-lc` (the AWS-LC source root) | + +## SSL Tests (8,037 tests) + +The SSL test suite exercises the full TLS stack: handshake flows, cipher +negotiation, session resumption, certificate handling, QUIC transport, and +more. It uses a Go test runner that drives the ClickHouse `ssl-shim` and +`ssl-handshaker` binaries. + +### Create symlinks + +The Go test runner invokes `bssl_shim` and `handshaker` by path. Create +symlinks from the ClickHouse binary: + +```bash +ln -sf "$CLICKHOUSE_BUILD/programs/clickhouse" "$CLICKHOUSE_BUILD/programs/clickhouse-ssl-shim" +ln -sf "$CLICKHOUSE_BUILD/programs/clickhouse" "$CLICKHOUSE_BUILD/programs/clickhouse-ssl-handshaker" +``` + +### Run the tests + +```bash +cd "$AWSLC_SRC/ssl/test/runner" + +go test -v . \ + -shim-path "$CLICKHOUSE_BUILD/programs/clickhouse-ssl-shim" \ + -handshaker-path "$CLICKHOUSE_BUILD/programs/clickhouse-ssl-handshaker" \ + -num-workers 16 +``` + +`-num-workers` defaults to the number of CPU cores. Adjust as needed. + +### Expected results + +All 8,037 tests should pass. A passing run ends with output similar to: + +``` +PASS +ok boringssl.googlesource.com/boringssl/ssl/test/runner 142.538s +``` + +If any tests fail, the runner prints the failing test name and a diff of +the expected vs. actual TLS behavior. + +## ACVP Tests (31 algorithm suites) + +The ACVP (Automated Cryptographic Validation Protocol) tests validate that +the FIPS cryptographic module produces correct outputs for known-answer test +vectors. AWS-LC bundles 31 algorithm suites with pre-computed expected +results. + +### Build the test tools + +```bash +cd "$AWSLC_SRC/util/fipstools/acvp/acvptool" + +# Build acvptool (the ACVP JSON ↔ modulewrapper translator) +go build -o /tmp/acvptool . + +# Build testmodulewrapper (needed by 2 of the 31 test suites) +cd "$AWSLC_SRC/util/fipstools/acvp/acvptool/testmodulewrapper" +go build -o /tmp/testmodulewrapper . +``` + +### Create the acvp-server symlink + +```bash +ln -sf "$CLICKHOUSE_BUILD/programs/clickhouse" "$CLICKHOUSE_BUILD/programs/clickhouse-acvp-server" +``` + +### Run the tests + +```bash +cd "$AWSLC_SRC/util/fipstools/acvp/acvptool/test" + +go run check_expected.go \ + -tool /tmp/acvptool \ + -module-wrappers "modulewrapper:$CLICKHOUSE_BUILD/programs/clickhouse-acvp-server,testmodulewrapper:/tmp/testmodulewrapper" \ + -tests tests.json +``` + +### Expected results + +All 31 test suites should pass (some have no expected output and only verify +that the module does not crash): + +``` +All tests passed +``` + +The 31 validated algorithm suites are: + +| Category | Algorithms | +|---|---| +| AES | CBC, CCM, CTR, ECB, GCM, GMAC, KW, KWP, XTS, CBC-CS3 (via testmodulewrapper) | +| DRBG | ctrDRBG, hmacDRBG (via testmodulewrapper) | +| ECDSA | KeyGen, SigGen, SigVer | +| HMAC | SHA-1, SHA2-224, SHA2-256, SHA2-384, SHA2-512, SHA2-512/256 | +| KAS | ECC-SSC, FFC-SSC | +| KDF | SP800-108, kdf-components (TLS, SSH), HKDF | +| RSA | KeyGen, SigGen, SigVer | +| TLS | TLS-1.2-KDF | +| Other | PBKDF, AES-GCM-internal-IV | + +## Troubleshooting + +### `clickhouse ssl-shim` says "unknown mode" + +The binary was built without the FIPS test targets. Re-run CMake with: + +```bash +cmake .. -DFIPS_CLICKHOUSE=1 -DAWSLC_SRC_DIR=/path/to/aws-lc +``` + +and rebuild. If `AWSLC_SRC_DIR` was auto-populated from the Docker build, +ensure the Docker build completed successfully (check that +`build/awslc-build/awslc-src/ssl/test/bssl_shim.cc` exists). + +### Go test runner shows fewer than 8,037 tests + +Make sure you are running from the `ssl/test/runner/` directory inside the +AWS-LC source tree, not from a different BoringSSL checkout. + +### ACVP `check_expected.go` fails with "wrapper returned error" + +Verify that `clickhouse-acvp-server` symlink exists and points to the +ClickHouse binary. Also verify it is executable: + +```bash +"$CLICKHOUSE_BUILD/programs/clickhouse-acvp-server" --help +``` + +### Permission denied on Docker socket during build + +The AWS-LC FIPS libraries are built inside a Docker container per the FIPS +140-3 security policy. Ensure the build user has access to the Docker +daemon (is in the `docker` group or has rootless Docker configured). diff --git a/programs/ssl-common/glibc_compat.c b/programs/ssl-common/glibc_compat.c new file mode 100644 index 000000000000..25348621262f --- /dev/null +++ b/programs/ssl-common/glibc_compat.c @@ -0,0 +1,15 @@ +#include + +/* glibc 2.32+ symbol used by libstdc++; sysroot may have older libc. */ +__attribute__((weak)) +char __libc_single_threaded = 0; + +__attribute__((weak)) +unsigned long long __isoc23_strtoull(const char *nptr, char **endptr, int base) { + return strtoull(nptr, endptr, base); +} + +__attribute__((weak)) +long long __isoc23_strtoll(const char *nptr, char **endptr, int base) { + return strtoll(nptr, endptr, base); +} diff --git a/programs/ssl-common/posix_spawn_2.c b/programs/ssl-common/posix_spawn_2.c new file mode 100644 index 000000000000..14e856c67dde --- /dev/null +++ b/programs/ssl-common/posix_spawn_2.c @@ -0,0 +1,265 @@ +/// Based on musl's posix_spawn.c: https://git.musl-libc.org/cgit/musl/tree/src/process/posix_spawn.c +/// +/// Compiled with the system compiler and linked only into the ssl-shim +/// and ssl-handshaker archives. The AWS-LC sources are compiled with +/// -Dposix_spawn=__ssl_posix_spawn so only their posix_spawn calls are +/// redirected here; the rest of ClickHouse continues to use the original +/// limited stub in glibc-compatibility, completely unaffected. +/// +/// The child function uses raw inline-asm syscalls (not glibc wrappers) +/// because CLONE_VM shares the parent's TLS, and glibc's syscall() +/// would clobber the parent's errno. + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ── raw x86-64 syscall wrappers (no errno, no TLS) ────────────── */ + +static inline long raw_sc0(long n) +{ + long ret; + __asm__ __volatile__ ("syscall" : "=a"(ret) + : "a"(n) : "rcx", "r11", "memory"); + return ret; +} + +static inline long raw_sc1(long n, long a1) +{ + long ret; + __asm__ __volatile__ ("syscall" : "=a"(ret) + : "a"(n), "D"(a1) : "rcx", "r11", "memory"); + return ret; +} + +static inline long raw_sc2(long n, long a1, long a2) +{ + long ret; + __asm__ __volatile__ ("syscall" : "=a"(ret) + : "a"(n), "D"(a1), "S"(a2) : "rcx", "r11", "memory"); + return ret; +} + +static inline long raw_sc3(long n, long a1, long a2, long a3) +{ + long ret; + __asm__ __volatile__ ("syscall" : "=a"(ret) + : "a"(n), "D"(a1), "S"(a2), "d"(a3) : "rcx", "r11", "memory"); + return ret; +} + +static inline long raw_sc4(long n, long a1, long a2, long a3, long a4) +{ + register long r10 __asm__("r10") = a4; + long ret; + __asm__ __volatile__ ("syscall" : "=a"(ret) + : "a"(n), "D"(a1), "S"(a2), "d"(a3), "r"(r10) + : "rcx", "r11", "memory"); + return ret; +} + +/* ── file-action constants & struct (must match glibc-compatibility.c) ── */ + +#define FDOP_CLOSE 1 +#define FDOP_DUP2 2 +#define FDOP_OPEN 3 +#define FDOP_CHDIR 4 +#define FDOP_FCHDIR 5 + +struct fdop { + struct fdop *next, *prev; + int cmd, fd, srcfd, oflag; + mode_t mode; + char path[]; +}; + +struct spawn_args { + int p[2]; + sigset_t oldmask; + const char *path; + int (*exec)(const char *, char *const *, char *const *); + const posix_spawn_file_actions_t *fa; + const posix_spawnattr_t *restrict attr; + char *const *argv, *const *envp; +}; + +static int spawn_child(void *args_vp) +{ + int i; + long ret; + struct spawn_args *args = args_vp; + int p = args->p[1]; + const posix_spawn_file_actions_t *fa = args->fa; + const posix_spawnattr_t *restrict attr = args->attr; + + raw_sc1(SYS_close, args->p[0]); + + if (attr->__flags & POSIX_SPAWN_SETSIGDEF) { + struct sigaction sa = {0}; + sa.sa_handler = SIG_DFL; + for (i = 1; i < _NSIG; i++) + if (sigismember(&attr->__sd, i)) + raw_sc4(SYS_rt_sigaction, i, + (long)&sa, 0, _NSIG/8); + } + + if (attr->__flags & POSIX_SPAWN_SETSID) + if ((ret = raw_sc0(SYS_setsid)) < 0) + goto fail; + + if (attr->__flags & POSIX_SPAWN_SETPGROUP) + if ((ret = raw_sc2(SYS_setpgid, 0, attr->__pgrp))) + goto fail; + + if (attr->__flags & POSIX_SPAWN_RESETIDS) + if ((ret = raw_sc1(SYS_setgid, raw_sc0(SYS_getgid))) || + (ret = raw_sc1(SYS_setuid, raw_sc0(SYS_getuid)))) + goto fail; + + if (fa && fa->__actions) { + struct fdop *op; + int fd; + for (op = fa->__actions; op->next; op = op->next); + for (; op; op = op->prev) { + if (op->fd == p) { + ret = raw_sc1(SYS_dup, p); + if (ret < 0) goto fail; + raw_sc1(SYS_close, p); + p = ret; + } + switch (op->cmd) { + case FDOP_CLOSE: + raw_sc1(SYS_close, op->fd); + break; + case FDOP_DUP2: + fd = op->srcfd; + if (fd == p) { + ret = -EBADF; + goto fail; + } + if (fd != op->fd) { + if ((ret = raw_sc2(SYS_dup2, + fd, op->fd)) < 0) + goto fail; + } else { + ret = raw_sc3(SYS_fcntl, fd, + F_GETFD, 0); + if (ret < 0) goto fail; + ret = raw_sc3(SYS_fcntl, fd, + F_SETFD, ret & ~FD_CLOEXEC); + if (ret < 0) goto fail; + } + break; + case FDOP_OPEN: + fd = raw_sc4(SYS_openat, AT_FDCWD, + (long)op->path, op->oflag, op->mode); + if ((ret = fd) < 0) goto fail; + if (fd != op->fd) { + if ((ret = raw_sc2(SYS_dup2, + fd, op->fd)) < 0) + goto fail; + raw_sc1(SYS_close, fd); + } + break; + case FDOP_CHDIR: + ret = raw_sc1(SYS_chdir, (long)op->path); + if (ret < 0) goto fail; + break; + case FDOP_FCHDIR: + ret = raw_sc1(SYS_fchdir, op->fd); + if (ret < 0) goto fail; + break; + } + } + } + + raw_sc3(SYS_fcntl, p, F_SETFD, FD_CLOEXEC); + + raw_sc4(SYS_rt_sigprocmask, SIG_SETMASK, + (long)((attr->__flags & POSIX_SPAWN_SETSIGMASK) + ? &attr->__ss : &args->oldmask), + 0, _NSIG/8); + + args->exec(args->path, args->argv, args->envp); + ret = -errno; + +fail: + ret = -ret; + if (ret) { + long r; + do r = raw_sc3(SYS_write, p, (long)&ret, sizeof ret); + while (r < 0 && r != -EPIPE); + } + _exit(127); +} + + +static int spawnx_impl(pid_t *restrict res, const char *restrict path, + int (*exec)(const char *, char *const *, char *const *), + const posix_spawn_file_actions_t *fa, + const posix_spawnattr_t *restrict attr, + char *const argv[restrict], char *const envp[restrict]) +{ + pid_t pid; + char stack[1024+PATH_MAX]; + int ec = 0, cs; + struct spawn_args args; + + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs); + + args.path = path; + args.exec = exec; + args.fa = fa; + args.attr = attr ? attr : &(const posix_spawnattr_t){0}; + args.argv = argv; + args.envp = envp; + + sigset_t allsigs; + sigfillset(&allsigs); + pthread_sigmask(SIG_BLOCK, &allsigs, &args.oldmask); + + if (pipe2(args.p, O_CLOEXEC)) { + ec = errno; + goto out; + } + + pid = clone(spawn_child, stack + sizeof stack, + CLONE_VM | CLONE_VFORK | SIGCHLD, &args); + close(args.p[1]); + + if (pid > 0) { + if (read(args.p[0], &ec, sizeof ec) != sizeof ec) ec = 0; + else waitpid(pid, &(int){0}, 0); + } else { + ec = -pid; + } + + close(args.p[0]); + + if (!ec && res) *res = pid; + +out: + pthread_sigmask(SIG_SETMASK, &args.oldmask, 0); + pthread_setcancelstate(cs, 0); + + return ec; +} + +/* Called by AWS-LC objects compiled with -Dposix_spawn=__ssl_posix_spawn. + * Only those translation units are redirected; all other CH code uses + * the original posix_spawn from glibc-compatibility. */ +int __ssl_posix_spawn(pid_t *restrict res, const char *restrict path, + const posix_spawn_file_actions_t *fa, + const posix_spawnattr_t *restrict attr, + char *const argv[restrict], char *const envp[restrict]) +{ + return spawnx_impl(res, path, execve, fa, attr, argv, envp); +} diff --git a/programs/ssl-handshaker/CMakeLists.txt b/programs/ssl-handshaker/CMakeLists.txt new file mode 100644 index 000000000000..040a9ed10ad0 --- /dev/null +++ b/programs/ssl-handshaker/CMakeLists.txt @@ -0,0 +1,117 @@ +if(NOT FIPS_CLICKHOUSE OR NOT DEFINED AWSLC_SRC_DIR) + return() +endif() + +if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux") + return() +endif() + +set(AWSLC_SSL_TEST_DIR "${AWSLC_SRC_DIR}/ssl/test") +set(AWSLC_LIBSSL "${CMAKE_BINARY_DIR}/awslc-build/output/libssl.a") +set(AWSLC_LIBCRYPTO "${CMAKE_BINARY_DIR}/awslc-build/output/libcrypto.a") + +set(HSHAKER_OBJ_DIR "${CMAKE_CURRENT_BINARY_DIR}/awslc_handshaker_objs") + +set(AWSLC_HSHAKER_CXX_SRCS + ${AWSLC_SSL_TEST_DIR}/async_bio.cc + ${AWSLC_SSL_TEST_DIR}/handshake_util.cc + ${AWSLC_SSL_TEST_DIR}/handshaker.cc + ${AWSLC_SSL_TEST_DIR}/mock_quic_transport.cc + ${AWSLC_SSL_TEST_DIR}/packeted_bio.cc + ${AWSLC_SSL_TEST_DIR}/settings_writer.cc + ${AWSLC_SSL_TEST_DIR}/test_config.cc + ${AWSLC_SSL_TEST_DIR}/test_state.cc + ${AWSLC_SRC_DIR}/crypto/test/test_util.cc +) + +set(AWSLC_HSHAKER_INCLUDE_FLAGS + -I${AWSLC_SRC_DIR}/include + -I${AWSLC_SRC_DIR} + -I${AWSLC_SSL_TEST_DIR} +) + +set(AWSLC_HSHAKER_OBJS) +foreach(src ${AWSLC_HSHAKER_CXX_SRCS}) + get_filename_component(base "${src}" NAME_WE) + get_filename_component(dir "${src}" DIRECTORY) + string(MD5 dir_hash "${dir}") + string(SUBSTRING "${dir_hash}" 0 8 dir_tag) + set(obj "${HSHAKER_OBJ_DIR}/${base}_${dir_tag}.o") + + set(extra_defs -Dposix_spawn=__ssl_posix_spawn) + if("${base}" STREQUAL "handshaker") + list(APPEND extra_defs -Dmain=handshaker_main) + endif() + + add_custom_command( + OUTPUT "${obj}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${HSHAKER_OBJ_DIR}" + COMMAND /usr/bin/c++ + -std=c++17 -fPIC -g -O2 + ${AWSLC_HSHAKER_INCLUDE_FLAGS} + ${extra_defs} + -w + -c "${src}" + -o "${obj}" + DEPENDS "${src}" build-awslc + COMMENT "Building aws-lc handshaker object ${base}.cc (system C++ ABI)" + ) + list(APPEND AWSLC_HSHAKER_OBJS "${obj}") +endforeach() + +set(GLIBC_COMPAT_HS_OBJ "${HSHAKER_OBJ_DIR}/glibc_compat.o") +add_custom_command( + OUTPUT "${GLIBC_COMPAT_HS_OBJ}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${HSHAKER_OBJ_DIR}" + COMMAND /usr/bin/cc -fPIC -O2 -w + -c "${CMAKE_CURRENT_SOURCE_DIR}/../ssl-common/glibc_compat.c" + -o "${GLIBC_COMPAT_HS_OBJ}" + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/../ssl-common/glibc_compat.c" + COMMENT "Building glibc C23 compat shim (handshaker)" +) +list(APPEND AWSLC_HSHAKER_OBJS "${GLIBC_COMPAT_HS_OBJ}") + +set(POSIX_SPAWN_2_HS_OBJ "${HSHAKER_OBJ_DIR}/posix_spawn_2.o") +add_custom_command( + OUTPUT "${POSIX_SPAWN_2_HS_OBJ}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${HSHAKER_OBJ_DIR}" + COMMAND /usr/bin/cc -fPIC -O2 -w + -c "${CMAKE_CURRENT_SOURCE_DIR}/../ssl-common/posix_spawn_2.c" + -o "${POSIX_SPAWN_2_HS_OBJ}" + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/../ssl-common/posix_spawn_2.c" + COMMENT "Building full posix_spawn for ssl-handshaker" +) +list(APPEND AWSLC_HSHAKER_OBJS "${POSIX_SPAWN_2_HS_OBJ}") + +set(AWSLC_HSHAKER_LIB "${HSHAKER_OBJ_DIR}/libawslc_handshaker.a") +add_custom_command( + OUTPUT "${AWSLC_HSHAKER_LIB}" + COMMAND ${CMAKE_AR} rcs "${AWSLC_HSHAKER_LIB}" ${AWSLC_HSHAKER_OBJS} + DEPENDS ${AWSLC_HSHAKER_OBJS} + COMMENT "Archiving libawslc_handshaker.a" +) +add_custom_target(awslc_handshaker_lib DEPENDS "${AWSLC_HSHAKER_LIB}") + +set(CLICKHOUSE_SSL_HANDSHAKER_SOURCES SslHandshaker.cpp) +find_file(LIBSTDCXX_STATIC_HS libstdc++.a + PATHS /usr/lib/gcc/x86_64-linux-gnu + PATH_SUFFIXES 13 12 11 + NO_DEFAULT_PATH +) +if(NOT LIBSTDCXX_STATIC_HS) + message(FATAL_ERROR "Could not find static libstdc++.a needed for ssl-handshaker") +endif() + +set(CLICKHOUSE_SSL_HANDSHAKER_LINK + PRIVATE + "${AWSLC_HSHAKER_LIB}" + "${AWSLC_LIBSSL}" + "${AWSLC_LIBCRYPTO}" + "${LIBSTDCXX_STATIC_HS}" +) +set(CLICKHOUSE_SSL_HANDSHAKER_INCLUDE PRIVATE ${AWSLC_SRC_DIR}/include) + +clickhouse_program_add(ssl-handshaker) +add_dependencies(clickhouse-ssl-handshaker-lib awslc_handshaker_lib build-awslc) +target_compile_options(clickhouse-ssl-handshaker-lib PRIVATE -Wno-everything) +target_link_options(clickhouse-ssl-handshaker-lib INTERFACE "LINKER:--allow-multiple-definition") diff --git a/programs/ssl-handshaker/SslHandshaker.cpp b/programs/ssl-handshaker/SslHandshaker.cpp new file mode 100644 index 000000000000..a86e927a7d47 --- /dev/null +++ b/programs/ssl-handshaker/SslHandshaker.cpp @@ -0,0 +1,6 @@ +extern int handshaker_main(int argc, char ** argv); + +int mainEntryClickHouseSslHandshaker(int argc, char ** argv) +{ + return handshaker_main(argc, argv); +} diff --git a/programs/ssl-shim/CMakeLists.txt b/programs/ssl-shim/CMakeLists.txt new file mode 100644 index 000000000000..b652770d1ccb --- /dev/null +++ b/programs/ssl-shim/CMakeLists.txt @@ -0,0 +1,135 @@ +if(NOT FIPS_CLICKHOUSE OR NOT DEFINED AWSLC_SRC_DIR) + return() +endif() + +set(AWSLC_SSL_TEST_DIR "${AWSLC_SRC_DIR}/ssl/test") +set(AWSLC_LIBSSL "${CMAKE_BINARY_DIR}/awslc-build/output/libssl.a") +set(AWSLC_LIBCRYPTO "${CMAKE_BINARY_DIR}/awslc-build/output/libcrypto.a") + +set(SHIM_OBJ_DIR "${CMAKE_CURRENT_BINARY_DIR}/awslc_shim_objs") + +set(AWSLC_SHIM_CXX_SRCS + ${AWSLC_SSL_TEST_DIR}/async_bio.cc + ${AWSLC_SSL_TEST_DIR}/bssl_shim.cc + ${AWSLC_SSL_TEST_DIR}/handshake_util.cc + ${AWSLC_SSL_TEST_DIR}/mock_quic_transport.cc + ${AWSLC_SSL_TEST_DIR}/packeted_bio.cc + ${AWSLC_SSL_TEST_DIR}/settings_writer.cc + ${AWSLC_SSL_TEST_DIR}/ssl_transfer.cc + ${AWSLC_SSL_TEST_DIR}/test_config.cc + ${AWSLC_SSL_TEST_DIR}/test_state.cc + ${AWSLC_SRC_DIR}/crypto/test/test_util.cc +) + +set(AWSLC_SHIM_INCLUDE_FLAGS + -I${AWSLC_SRC_DIR}/include + -I${AWSLC_SRC_DIR} + -I${AWSLC_SSL_TEST_DIR} +) + +# Build each aws-lc shim source file with the system compiler (matching +# the libstdc++ ABI of the Docker-built libssl.a / libcrypto.a). +# This avoids the libc++ ↔ libstdc++ C++ ABI mismatch for internal +# types like bssl::UniquePtr. +set(AWSLC_SHIM_OBJS) +foreach(src ${AWSLC_SHIM_CXX_SRCS}) + get_filename_component(base "${src}" NAME_WE) + get_filename_component(dir "${src}" DIRECTORY) + # Disambiguate files with same basename from different directories + string(MD5 dir_hash "${dir}") + string(SUBSTRING "${dir_hash}" 0 8 dir_tag) + set(obj "${SHIM_OBJ_DIR}/${base}_${dir_tag}.o") + + set(extra_defs -Dposix_spawn=__ssl_posix_spawn) + if("${base}" STREQUAL "bssl_shim") + list(APPEND extra_defs -Dmain=bssl_shim_main) + endif() + + add_custom_command( + OUTPUT "${obj}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${SHIM_OBJ_DIR}" + COMMAND /usr/bin/c++ + -std=c++17 -fPIC -g -O2 + ${AWSLC_SHIM_INCLUDE_FLAGS} + ${extra_defs} + -w + -c "${src}" + -o "${obj}" + DEPENDS "${src}" build-awslc + COMMENT "Building aws-lc shim object ${base}.cc (system C++ ABI)" + ) + list(APPEND AWSLC_SHIM_OBJS "${obj}") +endforeach() + +# Compile the glibc C23 compatibility shim (provides __isoc23_strtoll etc. +# which newer GCC/glibc emit but the ClickHouse sysroot's libc lacks). +set(GLIBC_COMPAT_OBJ "${SHIM_OBJ_DIR}/glibc_compat.o") +add_custom_command( + OUTPUT "${GLIBC_COMPAT_OBJ}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${SHIM_OBJ_DIR}" + COMMAND /usr/bin/cc -fPIC -O2 -w + -c "${CMAKE_CURRENT_SOURCE_DIR}/../ssl-common/glibc_compat.c" + -o "${GLIBC_COMPAT_OBJ}" + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/../ssl-common/glibc_compat.c" + COMMENT "Building glibc C23 compat shim" +) +list(APPEND AWSLC_SHIM_OBJS "${GLIBC_COMPAT_OBJ}") + +# Full posix_spawn with file-actions support (the base CH stub omits them). +# Bundled in the same archive so the linker pulls it in when +# handshake_util.cc's posix_spawn reference is resolved. +set(POSIX_SPAWN_2_OBJ "${SHIM_OBJ_DIR}/posix_spawn_2.o") +add_custom_command( + OUTPUT "${POSIX_SPAWN_2_OBJ}" + COMMAND ${CMAKE_COMMAND} -E make_directory "${SHIM_OBJ_DIR}" + COMMAND /usr/bin/cc -fPIC -O2 -w + -c "${CMAKE_CURRENT_SOURCE_DIR}/../ssl-common/posix_spawn_2.c" + -o "${POSIX_SPAWN_2_OBJ}" + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/../ssl-common/posix_spawn_2.c" + COMMENT "Building full posix_spawn for ssl-shim" +) +list(APPEND AWSLC_SHIM_OBJS "${POSIX_SPAWN_2_OBJ}") + +# Bundle all system-compiled objects into a static library +set(AWSLC_SHIM_LIB "${SHIM_OBJ_DIR}/libawslc_shim.a") +add_custom_command( + OUTPUT "${AWSLC_SHIM_LIB}" + COMMAND ${CMAKE_AR} rcs "${AWSLC_SHIM_LIB}" ${AWSLC_SHIM_OBJS} + DEPENDS ${AWSLC_SHIM_OBJS} + COMMENT "Archiving libawslc_shim.a" +) +add_custom_target(awslc_shim_lib DEPENDS "${AWSLC_SHIM_LIB}") + +# The CH-side entry point: only this file uses the ClickHouse toolchain. +# It contains a plain C-linkage function so there is no C++ ABI concern. +set(CLICKHOUSE_SSL_SHIM_SOURCES SslShim.cpp) +# The system-compiled shim objects use libstdc++ (matching the Docker-built +# libssl.a ABI). Link the full static libstdc++ to satisfy those symbols. +# The --allow-multiple-definition flag handles the few symbols that overlap +# between libstdc++ and ClickHouse's libc++/libcxxabi (e.g. std::exception). +find_file(LIBSTDCXX_STATIC libstdc++.a + PATHS /usr/lib/gcc/x86_64-linux-gnu + PATH_SUFFIXES 13 12 11 + NO_DEFAULT_PATH +) +if(NOT LIBSTDCXX_STATIC) + message(FATAL_ERROR "Could not find static libstdc++.a needed for ssl-shim") +endif() + +set(CLICKHOUSE_SSL_SHIM_LINK + PRIVATE + "${AWSLC_SHIM_LIB}" + "${AWSLC_LIBSSL}" + "${AWSLC_LIBCRYPTO}" + "${LIBSTDCXX_STATIC}" +) +set(CLICKHOUSE_SSL_SHIM_INCLUDE PRIVATE ${AWSLC_SRC_DIR}/include) + +clickhouse_program_add(ssl-shim) +add_dependencies(clickhouse-ssl-shim-lib awslc_shim_lib build-awslc) +target_compile_options(clickhouse-ssl-shim-lib PRIVATE -Wno-everything) +# The system-compiled shim objects use libstdc++ (matching the Docker-built +# libssl.a ABI), which creates a few duplicate symbols with ClickHouse's +# libc++/libcxxabi (e.g. std::exception). Propagate via INTERFACE so the +# flag only enters the link when this target is actually consumed. +target_link_options(clickhouse-ssl-shim-lib INTERFACE "LINKER:--allow-multiple-definition") diff --git a/programs/ssl-shim/SslShim.cpp b/programs/ssl-shim/SslShim.cpp new file mode 100644 index 000000000000..a96e97c3a8ad --- /dev/null +++ b/programs/ssl-shim/SslShim.cpp @@ -0,0 +1,6 @@ +extern int bssl_shim_main(int argc, char ** argv); + +int mainEntryClickHouseSslShim(int argc, char ** argv) +{ + return bssl_shim_main(argc, argv); +}