From a733a4ca2fe07731cbc22ca9f5f8395b4c84dfea Mon Sep 17 00:00:00 2001 From: Lindsay Stewart Date: Thu, 12 Sep 2024 20:11:44 -0700 Subject: [PATCH] test: seccomp + handshake --- .github/workflows/ci_ubuntu.yml | 100 ++++++++++++++++++++++++ CMakeLists.txt | 6 ++ tests/s2n_test.h | 6 +- tests/testlib/s2n_seccomp.c | 71 +++++++++++++++++ tests/testlib/s2n_testlib.h | 3 + tests/unit/s2n_seccomp_failure_test.c | 63 +++++++++++++++ tests/unit/s2n_seccomp_handshake_test.c | 66 ++++++++++++++++ utils/s2n_init.c | 4 + 8 files changed, 317 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/ci_ubuntu.yml create mode 100644 tests/testlib/s2n_seccomp.c create mode 100644 tests/unit/s2n_seccomp_failure_test.c create mode 100644 tests/unit/s2n_seccomp_handshake_test.c diff --git a/.github/workflows/ci_ubuntu.yml b/.github/workflows/ci_ubuntu.yml new file mode 100644 index 00000000000..cc6884b22f8 --- /dev/null +++ b/.github/workflows/ci_ubuntu.yml @@ -0,0 +1,100 @@ +name: Ubuntu +on: + pull_request: + branches: [main] + merge_group: + types: [checks_requested] + branches: [main] + +# Builds should match https://github.com/aws/s2n-tls/blob/main/docs/BUILD.md#building-s2n-tls +# If any of these builds need to be modified, BUILD.md also needs to be modified. +jobs: + default: + runs-on: ubuntu-latest + steps: + - name: Install dependencies + run: | + sudo apt update + sudo apt install cmake + sudo apt install libssl-dev + - uses: actions/checkout@v4 + + - name: Build s2n-tls + run: | + cmake . -Bbuild \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=./s2n-tls-install + cmake --build build -j $(nproc) + CTEST_PARALLEL_LEVEL=$(nproc) ctest --test-dir build + cmake --install build + + - name: Rebuild with seccomp + run: | + rm -rf build + sudo apt install libseccomp-dev + cmake . -Bbuild \ + -DCMAKE_BUILD_TYPE=Release \ + -DSECCOMP=1 \ + -DCMAKE_INSTALL_PREFIX=./s2n-tls-install + cmake --build build -j $(nproc) + CTEST_PARALLEL_LEVEL=$(nproc) ctest --test-dir build + + awslc: + runs-on: ubuntu-latest + steps: + - name: Install aws-lc dependencies + run: | + sudo apt update + sudo apt install cmake clang golang + - uses: actions/checkout@v4 + with: + repository: aws/aws-lc + path: awslc + - name: Build awslc + # See https://github.com/aws/aws-lc/blob/main/BUILDING.md#building + working-directory: awslc + run: | + cmake -B build + make -C build + cmake --install build --prefix install + + - name: Install s2n-tls dependencies + run: | + sudo apt update + sudo apt install cmake + - uses: actions/checkout@v4 + with: + path: s2ntls + - name: Build s2n-tls + working-directory: s2ntls + run: | + cmake . -Bbuild \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_PREFIX_PATH=../awslc/install \ + -DCMAKE_INSTALL_PREFIX=./s2n-tls-install + cmake --build build -j $(nproc) + CTEST_PARALLEL_LEVEL=$(nproc) ctest --test-dir build + cmake --install build + - name: Check libcrypto is aws-lc + working-directory: s2ntls/build/bin + run: s2nc localhost 8000 | grep "AWS-LC" + + - name: Install seccomp + run: | + sudo apt install libseccomp-dev + - uses: actions/checkout@v4 + with: + path: seccomp + - name: Build s2n-tls with seccomp + working-directory: seccomp + run: | + cmake . -Bbuild \ + -DCMAKE_BUILD_TYPE=Release \ + -DSECCOMP=1 \ + -DCMAKE_PREFIX_PATH=../awslc/install \ + -DCMAKE_INSTALL_PREFIX=./s2n-tls-install + cmake --build build -j $(nproc) + CTEST_PARALLEL_LEVEL=$(nproc) ctest --test-dir build + - name: Check libcrypto is aws-lc + working-directory: seccomp/build/bin + run: s2nc localhost 8000 | grep "AWS-LC" diff --git a/CMakeLists.txt b/CMakeLists.txt index c89bae8a817..fa01a3c7ee9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,7 @@ option(S2N_INSTALL_S2NC_S2ND "Install the binaries s2nc and s2nd" OFF) option(S2N_USE_CRYPTO_SHARED_LIBS "For S2N to use shared libs in Findcrypto" OFF) option(TSAN "Enable ThreadSanitizer to test thread safety" OFF) option(ASAN "Enable AddressSanitizer to test memory safety" OFF) +option(SECCOMP "Link with seccomp and run seccomp tests" OFF) # Turn BUILD_TESTING=ON by default include(CTest) @@ -452,6 +453,11 @@ if (BUILD_TESTING) target_include_directories(testss2n PUBLIC tests) target_compile_options(testss2n PRIVATE -std=gnu99) target_link_libraries(testss2n PUBLIC ${PROJECT_NAME}) + if (SECCOMP) + message(STATUS "Linking tests with seccomp") + target_link_libraries(testss2n PRIVATE seccomp) + target_compile_definitions(testss2n PRIVATE SECCOMP) + endif() if (S2N_INTERN_LIBCRYPTO) # if libcrypto was interned, rewrite libcrypto symbols so use of internal functions will link correctly diff --git a/tests/s2n_test.h b/tests/s2n_test.h index 696ba68ed0d..3ad2c168783 100644 --- a/tests/s2n_test.h +++ b/tests/s2n_test.h @@ -30,6 +30,8 @@ int test_count; +bool s2n_use_color_in_output = true; + /* Macro definitions for calls that occur within BEGIN_TEST() and END_TEST() to preserve the SKIPPED test behavior * by ignoring the test_count, keeping it as 0 to indicate that a test was skipped. */ #define EXPECT_TRUE_WITHOUT_COUNT( condition ) do { if ( !(condition) ) { FAIL_MSG( #condition " is not true "); } } while(0) @@ -40,7 +42,7 @@ int test_count; #define EXPECT_SUCCESS_WITHOUT_COUNT( function_call ) EXPECT_NOT_EQUAL_WITHOUT_COUNT( (function_call) , -1 ) #define END_TEST_PRINT() \ - if (isatty(fileno(stdout))) { \ + if (s2n_use_color_in_output && isatty(fileno(stdout))) { \ if (test_count) { \ fprintf(stdout, "\033[32;1mPASSED\033[0m %10d tests\n", test_count ); \ } \ @@ -105,7 +107,7 @@ int test_count; /* isatty and s2n_print_stacktrace will overwrite errno on failure */ \ int real_errno = errno; \ s2n_print_stacktrace(stderr); \ - if (isatty(fileno(stderr))) { \ + if (s2n_use_color_in_output && isatty(fileno(stderr))) { \ errno = real_errno; \ fprintf(stderr, "\033[31;1mFAILED test %d\033[0m\n%s (%s:%d)\nError Message: '%s'\n Debug String: '%s'\n System Error: %s (%d)\n", test_count, (msg), __FILE__, __LINE__, s2n_strerror(s2n_errno, "EN"), s2n_strerror_debug(s2n_errno, "EN"), strerror(errno), errno); \ } \ diff --git a/tests/testlib/s2n_seccomp.c b/tests/testlib/s2n_seccomp.c new file mode 100644 index 00000000000..f4c9861155d --- /dev/null +++ b/tests/testlib/s2n_seccomp.c @@ -0,0 +1,71 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include "testlib/s2n_testlib.h" +#include "utils/s2n_safety.h" + +#ifdef SECCOMP + +#include + +DEFINE_POINTER_CLEANUP_FUNC(scmp_filter_ctx, seccomp_release); + +extern bool s2n_use_color_in_output; + +bool s2n_is_seccomp_supported() +{ + return true; +} + +S2N_RESULT s2n_seccomp_init() +{ + /* Using SCMP_ACT_TRAP instead of SCMP_ACT_KILL makes this test easier + * to debug. GDB will report exactly which syscalls triggered the signal. + */ + DEFER_CLEANUP(scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_TRAP), + seccomp_release_pointer); + RESULT_ENSURE_REF(ctx); + + /* Basic requirements */ + RESULT_GUARD_POSIX(seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(brk), 0)); + RESULT_GUARD_POSIX(seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(close), 0)); + RESULT_GUARD_POSIX(seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0)); + RESULT_GUARD_POSIX(seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fstat), 0)); + RESULT_GUARD_POSIX(seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(futex), 0)); + RESULT_GUARD_POSIX(seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0)); + RESULT_GUARD_POSIX(seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0)); + + /* Requirements for Ubuntu22 */ + RESULT_GUARD_POSIX(seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(clock_gettime), 0)); + RESULT_GUARD_POSIX(seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(newfstatat), 0)); + + RESULT_GUARD_POSIX(seccomp_load(ctx)); + s2n_use_color_in_output = false; + return S2N_RESULT_OK; +} + +#else + +bool s2n_is_seccomp_supported() +{ + return false; +} + +S2N_RESULT s2n_seccomp_init() +{ + return S2N_RESULT_OK; +} + +#endif diff --git a/tests/testlib/s2n_testlib.h b/tests/testlib/s2n_testlib.h index 60effca24de..ec9a3a36b7f 100644 --- a/tests/testlib/s2n_testlib.h +++ b/tests/testlib/s2n_testlib.h @@ -298,3 +298,6 @@ S2N_RESULT s2n_resumption_test_ticket_key_setup(struct s2n_config *config); #define S2N_CHECKED_BLOB_FROM_HEX(name, check, hex) \ DEFER_CLEANUP(struct s2n_blob name = { 0 }, s2n_free); \ check(s2n_blob_alloc_from_hex_with_whitespace(&name, (const char *) hex)); + +bool s2n_is_seccomp_supported(); +S2N_RESULT s2n_seccomp_init(); diff --git a/tests/unit/s2n_seccomp_failure_test.c b/tests/unit/s2n_seccomp_failure_test.c new file mode 100644 index 00000000000..6fd1cdc6141 --- /dev/null +++ b/tests/unit/s2n_seccomp_failure_test.c @@ -0,0 +1,63 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include +#include +#include + +#include "s2n_test.h" +#include "testlib/s2n_testlib.h" + +bool s2n_fstat_success = false; +bool s2n_open_success = false; + +void s2n_detect_open_violation(int sig) +{ + EXPECT_EQUAL(sig, SIGSYS); + + EXPECT_TRUE(s2n_fstat_success); + EXPECT_FALSE(s2n_open_success); + + END_TEST_PRINT(); + exit(0); +} + +int main(int argc, char **argv) +{ + BEGIN_TEST(); + + if (!s2n_is_seccomp_supported()) { + END_TEST(); + } + + const struct sigaction action = { + .sa_handler = s2n_detect_open_violation, + }; + EXPECT_EQUAL(sigaction(SIGSYS, &action, NULL), 0); + + EXPECT_OK(s2n_seccomp_init()); + fprintf(stdout, "SETUP\n"); + + /* The seccomp filter allows fstat */ + struct stat st = { 0 }; + EXPECT_SUCCESS(fstat(0, &st)); + s2n_fstat_success = true; + + /* The seccomp filter does NOT allow open */ + fopen(S2N_DEFAULT_TEST_CERT_CHAIN, "r"); + s2n_open_success = true; + + FAIL_MSG("test unexpectedly succeeded"); +} diff --git a/tests/unit/s2n_seccomp_handshake_test.c b/tests/unit/s2n_seccomp_handshake_test.c new file mode 100644 index 00000000000..129454ba997 --- /dev/null +++ b/tests/unit/s2n_seccomp_handshake_test.c @@ -0,0 +1,66 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +#include "api/s2n.h" +#include "s2n_test.h" +#include "testlib/s2n_testlib.h" + +int main(int argc, char **argv) +{ + BEGIN_TEST(); + + /* We don't use s2n_test_cert_chain_and_key_new because we want to explicitly + * only do the PEM file read before enabling seccomp. + */ + char cert_chain_pem[S2N_MAX_TEST_PEM_SIZE] = { 0 }; + char private_key_pem[S2N_MAX_TEST_PEM_SIZE] = { 0 }; + EXPECT_SUCCESS(s2n_read_test_pem(S2N_DEFAULT_ECDSA_TEST_CERT_CHAIN, cert_chain_pem, S2N_MAX_TEST_PEM_SIZE)); + EXPECT_SUCCESS(s2n_read_test_pem(S2N_DEFAULT_ECDSA_TEST_PRIVATE_KEY, private_key_pem, S2N_MAX_TEST_PEM_SIZE)); + + /* No unexpected syscalls allowed beyond this point */ + EXPECT_OK(s2n_seccomp_init()); + + DEFER_CLEANUP(struct s2n_cert_chain_and_key *chain_and_key = s2n_cert_chain_and_key_new(), + s2n_cert_chain_and_key_ptr_free); + EXPECT_SUCCESS(s2n_cert_chain_and_key_load_pem(chain_and_key, cert_chain_pem, private_key_pem)); + + DEFER_CLEANUP(struct s2n_config *config = s2n_config_new_minimal(), s2n_config_ptr_free); + EXPECT_SUCCESS(s2n_config_add_cert_chain_and_key_to_store(config, chain_and_key)); + EXPECT_SUCCESS(s2n_config_set_unsafe_for_testing(config)); + + const char* security_policies[] = { "default", "default_tls13" }; + + for (size_t i = 0; i < s2n_array_len(security_policies); i++) { + DEFER_CLEANUP(struct s2n_connection *client = s2n_connection_new(S2N_CLIENT), + s2n_connection_ptr_free); + EXPECT_NOT_NULL(client); + EXPECT_SUCCESS(s2n_connection_set_config(client, config)); + EXPECT_SUCCESS(s2n_connection_set_cipher_preferences(client, security_policies[i])); + + DEFER_CLEANUP(struct s2n_connection *server = s2n_connection_new(S2N_SERVER), + s2n_connection_ptr_free); + EXPECT_NOT_NULL(server); + EXPECT_SUCCESS(s2n_connection_set_config(server, config)); + EXPECT_SUCCESS(s2n_connection_set_cipher_preferences(server, security_policies[i])); + + DEFER_CLEANUP(struct s2n_test_io_stuffer_pair io_pair = { 0 }, s2n_io_stuffer_pair_free); + EXPECT_OK(s2n_io_stuffer_pair_init(&io_pair)); + EXPECT_OK(s2n_connections_set_io_stuffer_pair(client, server, &io_pair)); + EXPECT_SUCCESS(s2n_negotiate_test_server_and_client(server, client)); + } + + END_TEST(); +} + diff --git a/utils/s2n_init.c b/utils/s2n_init.c index 3c561106ea8..9e9f91314b3 100644 --- a/utils/s2n_init.c +++ b/utils/s2n_init.c @@ -87,6 +87,10 @@ int s2n_init(void) s2n_stack_traces_enabled_set(true); } +#if defined(OPENSSL_IS_BORINGSSL) || defined(OPENSSL_IS_AWSLC) + CRYPTO_pre_sandbox_init(); +#endif + initialized = true; return S2N_SUCCESS;