diff --git a/tests/ci/cdk/cdk/accp_github_fuzz_ci_stack.py b/tests/ci/cdk/cdk/accp_github_fuzz_ci_stack.py new file mode 100644 index 00000000..d99cc453 --- /dev/null +++ b/tests/ci/cdk/cdk/accp_github_fuzz_ci_stack.py @@ -0,0 +1,131 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 OR ISC + +from aws_cdk import Duration, Size, Stack, aws_codebuild as codebuild, aws_iam as iam, aws_ec2 as ec2, aws_efs as efs +from constructs import Construct + +from cdk.components import PruneStaleGitHubBuilds +from util.ecr_util import ecr_arn +from util.iam_policies import code_build_batch_policy_in_json, \ + code_build_publish_metrics_in_json +from util.metadata import AWS_ACCOUNT, AWS_REGION, GITHUB_PUSH_CI_BRANCH_TARGETS, GITHUB_REPO_OWNER, GITHUB_REPO_NAME +from util.build_spec_loader import BuildSpecLoader + + +class AwsLcGitHubFuzzCIStack(Stack): + """Define a stack used to batch execute AWS-LC tests in GitHub.""" + + def __init__(self, + scope: Construct, + id: str, + spec_file_path: str, + **kwargs) -> None: + super().__init__(scope, id, **kwargs) + + # Define CodeBuild resource. + git_hub_source = codebuild.Source.git_hub( + owner=GITHUB_REPO_OWNER, + repo=GITHUB_REPO_NAME, + webhook=True, + webhook_filters=[ + codebuild.FilterGroup.in_event_of( + codebuild.EventAction.PULL_REQUEST_CREATED, + codebuild.EventAction.PULL_REQUEST_UPDATED, + codebuild.EventAction.PULL_REQUEST_REOPENED), + codebuild.FilterGroup.in_event_of(codebuild.EventAction.PUSH).and_branch_is( + GITHUB_PUSH_CI_BRANCH_TARGETS), + ], + webhook_triggers_batch_build=True) + + # Define a IAM role for this stack. + code_build_batch_policy = iam.PolicyDocument.from_json( + code_build_batch_policy_in_json([id]) + ) + fuzz_policy = iam.PolicyDocument.from_json(code_build_publish_metrics_in_json()) + inline_policies = {"code_build_batch_policy": code_build_batch_policy, + "fuzz_policy": fuzz_policy} + role = iam.Role(scope=self, + id="{}-role".format(id), + assumed_by=iam.ServicePrincipal("codebuild.amazonaws.com"), + inline_policies=inline_policies) + + # Create the VPC for EFS and CodeBuild + public_subnet = ec2.SubnetConfiguration(name="PublicFuzzingSubnet", subnet_type=ec2.SubnetType.PUBLIC) + private_subnet = ec2.SubnetConfiguration(name="PrivateFuzzingSubnet", subnet_type=ec2.SubnetType.PRIVATE_WITH_EGRESS) + + # Create a VPC with a single public and private subnet in a single AZ. This is to avoid the elastic IP limit + # being used up by a bunch of idle NAT gateways + fuzz_vpc = ec2.Vpc( + scope=self, + id="{}-FuzzingVPC".format(id), + subnet_configuration=[public_subnet, private_subnet], + max_azs=1 + ) + build_security_group = ec2.SecurityGroup( + scope=self, + id="{}-FuzzingSecurityGroup".format(id), + vpc=fuzz_vpc + ) + + build_security_group.add_ingress_rule( + peer=build_security_group, + connection=ec2.Port.all_traffic(), + description="Allow all traffic inside security group" + ) + + efs_subnet_selection = ec2.SubnetSelection(subnet_type=ec2.SubnetType.PRIVATE_WITH_EGRESS) + + # Create the EFS to store the corpus and logs. EFS allows new filesystems to burst to 100 MB/s for the first 2 + # TB of data read/written, after that the rate is limited based on the size of the filesystem. As of late + # 2021 our corpus is less than one GB which results in EFS limiting all reads and writes to the minimum 1 MB/s. + # To have the fuzzing be able to finish in a reasonable amount of time use the Provisioned capacity option. + # For now this uses 100 MB/s which matches the performance used for 2021. Looking at EFS metrics in late 2021 + # during fuzz runs EFS sees 4-22 MB/s of transfers thus 100 MB/s gives lots of buffer and allows ~4-5 fuzz runs + # to start at the same time with no issue. + # https://docs.aws.amazon.com/efs/latest/ug/performance.html + fuzz_filesystem = efs.FileSystem( + scope=self, + id="{}-FuzzingEFS".format(id), + file_system_name="AWS-LC-Fuzz-Corpus", + enable_automatic_backups=True, + encrypted=True, + security_group=build_security_group, + vpc=fuzz_vpc, + vpc_subnets=efs_subnet_selection, + performance_mode=efs.PerformanceMode.GENERAL_PURPOSE, + throughput_mode=efs.ThroughputMode.PROVISIONED, + provisioned_throughput_per_second=Size.mebibytes(100), + ) + + # Define CodeBuild. + fuzz_codebuild = codebuild.Project( + scope=self, + id="FuzzingCodeBuild", + project_name=id, + source=git_hub_source, + role=role, + timeout=Duration.minutes(120), + environment=codebuild.BuildEnvironment(compute_type=codebuild.ComputeType.LARGE, + privileged=True, + build_image=codebuild.LinuxBuildImage.STANDARD_4_0), + build_spec=BuildSpecLoader.load(spec_file_path), + vpc=fuzz_vpc, + security_groups=[build_security_group]) + fuzz_codebuild.enable_batch_builds() + + # CDK raw overrides: https://docs.aws.amazon.com/cdk/latest/guide/cfn_layer.html#cfn_layer_raw + # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-codebuild-project.html#aws-resource-codebuild-project-properties + # The EFS identifier needs to match tests/ci/common_fuzz.sh, CodeBuild defines an environment variable named + # codebuild_$identifier. + # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-codebuild-project-projectfilesystemlocation.html + # + # TODO: add this to the CDK project above when it supports EfsFileSystemLocation + cfn_codebuild = fuzz_codebuild.node.default_child + cfn_codebuild.add_override("Properties.FileSystemLocations", [{ + "Identifier": "fuzzing_root", + "Location": "%s.efs.%s.amazonaws.com:/" % (fuzz_filesystem.file_system_id, AWS_REGION), + "MountPoint": "/efs_fuzzing_root", + "Type": "EFS" + }]) + + PruneStaleGitHubBuilds(scope=self, id="PruneStaleGitHubBuilds", project=fuzz_codebuild) diff --git a/tests/ci/common_fuzz.sh b/tests/ci/common_fuzz.sh new file mode 100644 index 00000000..3adb2c12 --- /dev/null +++ b/tests/ci/common_fuzz.sh @@ -0,0 +1,144 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 OR ISC + +source tests/ci/common_posix_setup.sh + +if [ -v CODEBUILD_FUZZING_ROOT ]; then + CORPUS_ROOT="${CODEBUILD_FUZZING_ROOT}/fuzzing" +else + CORPUS_ROOT="${BUILD_ROOT}/mock_efs/fuzzing" +fi +echo "$CORPUS_ROOT" + +if [ -v CODEBUILD_BUILD_ID ]; then + BUILD_ID=$CODEBUILD_BUILD_ID +else + # Generate a random string in bash https://unix.stackexchange.com/questions/230673/how-to-generate-a-random-string + BUILD_ID=$(tr -dc A-Za-z0-9 &1 | tee "$SUMMARY_LOG" + # This gets the status of the fuzz run which determines if we want to fail the build or not, otherwise we'd get the results of tee + if [ "${PIPESTATUS[0]}" == 1 ]; then + FUZZ_RUN_FAILURE=1 + fi + + # The libfuzzer logs are written to the current working directory and need to be moved after the test is done + mv ./*.log "${LOCAL_FUZZ_RUN_LOGS}/." + + if [ "$FUZZ_RUN_FAILURE" == 1 ]; then + FUZZ_TEST_FAILURE_ROOT="${SHARED_FAILURE_ROOT}/${FUZZ_NAME}" + mkdir -p "$FUZZ_TEST_FAILURE_ROOT" + + if [[ "$FUZZ_NAME" == "cryptofuzz" ]]; then + for ARTIFACT in "$LOCAL_ARTIFACTS_FOLDER"/*; do + base64 $ARTIFACT + ARTIFACT_NAME=$(basename "$ARTIFACT") + "${FUZZ_TEST_PATH}" --debug "$ARTIFACT" | tee "${LOCAL_FUZZ_RUN_LOGS}/${ARTIFACT_NAME}.log" + done + fi + + cp -r "$LOCAL_FUZZ_TEST_ROOT" "$SHARED_FAILURE_ROOT" + cp "$FUZZ_TEST_PATH" "${FUZZ_TEST_FAILURE_ROOT}/${FUZZ_NAME}" + + # If this fuzz run has failed the below metrics won't make a lot of sense, it could fail on the first input and + # publish a TestCount of 1 which makes all the metrics look weird + echo "${FUZZ_NAME} failed, see the above output for details. For all the logs see ${SHARED_FAILURE_ROOT} in EFS" + exit 1 + else + echo "Fuzz test ${FUZZ_NAME} finished successfully, not copying run logs and run corpus" + fi + + set -e + + # Step 2 merge any new files from the run corpus and GitHub src corpus into the shared corpus, the first folder is + # where to merge the new corpus (SHARED_FUZZ_TEST_CORPUS), the second two are where to read new inputs from + # (LOCAL_RUN_CORPUS and SRC_CORPUS). + time "${FUZZ_TEST_PATH}" -merge=1 "$SHARED_FUZZ_TEST_CORPUS" "$LOCAL_RUN_CORPUS" "$SRC_CORPUS" + + # Calculate interesting metrics and post results to CloudWatch, this checks the shared (EFS) corpus after the new test + # run corpus has been merged in + FINAL_SHARED_CORPUS_FILE_COUNT=$(find "$SHARED_FUZZ_TEST_CORPUS" -type f | wc -l) + put_metric_count --metric-name SharedCorpusFileCount --value "$FINAL_SHARED_CORPUS_FILE_COUNT" --dimensions "FuzzTest=$FUZZ_NAME" + + RUN_CORPUS_FILE_COUNT=$(find "$LOCAL_RUN_CORPUS" -type f | wc -l) + put_metric_count --metric-name RunCorpusFileCount --value "$RUN_CORPUS_FILE_COUNT" --dimensions "FuzzTest=$FUZZ_NAME,Platform=$PLATFORM" + + TEST_COUNT=$(grep -o "stat::number_of_executed_units: [0-9]*" "$SUMMARY_LOG" | awk '{test_count += $2} END {print test_count}') + put_metric_count --metric-name TestCount --value "$TEST_COUNT" --dimensions "FuzzTest=$FUZZ_NAME,Platform=$PLATFORM" + + TESTS_PER_SECOND=$((TEST_COUNT/TIME_FOR_EACH_FUZZ)) + put_metric --metric-name TestRate --value "$TESTS_PER_SECOND" --unit Count/Second --dimensions "FuzzTest=$FUZZ_NAME,Platform=$PLATFORM" + + FEATURE_COVERAGE=$(grep -o "ft: [0-9]*" "$SUMMARY_LOG" | awk '{print $2}' | sort -n | tail -1) + put_metric_count --metric-name FeatureCoverage --value "$FEATURE_COVERAGE" --dimensions "FuzzTest=$FUZZ_NAME,Platform=$PLATFORM" + + BLOCK_COVERAGE=$(grep -o "cov: [0-9]*" "$SUMMARY_LOG" | awk '{print $2}' | sort -n | tail -1) + put_metric_count --metric-name BlockCoverage --value "$BLOCK_COVERAGE" --dimensions "FuzzTest=$FUZZ_NAME,Platform=$PLATFORM" + + echo "${FUZZ_NAME} starting shared ${ORIGINAL_CORPUS_FILE_COUNT} final shared ${FINAL_SHARED_CORPUS_FILE_COUNT} new files ${RUN_CORPUS_FILE_COUNT} total test count ${TEST_COUNT} test rate ${TESTS_PER_SECOND} code coverage ${BLOCK_COVERAGE} feature coverage ${FEATURE_COVERAGE}" +} diff --git a/tests/ci/common_posix_setup.sh b/tests/ci/common_posix_setup.sh new file mode 100644 index 00000000..57605037 --- /dev/null +++ b/tests/ci/common_posix_setup.sh @@ -0,0 +1,244 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 OR ISC + +if [ -v CODEBUILD_SRC_DIR ]; then + SRC_ROOT="$CODEBUILD_SRC_DIR" +else + SRC_ROOT=$(pwd) +fi +echo "$SRC_ROOT" + +cd ../ +SYS_ROOT=$(pwd) +cd $SRC_ROOT + +BUILD_ROOT="${SRC_ROOT}/test_build_dir" +echo "$BUILD_ROOT" + +PLATFORM=$(uname -m) + +NUM_CPU_THREADS='' +KERNEL_NAME=$(uname -s) +if [[ "${KERNEL_NAME}" == "Darwin" ]]; then + # On MacOS, /proc/cpuinfo does not exist. + NUM_CPU_THREADS=$(sysctl -n hw.ncpu) +else + # Assume KERNEL_NAME is Linux. + NUM_CPU_THREADS=$(grep -c ^processor /proc/cpuinfo) + if [[ $PLATFORM == "aarch64" ]]; then + CPU_PART=$(grep -Po -m 1 'CPU part.*:\s\K.*' /proc/cpuinfo) + NUM_CPU_PART=$(grep -c $CPU_PART /proc/cpuinfo) + # Set capabilities via the static flags for valgrind tests. + # This is because valgrind reports the instruction + # mrs %0, MIDR_EL1 + # which fetches the CPU part number, as illegal. + # For some reason, valgrind also reports SHA512 instructions illegal, + # so the SHA512 capability is not included below. + VALGRIND_STATIC_CAP_FLAGS="-DOPENSSL_STATIC_ARMCAP -DOPENSSL_STATIC_ARMCAP_NEON" + VALGRIND_STATIC_CAP_FLAGS+=" -DOPENSSL_STATIC_ARMCAP_AES -DOPENSSL_STATIC_ARMCAP_PMULL " + VALGRIND_STATIC_CAP_FLAGS+=" -DOPENSSL_STATIC_ARMCAP_SHA1 -DOPENSSL_STATIC_ARMCAP_SHA256 " + if [[ $NUM_CPU_PART == $NUM_CPU_THREADS ]] && [[ ${CPU_PART} =~ 0x[dD]40 ]]; then + VALGRIND_STATIC_CAP_FLAGS+=" -DOPENSSL_STATIC_ARMCAP_SHA3 -DOPENSSL_STATIC_ARMCAP_NEOVERSE_V1" + fi + fi +fi + +# Pick cmake3 if possible. We don't know of any OS that installs a cmake3 +# executable that is not at least version 3.0. +if [[ -x "$(command -v cmake3)" ]] ; then + CMAKE_COMMAND="cmake3" +else + CMAKE_COMMAND="cmake" +fi + +function run_build { + local cflags=("$@") + rm -rf "$BUILD_ROOT" + mkdir -p "$BUILD_ROOT" + cd "$BUILD_ROOT" || exit 1 + + if [[ "${AWSLC_32BIT}" == "1" ]]; then + cflags+=("-DCMAKE_TOOLCHAIN_FILE=${SRC_ROOT}/util/32-bit-toolchain.cmake") + fi + + if [[ -x "$(command -v ninja)" ]]; then + echo "Using Ninja build system (ninja)." + BUILD_COMMAND="ninja" + cflags+=(-GNinja) + elif [[ -x "$(command -v ninja-build)" ]]; then + echo "Using Ninja build system (ninja-build)." + BUILD_COMMAND="ninja-build" + cflags+=(-GNinja) + else + echo "Using Make." + BUILD_COMMAND="make -j${NUM_CPU_THREADS}" + fi + + ${CMAKE_COMMAND} "${cflags[@]}" "$SRC_ROOT" + $BUILD_COMMAND + cd "$SRC_ROOT" +} + +function run_cmake_custom_target { + $BUILD_COMMAND -C "$BUILD_ROOT" "$@" +} + +function build_and_test { + run_build "$@" + run_cmake_custom_target 'run_tests' +} + +function generate_symbols_file { + # read_symbols.go currently only support static libraries + if [ ! -f "$BUILD_ROOT"/crypto/libcrypto.a ]; then + echo "Static library not found: ${BUILD_ROOT}/crypto/libcrypto.a" + exit 1 + fi + + go run "$SRC_ROOT"/util/read_symbols.go -out "$BUILD_ROOT"/symbols_crypto.txt "$BUILD_ROOT"/crypto/libcrypto.a + go run "$SRC_ROOT"/util/read_symbols.go -out "$BUILD_ROOT"/symbols_ssl.txt "$BUILD_ROOT"/ssl/libssl.a + + # The $BUILD_ROOT gets deleted on each run. symbols.txt must be placed elsewhere. + cat "$BUILD_ROOT"/symbols_crypto.txt "$BUILD_ROOT"/symbols_ssl.txt > "$SRC_ROOT"/symbols.txt +} + + +function verify_symbols_prefixed { + go run "$SRC_ROOT"/util/read_symbols.go -out "$BUILD_ROOT"/symbols_final_crypto.txt "$BUILD_ROOT"/crypto/libcrypto.a + go run "$SRC_ROOT"/util/read_symbols.go -out "$BUILD_ROOT"/symbols_final_ssl.txt "$BUILD_ROOT"/ssl/libssl.a + # For grep's basic regular expression language the meta-characters (e.g. "?", + # "|", etc.) are interpreted as literal characters. To keep their + # meta-character semantics, they must be escaped with "\". + # Deciphering the pattern "^_\?\(bignum\|curve25519_x25519\)": + # * "^": anchor at start of line. + # * "_\?": might contain underscore. + # * "\(bignum\|curve25519_x25519\)": match string of either "bignum" or "curve25519_x25519". + # Recall that the option "-v" reverse the pattern matching. So, we are really + # filtering out lines that contain either "bignum" or "curve25519_x25519". + cat "$BUILD_ROOT"/symbols_final_crypto.txt "$BUILD_ROOT"/symbols_final_ssl.txt | grep -v -e '^_\?\(bignum\|curve25519_x25519\)' > "$SRC_ROOT"/symbols_final.txt + # Now filter out every line that has the unique prefix $CUSTOM_PREFIX. If we + # have any lines left, then some symbol(s) weren't prefixed, unexpectedly. + if [ $(grep -c -v ${CUSTOM_PREFIX} "$SRC_ROOT"/symbols_final.txt) -ne 0 ]; then + echo "Symbol(s) missing prefix!" + exit 1 + fi +} + + +function build_prefix_and_test { + CUSTOM_PREFIX=aws_lc_1_1_0 + run_build "$@" + generate_symbols_file + run_build "$@" "-DBORINGSSL_PREFIX=${CUSTOM_PREFIX}" "-DBORINGSSL_PREFIX_SYMBOLS=${SRC_ROOT}/symbols.txt" + verify_symbols_prefixed + run_cmake_custom_target 'run_tests' +} + +function fips_build_and_test { + run_build "$@" -DFIPS=1 + # Upon completion of the build process. The module’s status can be verified by 'tool/bssl isfips'. + # https://csrc.nist.gov/CSRC/media/projects/cryptographic-module-validation-program/documents/security-policies/140sp3678.pdf + # FIPS mode is enabled when 'defined(BORINGSSL_FIPS) && !defined(OPENSSL_ASAN)'. + # https://github.com/aws/aws-lc/blob/220e266d4e415cf0101388b89a2bd855e0e4e203/crypto/fipsmodule/is_fips.c#L22 + expect_fips_mode=1 + for build_flag in "$@" + do + if [[ "${build_flag}" == '-DASAN=1' ]]; then + expect_fips_mode=0 + break + fi + done + module_status=$("${BUILD_ROOT}/tool/bssl" isfips) + [[ "${expect_fips_mode}" == "${module_status}" ]] || { echo >&2 "FIPS Mode validation failed."; exit 1; } + # Run tests. + run_cmake_custom_target 'run_tests' + "${BUILD_ROOT}/util/fipstools/test_fips" +} + +function build_and_test_valgrind { + if [[ $PLATFORM == "aarch64" ]]; then + run_build "$@" -DCMAKE_C_FLAGS="$VALGRIND_STATIC_CAP_FLAGS" + run_cmake_custom_target 'run_tests_valgrind' + + # Disable all capabilities and run again + # (We don't use the env. variable OPENSSL_armcap because it is currently + # restricted to the case of runtime discovery of capabilities + # in cpu_aarch64_linux.c) + run_build "$@" -DCMAKE_C_FLAGS="-DOPENSSL_STATIC_ARMCAP" + run_cmake_custom_target 'run_tests_valgrind' + else + run_build "$@" + run_cmake_custom_target 'run_tests_valgrind' + fi +} + +function build_and_test_ssl_runner_valgrind { + export AWS_LC_GO_TEST_TIMEOUT="60m" + + if [[ $PLATFORM == "aarch64" ]]; then + run_build "$@" -DCMAKE_C_FLAGS="$VALGRIND_STATIC_CAP_FLAGS" + else + run_build "$@" + fi + run_cmake_custom_target 'run_ssl_runner_tests_valgrind' +} + +function build_and_test_with_sde { + run_build "$@" + run_cmake_custom_target 'run_tests_with_sde' +} + +function build_and_run_minimal_test { + run_build "$@" + run_cmake_custom_target 'run_minimal_tests' +} + +# Install local build of AWS-LC for integration testing. +function aws_lc_build() { + AWS_LC_DIR=${1} + BUILD_FOLDER=${2} + INSTALL_FOLDER=${3} + + echo "Building AWS-LC to ${BUILD_FOLDER} and installing to ${INSTALL_FOLDER} with CFlags "${@:4}"" + ${CMAKE_COMMAND} ${AWS_LC_DIR} -GNinja "-B${BUILD_FOLDER}" "-DCMAKE_INSTALL_PREFIX=${INSTALL_FOLDER}" "${@:4}" + ninja -C ${BUILD_FOLDER} install + ls -R ${INSTALL_FOLDER} + rm -rf ${BUILD_FOLDER:?}/* +} + +function print_executable_information { + EXE_NAME=${1} + EXE_ARGUMENT=${2} + LABEL=${3} + + echo "" + echo "${LABEL}:" + if command -v ${EXE_NAME} &> /dev/null + then + ${EXE_NAME} ${EXE_ARGUMENT} + else + echo "${EXE_NAME} not found" + fi +} + +print_executable_information "cmake" "--version" "CMake version" +print_executable_information "cmake3" "--version" "CMake version (cmake3 executable)" +print_executable_information "go" "version" "Go version" +print_executable_information "perl" "--version" "Perl version" +# Ninja executable names are not uniform over operating systems +print_executable_information "ninja-build" "--version" "Ninja version (ninja-build executable)" +print_executable_information "ninja" "--version" "Ninja version (ninja executable)" +print_executable_information "gcc" "--version" "gcc version" +print_executable_information "g++" "--version" "g++ version" +print_executable_information "clang" "--version" "clang version" +print_executable_information "clang++" "--version" "clang++ version" +print_executable_information "cc" "--version" "cc version" +print_executable_information "c++" "--version" "c++ version" +print_executable_information "make" "--version" "Make version" +print_executable_information "rustup" "show" "Rust toolchain" +echo "" +echo "Operating system information:" +uname -a +echo "" +echo "Environment variables:" +env diff --git a/tests/ci/docker_images/dependencies/build_cryptofuzz_modules.sh b/tests/ci/docker_images/dependencies/build_cryptofuzz_modules.sh new file mode 100755 index 00000000..802d8adc --- /dev/null +++ b/tests/ci/docker_images/dependencies/build_cryptofuzz_modules.sh @@ -0,0 +1,56 @@ +#!/bin/bash +set -exo pipefail + +function env { + export "$1"="$2" + echo "export ${1}=\"${2}\"" >> "${FUZZ_ROOT}/fuzz_env.sh" +} +# Recommended flags from https://github.com/guidovranken/cryptofuzz/blob/master/docs/building.md +# Remove-fsanitize=undefined which doesn't fail the build but creates additional noise in build output +export CFLAGS="-fsanitize=address,fuzzer-no-link -O2 -g -Wno-gnu-designator" +export CXXFLAGS="-fsanitize=address,fuzzer-no-link -D_GLIBCXX_DEBUG -O2 -g" + +# Setup base of Cryptofuzz +cd "$FUZZ_ROOT" +MODULES_ROOT="${FUZZ_ROOT}/modules" +git clone --depth 1 https://github.com/guidovranken/cryptofuzz.git +cd cryptofuzz +git rev-parse HEAD +CRYPTOFUZZ_SRC=$(pwd) +python3 gen_repository.py + +mkdir "$MODULES_ROOT" +cd "$MODULES_ROOT" + +# Setup the other crypto libraries for differential fuzzing +# Crypto++ https://github.com/guidovranken/cryptofuzz/blob/master/docs/cryptopp.md +cd "$MODULES_ROOT" +git clone --depth 1 https://github.com/weidai11/cryptopp.git +cd cryptopp/ +git rev-parse HEAD +make libcryptopp.a -j$(nproc) +export CXXFLAGS="$CXXFLAGS -DCRYPTOFUZZ_CRYPTOPP" +env LIBCRYPTOPP_A_PATH `realpath libcryptopp.a` +env CRYPTOPP_INCLUDE_PATH `realpath .` +cd "${CRYPTOFUZZ_SRC}/modules/cryptopp/" +make -j$(nproc) + +# Extract the seed corpus, docker layers are already compressed so this won't use any more space and save time when running +cd "$FUZZ_ROOT" +unzip cryptofuzz_data.zip +rm cryptofuzz_data.zip +env CRYPTOFUZZ_SEED_CORPUS `realpath cryptofuzz_seed_corpus` +env CRYPTOFUZZ_DICT `realpath cryptofuzz-dict.txt` + +# Save final common flags +env CFLAGS "$CFLAGS" +env CXXFLAGS "$CXXFLAGS" +env CRYPTOFUZZ_SRC "$CRYPTOFUZZ_SRC" + +# Cryptofuzz builds its modules into $CRYPTOFUZZ_SRC/modules that includes everything it needs, deleting the module source +# code saves a substantial amount of space in the docker image +rm -rf "$MODULES_ROOT" + +# Prebuild the required libcpu_features to save time +cd "$CRYPTOFUZZ_SRC" +make third_party/cpu_features/build/libcpu_features.a diff --git a/tests/ci/docker_images/dependencies/cryptofuzz_data.zip b/tests/ci/docker_images/dependencies/cryptofuzz_data.zip new file mode 100644 index 00000000..2fd98997 Binary files /dev/null and b/tests/ci/docker_images/dependencies/cryptofuzz_data.zip differ diff --git a/tests/ci/docker_images/linux-x86/amazonlinux-2_clang-11x/Dockerfile b/tests/ci/docker_images/linux-x86/amazonlinux-2_clang-11x/Dockerfile new file mode 100644 index 00000000..c5744f36 --- /dev/null +++ b/tests/ci/docker_images/linux-x86/amazonlinux-2_clang-11x/Dockerfile @@ -0,0 +1,19 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 OR ISC + +FROM amazonlinux-2023:base + +SHELL ["/bin/bash", "-c"] + +# clang 15.0.6 is the latest version versions `yum --showduplicates list clang` +RUN set -ex && \ + dnf -y upgrade --releasever=latest && dnf install -y \ + clang && \ + dnf clean packages && \ + dnf clean metadata && \ + dnf clean all && \ + rm -rf /tmp/* && \ + rm -rf /var/cache/dnf + +ENV CC=clang +ENV CXX=clang++ diff --git a/tests/ci/docker_images/linux-x86/amazonlinux-2_clang-11x_cryptofuzz/Dockerfile b/tests/ci/docker_images/linux-x86/amazonlinux-2_clang-11x_cryptofuzz/Dockerfile new file mode 100644 index 00000000..f3c6c599 --- /dev/null +++ b/tests/ci/docker_images/linux-x86/amazonlinux-2_clang-11x_cryptofuzz/Dockerfile @@ -0,0 +1,26 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 OR ISC + +FROM amazonlinux-2023:clang-15x + +SHELL ["/bin/bash", "-c"] + +RUN set -ex && \ + dnf -y upgrade --releasever=latest && dnf install -y \ + libtool \ + boost-devel \ + unzip \ + llvm \ + llvm-devel \ + lld && \ + dnf clean packages && \ + dnf clean metadata && \ + dnf clean all && \ + rm -rf /tmp/* && \ + rm -rf /var/cache/dnf + +ENV FUZZ_ROOT=${DEPENDENCIES_DIR} +ENV MODULE_ROOT="${FUZZ_ROOT}/modules" + +COPY build_cryptofuzz_modules.sh cryptofuzz_data.zip $FUZZ_ROOT/ +RUN set -ex && cd $FUZZ_ROOT && "./build_cryptofuzz_modules.sh" diff --git a/tests/ci/run_cryptofuzz.sh b/tests/ci/run_cryptofuzz.sh new file mode 100755 index 00000000..45e829ae --- /dev/null +++ b/tests/ci/run_cryptofuzz.sh @@ -0,0 +1,78 @@ +#!/bin/bash +set -exo pipefail +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 OR ISC + +# Sourcing these files check for environment variables which may be unset so wait to enable -u +source tests/ci/common_fuzz.sh +source "${FUZZ_ROOT}/fuzz_env.sh" +# After loading everything any undefined variables should fail the build +set -u + +rm -rf "$BUILD_ROOT" +mkdir -p "$BUILD_ROOT" +cd "$BUILD_ROOT" + +# Build AWS-LC based on https://github.com/guidovranken/cryptofuzz/blob/master/docs/openssl.md +${CMAKE_COMMAND} -DCMAKE_CXX_FLAGS="$CXXFLAGS" -DCMAKE_C_FLAGS="$CFLAGS" -DBORINGSSL_ALLOW_CXX_RUNTIME=1 \ + -GNinja -DBUILD_TESTING=OFF -DBUILD_LIBSSL=OFF -DCMAKE_BUILD_TYPE=RelWithDebInfo ../ +ninja +cd ../ + +# CRYPTOFUZZ_AWSLC will enable AES_256_XTS in upstream code. See `V610453347`. +# TODO: remove CRYPTOFUZZ_BORINGSSL when CRYPTOFUZZ_AWSLC is fully integrated. +export CXXFLAGS="$CXXFLAGS -DCRYPTOFUZZ_BORINGSSL -DCRYPTOFUZZ_AWSLC" +export OPENSSL_INCLUDE_PATH=`realpath include/` +export OPENSSL_LIBCRYPTO_A_PATH=`realpath ${BUILD_ROOT}/crypto/libcrypto.a` + +# For cryptofuzz development only, override CRYPTOFUZZ_SRC with CUSTOM_CRYPTOFUZZ_REPO_DIR. +CUSTOM_CRYPTOFUZZ_REPO_DIR='' +if [[ -z "${CUSTOM_CRYPTOFUZZ_REPO_DIR}" ]]; then + echo "CUSTOM_CRYPTOFUZZ_REPO_DIR is empty." +else + export CRYPTOFUZZ_SRC="${CUSTOM_CRYPTOFUZZ_REPO_DIR}" + # Local development does not need differential fuzzing. + # Remove these libs related build flags. + export CXXFLAGS="${CXXFLAGS// -DCRYPTOFUZZ_BOTAN/}" + export CXXFLAGS="${CXXFLAGS// -DCRYPTOFUZZ_CRYPTOPP/}" + cd "$CRYPTOFUZZ_SRC" + # This step is to generate required header and cpp files. + python3 gen_repository.py +fi + +# Build the common OpenSSL module with AWS-LC +cd "${CRYPTOFUZZ_SRC}/modules/openssl" +make "-j${NUM_CPU_THREADS}" + +export CFLAGS="${CFLAGS} -I $OPENSSL_INCLUDE_PATH" +export CXXFLAGS="${CXXFLAGS} -I $OPENSSL_INCLUDE_PATH" +export LIBFUZZER_LINK="-fsanitize=fuzzer" + +# Build the overall cryptofuzz and generate_corpus binary +cd "$CRYPTOFUZZ_SRC" +rm -rf cryptofuzz +rm -rf generate_corpus +make "-j${NUM_CPU_THREADS}" cryptofuzz generate_corpus + +# Common AWS-LC fuzzing setup, the cryptofuzz binary is in this folder so FUZZ_TEST_PATH=FUZZ_NAME +FUZZ_NAME="cryptofuzz" +FUZZ_TEST_PATH="${CRYPTOFUZZ_SRC}/${FUZZ_NAME}" +SRC_CORPUS="$CRYPTOFUZZ_SEED_CORPUS" + +# For cryptofuzz development only, uncomment below code to generate new corpus. +# rm -rf "$CRYPTOFUZZ_SEED_CORPUS" && mkdir "$CRYPTOFUZZ_SEED_CORPUS" +# ./generate_corpus "$CRYPTOFUZZ_SEED_CORPUS" + +# Perform the actual fuzzing. We want the total build time to be about 45 minutes: +# 5 minutes for building AWS-LC and Cryptofuzz +# 16 minutes (1000 seconds) of fuzzing +# 24 minutes of cleanup and merging in new inputs +TIME_FOR_EACH_FUZZ=1000 + +# Some fuzz tests can take a while but still pass. This is a tradeoff: less false positive noise, but some inputs that take +# a long time could lead to a denial of service avenue. We're mostly interested in correctness and memory safety at this +# time so we're willing to take the fit on fuzz speed +FUZZ_TEST_TIMEOUT=30 + +# Call the common fuzzing logic +run_fuzz_test