Skip to content

Commit

Permalink
No public description
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 659581896
  • Loading branch information
xinhaoyuan authored and copybara-github committed Aug 6, 2024
1 parent cf44f24 commit 2dd693e
Show file tree
Hide file tree
Showing 32 changed files with 648 additions and 218 deletions.
37 changes: 37 additions & 0 deletions .github/workflows/bazel_test_centipede.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,40 @@ jobs:
with:
path: "~/.cache/bazel"
key: bazel-centipede-cache-${{ matrix.config }}-${{ github.run_id }}
run_tests_mac:
name: Run Centipede tests (MacOS)
runs-on: macos-13
timeout-minutes: 60
strategy:
matrix:
config: ['noriegeli']
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Restore latest cache
uses: actions/cache/restore@v4
with:
path: "~/.cache/bazel"
key: bazel-centipede-cache-mac-${{ matrix.config }}
restore-keys: bazel-centipede-cache-mac-${{ matrix.config }}-
- name: Add LLVM symbolizer to path
run: |
ln -s $(brew --prefix llvm@15)/bin/llvm-symbolizer /usr/local/bin
- name: Run unit tests without Riegeli
if: matrix.config == 'noriegeli'
run: |
bazel test --local_test_jobs=1 --test_output=streamed --no//fuzztest:use_riegeli --linkopt=-Wl,-undefined,dynamic_lookup centipede:all
- name: Run e2e tests without Riegeli
if: matrix.config == 'noriegeli'
run: |
bazel test --test_output=errors --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 --test_output=errors --no//fuzztest:use_riegeli --linkopt=-Wl,-undefined,dynamic_lookup centipede/puzzles:run_1_memcmp_4
- 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-mac-${{ matrix.config }}-${{ github.run_id }}
9 changes: 9 additions & 0 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -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
################################################################################
Expand Down
36 changes: 29 additions & 7 deletions centipede/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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,
)

Expand Down Expand Up @@ -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 = [
Expand Down Expand Up @@ -1162,8 +1181,11 @@ sh_library(
sh_library(
name = "test_util_sh",
srcs = ["test_util.sh"],
data = [
],
data = select({
"@platforms//os:macos": [],
"//conditions:default": [
],
}),
)

################################################################################
Expand Down
4 changes: 2 additions & 2 deletions centipede/centipede_callbacks.h
Original file line number Diff line number Diff line change
Expand Up @@ -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_;
Expand Down
128 changes: 70 additions & 58 deletions centipede/centipede_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down Expand Up @@ -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-<HASH>" 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<std::string> 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-<HASH>" 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<std::string> 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.
Expand Down
48 changes: 26 additions & 22 deletions centipede/command.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down Expand Up @@ -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/<PID>/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/<PID>/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;
Expand All @@ -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/<PID>/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/<PID>/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(
Expand Down
3 changes: 2 additions & 1 deletion centipede/command_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
2 changes: 1 addition & 1 deletion centipede/control_flow_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading

0 comments on commit 2dd693e

Please sign in to comment.