From 0c95d9962cbae11e9c26ba0984dc6080bf1b93a5 Mon Sep 17 00:00:00 2001 From: Andrew Kaster Date: Sat, 19 Feb 2022 16:09:40 -0700 Subject: [PATCH] Lagom: Add two-stage build for Fuzzers to enable fuzzing generated code This allows us to fuzz the generated unicode and timezone database helpers, and to fuzz things like LibJS using Fuzzilli to get proper coverage of our unicode handling code. Update the Azure CI to use the new two-stage build as well, and cleanup some unused CMake options there. --- Meta/Azure/Lagom.yml | 26 +++++------- Meta/Lagom/BuildFuzzers.sh | 84 ++++++++++++++++++++++++++++++++++++++ Meta/Lagom/CMakeLists.txt | 41 +++++++++---------- Meta/Lagom/ReadMe.md | 18 ++++++-- 4 files changed, 129 insertions(+), 40 deletions(-) create mode 100755 Meta/Lagom/BuildFuzzers.sh diff --git a/Meta/Azure/Lagom.yml b/Meta/Azure/Lagom.yml index 5366468612..5fbe64de1e 100644 --- a/Meta/Azure/Lagom.yml +++ b/Meta/Azure/Lagom.yml @@ -43,42 +43,38 @@ jobs: ${{ if eq(parameters.fuzzer, 'NoFuzz') }}: with_remote_data_caches: true - - script: | - mkdir -p Meta/Lagom/Build - displayName: 'Create Build Directory' - - ${{ if eq(parameters.fuzzer, 'Fuzz') }}: - script: | - cmake -GNinja \ + cmake -GNinja -B tools-build \ + -DBUILD_LAGOM=OFF \ + -DENABLE_LAGOM_CCACHE=ON \ + -DCMAKE_INSTALL_PREFIX=tool-install + ninja -C tools-build install + cmake -GNinja -B Build \ -DBUILD_LAGOM=ON \ -DENABLE_LAGOM_CCACHE=ON \ -DENABLE_FUZZER_SANITIZER=ON \ -DENABLE_ADDRESS_SANITIZER=ON \ - -DENABLE_PCI_IDS_DOWNLOAD=OFF \ - -DENABLE_USB_IDS_DOWNLOAD=OFF \ -DCMAKE_C_COMPILER=clang \ -DCMAKE_CXX_COMPILER=clang++ \ - .. + -DCMAKE_PREFIX_PATH=tool-install displayName: 'Create Build Environment' - workingDirectory: $(Build.SourcesDirectory)/Meta/Lagom/Build + workingDirectory: $(Build.SourcesDirectory)/Meta/Lagom env: CCACHE_DIR: '$(SERENITY_CCACHE_DIR)' - ${{ if eq(parameters.fuzzer, 'NoFuzz') }}: - script: | - cmake -GNinja \ + cmake -GNinja -B Build \ -DBUILD_LAGOM=ON \ -DENABLE_LAGOM_CCACHE=ON \ -DINCLUDE_WASM_SPEC_TESTS=ON \ -DWASM_SPEC_TEST_SKIP_FORMATTING=ON \ -DENABLE_UNDEFINED_SANITIZER=ON \ -DENABLE_ADDRESS_SANITIZER=ON \ - -DENABLE_PCI_IDS_DOWNLOAD=OFF \ - -DENABLE_USB_IDS_DOWNLOAD=OFF \ -DCMAKE_C_COMPILER=gcc-11 \ - -DCMAKE_CXX_COMPILER=g++-11 \ - .. + -DCMAKE_CXX_COMPILER=g++-11 displayName: 'Create Build Environment' - workingDirectory: $(Build.SourcesDirectory)/Meta/Lagom/Build + workingDirectory: $(Build.SourcesDirectory)/Meta/Lagom env: PATH: '$(PATH):$(Build.SourcesDirectory)/wabt-1.0.23/bin' CCACHE_DIR: '$(SERENITY_CCACHE_DIR)' diff --git a/Meta/Lagom/BuildFuzzers.sh b/Meta/Lagom/BuildFuzzers.sh new file mode 100755 index 0000000000..78371d8fdf --- /dev/null +++ b/Meta/Lagom/BuildFuzzers.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash + +set -e + +BEST_CLANG_CANDIDATE="" + +die() { + >&2 echo "die: $*" + exit 1 +} + +pick_clang() { + local BEST_VERSION=0 + for CLANG_CANDIDATE in clang clang-13 clang-14 /usr/local/bin/clang-13 /usr/local/bin/clang-14; do + if ! command -v $CLANG_CANDIDATE >/dev/null 2>&1; then + continue + fi + if $CLANG_CANDIDATE --version 2>&1 | grep "Apple clang" >/dev/null; then + continue + fi + if ! $CLANG_CANDIDATE -dumpversion >/dev/null 2>&1; then + continue + fi + local VERSION="" + VERSION="$($CLANG_CANDIDATE -dumpversion)" + local MAJOR_VERSION="${VERSION%%.*}" + if [ "$MAJOR_VERSION" -gt "$BEST_VERSION" ]; then + BEST_VERSION=$MAJOR_VERSION + BEST_CLANG_CANDIDATE="$CLANG_CANDIDATE" + fi + done + if [ "$BEST_VERSION" -lt 13 ]; then + die "Please make sure that Clang version 13 or higher is installed." + fi +} + +# Save flags for oss-fuzz to avoid fuzzing Tools/ +# https://google.github.io/oss-fuzz/getting-started/new-project-guide/#temporarily-disabling-code-instrumentation-during-builds +CFLAGS_SAVE="$CFLAGS" +CXXFLAGS_SAVE="$CXXFLAGS" +unset CFLAGS +unset CXXFLAGS +export AFL_NOOPT=1 + +# FIXME: Replace these CMake invocations with a CMake superbuild? +echo "Building Lagom Tools..." +cmake -GNinja -B Build/tools \ + -DBUILD_LAGOM=OFF \ + -DCMAKE_INSTALL_PREFIX=Build/tool-install +ninja -C Build/tools install + +# Restore flags for oss-fuzz +export CFLAGS="${CFLAGS_SAVE}" +export CXXFLAGS="${CXXFLAGS_SAVE}" +unset AFL_NOOPT + +echo "Building Lagom Fuzzers..." + +if [ "$#" -gt "0" ] && [ "--oss-fuzz" = "$1" ] ; then + echo "Building for oss-fuzz configuration..." + cmake -GNinja -B Build/fuzzers \ + -DBUILD_LAGOM=ON \ + -DBUILD_SHARED_LIBS=OFF \ + -DENABLE_OSS_FUZZ=ON \ + -DCMAKE_C_COMPILER="$CC" \ + -DCMAKE_CXX_COMPILER="$CXX" \ + -DCMAKE_CXX_FLAGS="$CXXFLAGS -DOSS_FUZZ=ON" \ + -DLINKER_FLAGS="$LIB_FUZZING_ENGINE" \ + -DCMAKE_PREFIX_PATH=Build/tool-install + ninja -C Build/fuzzers + cp Build/fuzzers/Fuzzers/Fuzz* "$OUT"/ +else + echo "Building for local fuzz configuration..." + pick_clang + cmake -GNinja -B Build/lagom-fuzzers \ + -DBUILD_LAGOM=ON \ + -DENABLE_FUZZER_SANITIZER=ON \ + -DENABLE_ADDRESS_SANITIZER=ON \ + -DENABLE_UNDEFINED_SANITIZER=ON \ + -DCMAKE_PREFIX_PATH=Build/tool-install \ + -DCMAKE_C_COMPILER=$BEST_CLANG_CANDIDATE \ + -DCMAKE_CXX_COMPILER="${BEST_CLANG_CANDIDATE/clang/clang++}" + ninja -C Build/lagom-fuzzers +fi diff --git a/Meta/Lagom/CMakeLists.txt b/Meta/Lagom/CMakeLists.txt index 2b42dfa1fb..32b4eed19b 100644 --- a/Meta/Lagom/CMakeLists.txt +++ b/Meta/Lagom/CMakeLists.txt @@ -41,14 +41,8 @@ endif() # FIXME: BUILD_SHARED_LIBS has a default of OFF, as it's intended to be set by the # user when configuring the project. We should instead change libjs-test262 # and oss-fuzz to set this option on their end, and enable it by default in -# Meta/serenity.sh -# This is #9867. We can change the oss-fuzz escape hatch to be a FATAL_ERROR -# message instead when implementing it. +# Meta/serenity.sh. This is #9867. option(BUILD_SHARED_LIBS "Build shared libraries instead of static libraries" ON) -if (ENABLE_OSS_FUZZ) - # Don't use shared libraries on oss-fuzz, for ease of integration with their infrastructure - set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build shared libraries instead of static libraries" FORCE) -endif() find_package(Threads REQUIRED) @@ -120,6 +114,12 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "Clang$") elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") add_compile_options(-Wno-expansion-to-defined) + if (ENABLE_FUZZER_SANITIZER) + message(FATAL_ERROR + "Fuzzer Sanitizer (-fsanitize=fuzzer) is only supported for Fuzzer targets with LLVM. " + "Reconfigure CMake with -DCMAKE_C_COMPILER and -DCMAKE_CXX_COMPILER pointing to a clang-based toolchain" + ) + endif() endif() # These are here to support Fuzzili builds further down the directory stack @@ -168,8 +168,13 @@ function(lagom_lib library fs_name) cmake_parse_arguments(LAGOM_LIBRARY "" "" "SOURCES;LIBS" ${ARGN}) set(target_name "Lagom${library}") add_library(${target_name} ${LAGOM_LIBRARY_SOURCES}) - # alias for parity with exports - add_library(Lagom::${library} ALIAS ${target_name}) + + # Don't make alias when we're going to import a previous build for Tools + # FIXME: Is there a better way to write this? + if (NOT ENABLE_OSS_FUZZ AND NOT ENABLE_FUZZER_SANITIZER) + # alias for parity with exports + add_library(Lagom::${library} ALIAS ${target_name}) + endif() set_target_properties( ${target_name} PROPERTIES @@ -247,11 +252,7 @@ endif() # TimeZone # This is needed even if Lagom is not enabled because it is depended upon by code generators. -if (NOT ENABLE_OSS_FUZZ AND NOT ENABLE_FUZZER_SANITIZER) - include(time_zone_data) -else() - set(ENABLE_TIME_ZONE_DATABASE_DOWNLOAD OFF) -endif() +include(time_zone_data) file(GLOB LIBTIMEZONE_SOURCES CONFIGURE_DEPENDS "../../Userland/Libraries/LibTimeZone/*.cpp") lagom_lib(TimeZone timezone SOURCES ${LIBTIMEZONE_SOURCES} ${TIME_ZONE_DATA_SOURCES} @@ -268,7 +269,10 @@ install( # Code Generators and other host tools # We need to make sure not to build code generators for Fuzzer builds, as they already have their own main.cpp -if (NOT ENABLE_OSS_FUZZ AND NOT ENABLE_FUZZER_SANITIZER) +# Instead, we import them from a previous install of Lagom. This mandates a two-stage build for fuzzers. +if (ENABLE_OSS_FUZZ OR ENABLE_FUZZER_SANITIZER) + find_package(Lagom REQUIRED) +else() add_subdirectory(Tools) endif() @@ -442,12 +446,7 @@ if (BUILD_LAGOM) ) # Unicode - # Don't include UnicodeData for Fuzzer builds, we didn't build the CodeGenerators - if (NOT ENABLE_OSS_FUZZ AND NOT ENABLE_FUZZER_SANITIZER) - include(unicode_data) - else() - set(ENABLE_UNICODE_DATABASE_DOWNLOAD OFF) - endif() + include(unicode_data) file(GLOB LIBUNICODE_SOURCES CONFIGURE_DEPENDS "../../Userland/Libraries/LibUnicode/*.cpp") lagom_lib(Unicode unicode SOURCES ${LIBUNICODE_SOURCES} ${UNICODE_DATA_SOURCES} diff --git a/Meta/Lagom/ReadMe.md b/Meta/Lagom/ReadMe.md index aee06fef36..dcc4c6d9a3 100644 --- a/Meta/Lagom/ReadMe.md +++ b/Meta/Lagom/ReadMe.md @@ -14,22 +14,32 @@ Lagom can be used to fuzz parts of SerenityOS's code base. Fuzzers can be run lo ### Fuzzing locally -Lagom can be used to fuzz parts of SerenityOS's code base. This requires building with `clang`, so it's convenient to use a different build directory for that. Fuzzers work best with Address Sanitizer enabled. Run CMake like this: +Lagom can be used to fuzz parts of SerenityOS's code base. This requires building with `clang`, so it's convenient to use a different build directory for that. Fuzzers work best with Address Sanitizer enabled. The fuzzer build requires code generators to be pre-built without fuzzing in a two stage build. To build with LLVM's libFuzzer, invoke +the ``BuildFuzzers.sh`` script with no arguments. The script does the equivalent of the CMake commands below: - # From the root of the SerenityOS checkout: - cmake -GNinja -S Meta/Lagom -B Build/lagom-fuzzers \ +```sh + # From the Meta/Lagom directory: + # Stage 1: Build and install code generators and other tools + cmake -GNinja -B Build/tools \ + -DBUILD_LAGOM=OFF \ + -DCMAKE_INSTALL_PREFIX=Build/tool-install + ninja -C Build/tools install + # Stage 2: Build fuzzers, making sure the build can find the tools we just built + cmake -GNinja -B Build/lagom-fuzzers \ -DBUILD_LAGOM=ON \ -DENABLE_FUZZER_SANITIZER=ON \ -DENABLE_ADDRESS_SANITIZER=ON \ -DENABLE_UNDEFINED_SANITIZER=ON \ + -DCMAKE_PREFIX_PATH=Build/tool-install \ -DCMAKE_CXX_COMPILER=clang++ \ -DCMAKE_C_COMPILER=clang cd Build/lagom-fuzzers ninja # Or as a handy rebuild-rerun line: ninja FuzzJs && ./Fuzzers/FuzzJs +``` -(Note that we require clang >= 12, so depending on your package manager you may need to specify `clang++-12` and `clang-12` instead.) +(Note that we require clang >= 13, see the pick_clang() function in the script for the paths that are searched) Any fuzzing results (particularly slow inputs, crashes, etc.) will be dropped in the current directory.