diff --git a/.github/workflows/bazel_test.yml b/.github/workflows/bazel_test.yml deleted file mode 100644 index 93e32d9a..00000000 --- a/.github/workflows/bazel_test.yml +++ /dev/null @@ -1,72 +0,0 @@ -# Copyright 2022 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. - -name: Bazel Test - -on: - push: - branches: - - main - pull_request: - schedule: - # Triggered nightly at 8:00 AM UTC to reset the Bazel cache. - - cron: '0 8 * * *' - workflow_dispatch: - -jobs: - run_tests: - name: Run tests - runs-on: ubuntu-latest - timeout-minutes: 60 - strategy: - matrix: - config: ['default', 'fuzztest'] - compilation_mode: ['fastbuild', 'opt', 'dbg'] - steps: - # TODO(lszekeres): Remove once the following is fixed: - # https://github.com/actions/runner-images/issues/9491 - - name: Reduce ASLR entropy as a temporary workaround - run: | - sudo sysctl -w vm.mmap_rnd_bits=28 - - name: Checkout repository - uses: actions/checkout@v4 - - name: Install dependencies - run: | - sudo apt-get update && sudo apt-get install -yq \ - clang - - name: Setup --config=fuzztest for Bazel - run: | - bazel run //bazel:setup_configs > fuzztest.bazelrc - - name: Restore latest cache - uses: actions/cache/restore@v4 - with: - path: "~/.cache/bazel" - key: bazel-cache-${{ matrix.config }}-${{ matrix.compilation_mode }} - restore-keys: bazel-cache-${{ matrix.config }}-${{ matrix.compilation_mode }}- - - name: Run all tests with default --config - if: matrix.config == 'default' - run: | - bazel test --build_tests_only --test_output=errors \ - -c ${{ matrix.compilation_mode }} -- //... -//centipede/... - - name: Run end-to-end tests with --config=fuzztest - if: matrix.config == 'fuzztest' - run: | - bazel test --build_tests_only --test_output=errors \ - -c ${{ matrix.compilation_mode }} --config=fuzztest //e2e_tests:all - - name: Save new cache based on main - if: github.ref == 'refs/heads/main' - uses: actions/cache/save@v4 - with: - path: "~/.cache/bazel" - key: bazel-cache-${{ matrix.config }}-${{ matrix.compilation_mode }}-${{ github.run_id }} diff --git a/.github/workflows/bazel_test_centipede.yml b/.github/workflows/bazel_test_centipede.yml index e1e9dfd9..9fc01a6d 100644 --- a/.github/workflows/bazel_test_centipede.yml +++ b/.github/workflows/bazel_test_centipede.yml @@ -25,66 +25,44 @@ on: workflow_dispatch: jobs: - run_tests: - name: Run Centipede tests - runs-on: ubuntu-latest + run_tests_mac: + name: Run Centipede tests (MacOS) + runs-on: macos-13 timeout-minutes: 60 strategy: matrix: - config: ['default', 'noriegeli', 'asan'] + config: ['noriegeli'] steps: - # TODO(lszekeres): Remove once the following is fixed: - # https://github.com/actions/runner-images/issues/9491 - - name: Reduce ASLR entropy as a temporary workaround - run: | - sudo sysctl -w vm.mmap_rnd_bits=28 - name: Checkout repository uses: actions/checkout@v4 - - name: Install dependencies - run: | - sudo apt-get update && sudo apt-get install -yq \ - clang llvm libssl-dev - name: Restore latest cache uses: actions/cache/restore@v4 with: path: "~/.cache/bazel" - key: bazel-centipede-cache-${{ matrix.config }} - restore-keys: bazel-centipede-cache-${{ matrix.config }}- - - name: Run unit tests - if: matrix.config == 'default' - run: | - bazel test --local_test_jobs=1 --test_output=streamed centipede:all - - name: Run e2e tests - if: matrix.config == 'default' - run: | - bazel test --test_output=errors centipede/testing:instrumentation_test centipede/testing:runner_test - - name: Run puzzles - if: matrix.config == 'default' + key: bazel-centipede-cache-mac-${{ matrix.config }} + restore-keys: bazel-centipede-cache-mac-${{ matrix.config }}- + - name: Add LLVM symbolizer to path run: | - bazel test --test_output=errors centipede/puzzles:all - - name: Run puzzles with ASAN - if: matrix.config == 'asan' - run: | - bazel test --test_output=errors --linkopt=-fsanitize=address --copt=-fsanitize=address centipede/puzzles:all - - name: Run unit tests without Riegeli - if: matrix.config == 'noriegeli' - run: | - bazel test --no//fuzztest:use_riegeli --local_test_jobs=1 --test_output=streamed centipede:all + ln -s $(brew --prefix llvm@15)/bin/llvm-symbolizer /usr/local/bin + # - name: Run unit tests + # if: ${{ !cancelled() && matrix.config == 'noriegeli' }} + # run: | + # bazel test --local_test_jobs=1 --test_output=errors --no//fuzztest:use_riegeli --linkopt=-Wl,-undefined,dynamic_lookup centipede:all - name: Run e2e tests without Riegeli - if: matrix.config == 'noriegeli' + if: ${{ !cancelled() && matrix.config == 'noriegeli' }} run: | - bazel test --no//fuzztest:use_riegeli --test_output=errors centipede/testing:instrumentation_test centipede/testing:runner_test + bazel test --test_output=streamed --no//fuzztest:use_riegeli --linkopt=-Wl,-undefined,dynamic_lookup centipede/testing:instrumentation_test centipede/testing:runner_test - name: Run puzzles without Riegeli - if: matrix.config == 'noriegeli' - run: | - bazel test --no//fuzztest:use_riegeli --test_output=errors centipede/puzzles:all - - name: Run puzzles without Riegeli with ASAN - if: matrix.config == 'noriegeli' + if: ${{ !cancelled() && matrix.config == 'noriegeli' }} run: | - bazel test --no//fuzztest:use_riegeli --test_output=errors --linkopt=-fsanitize=address --copt=-fsanitize=address centipede/puzzles:all + bazel test --test_output=streamed --no//fuzztest:use_riegeli --linkopt=-Wl,-undefined,dynamic_lookup centipede/puzzles:run_1_per_input_timeout + # - name: Run puzzles without Riegeli with ASAN + # if: ${{ !cancelled() && matrix.config == 'noriegeli' }} + # run: | + # bazel test --no//fuzztest:use_riegeli --test_output=errors --linkopt=-Wl,-undefined,dynamic_lookup --linkopt=-fsanitize=address --copt=-fsanitize=address centipede/puzzles:all - name: Save new cache based on main if: github.ref == 'refs/heads/main' uses: actions/cache/save@v4 with: path: "~/.cache/bazel" - key: bazel-centipede-cache-${{ matrix.config }}-${{ github.run_id }} + key: bazel-centipede-cache-mac-${{ matrix.config }}-${{ github.run_id }} diff --git a/.github/workflows/cmake_test.yml b/.github/workflows/cmake_test.yml deleted file mode 100644 index d04aa0d7..00000000 --- a/.github/workflows/cmake_test.yml +++ /dev/null @@ -1,100 +0,0 @@ -# Copyright 2022 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License 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. - -name: CMake Test - -on: - push: - branches: - - main - pull_request: - schedule: - # Triggered nightly at 8:00 AM UTC to re-create the build cache (ccache). - - cron: '0 8 * * *' - workflow_dispatch: - -jobs: - run_tests: - name: Run CMake tests - runs-on: ubuntu-latest - timeout-minutes: 30 - env: - CCACHE_BASEDIR: ${{ github.workspace }} - CCACHE_DIR: ${{ github.workspace }}/.ccache - strategy: - matrix: - mode: ['default', 'fuzzing', 'codelab'] - steps: - # TODO(lszekeres): Remove once the following is fixed: - # https://github.com/actions/runner-images/issues/9491 - - name: Reduce ASLR entropy as a temporary workaround - run: | - sudo sysctl -w vm.mmap_rnd_bits=28 - - name: Checkout repository - uses: actions/checkout@v4 - - name: Install dependencies - run: | - sudo apt-get update && sudo apt-get install -yq \ - ccache clang cmake ninja-build libprotobuf-dev protobuf-compiler - - name: Restore latest cache - uses: actions/cache/restore@v4 - with: - path: ${{ env.CCACHE_DIR }} - key: cmake-cache-${{ matrix.mode }} - restore-keys: cmake-cache-${{ matrix.mode }}- - - name: Check whether FuzzTest can be build using FetchContent - if: matrix.mode == 'codelab' - run: | - CC=clang CXX=clang++ cmake \ - -S codelab \ - -B build_codelab \ - -G Ninja \ - -D CMAKE_C_COMPILER_LAUNCHER=ccache \ - -D CMAKE_CXX_COMPILER_LAUNCHER=ccache \ - -D CMAKE_BUILD_TYPE=RelWithDebug \ - -D FUZZTEST_REPO_BRANCH="${{ github.head_ref || github.ref_name }}" \ - && cmake --build build_codelab -j $(nproc) - - name: Run all tests in default mode - if: matrix.mode == 'default' - run: | - CC=clang CXX=clang++ cmake \ - -S . \ - -B build \ - -G Ninja \ - -D CMAKE_C_COMPILER_LAUNCHER=ccache \ - -D CMAKE_CXX_COMPILER_LAUNCHER=ccache \ - -D CMAKE_BUILD_TYPE=RelWithDebug \ - -D FUZZTEST_BUILD_TESTING=on \ - && cmake --build build -j $(nproc) \ - && ctest --test-dir build -j $(nproc) --output-on-failure - - name: Run end-to-end tests in fuzzing mode - if: matrix.mode == 'fuzzing' - run: | - CC=clang CXX=clang++ cmake \ - -S . \ - -B build \ - -G Ninja \ - -D CMAKE_C_COMPILER_LAUNCHER=ccache \ - -D CMAKE_CXX_COMPILER_LAUNCHER=ccache \ - -D CMAKE_BUILD_TYPE=RelWithDebug \ - -D FUZZTEST_FUZZING_MODE=on \ - -D FUZZTEST_BUILD_TESTING=on \ - && cmake --build build -j $(nproc) \ - && ctest --test-dir build -j $(nproc) --output-on-failure -R "functional_test" - - name: Save new cache based on main - if: github.ref == 'refs/heads/main' - uses: actions/cache/save@v4 - with: - path: ${{ env.CCACHE_DIR }} - key: cmake-cache-${{ matrix.mode }}-${{ github.run_id }} diff --git a/WORKSPACE b/WORKSPACE index 75dd5391..886a33e0 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -27,6 +27,15 @@ http_archive( url = "https://github.com/abseil/abseil-cpp/releases/download/20240116.0/abseil-cpp-20240116.0.tar.gz" ) +http_archive( + name = "platforms", + urls = [ + "https://mirror.bazel.build/github.com/bazelbuild/platforms/releases/download/0.0.10/platforms-0.0.10.tar.gz", + "https://github.com/bazelbuild/platforms/releases/download/0.0.10/platforms-0.0.10.tar.gz", + ], + sha256 = "218efe8ee736d26a3572663b374a253c012b716d8af0c07e842e82f238a0a7ee", +) + ################################################################################ # Transitive dependencies for core FuzzTest ################################################################################ diff --git a/centipede/BUILD b/centipede/BUILD index cfa08753..333e9563 100644 --- a/centipede/BUILD +++ b/centipede/BUILD @@ -351,7 +351,12 @@ cc_library( name = "stats", srcs = ["stats.cc"], hdrs = ["stats.h"], - linkopts = ["-latomic"], # for std::atomic::load()/store(). + linkopts = select({ + "@platforms//os:macos": [], + "//conditions:default": [ + "-latomic", # for std::atomic::load()/store(). + ], + }), deps = [ ":environment", ":workdir", @@ -420,7 +425,12 @@ cc_library( name = "shared_memory_blob_sequence", srcs = ["shared_memory_blob_sequence.cc"], hdrs = ["shared_memory_blob_sequence.h"], - linkopts = ["-lrt"], # for shm_open. + linkopts = select({ + "@platforms//os:macos": [], + "//conditions:default": [ + "-lrt", # for shm_open + ], + }), visibility = PUBLIC_API_VISIBILITY, deps = ["@com_google_absl//absl/base:nullability"], # don't add any dependencies. @@ -702,7 +712,12 @@ cc_library( name = "early_exit", srcs = ["early_exit.cc"], hdrs = ["early_exit.h"], - linkopts = ["-latomic"], # for std::atomic::load()/store(). + linkopts = select({ + "@platforms//os:macos": [], + "//conditions:default": [ + "-latomic", # for std::atomic::load()/store(). + ], + }), visibility = PUBLIC_API_VISIBILITY, ) @@ -1015,9 +1030,13 @@ RUNNER_SANITIZED_COPTS = [ RUNNER_LINKOPTS = [ "-ldl", # for dlsym - "-lrt", # for shm_open "-lpthread", # for pthread_once -] +] + select({ + "@platforms//os:macos": [], + "//conditions:default": [ + "-lrt", # for shm_open + ], +}) # WARNING: be careful with more deps here. Use only the most trivial ones. RUNNER_DEPS = [ @@ -1162,8 +1181,11 @@ sh_library( sh_library( name = "test_util_sh", srcs = ["test_util.sh"], - data = [ - ], + data = select({ + "@platforms//os:macos": [], + "//conditions:default": [ + ], + }), ) ################################################################################ diff --git a/centipede/centipede_callbacks.cc b/centipede/centipede_callbacks.cc index e3934324..ddcdd22a 100644 --- a/centipede/centipede_callbacks.cc +++ b/centipede/centipede_callbacks.cc @@ -139,8 +139,8 @@ Command &CentipedeCallbacks::GetOrCreateCommandForBinary( Command &cmd = commands_.emplace_back(Command( /*path=*/binary, /*args=*/{}, /*env=*/env, - /*out=*/execute_log_path_, - /*err=*/execute_log_path_, + /*out=*/"", + /*err=*/"", /*timeout=*/amortized_timeout, /*temp_file_path=*/temp_input_file_path_)); if (env_.fork_server) cmd.StartForkServer(temp_dir_, Hash(binary)); diff --git a/centipede/centipede_callbacks.h b/centipede/centipede_callbacks.h index 628dd969..264dbc2a 100644 --- a/centipede/centipede_callbacks.h +++ b/centipede/centipede_callbacks.h @@ -176,8 +176,8 @@ class CentipedeCallbacks { std::filesystem::path(temp_dir_).append("log"); std::string failure_description_path_ = std::filesystem::path(temp_dir_).append("failure_description"); - const std::string shmem_name1_ = ProcessAndThreadUniqueID("/centipede-shm1-"); - const std::string shmem_name2_ = ProcessAndThreadUniqueID("/centipede-shm2-"); + const std::string shmem_name1_ = ProcessAndThreadUniqueID("/ctpd-shm1-"); + const std::string shmem_name2_ = ProcessAndThreadUniqueID("/ctpd-shm2-"); SharedMemoryBlobSequence inputs_blobseq_; SharedMemoryBlobSequence outputs_blobseq_; diff --git a/centipede/centipede_test.cc b/centipede/centipede_test.cc index 375416db..e0326338 100644 --- a/centipede/centipede_test.cc +++ b/centipede/centipede_test.cc @@ -173,23 +173,29 @@ TEST(Centipede, ReadFirstCorpusDir) { env.require_pc_table = false; // No PC table here. env.corpus_dir.push_back(corpus_dir.path()); - // First, generate corpus files in corpus_dir. - CentipedeMock mock_1(env); - MockFactory factory_1(mock_1); - CentipedeMain(env, factory_1); - ASSERT_EQ(mock_1.observed_1byte_inputs_.size(), 256); // all 1-byte seqs. - ASSERT_EQ(mock_1.observed_2byte_inputs_.size(), 65536); // all 2-byte seqs. - ASSERT_EQ(CountFilesInDir(env.corpus_dir[0]), - 512); // All 1-byte and 2-byte inputs. - - // Second, run without fuzzing using the same corpus_dir. - env.workdir = workdir_2.path(); - env.num_runs = 0; - CentipedeMock mock_2(env); - MockFactory factory_2(mock_2); - CentipedeMain(env, factory_2); - // Should observe all inputs in corpus_dir. - EXPECT_EQ(mock_2.num_inputs_, 512); + // Need to wrap each CentipedeMain in a scope to make sure the shmem is + // released before the next call. Otherwise it may fail in MacOS. + { + // First, generate corpus files in corpus_dir. + CentipedeMock mock_1(env); + MockFactory factory_1(mock_1); + CentipedeMain(env, factory_1); + ASSERT_EQ(mock_1.observed_1byte_inputs_.size(), 256); // all 1-byte seqs. + ASSERT_EQ(mock_1.observed_2byte_inputs_.size(), 65536); // all 2-byte seqs. + ASSERT_EQ(CountFilesInDir(env.corpus_dir[0]), + 512); // All 1-byte and 2-byte inputs. + } + + { + // Second, run without fuzzing using the same corpus_dir. + env.workdir = workdir_2.path(); + env.num_runs = 0; + CentipedeMock mock_2(env); + MockFactory factory_2(mock_2); + CentipedeMain(env, factory_2); + // Should observe all inputs in corpus_dir. + EXPECT_EQ(mock_2.num_inputs_, 512); + } } TEST(Centipede, DoesNotReadFirstCorpusDirIfOutputOnly) { @@ -204,24 +210,29 @@ TEST(Centipede, DoesNotReadFirstCorpusDirIfOutputOnly) { env.require_pc_table = false; // No PC table here. env.corpus_dir.push_back(corpus_dir.path()); - // First, generate corpus files in corpus_dir. - CentipedeMock mock_1(env); - MockFactory factory_1(mock_1); - CentipedeMain(env, factory_1); - ASSERT_EQ(mock_1.observed_1byte_inputs_.size(), 256); // all 1-byte seqs. - ASSERT_EQ(mock_1.observed_2byte_inputs_.size(), 65536); // all 2-byte seqs. - ASSERT_EQ(CountFilesInDir(env.corpus_dir[0]), - 512); // All 1-byte and 2-byte inputs. - - // Second, run without fuzzing using the same corpus_dir, but as output-only. - env.workdir = workdir_2.path(); - env.num_runs = 0; - env.first_corpus_dir_output_only = true; - CentipedeMock mock_2(env); - MockFactory factory_2(mock_2); - CentipedeMain(env, factory_2); - // Should observe no inputs other than the seed input {0}. - EXPECT_EQ(mock_2.num_inputs_, 1); + { + // First, generate corpus files in corpus_dir. + CentipedeMock mock_1(env); + MockFactory factory_1(mock_1); + CentipedeMain(env, factory_1); + ASSERT_EQ(mock_1.observed_1byte_inputs_.size(), 256); // all 1-byte seqs. + ASSERT_EQ(mock_1.observed_2byte_inputs_.size(), 65536); // all 2-byte seqs. + ASSERT_EQ(CountFilesInDir(env.corpus_dir[0]), + 512); // All 1-byte and 2-byte inputs. + } + + { + // Second, run without fuzzing using the same corpus_dir, but as + // output-only. + env.workdir = workdir_2.path(); + env.num_runs = 0; + env.first_corpus_dir_output_only = true; + CentipedeMock mock_2(env); + MockFactory factory_2(mock_2); + CentipedeMain(env, factory_2); + // Should observe no inputs other than the seed input {0}. + EXPECT_EQ(mock_2.num_inputs_, 1); + } } TEST(Centipede, SkipsOutputIfFirstCorpusDirIsEmptyPath) { @@ -785,31 +796,32 @@ TEST(Centipede, UndetectedCrashingInput) { env.require_pc_table = false; env.exit_on_crash = true; - UndetectedCrashingInputMock mock(env, kCrashingInputIdx); - MockFactory factory(mock); - CentipedeMain(env, factory); + { + UndetectedCrashingInputMock mock(env, kCrashingInputIdx); + MockFactory factory(mock); + CentipedeMain(env, factory); - // Verify that we see the expected inputs from the batch. - // The "crashes/unreliable_batch-" dir must contain all inputs from the - // batch that were executing during the session. - // We simply verify the number of saved inputs matches the number of executed - // inputs. - const auto crashing_input_hash = Hash(mock.crashing_input()); - const auto crashes_dir_path = std::filesystem::path(temp_dir.path()) - .append("crashes") - .append("crashing_batch-") - .concat(crashing_input_hash); - EXPECT_TRUE(std::filesystem::exists(crashes_dir_path)) << crashes_dir_path; - std::vector found_crash_file_names; - for (auto const &dir_ent : - std::filesystem::directory_iterator(crashes_dir_path)) { - found_crash_file_names.push_back(dir_ent.path().filename()); + // Verify that we see the expected inputs from the batch. + // The "crashes/unreliable_batch-" dir must contain all inputs from + // the batch that were executing during the session. We simply verify the + // number of saved inputs matches the number of executed inputs. + const auto crashing_input_hash = Hash(mock.crashing_input()); + const auto crashes_dir_path = std::filesystem::path(temp_dir.path()) + .append("crashes") + .append("crashing_batch-") + .concat(crashing_input_hash); + EXPECT_TRUE(std::filesystem::exists(crashes_dir_path)) << crashes_dir_path; + std::vector found_crash_file_names; + for (auto const &dir_ent : + std::filesystem::directory_iterator(crashes_dir_path)) { + found_crash_file_names.push_back(dir_ent.path().filename()); + } + // TODO(ussuri): Verify exact names/contents of the files, not just count. + EXPECT_EQ(found_crash_file_names.size(), kCrashingInputIdxInBatch + 1); + // Suspected input first, then every input in the batch (including the + // suspected input again). + EXPECT_EQ(mock.num_inputs_triaged(), kBatchSize + 1); } - // TODO(ussuri): Verify exact names/contents of the files, not just count. - EXPECT_EQ(found_crash_file_names.size(), kCrashingInputIdxInBatch + 1); - // Suspected input first, then every input in the batch (including the - // suspected input again). - EXPECT_EQ(mock.num_inputs_triaged(), kBatchSize + 1); // Verify that when `env.batch_triage_suspect_only` is set, only triage the // suspect. diff --git a/centipede/command.cc b/centipede/command.cc index c125798f..0d4b7e43 100644 --- a/centipede/command.cc +++ b/centipede/command.cc @@ -176,7 +176,7 @@ bool Command::StartForkServer(std::string_view temp_dir_path, CENTIPEDE_FORK_SERVER_FIFO1="%s" \ %s } & - echo -n $! > "%s" + printf "%%s" $! > "%s" )sh"; const std::string fork_server_command = absl::StrFormat( kForkServerCommandStub, fork_server_->fifo_path_[0], @@ -215,20 +215,24 @@ bool Command::StartForkServer(std::string_view temp_dir_path, return false; } - // The fork server has started and the comms pipes got opened successfully. - // Read the fork server's PID and the initial /proc//exe symlink pointing - // at the fork server's binary, written to the provided files by `command`. - // `Execute()` uses these to monitor the fork server health. std::string pid_str; ReadFromLocalFile(pid_file_path, pid_str); CHECK(absl::SimpleAtoi(pid_str, &fork_server_->pid_)) << VV(pid_str); - std::string proc_exe = absl::StrFormat("/proc/%d/exe", fork_server_->pid_); - if (stat(proc_exe.c_str(), &fork_server_->exe_stat_) != EXIT_SUCCESS) { - LogProblemInfo( - absl::StrCat("Fork server appears not running; will proceed without it " - "(failed to stat ", - proc_exe, ")")); - return false; + + // TODO(b/281882892): Disable for now. Find a proper solution later. + if constexpr (false) { + // The fork server has started and the comms pipes got opened successfully. + // Read the fork server's PID and the initial /proc//exe symlink + // pointing at the fork server's binary, written to the provided files by + // `command`. `Execute()` uses these to monitor the fork server health. + std::string proc_exe = absl::StrFormat("/proc/%d/exe", fork_server_->pid_); + if (stat(proc_exe.c_str(), &fork_server_->exe_stat_) != EXIT_SUCCESS) { + LogProblemInfo(absl::StrCat( + "Fork server appears not running; will proceed without it " + "(failed to stat ", + proc_exe, ")")); + return false; + } } return true; @@ -249,18 +253,18 @@ absl::Status Command::VerifyForkServerIsHealthy() { return absl::UnknownError(absl::StrCat( "Can't communicate with fork server, PID=", fork_server_->pid_)); } - // ...and it is a process with our expected binary, so it's practically - // guaranteed to be our original fork server process. - const std::string proc_exe = - absl::StrFormat("/proc/%d/exe", fork_server_->pid_); - struct stat proc_exe_stat = {}; - if (stat(proc_exe.c_str(), &proc_exe_stat) != EXIT_SUCCESS) { - return absl::UnknownError(absl::StrCat( - "Failed to stat fork server's /proc//exe symlink, PID=", - fork_server_->pid_)); - } // TODO(b/281882892): Disable for now. Find a proper solution later. if constexpr (false) { + // ...and it is a process with our expected binary, so it's practically + // guaranteed to be our original fork server process. + const std::string proc_exe = + absl::StrFormat("/proc/%d/exe", fork_server_->pid_); + struct stat proc_exe_stat = {}; + if (stat(proc_exe.c_str(), &proc_exe_stat) != EXIT_SUCCESS) { + return absl::UnknownError(absl::StrCat( + "Failed to stat fork server's /proc//exe symlink, PID=", + fork_server_->pid_)); + } if (proc_exe_stat.st_dev != fork_server_->exe_stat_.st_dev || proc_exe_stat.st_ino != fork_server_->exe_stat_.st_ino) { return absl::UnknownError(absl::StrCat( diff --git a/centipede/command_test.cc b/centipede/command_test.cc index 8558d333..b4ec6af7 100644 --- a/centipede/command_test.cc +++ b/centipede/command_test.cc @@ -124,7 +124,8 @@ TEST(CommandTest, ForkServer) { const std::string log = std::filesystem::path{test_tmpdir} / input; Command cmd(helper, {input}, {}, log, log); EXPECT_TRUE(cmd.StartForkServer(test_tmpdir, "ForkServer")); - EXPECT_EQ(WTERMSIG(cmd.Execute()), SIGABRT); + const int ret = cmd.Execute(); + EXPECT_EQ(WTERMSIG(ret), SIGABRT); std::string log_contents; ReadFromLocalFile(log, log_contents); EXPECT_EQ(log_contents, absl::Substitute("Got input: $0", input)); diff --git a/centipede/control_flow_test.cc b/centipede/control_flow_test.cc index 401dc4eb..844b791d 100644 --- a/centipede/control_flow_test.cc +++ b/centipede/control_flow_test.cc @@ -306,7 +306,7 @@ static void SymbolizeBinary(std::string_view test_dir, has_llvm_fuzzer_test_one_input = true; EXPECT_THAT( symbols.location(i), - testing::HasSubstr("centipede/testing/test_fuzz_target.cc:70")); + testing::HasSubstr("centipede/testing/test_fuzz_target.cc:71")); } } EXPECT_TRUE(has_llvm_fuzzer_test_one_input); diff --git a/centipede/environment.h b/centipede/environment.h index 47f4db66..d21392f5 100644 --- a/centipede/environment.h +++ b/centipede/environment.h @@ -54,7 +54,13 @@ struct Environment { bool serialize_shard_loads = false; size_t seed = 0; size_t prune_frequency = 100; +#ifdef __APPLE__ + // Address space limit is ignored on MacOS. + // Reference: https://bugs.chromium.org/p/chromium/issues/detail?id=853873#c2 + size_t address_space_limit_mb = 0; +#else // __APPLE__ size_t address_space_limit_mb = 8192; +#endif // __APPLE__ size_t rss_limit_mb = 4096; size_t timeout_per_input = 60; size_t timeout_per_batch = 0; @@ -98,7 +104,11 @@ struct Environment { std::string minimize_crash_file_path; bool batch_triage_suspect_only = false; size_t shmem_size_mb = 1024; +#ifdef __APPLE__ + bool use_posix_shmem = true; +#else bool use_posix_shmem = false; +#endif bool dry_run = false; bool save_binary_info = false; bool populate_binary_info = true; diff --git a/centipede/execution_metadata_test.cc b/centipede/execution_metadata_test.cc index ccfb565c..3a1acf2c 100644 --- a/centipede/execution_metadata_test.cc +++ b/centipede/execution_metadata_test.cc @@ -96,8 +96,9 @@ TEST(ExecutionMetadata, AppendCmpEntryReturnsFalseAndSkipsOnBadArgs) { TEST(ExecutionMetadata, ReadAndWriteKeepsCmpEntries) { ExecutionMetadata metadata_in; ASSERT_TRUE(metadata_in.AppendCmpEntry({1, 2}, {3, 4})); - SharedMemoryBlobSequence blobseq("test", /*size=*/1024, - /*use_posix_shmem=*/false); + std::vector blob_storage; + blob_storage.resize(1024); + BlobSequence blobseq(blob_storage.data(), blob_storage.size()); EXPECT_TRUE(metadata_in.Write(/*tag=*/1, blobseq)); blobseq.Reset(); Blob blob = blobseq.Read(); diff --git a/centipede/puzzles/deep_recursion.cc b/centipede/puzzles/deep_recursion.cc index bd6fb64e..a0c319e6 100644 --- a/centipede/puzzles/deep_recursion.cc +++ b/centipede/puzzles/deep_recursion.cc @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Centipede puzzle: deep recursion that may cause stack overflow. +// Centipede puzzle: use callstack features to reach deep recursion // clang-format off // RUN: Run --callstack_level=10 --use_cmp_features=0 --max_len=10 --num_runs=10000000 # NOLINT // RUN: SolutionIs ABCDEF @@ -21,25 +21,14 @@ #include #include +#include #include "absl/base/nullability.h" -// Set the stack size to something small (64K). -static struct rlimit rlim = {1 << 16, 1 << 16}; -static int unused = setrlimit(RLIMIT_STACK, &rlim); - -// Don't let the compiler be too smart. -static inline void BreakOptimization(absl::Nonnull arg) { - __asm__ __volatile__("" : : "r"(arg) : "memory"); -} - -// Causes deep recursion on inputs like "ABCDEFGHIJKLMNOPQRSTUVWXYZABC...". -// Has a large stack frame, so that a relatively shallow recursion will overflow -// the stack. +// Deep recursion triggered by 'ABCDEF...' void Recursive(const uint8_t *data, size_t size, size_t idx) { + if (idx > 5) std::abort(); if (idx >= size) return; - char large_stack_object[1024 * 16]; - BreakOptimization(&large_stack_object[0]); if (data[idx] == 'A' + (idx % 26)) Recursive(data, size, idx + 1); } diff --git a/centipede/puzzles/per_input_timeout.cc b/centipede/puzzles/per_input_timeout.cc index d2865430..64bbf2cb 100644 --- a/centipede/puzzles/per_input_timeout.cc +++ b/centipede/puzzles/per_input_timeout.cc @@ -19,9 +19,11 @@ #include #include +#include extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { if (size == 3 && data[0] == 'S' && data[1] == 'L' && data[2] == 'O') { + fprintf(stderr, "Going to sleep!\n"); sleep(1000); // Dies with timeout. } return 0; diff --git a/centipede/puzzles/run_puzzle.sh b/centipede/puzzles/run_puzzle.sh index 60894021..1d8be68e 100755 --- a/centipede/puzzles/run_puzzle.sh +++ b/centipede/puzzles/run_puzzle.sh @@ -71,7 +71,7 @@ function Run() { --timeout_per_input=10 \ --exit_on_crash \ "$@" \ - |& tee "${log}" + 2>&1 | tee "${log}" then # Centipede must exit with failure. return 1 diff --git a/centipede/resource_pool.cc b/centipede/resource_pool.cc index 957909ad..6dbb33c3 100644 --- a/centipede/resource_pool.cc +++ b/centipede/resource_pool.cc @@ -14,7 +14,9 @@ #include "./centipede/resource_pool.h" +#include #include +#include // NOLINT: For thread::get_id() only #include #include "absl/log/check.h" @@ -63,11 +65,13 @@ const absl::Status& ResourcePool::LeaseToken::status() const { template std::string ResourcePool::LeaseToken::id() const { - return absl::StrCat("lease_tid_", thread_id_, "_rid_", request_.id); + std::stringstream ss; + ss << thread_id_; + return absl::StrCat("lease_tid_", ss.str(), "_rid_", request_.id); } template -pid_t ResourcePool::LeaseToken::thread_id() const { +std::thread::id ResourcePool::LeaseToken::thread_id() const { return thread_id_; } diff --git a/centipede/resource_pool.h b/centipede/resource_pool.h index 097a3d91..3b818a52 100644 --- a/centipede/resource_pool.h +++ b/centipede/resource_pool.h @@ -21,6 +21,7 @@ #include #include +#include // NOLINT: for std::this_thread::get_id() #include "absl/base/thread_annotations.h" #include "absl/status/status.h" @@ -144,7 +145,7 @@ class ResourcePool { // A short description that can be used in logs. std::string id() const; // The thread ID that submitted the request. - pid_t thread_id() const; + std::thread::id thread_id() const; // The creation time and the age of the lease. absl::Time created_at() const; absl::Duration age() const; @@ -166,7 +167,7 @@ class ResourcePool { LeaseRequest request_ = {}; absl::Status status_ = absl::OkStatus(); mutable bool status_checked_ = false; - pid_t thread_id_ = ::syscall(__NR_gettid); + std::thread::id thread_id_ = std::this_thread::get_id(); absl::Time created_at_ = absl::Now(); }; diff --git a/centipede/runner.cc b/centipede/runner.cc index 77098736..8eb18354 100644 --- a/centipede/runner.cc +++ b/centipede/runner.cc @@ -23,8 +23,6 @@ #include "./centipede/runner.h" #include // NOLINT: use pthread to avoid extra dependencies. -#include -#include #include #include #include @@ -95,10 +93,6 @@ thread_local ThreadTerminationDetector termination_detector; } // namespace -// Use of the fixed init priority allows to call CentipedeRunnerMain -// from constructor functions (CentipedeRunnerMain needs to run after -// state constructor). -// Note: it must run after ForkServerCallMeVeryEarly, see comment there. GlobalRunnerState state __attribute__((init_priority(200))); // We use __thread instead of thread_local so that the compiler warns if // the initializer for `tls` is not a constant expression. @@ -193,8 +187,14 @@ void ThreadLocalRunnerState::OnThreadStop() { static size_t GetPeakRSSMb() { struct rusage usage = {}; if (getrusage(RUSAGE_SELF, &usage) != 0) return 0; +#ifdef __APPLE__ + // On MacOS, the unit seems to be byte according to experiment, while some + // documents mentioned KiB. This could depend on OS variants. + return usage.ru_maxrss >> 20; +#else // __APPLE__ // On Linux, ru_maxrss is in KiB return usage.ru_maxrss >> 10; +#endif // __APPLE__ } // Returns the current time in microseconds. @@ -281,7 +281,8 @@ __attribute__((noinline)) void CheckStackLimit(uintptr_t sp) { tls.top_frame_sp - sp > stack_limit) { if (stack_limit_exceeded.test_and_set()) return; fprintf(stderr, - "========= Stack limit exceeded: %" PRIuPTR " > %" PRIu64 + "========= Stack limit exceeded: %" PRIuPTR + " > %zu" " (byte); aborting\n", tls.top_frame_sp - sp, stack_limit); centipede::WriteFailureDescription( @@ -948,13 +949,17 @@ static size_t GetVmSizeInBytes() { // NOTE: Ignore any (unlikely) failures to suppress a compiler warning. (void)fscanf(f, "%zd", &vm_size); fclose(f); - return vm_size * getauxval(AT_PAGESZ); // proc gives VmSize in pages. + return vm_size * getpagesize(); // proc gives VmSize in pages. } // Sets RLIMIT_CORE, RLIMIT_AS static void SetLimits() { - // no core files anywhere. - prctl(PR_SET_DUMPABLE, 0); + // Disable core dumping. + struct rlimit core_limits; + getrlimit(RLIMIT_CORE, &core_limits); + core_limits.rlim_cur = 0; + core_limits.rlim_max = 0; + setrlimit(RLIMIT_CORE, &core_limits); // ASAN/TSAN/MSAN can not be used with RLIMIT_AS. // We get the current VmSize, if it is greater than 1Tb, we assume we @@ -1010,6 +1015,9 @@ extern void RunnerInterceptor(); &RunnerInterceptor; GlobalRunnerState::GlobalRunnerState() { + // Make sure fork server is started if needed. + ForkServerCallMeVeryEarly(); + // TODO(kcc): move some code from CentipedeRunnerMain() here so that it works // even if CentipedeRunnerMain() is not called. tls.OnThreadStart(); diff --git a/centipede/runner.h b/centipede/runner.h index 4a4440d6..329f580c 100644 --- a/centipede/runner.h +++ b/centipede/runner.h @@ -99,7 +99,7 @@ struct ThreadLocalRunnerState { // Paths are thread-local, so we maintain the current bounded path here. // We allow paths of up to 100, controlled at run-time via the "path_level". - static constexpr size_t kBoundedPathLength = 100; + static constexpr uint64_t kBoundedPathLength = 100; HashedRingBuffer path_ring_buffer; // Value of SP in the top call frame of the thread, computed in OnThreadStart. diff --git a/centipede/runner_dl_info.cc b/centipede/runner_dl_info.cc index 4e8a3a18..6bcbf669 100644 --- a/centipede/runner_dl_info.cc +++ b/centipede/runner_dl_info.cc @@ -14,8 +14,13 @@ #include "./centipede/runner_dl_info.h" +#ifdef __APPLE__ +#include +#include +#else // __APPLE__ #include #include // dl_iterate_phdr +#endif // __APPLE__ #include #include @@ -31,6 +36,158 @@ namespace centipede { namespace { +constexpr bool kDlDebug = false; // we may want to make it a runtime flag. + +bool StringEndsWithSuffix(const char* string, + absl::Nonnull suffix) { + const char* pos = strstr(string, suffix); + if (pos == nullptr) return false; + return pos == string + strlen(string) - strlen(suffix); +} + +} // namespace + +#ifdef __APPLE__ +// Reference: +// https://opensource.apple.com/source/xnu/xnu-4903.221.2/EXTERNAL_HEADERS/mach-o/loader.h.auto.html + +namespace { + +// Calls `callback` on the assumed unique text segment with the link-time start +// address and size. +void FindTextSegment( + const mach_header* header, + const std::function& callback) { + const load_command* cmd = nullptr; + bool found = false; + if (header->magic == MH_MAGIC) { + cmd = reinterpret_cast( + reinterpret_cast(header) + sizeof(mach_header)); + } else if (header->magic == MH_MAGIC_64) { + cmd = reinterpret_cast( + reinterpret_cast(header) + sizeof(mach_header_64)); + } + RunnerCheck(cmd != nullptr, "bad magic number of mach image header"); + for (size_t cmd_index = 0; cmd_index < header->ncmds; + ++cmd_index, cmd = reinterpret_cast( + reinterpret_cast(cmd) + cmd->cmdsize)) { + if constexpr (kDlDebug) { + fprintf(stderr, "%s command at %p with size 0x%" PRIx32 "\n", __func__, + cmd, cmd->cmdsize); + } + uintptr_t base, size; + const char* name; + if (cmd->cmd == LC_SEGMENT) { + const auto* seg = reinterpret_cast(cmd); + base = seg->vmaddr; + size = seg->vmsize; + name = seg->segname; + } else if (cmd->cmd == LC_SEGMENT_64) { + const auto* seg = reinterpret_cast(cmd); + base = seg->vmaddr; + size = seg->vmsize; + name = seg->segname; + } else { + continue; + } + if constexpr (kDlDebug) { + fprintf(stderr, + "%s segment name %s addr seg 0x%" PRIxPTR " size 0x%" PRIxPTR + "\n", + __func__, name, base, size); + } + if (strcmp(name, "__TEXT") != 0) continue; + if (found) { + fprintf(stderr, + "%s found more than one __TEXT segument: this breaks Centipede " + "assumption. Ignoring the later segments.\n", + __func__); + break; + } + found = true; + callback(base, size); + } + if constexpr (kDlDebug) { + fprintf(stderr, "%s finished\n", __func__); + } +} + +DlInfo GetDlInfoFromImage( + const std::function& + image_filter) { + DlInfo result; + result.Clear(); + const auto image_count = _dyld_image_count(); + for (uint32_t i = 0; i < image_count; ++i) { + const mach_header* header = _dyld_get_image_header(i); + RunnerCheck(header != nullptr, "failed to get image header"); + const char* name = _dyld_get_image_name(i); + RunnerCheck(name != nullptr, "bad image name"); + if constexpr (kDlDebug) { + fprintf(stderr, "%s image header at %p, name %s\n", __func__, header, + name); + } + if (!image_filter(header, name)) continue; + uintptr_t text_start = 0; + uintptr_t text_size = 0; + FindTextSegment(header, + [&text_start, &text_size](uintptr_t start, uintptr_t size) { + text_start = start; + text_size = size; + }); + result.link_offset = _dyld_get_image_vmaddr_slide(i); + result.start_address = text_start + result.link_offset; + result.size = text_size; + RunnerCheck(result.size > 0, "bad text size"); + strncpy(result.path, name, sizeof(result.path)); + break; + } + if constexpr (kDlDebug) { + fprintf(stderr, "%s succeeded? %d\n", __func__, result.IsSet()); + if (result.IsSet()) { + fprintf(stderr, + " start 0x%" PRIxPTR " size 0x%" PRIxPTR + " link_offset 0x%" PRIxPTR "\n", + result.start_address, result.size, result.link_offset); + } + } + return result; +} + +} // namespace + +DlInfo GetDlInfo(absl::Nullable dl_path_suffix) { + if constexpr (kDlDebug) { + fprintf(stderr, "GetDlInfo for path suffix %s\n", dl_path_suffix); + } + return GetDlInfoFromImage( + [dl_path_suffix](const mach_header* unused_header, const char* name) { + return dl_path_suffix == nullptr || + StringEndsWithSuffix(name, dl_path_suffix); + }); +} + +DlInfo GetDlInfo(uintptr_t pc) { + if constexpr (kDlDebug) { + fprintf(stderr, "GetDlInfo for pc 0x%" PRIxPTR "\n", pc); + } + return GetDlInfoFromImage([pc](const mach_header* header, const char* name) { + bool matched = false; + FindTextSegment( + header, [pc, header, &matched](uintptr_t start, uintptr_t size) { + const uintptr_t actual_start = reinterpret_cast(header); + if (pc >= actual_start && actual_start + size) { + matched = true; + } + }); + return matched; + }); +} + +#else // __APPLE__ + +namespace { + // Struct to pass to dl_iterate_phdr's callback. struct DlCallbackParam { // Full path to the instrumented library or nullptr for the main binary. @@ -41,17 +198,8 @@ struct DlCallbackParam { DlInfo &result; }; -bool StringEndsWithSuffix(const char *string, - absl::Nonnull suffix) { - const char *pos = strstr(string, suffix); - if (pos == nullptr) return false; - return pos == string + strlen(string) - strlen(suffix); -} - int g_some_global; // Used in DlIteratePhdrCallback. -constexpr bool kDlDebug = false; // we may want to make it a runtime flag. - // Returns the size of the DL represented by `info`. size_t DlSize(absl::Nonnull info) { size_t size = 0; @@ -107,6 +255,7 @@ int DlIteratePhdrCallback(absl::Nonnull info, result.start_address = info->dlpi_addr; result.size = DlSize(info); + result.link_offset = result.start_address; // copy dlpi_name to result.path. strncpy(result.path, info->dlpi_name, sizeof(result.path)); result.path[sizeof(result.path) - 1] = 0; @@ -150,6 +299,7 @@ int DlIteratePhdrPCCallback(absl::Nonnull info, if (param->pc >= info->dlpi_addr + size) return 0; // wrong DSO. result.start_address = info->dlpi_addr; result.size = size; + result.link_offset = result.start_address; if (strlen(info->dlpi_name) != 0) { // copy dlpi_name to result.path. strncpy(result.path, info->dlpi_name, sizeof(result.path)); @@ -180,4 +330,6 @@ DlInfo GetDlInfo(uintptr_t pc) { return result; } +#endif // __APPLE__ + } // namespace centipede diff --git a/centipede/runner_dl_info.h b/centipede/runner_dl_info.h index d780415f..295f2cb2 100644 --- a/centipede/runner_dl_info.h +++ b/centipede/runner_dl_info.h @@ -27,6 +27,8 @@ namespace centipede { struct DlInfo { uintptr_t start_address; // Address in memory where the object is loaded. uintptr_t size; // Number of bytes in the object. + intptr_t link_offset; // Difference between runtime addresses and link-time + // addresses. char path[4096]; // Pathname from which the object was loaded. void Clear() { memset(this, 0, sizeof(*this)); } diff --git a/centipede/runner_fork_server.cc b/centipede/runner_fork_server.cc index 5c790e49..9cb2d9ad 100644 --- a/centipede/runner_fork_server.cc +++ b/centipede/runner_fork_server.cc @@ -52,7 +52,11 @@ // works too early in the process. E.g. getenv() will not work yet. #include +#ifdef __APPLE__ +#include +#else // __APPLE__ #include // ARG_MAX +#endif // __APPLE__ #include #include @@ -64,11 +68,20 @@ namespace centipede { +namespace { + +[[maybe_unused]] constexpr bool kForkServerDebug = false; +[[maybe_unused]] constexpr bool kForkServerDumpEnvAtStart = false; + +} // namespace + // Writes a C string to stderr when debugging, no-op otherwise. void Log(absl::Nonnull str) { // Uncomment these lines to debug. - // (void)write(STDERR_FILENO, str, strlen(str)); - // fsync(STDERR_FILENO); + if constexpr (kForkServerDebug) { + (void)write(STDERR_FILENO, str, strlen(str)); + fsync(STDERR_FILENO); + } } // Maybe writes the `reason` to stderr; then calls _exit. @@ -82,13 +95,63 @@ void Exit(absl::Nonnull reason) { static char env[ARG_MAX]; static ssize_t env_size; -// Reads /proc/self/environ into env. void GetAllEnv() { +#ifdef __APPLE__ + // Reference: + // https://chromium.googlesource.com/crashpad/crashpad/+/360e441c53ab4191a6fd2472cc57c3343a2f6944/util/posix/process_util_mac.cc + char args[ARG_MAX]; + size_t args_size = sizeof(args); + int mib[] = {CTL_KERN, KERN_PROCARGS2, getpid()}; + int rv = + sysctl(mib, sizeof(mib) / sizeof(mib[0]), args, &args_size, nullptr, 0); + if (rv != 0) { + Exit("GetEnv: sysctl({CTK_KERN, KERN_PROCARGS2, ...}) failed"); + } + if (args_size < sizeof(int)) { + Exit("GetEnv: args_size too small"); + } + int argc = 0; + memcpy(&argc, &args[0], sizeof(argc)); + size_t start_pos = sizeof(argc); + // Find the end of the executable path. + while (start_pos < args_size && args[start_pos] != 0) ++start_pos; + if (start_pos == args_size) { + Exit("GetEnv: envp not found"); + } + // Find the beginning of the string area. + while (start_pos < args_size && args[start_pos] == 0) ++start_pos; + if (start_pos == args_size) { + Exit("GetEnv: envp not found"); + } + // Ignore the first argc strings, after which is the envp. + for (int i = 0; i < argc; ++i) { + while (start_pos < args_size && args[start_pos] != 0) ++start_pos; + if (start_pos == args_size) { + Exit("GetEnv: envp not found"); + } + ++start_pos; + } + const size_t end_pos = args_size; + memcpy(env, &args[start_pos], end_pos - start_pos); + env_size = end_pos - start_pos; + if constexpr (kForkServerDumpEnvAtStart) { + size_t pos = start_pos; + while (pos < args_size) { + const size_t len = strnlen(&args[pos], args_size - pos); + (void)write(STDERR_FILENO, &args[pos], len); + (void)write(STDERR_FILENO, "\n", 1); + fsync(STDERR_FILENO); + pos += len + 1; + } + } +#else // __APPLE__ + // Reads /proc/self/environ into env. int fd = open("/proc/self/environ", O_RDONLY); if (fd < 0) Exit("GetEnv: can't open /proc/self/environ\n"); env_size = read(fd, env, sizeof(env)); if (env_size < 0) Exit("GetEnv: can't read to env\n"); if (close(fd) != 0) Exit("GetEnv: can't close /proc/self/environ\n"); +#endif // __APPLE__ env[sizeof(env) - 1] = 0; // Just in case. } @@ -183,7 +246,11 @@ __attribute__((constructor(150))) void ForkServerCallMeVeryEarly() { __builtin_unreachable(); } +#ifdef __APPLE__ +// .preinit_array is not supported in MacOS. +#else // __APPLE__ __attribute__((section(".preinit_array"))) auto call_very_early = ForkServerCallMeVeryEarly; +#endif // __APPLE__ } // namespace centipede diff --git a/centipede/runner_sancov_object.cc b/centipede/runner_sancov_object.cc index abc845b4..4768bb0f 100644 --- a/centipede/runner_sancov_object.cc +++ b/centipede/runner_sancov_object.cc @@ -132,8 +132,8 @@ std::vector SanCovObjectArray::CreatePCTable() const { const auto &object = objects_[i]; for (const auto *ptr = object.pcs_beg; ptr != object.pcs_end; ++ptr) { auto pc_info = *ptr; - // Subtract the ASLR base. - pc_info.pc -= object.dl_info.start_address; + // Convert into the link-time address + pc_info.pc -= object.dl_info.link_offset; result.push_back(pc_info); } } @@ -148,8 +148,8 @@ std::vector SanCovObjectArray::CreateCfTable() const { for (const auto *ptr = object.cfs_beg; ptr != object.cfs_end; ++ptr) { uintptr_t data = *ptr; // CF table is an array of PCs, except for delimiter (Null) and indirect - // call indicator (-1). Subtract the ASLR base only from PCs. - if (data != 0 && data != -1ULL) data -= object.dl_info.start_address; + // call indicator (-1). Convert into link-time address. + if (data != 0 && data != -1ULL) data -= object.dl_info.link_offset; result.push_back(data); } } diff --git a/centipede/runner_utils.cc b/centipede/runner_utils.cc index c1a2b9c2..da87b1aa 100644 --- a/centipede/runner_utils.cc +++ b/centipede/runner_utils.cc @@ -31,6 +31,13 @@ void PrintErrorAndExitIf(bool condition, absl::Nonnull error) { } uintptr_t GetCurrentThreadStackRegionLow() { +#ifdef __APPLE__ + pthread_t self = pthread_self(); + const auto stack_addr = + reinterpret_cast(pthread_get_stackaddr_np(self)); + const auto stack_size = pthread_get_stacksize_np(self); + return stack_addr - stack_size; +#else // __APPLE__ pthread_attr_t attr = {}; if (pthread_getattr_np(pthread_self(), &attr) != 0) { fprintf(stderr, "Failed to get the pthread attr of the current thread.\n"); @@ -48,6 +55,7 @@ uintptr_t GetCurrentThreadStackRegionLow() { RunnerCheck(stack_region_low != 0, "the current thread stack region starts from 0 - unexpected!"); return stack_region_low; +#endif // __APPLE__ } } // namespace centipede diff --git a/centipede/rusage_stats.cc b/centipede/rusage_stats.cc index 7b1cf312..8d5586f1 100644 --- a/centipede/rusage_stats.cc +++ b/centipede/rusage_stats.cc @@ -14,10 +14,16 @@ #include "./centipede/rusage_stats.h" +#ifdef __APPLE__ +#include +#include +#include +#endif // __APPLE__ #include #include #include +#include #include #include #include @@ -26,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -66,6 +73,44 @@ void ProcessTimer::Get(double& user, double& sys, double& wall) const { // RUsageScope //------------------------------------------------------------------------------ +#ifdef __APPLE__ +class RUsageScope::PlatformInfo { + public: + PlatformInfo(pid_t pid) : pid_(pid) {} + + pid_t pid() const { return pid_; } + + private: + pid_t pid_; +}; +#else +class RUsageScope::PlatformInfo { + public: + enum ProcFile : size_t { kSched = 0, kStatm = 1, kStatus = 2, kNum = 3 }; + + using ProcFilePaths = std::array; + + PlatformInfo(pid_t pid) + : proc_file_paths_{ + absl::StrFormat("/proc/%d/sched", pid), + absl::StrFormat("/proc/%d/statm", pid), + absl::StrFormat("/proc/%d/status", pid), + } {} + + // Returns a path to the /proc// or /proc//task//. + [[nodiscard]] const std::string& GetProcFilePath(ProcFile file) const { + return proc_file_paths_[file]; + } + + private: + std::array proc_file_paths_; +}; +#endif + +RUsageScope::RUsageScope(RUsageScope&&) = default; +RUsageScope& RUsageScope::operator=(RUsageScope&&) = default; +RUsageScope::~RUsageScope() = default; + RUsageScope RUsageScope::ThisProcess() { // return RUsageScope{getpid()}; } @@ -74,37 +119,9 @@ RUsageScope RUsageScope::Process(pid_t pid) { // return RUsageScope{pid}; } -RUsageScope RUsageScope::ThisThread() { - return RUsageScope{getpid(), static_cast(syscall(__NR_gettid))}; -} - -RUsageScope RUsageScope::ThisProcessThread(pid_t tid) { - return RUsageScope{getpid(), tid}; -} - -RUsageScope RUsageScope::Thread(pid_t pid, pid_t tid) { - return RUsageScope{pid, tid}; -} - RUsageScope::RUsageScope(pid_t pid) : description_{absl::StrFormat("PID=%d", pid)}, - proc_file_paths_{ - absl::StrFormat("/proc/%d/sched", pid), - absl::StrFormat("/proc/%d/statm", pid), - absl::StrFormat("/proc/%d/status", pid), - } {} - -RUsageScope::RUsageScope(pid_t pid, pid_t tid) - : description_{absl::StrFormat("PID=%d TID=%d", pid, tid)}, - proc_file_paths_{ - absl::StrFormat("/proc/%d/task/%d/sched", pid, tid), - absl::StrFormat("/proc/%d/task/%d/statm", pid, tid), - absl::StrFormat("/proc/%d/task/%d/status", pid, tid), - } {} - -const std::string& RUsageScope::GetProcFilePath(ProcFile file) const { - return proc_file_paths_[file]; -} + info_(std::make_shared(pid)) {} namespace detail { namespace { @@ -344,21 +361,33 @@ RUsageTiming RUsageTiming::Snapshot( // double user_time = 0, sys_time = 0, wall_time = 0; // TODO(b/265480321): This does not honor `scope`. timer.Get(user_time, sys_time, wall_time); + double cpu_utilization = 0; +#ifdef __APPLE__ + int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, scope.info().pid()}; + struct kinfo_proc info = {}; + size_t size = sizeof(info); + // Get process information + CHECK(sysctl(mib, sizeof(mib) / sizeof(mib[0]), &info, &size, NULL, 0) == 0) + << "Error getting process information: " << strerror(errno); + cpu_utilization = info.kp_proc.p_pctcpu; +#else // __APPLE__ // Get the CPU utilization in 1/1024th units of the maximum from // /proc/self/sched. The maximum se.avg.util_avg field == SCHED_CAPACITY_SCALE // == 1024, as defined by the Linux scheduler code. - double cpu_utilization = 0; // TODO(b/265461840): Handle reading errors. (void)detail::ReadProcFileKeyword( // ignore errors (which are unlikely) - scope.GetProcFilePath(RUsageScope::ProcFile::kSched), // - "se.avg.util_avg : %lf", // + scope.info().GetProcFilePath( + RUsageScope::PlatformInfo::ProcFile::kSched), // + "se.avg.util_avg : %lf", // &cpu_utilization); constexpr double kLinuxSchedCapacityScale = 1024; + cpu_utilization /= kLinuxSchedCapacityScale; +#endif // __APPLE__ return RUsageTiming{ .wall_time = absl::Seconds(wall_time), .user_time = absl::Seconds(user_time), .sys_time = absl::Seconds(sys_time), - .cpu_utilization = cpu_utilization / kLinuxSchedCapacityScale, + .cpu_utilization = cpu_utilization, .cpu_hyper_cores = (user_time + sys_time) / wall_time, .is_delta = false, }; @@ -488,30 +517,57 @@ RUsageMemory RUsageMemory::Max() { } RUsageMemory RUsageMemory::Snapshot(const RUsageScope& scope) { + [[maybe_unused]] MemSize vsize = 0, rss = 0, shared = 0, code = 0, unused = 0, + data = 0, vpeak = 0; +#ifdef __APPLE__ + if (scope.info().pid() != getpid()) return {}; + struct proc_taskinfo pti = {}; + CHECK(proc_pidinfo(scope.info().pid(), PROC_PIDTASKINFO, 0, &pti, + PROC_PIDTASKINFO_SIZE) == PROC_PIDTASKINFO_SIZE) + << "Unable to get system resource information"; + vsize = pti.pti_virtual_size; + rss = pti.pti_resident_size; + struct rusage rusage = {}; + CHECK(getrusage(RUSAGE_SELF, &rusage) == 0) + << "Failed to get memory stats by getrusage"; + // `data` and `shared` are not supported in MacOS. + // MacOS does not have a builtin way to query the peak size of virutal memory. + // Here provide an estimatation assuming only RSS memory can shrink or be + // swapped out. + // + // Here we assume `ru_maxrss` is in bytes according to some experiments. + vpeak = vsize + (rusage.ru_maxrss - rss); +#else // __APPLE__ // Get memory stats except the VM peak from /proc/self/statm (see `man proc`). - MemSize vsize = 0, rss = 0, shared = 0, code = 0, unused = 0, data = 0; // TODO(b/265461840): Handle reading errors. - (void)detail::ReadProcFileFields( // ignore errors - scope.GetProcFilePath(RUsageScope::ProcFile::kStatm), // - "%lld %lld %lld %lld %lld %lld", // + (void)detail::ReadProcFileFields( // ignore errors + scope.info().GetProcFilePath( + RUsageScope::PlatformInfo::ProcFile::kStatm), // + "%lld %lld %lld %lld %lld %lld", // &vsize, &rss, &shared, &code, &unused, &data); // Get the VM peak from /proc/self/status (see `man proc`). - MemSize vpeak = 0; // TODO(b/265461840): Handle reading errors. - (void)detail::ReadProcFileKeyword( // ignore errors - scope.GetProcFilePath(RUsageScope::ProcFile::kStatus), // - "VmPeak : %" SCNd64 " kB", // + (void)detail::ReadProcFileKeyword( // ignore errors + scope.info().GetProcFilePath( + RUsageScope::PlatformInfo::ProcFile::kStatus), // + "VmPeak : %" SCNd64 " kB", // &vpeak); static const int page_size = getpagesize(); + vsize *= page_size; + rss *= page_size; + data *= page_size; + shared *= page_size; // NOTE: The units are specified in the file itself, but they are always kB. static constexpr int kVPeakUnits = 1024; + vpeak *= kVPeakUnits; +#endif // __APPLE__ // clang-format off return RUsageMemory{ - .mem_vsize = vsize * page_size, - .mem_vpeak = vpeak * kVPeakUnits, - .mem_rss = rss * page_size, - .mem_data = data * page_size, - .mem_shared = shared * page_size, + .mem_vsize = vsize, + .mem_vpeak = vpeak, + .mem_rss = rss, + .mem_data = data, + .mem_shared = shared, .is_delta = false, }; // clang-format on diff --git a/centipede/rusage_stats.h b/centipede/rusage_stats.h index 9b63b29e..7daf3672 100644 --- a/centipede/rusage_stats.h +++ b/centipede/rusage_stats.h @@ -54,36 +54,33 @@ using CpuUtilization = long double; //------------------------------------------------------------------------------ class RUsageScope { public: - enum ProcFile : size_t { kSched = 0, kStatm = 1, kStatus = 2, kNum = 3 }; - // Static ctors for supported use cases. If the same scope is used repeatedly, // callers should prefer caching it, as construction may involve syscalls. static RUsageScope ThisProcess(); static RUsageScope Process(pid_t pid); - static RUsageScope ThisThread(); - static RUsageScope ThisProcessThread(pid_t tid); - static RUsageScope Thread(pid_t pid, pid_t tid); // Copyable and movable. RUsageScope(const RUsageScope&) = default; RUsageScope& operator=(const RUsageScope&) = default; - RUsageScope(RUsageScope&&) = default; - RUsageScope& operator=(RUsageScope&&) = default; - - // Returns a path to the /proc// or /proc//task//. - [[nodiscard]] const std::string& GetProcFilePath(ProcFile file) const; + RUsageScope(RUsageScope&&); + RUsageScope& operator=(RUsageScope&&); + ~RUsageScope(); template friend OStream& operator<<(OStream& os, const RUsageScope& s) { return os << s.description_; } + // Opaque platform dependent information for rusage monitoring. + class PlatformInfo; + + const PlatformInfo& info() const { return *info_; } + private: explicit RUsageScope(pid_t pid); - RUsageScope(pid_t pid, pid_t tid); std::string description_; - std::array proc_file_paths_; + std::shared_ptr info_; }; //------------------------------------------------------------------------------ diff --git a/centipede/rusage_stats_test.cc b/centipede/rusage_stats_test.cc index cbfddff0..50fd2fb7 100644 --- a/centipede/rusage_stats_test.cc +++ b/centipede/rusage_stats_test.cc @@ -296,7 +296,11 @@ TEST(RUsageMemoryTest, Accuracy) { #if !defined(ADDRESS_SANITIZER) && !defined(THREAD_SANITIZER) && \ !defined(MEMORY_SANITIZER) EXPECT_NEAR(mem_rss_histo.Average(), kBytes, kRssLeeway) << mem_rss_histo; +#ifdef __APPLE__ + // `data` is not supported. +#else EXPECT_NEAR(mem_data_histo.Average(), kBytes, kDataLeeway) << mem_data_histo; +#endif #else LOG(WARNING) << "Validation of test results omitted under *SAN: see code"; #endif diff --git a/centipede/shared_memory_blob_sequence.cc b/centipede/shared_memory_blob_sequence.cc index 5ad51b8f..a60ad856 100644 --- a/centipede/shared_memory_blob_sequence.cc +++ b/centipede/shared_memory_blob_sequence.cc @@ -108,6 +108,9 @@ SharedMemoryBlobSequence::SharedMemoryBlobSequence(const char *name, "shm_open() path length exceeds PATH_MAX."); path_is_owned_ = true; } else { +#ifdef __APPLE__ + ErrorOnFailure(true, "must use POSIX shmem"); +#else // __APPLE__ fd_ = memfd_create(name, MFD_CLOEXEC); ErrorOnFailure(fd_ < 0, "memfd_create() failed"); const size_t path_size = @@ -116,8 +119,9 @@ SharedMemoryBlobSequence::SharedMemoryBlobSequence(const char *name, "internal fd path length exceeds PATH_MAX."); // memfd_create descriptors are automatically freed on close(). path_is_owned_ = false; +#endif // __APPLE__ } - ErrorOnFailure(ftruncate(fd_, static_cast<__off_t>(size_)), + ErrorOnFailure(ftruncate(fd_, static_cast(size_)), "ftruncate() failed)"); MmapData(); } @@ -154,10 +158,16 @@ SharedMemoryBlobSequence::~SharedMemoryBlobSequence() { } void SharedMemoryBlobSequence::ReleaseSharedMemory() { +#ifdef __APPLE__ + // MacOS only allows ftruncate shm once + // (https://stackoverflow.com/questions/25502229/ftruncate-not-working-on-posix-shared-memory-in-mac-os-x). + // So nothing we can do here. +#else // __APPLE__ // Setting size to 0 releases the memory to OS. ErrorOnFailure(ftruncate(fd_, 0) != 0, "ftruncate(0) failed)"); // Set the size back to `size`. The memory is not actually reserved. ErrorOnFailure(ftruncate(fd_, size_) != 0, "ftruncate(size_) failed)"); +#endif // __APPLE__ } size_t SharedMemoryBlobSequence::NumBytesUsed() const { diff --git a/centipede/shared_memory_blob_sequence_test.cc b/centipede/shared_memory_blob_sequence_test.cc index c549b995..83b9df16 100644 --- a/centipede/shared_memory_blob_sequence_test.cc +++ b/centipede/shared_memory_blob_sequence_test.cc @@ -28,8 +28,7 @@ namespace centipede { std::string ShmemName() { std::ostringstream oss; - oss << "/shared_memory_blob_sequence_test-" << getpid() << "-" - << std::this_thread::get_id(); + oss << "/shm_test-" << getpid() << "-" << std::this_thread::get_id(); return oss.str(); } @@ -54,7 +53,17 @@ TEST(BlobSequence, WriteAndReadAnEmptyBlob) { } class SharedMemoryBlobSequenceTest - : public testing::TestWithParam {}; + : public testing::TestWithParam { + public: + void SetUp() override { +#ifdef __APPLE__ + const bool use_shm = GetParam(); + if (!use_shm) { + GTEST_SKIP() << "Skipping test that does not use POSIX shmem on MacOS"; + } +#endif // __APPLE__ + } +}; INSTANTIATE_TEST_SUITE_P(SharedMemoryBlobSequenceParametrizedTest, SharedMemoryBlobSequenceTest, @@ -195,6 +204,8 @@ TEST_P(SharedMemoryBlobSequenceTest, WriteAfterReset) { EXPECT_FALSE(blob2.IsValid()); } +// MacOS does not support releasing the shm memory. +#ifndef __APPLE__ // Test ReleaseSharedMemory and NumBytesUsed. TEST_P(SharedMemoryBlobSequenceTest, ReleaseSharedMemory) { // Allocate a blob sequence with 1M bytes of storage. @@ -207,5 +218,6 @@ TEST_P(SharedMemoryBlobSequenceTest, ReleaseSharedMemory) { EXPECT_TRUE(blobseq.Write(Blob({1, 2, 3, 4}))); EXPECT_GT(blobseq.NumBytesUsed(), 5); } +#endif } // namespace centipede diff --git a/centipede/test_util.sh b/centipede/test_util.sh index 426e0bf6..ad8a1131 100644 --- a/centipede/test_util.sh +++ b/centipede/test_util.sh @@ -64,17 +64,15 @@ function centipede::get_objdump_path() { # an executable file. function centipede::maybe_set_var_to_executable_path() { local var_name="$1" - # NOTE: `local -n` creates a reference to the var named "$1". - local -n var_ref="$1" local path="$2" - if [[ -n "${var_ref+x}" ]]; then - echo "Not overriding ${var_name} -- already set to '${var_ref}'" >&2 + if [[ -n "${!var_name:-}" ]]; then + echo "Not overriding ${var_name} -- already set to '${!var_name}'" >&2 else echo "Setting ${var_name} to '${path}'" >&2 - var_ref="${path}" + eval "$(printf "${var_name}=%q" "${path}")" fi - if ! [[ -x "${var_ref}" ]]; then - die "Path '${var_ref}' doesn't exist or is not executable" + if ! [[ -x "${!var_name}" ]]; then + die "Path '${!var_name}' doesn't exist or is not executable" fi } @@ -83,17 +81,15 @@ function centipede::maybe_set_var_to_executable_path() { # TODO(ussuri): Reduce code duplication with the above. function centipede::maybe_set_var_to_built_executable_path() { local var_name="$1" - # NOTE: `local -n` creates a reference to the var named "$1". - local -n var_ref="$1" local bazel_build_cmd="$2" - if [[ -n "${var_ref+x}" ]]; then - echo "Not overriding ${var_name} -- already set to '${var_ref}'" >&2 + if [[ -n "${!var_name:-}" ]]; then + echo "Not overriding ${var_name} -- already set to '${!var_name}'" >&2 else echo "Setting ${var_name} to output of '${bazel_build_cmd}'" >&2 - var_ref="$(set -e; ${bazel_build_cmd})" + eval "$(printf "${var_name}=%q" "$(set -e; ${bazel_build_cmd})")" fi - if ! [[ -x "${var_ref}" ]]; then - die "Path '${var_ref}' doesn't exist or is not executable" + if ! [[ -x "${!var_name}" ]]; then + die "Path '${!var_name}' doesn't exist or is not executable" fi } @@ -108,7 +104,7 @@ function _assert_regex_in_file_impl() { local -r file="$2" local -r expected_found="$3" # Make the shell option change below local. - local - + local -r saved_opts="$(set +o)" set -o pipefail if ! fileop ls "${file}" > /dev/null; then die "Expected file ${file} doesn't exist" @@ -131,6 +127,7 @@ function _assert_regex_in_file_impl() { die "^^^ File ${file} contains unexpected regex /${regex}/" fi fi + eval "${saved_opts}" } # Makes sure that string "$1" exists in file "$2". Works for local and CNS. diff --git a/centipede/testing/build_defs.bzl b/centipede/testing/build_defs.bzl index 5cbe8fb5..e9d54f6a 100644 --- a/centipede/testing/build_defs.bzl +++ b/centipede/testing/build_defs.bzl @@ -151,9 +151,13 @@ def centipede_fuzz_target( copts = copts, linkopts = linkopts + [ "-ldl", - "-lrt", - "-lpthread" - ], + "-lpthread", + ] + select({ + "@platforms//os:macos": [], + "//conditions:default": [ + "-lrt", # for shm_open + ], + }), testonly = True, ) diff --git a/centipede/testing/centipede_main_test.sh b/centipede/testing/centipede_main_test.sh index 9c36ee7e..80478a75 100755 --- a/centipede/testing/centipede_main_test.sh +++ b/centipede/testing/centipede_main_test.sh @@ -71,7 +71,7 @@ test_debug_symbols() { --symbolizer_path="${LLVM_SYMBOLIZER}" | tee "${LOG}" centipede::assert_regex_in_file 'Custom mutator detected: will use it' "${LOG}" # Note: the test assumes LLVMFuzzerTestOneInput is defined on a specific line. - centipede::assert_regex_in_file "FUNC: LLVMFuzzerTestOneInput .*testing/test_fuzz_target.cc:70" "${LOG}" + centipede::assert_regex_in_file "FUNC: LLVMFuzzerTestOneInput .*testing/test_fuzz_target.cc:71" "${LOG}" centipede::assert_regex_in_file "EDGE: LLVMFuzzerTestOneInput .*testing/test_fuzz_target.cc" "${LOG}" echo "============ ${FUNC}: add func1/func2-A inputs to the corpus." diff --git a/centipede/testing/data_only_dso_test.sh b/centipede/testing/data_only_dso_test.sh index c3fc26e0..6d4107e8 100755 --- a/centipede/testing/data_only_dso_test.sh +++ b/centipede/testing/data_only_dso_test.sh @@ -38,7 +38,7 @@ centipede::ensure_empty_dir "${WD}" "${CENTIPEDE_BINARY}" --binary="${TARGET_BINARY}" --workdir="${WD}" \ --exit_on_crash=1 --seed=1 --log_features_shards=1 \ --symbolizer_path="${LLVM_SYMBOLIZER}" \ - |& tee "${LOG}" + 2>&1 | tee "${LOG}" echo "Fuzzing DONE" diff --git a/centipede/testing/external_target_test.sh b/centipede/testing/external_target_test.sh index ac6d8eca..70c431ce 100755 --- a/centipede/testing/external_target_test.sh +++ b/centipede/testing/external_target_test.sh @@ -51,7 +51,7 @@ env TARGET_PORT="${TARGET_PORT}" \ "${CENTIPEDE_BINARY}" --binary="${TARGET_BINARY}" --workdir="${WD}" \ --coverage_binary="${SERVER_BINARY}" --symbolizer_path="${LLVM_SYMBOLIZER}" \ --exit_on_crash=1 --seed=1 --log_features_shards=1 \ - |& tee "${LOG}" || true + 2>&1 | tee "${LOG}" || true # Check that Centipede finds the crashing input. centipede::assert_regex_in_file "Input bytes.*: Secret" "${LOG}" diff --git a/centipede/testing/instrumentation_test.sh b/centipede/testing/instrumentation_test.sh index 8d9ef522..ba2f1a43 100755 --- a/centipede/testing/instrumentation_test.sh +++ b/centipede/testing/instrumentation_test.sh @@ -37,7 +37,11 @@ CENTIPEDE_RUNNER_FLAGS=":dump_binary_info:arg1=${pc_table}:arg2=${unused1}:arg3= "${target}" # Check the pc table size. -size=$(stat -c %s "${pc_table}") +if [[ "$OSTYPE" == 'darwin'* ]]; then + size=$(stat -f %z "${pc_table}") +else + size=$(stat -c %s "${pc_table}") +fi echo "pc table size: ${size}" (( size < 1 )) && die "pc table is too small: ${size}" (( size > ALLOWED_SIZE )) && die "pc table is too large: ${size}" diff --git a/centipede/testing/runner_test.sh b/centipede/testing/runner_test.sh index 9073d639..f9deae8c 100755 --- a/centipede/testing/runner_test.sh +++ b/centipede/testing/runner_test.sh @@ -85,14 +85,19 @@ echo ======== Run f1 func1 "${target}" "${f1}" "${func1}" check_features_files_f1_and_f02 -# Check OOM. The test target allocates 4Gb, and we make sure -# this can be detected with appropriate limit (address_space_limit_mb), -# and can not be detected with a larger limit. -echo ======== Check OOM behaviour with address_space_limit_mb -CENTIPEDE_RUNNER_FLAGS=":address_space_limit_mb=4096:" $target "${oom}" \ - && die "failed to die on 4G OOM (address_space_limit_mb)" -# must pass. -CENTIPEDE_RUNNER_FLAGS=":address_space_limit_mb=8192:" $target "${oom}" + +# Address space limit is ignored by MacOS. +# Reference: https://bugs.chromium.org/p/chromium/issues/detail?id=853873#c2 +if [[ "${OSTYPE}" != 'darwin'* ]]; then + # Check OOM. The test target allocates 4Gb, and we make sure + # this can be detected with appropriate limit (address_space_limit_mb), + # and can not be detected with a larger limit. + echo ======== Check OOM behaviour with address_space_limit_mb + CENTIPEDE_RUNNER_FLAGS=":address_space_limit_mb=4096:" $target "${oom}" \ + && die "failed to die on 4G OOM (address_space_limit_mb)" + # must pass. + CENTIPEDE_RUNNER_FLAGS=":address_space_limit_mb=8192:" $target "${oom}" +fi echo ======== Check OOM behaviour with rss_limit_mb CENTIPEDE_RUNNER_FLAGS=":rss_limit_mb=4096:" $target "${oom}" \ @@ -105,7 +110,8 @@ CENTIPEDE_RUNNER_FLAGS=":timeout_per_input=567:" "${target}" \ 2>&1 | grep "timeout_per_input:.567" CENTIPEDE_RUNNER_FLAGS=":timeout_per_input=2:" "${target}" "${slo}" \ - 2>&1 | grep "Per-input timeout exceeded" + 2>&1 | tee "${TEST_TMPDIR}/timeout" +cat "${TEST_TMPDIR}/timeout" | grep "Per-input timeout exceeded" echo ======== Check stack limit check with stack_limit CENTIPEDE_RUNNER_FLAGS=":use_pc_features:stack_limit_kb=200:" "${target}" "${stk}" # must pass diff --git a/centipede/testing/test_fuzz_target.cc b/centipede/testing/test_fuzz_target.cc index e9ca85eb..32e07ebd 100644 --- a/centipede/testing/test_fuzz_target.cc +++ b/centipede/testing/test_fuzz_target.cc @@ -13,6 +13,7 @@ // limitations under the License. // A fuzz target used for testing Centipede. +#include #include #include #include @@ -153,9 +154,15 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { } // Disable and free the previous signal stack. stack_t disabled_sigstk = {}; +#ifdef __APPLE__ + // Needed for MacOS. + // Reference: + // https://chromium.googlesource.com/native_client/src/native_client/+/ad617ab7dd5f23a67fcff244b3c3263ffcc7e66d/src/trusted/service_runtime/posix/nacl_signal_stack.c#117 + disabled_sigstk.ss_size = MINSIGSTKSZ; +#endif disabled_sigstk.ss_flags = SS_DISABLE; if (sigaltstack(&disabled_sigstk, nullptr) != 0) { - printf("failed to disable the signal stack\n"); + printf("failed to disable the signal stack: %d\n", errno); abort(); } free(sigstk.ss_sp); diff --git a/common/BUILD b/common/BUILD index be69694a..1ed11092 100644 --- a/common/BUILD +++ b/common/BUILD @@ -82,7 +82,10 @@ cc_library( name = "hash", srcs = ["hash.cc"], hdrs = ["hash.h"], - linkopts = ["-Wl,-Bstatic -lcrypto -Wl,-Bdynamic -ldl"], + linkopts = select({ + "@platforms//os:macos": ["-lcrypto"], + "//conditions:default": ["-Wl,-Bstatic -lcrypto -Wl,-Bdynamic -ldl"], + }), visibility = EXTENDED_API_VISIBILITY, deps = [ ":defs",