diff --git a/.github/workflows/seccomp.yml b/.github/workflows/seccomp.yml new file mode 100644 index 00000000000..053bce0dd8c --- /dev/null +++ b/.github/workflows/seccomp.yml @@ -0,0 +1,67 @@ +name: seccomp +on: + pull_request: + branches: [main] + merge_group: + types: [checks_requested] + branches: [main] + +jobs: + ubuntu: + runs-on: ubuntu-latest + steps: + - name: install dependencies + run: | + sudo apt update + sudo apt install cmake + # For default libcrypto + sudo apt install libssl-dev + # For seccomp + sudo apt install libseccomp-dev + # For aws-lc + sudo apt install clang golang + + - name: checkout s2n-tls + uses: actions/checkout@v4 + + - name: checkout aws-lc + 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: seccomp with default libcrypto + # TODO: There are still issues with openssl running with seccomp. + # Disable for now. + if: false + run: | + cmake -Bbuild \ + -DSECCOMP=1 \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=install + cmake --build build -j $(nproc) + CTEST_PARALLEL_LEVEL=$(nproc) ctest --test-dir build + cmake --install build + ./build/bin/s2nc localhost 8000 | grep "libcrypto" | grep -v "AWS-LC" + rm -rf build + + - name: seccomp with aws-lc + run: | + cmake -Bbuild \ + -DSECCOMP=1 \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_PREFIX_PATH=awslc/install \ + -DCMAKE_INSTALL_PREFIX=install + cmake --build build -j $(nproc) + CTEST_PARALLEL_LEVEL=$(nproc) ctest --test-dir build + cmake --install build + ./build/bin/s2nc localhost 8000 | grep "libcrypto" | grep "AWS-LC" + rm -rf build 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/docs/BUILD.md b/docs/BUILD.md index 8c3346662be..d79e1241f6a 100644 --- a/docs/BUILD.md +++ b/docs/BUILD.md @@ -110,6 +110,8 @@ cmake . -LH s2n-tls has a dependency on a libcrypto library. A supported libcrypto must be linked to s2n-tls when building. The following libcrypto libraries are currently supported: - [AWS-LC](https://github.com/aws/aws-lc) + - Limited ["Sandboxing"](https://github.com/aws/aws-lc/blob/main/SANDBOXING.md) is only supported and tested with AWS-LC. + - [PQ key exchange](https://aws.github.io/s2n-tls/usage-guide/ch15-post-quantum.html) is only supported with AWS-LC. - [OpenSSL](https://www.openssl.org/) (versions 1.0.2 - 3.0) - ChaChaPoly is not supported before Openssl-1.1.1. - RSA-PSS is not supported before Openssl-1.1.1. 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..29fecbc85c6 --- /dev/null +++ b/tests/testlib/s2n_seccomp.c @@ -0,0 +1,94 @@ +/* + * 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; +} + +/* "seccomp" allows the kernel to control what system calls an application + * is allowed to make based on a provided filter. + * + * seccomp is commonly used for "sandboxing" programs for security reasons. + */ +S2N_RESULT s2n_seccomp_init() +{ + /* Using SCMP_ACT_TRAP instead of SCMP_ACT_KILL as the default action + * makes this test easier to debug. GDB can be used to debug failures caused + * by SCMP_ACT_TRAP, but not caused by SCMP_ACT_KILL. + */ + DEFER_CLEANUP(scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_TRAP), + seccomp_release_pointer); + RESULT_ENSURE_REF(ctx); + + /* Basic requirements: s2n-tls is known to need these system calls in order + * to operate. Adding a new system call to this list means that any application + * using s2n-tls with seccomp will potentially also need to update its filter rules. + * + * Do not add any variation of "open" to this list. One of the primary reasons + * that an application would choose to use seccomp is to prevent opening files, + * similar to chroot. + */ + RESULT_GUARD_POSIX(seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(access), 0)); + 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(clock_gettime), 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(getrandom), 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)); + + /* Ubuntu22 uses "newfstatat" instead of "fstat" */ + RESULT_GUARD_POSIX(seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(newfstatat), 0)); + + /* See https://github.com/aws/aws-lc/blob/main/SANDBOXING.md#fork-protection: + * We can just cause the madavise call to fail rather than blocking it entirely. */ + RESULT_GUARD_POSIX(seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EINVAL), SCMP_SYS(madvise), 0)); + + /* Checking whether the terminal supports color requires an additional + * system call. Preemptively disable color. + */ + s2n_use_color_in_output = false; + + RESULT_GUARD_POSIX(seccomp_load(ctx)); + 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..916b074b217 --- /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()); + + /* 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 */ + FILE *file = fopen(S2N_DEFAULT_TEST_CERT_CHAIN, "r"); + s2n_open_success = true; + EXPECT_NOT_NULL(file); + + 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..d85df42e6b9 --- /dev/null +++ b/tests/unit/s2n_seccomp_handshake_test.c @@ -0,0 +1,87 @@ +/* + * 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_NO_INIT(); + + /* One of the primary purposes of seccomp is to block opening new files. + * So before we enable seccomp, we need to open any files that the test would + * need. In this case, we need to load certificate pems from files. + * + * An application using s2n-tls with seccomp would need to do the same. + */ + 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)); + + /* We need to execute s2n_init before the seccomp filter is applied. + * Some one-time initialization involves opening files, like "dev/urandom". + * If built with aws-lc, s2n-tls also needs to call CRYPTO_pre_sandbox_init() + * before seccomp starts sandboxing. + * + * An application using s2n-tls with seccomp would need to do the same. + */ + EXPECT_SUCCESS(s2n_init()); + + /* 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_add_pem_to_trust_store(config, cert_chain_pem)); + + const char *security_policies[] = { "test_all_tls12", "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])); + EXPECT_SUCCESS(s2n_set_server_name(client, "127.0.0.1")); + + 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)); + + const uint8_t data[] = "hello world"; + uint8_t buffer[100] = { 0 }; + s2n_blocked_status blocked = S2N_NOT_BLOCKED; + EXPECT_EQUAL(s2n_send(client, data, sizeof(data), &blocked), sizeof(data)); + EXPECT_EQUAL(s2n_recv(server, buffer, sizeof(buffer), &blocked), sizeof(data)); + EXPECT_BYTEARRAY_EQUAL(buffer, data, sizeof(data)); + } + + END_TEST(); +} diff --git a/utils/s2n_init.c b/utils/s2n_init.c index 3c561106ea8..550fcc0cfd8 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_AWSLC) + CRYPTO_pre_sandbox_init(); +#endif + initialized = true; return S2N_SUCCESS;