No public description PiperOrigin-RevId: 953590931
diff --git a/.github/workflows/bazel_test_centipede.yml b/.github/workflows/bazel_test_centipede.yml index 1bbcba0..d8ac95e 100644 --- a/.github/workflows/bazel_test_centipede.yml +++ b/.github/workflows/bazel_test_centipede.yml
@@ -25,109 +25,149 @@ workflow_dispatch: jobs: - run_tests: - name: Run Centipede tests - # TODO(xinhaoyuan): Bump to 24.04 after https://github.com/llvm/llvm-project/issues/102443 - # is fixed. - runs-on: ubuntu-22.04 - timeout-minutes: 60 - strategy: - matrix: - config: ['default', 'noriegeli', 'asan'] - steps: - - name: Disable core dumping and piping due to slowness - run: | - sudo sysctl -w kernel.core_pattern="" - - 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' - run: | - bazel test --test_output=errors --test_timeout=600 centipede/puzzles:all - - name: Run puzzles with ASAN - if: matrix.config == 'asan' - run: | - bazel test --test_output=errors --linkopt=-fsanitize=address --copt=-fsanitize=address --test_env=ASAN_OPTIONS=detect_leaks=0 --test_timeout=600 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 - - name: Run e2e tests without Riegeli - if: matrix.config == 'noriegeli' - run: | - bazel test --no//fuzztest:use_riegeli --test_output=errors 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 --test_timeout=600 centipede/puzzles:all - - name: Run puzzles without Riegeli with ASAN - if: matrix.config == 'noriegeli' - run: | - bazel test --no//fuzztest:use_riegeli --test_output=errors --linkopt=-fsanitize=address --copt=-fsanitize=address --test_env=ASAN_OPTIONS=detect_leaks=0 --platform_suffix=asan --test_timeout=600 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 }} - run_tests_mac: - name: Run Centipede tests (MacOS) - runs-on: macos-15 + # run_tests: + # name: Run Centipede tests + # # TODO(xinhaoyuan): Bump to 24.04 after https://github.com/llvm/llvm-project/issues/102443 + # # is fixed. + # runs-on: ubuntu-22.04 + # timeout-minutes: 60 + # strategy: + # matrix: + # config: ['default', 'noriegeli', 'asan'] + # steps: + # - name: Disable core dumping and piping due to slowness + # run: | + # sudo sysctl -w kernel.core_pattern="" + # - 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' + # run: | + # bazel test --test_output=errors --test_timeout=600 centipede/puzzles:all + # - name: Run puzzles with ASAN + # if: matrix.config == 'asan' + # run: | + # bazel test --test_output=errors --linkopt=-fsanitize=address --copt=-fsanitize=address --test_env=ASAN_OPTIONS=detect_leaks=0 --test_timeout=600 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 + # - name: Run e2e tests without Riegeli + # if: matrix.config == 'noriegeli' + # run: | + # bazel test --no//fuzztest:use_riegeli --test_output=errors 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 --test_timeout=600 centipede/puzzles:all + # - name: Run puzzles without Riegeli with ASAN + # if: matrix.config == 'noriegeli' + # run: | + # bazel test --no//fuzztest:use_riegeli --test_output=errors --linkopt=-fsanitize=address --copt=-fsanitize=address --test_env=ASAN_OPTIONS=detect_leaks=0 --platform_suffix=asan --test_timeout=600 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 }} + # run_tests_mac: + # name: Run Centipede tests (MacOS) + # runs-on: macos-15 + # timeout-minutes: 60 + # 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 + # restore-keys: bazel-centipede-cache-mac- + # - name: Add LLVM symbolizer to path + # run: | + # ln -s $(brew --prefix llvm@18)/bin/llvm-symbolizer /usr/local/bin + # - name: Run unit tests + # if: ${{ !cancelled() }} + # run: | + # # Timed wait and rusage is inaccurate for unknown reasons in MacOS for a few testing + # # environments, thus disabling a few related non-critical tests. + # bazel --output_user_root="${HOME}/.cache/bazel" test --local_test_jobs=1 --test_output=errors --no//fuzztest:use_riegeli -- centipede:all \ + # -centipede:periodic_action_test -centipede:rusage_profiler_test -centipede:rusage_stats_test && \ + # bazel --output_user_root="${HOME}/.cache/bazel" test --local_test_jobs=1 --test_output=errors --no//fuzztest:use_riegeli --test_filter='-*ValidateTimelapseSnapshots' -- centipede:rusage_profiler_test && \ + # bazel --output_user_root="${HOME}/.cache/bazel" test --local_test_jobs=1 --test_output=errors --no//fuzztest:use_riegeli --test_filter='-RUsageMemoryTest.Accuracy' -- centipede:rusage_stats_test + # - name: Run e2e tests without Riegeli + # if: ${{ !cancelled() }} + # run: | + # bazel --output_user_root="${HOME}/.cache/bazel" test --test_output=errors --no//fuzztest:use_riegeli centipede/testing:instrumentation_test centipede/testing:runner_test centipede/testing:multi_dso_test + # - name: Run puzzles without Riegeli + # if: ${{ !cancelled() }} + # run: | + # bazel --output_user_root="${HOME}/.cache/bazel" test --test_output=errors --no//fuzztest:use_riegeli --test_timeout=600 centipede/puzzles:all + # - name: Run puzzles without Riegeli with ASAN + # if: ${{ !cancelled() }} + # run: | + # bazel --output_user_root="${HOME}/.cache/bazel" test --test_output=errors --no//fuzztest:use_riegeli --linkopt=-fsanitize=address --copt=-fsanitize=address --test_env=ASAN_OPTIONS=detect_leaks=0 --platform_suffix=asan --test_timeout=600 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-mac-${{ github.run_id }} + run_tests_win: + name: Run Centipede tests (Windows) + runs-on: windows-latest + permissions: + contents: read timeout-minutes: 60 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 - restore-keys: bazel-centipede-cache-mac- - - name: Add LLVM symbolizer to path - run: | - ln -s $(brew --prefix llvm@18)/bin/llvm-symbolizer /usr/local/bin - - name: Run unit tests - if: ${{ !cancelled() }} - run: | - # Timed wait and rusage is inaccurate for unknown reasons in MacOS for a few testing - # environments, thus disabling a few related non-critical tests. - bazel --output_user_root="${HOME}/.cache/bazel" test --local_test_jobs=1 --test_output=errors --no//fuzztest:use_riegeli -- centipede:all \ - -centipede:periodic_action_test -centipede:rusage_profiler_test -centipede:rusage_stats_test && \ - bazel --output_user_root="${HOME}/.cache/bazel" test --local_test_jobs=1 --test_output=errors --no//fuzztest:use_riegeli --test_filter='-*ValidateTimelapseSnapshots' -- centipede:rusage_profiler_test && \ - bazel --output_user_root="${HOME}/.cache/bazel" test --local_test_jobs=1 --test_output=errors --no//fuzztest:use_riegeli --test_filter='-RUsageMemoryTest.Accuracy' -- centipede:rusage_stats_test - - name: Run e2e tests without Riegeli - if: ${{ !cancelled() }} - run: | - bazel --output_user_root="${HOME}/.cache/bazel" test --test_output=errors --no//fuzztest:use_riegeli centipede/testing:instrumentation_test centipede/testing:runner_test centipede/testing:multi_dso_test - - name: Run puzzles without Riegeli - if: ${{ !cancelled() }} - run: | - bazel --output_user_root="${HOME}/.cache/bazel" test --test_output=errors --no//fuzztest:use_riegeli --test_timeout=600 centipede/puzzles:all + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + # - name: Restore latest cache + # uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0 + # with: + # path: "~/.cache/bazel" + # key: bazel-centipede-cache-win-${{ matrix.config }} + # restore-keys: bazel-centipede-cache-win-${{ matrix.config }}- + - name: Set environment variable + run: echo "USE_BAZEL_VERSION=8.7.0" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - name: Run puzzles without Riegeli with ASAN if: ${{ !cancelled() }} run: | - bazel --output_user_root="${HOME}/.cache/bazel" test --test_output=errors --no//fuzztest:use_riegeli --linkopt=-fsanitize=address --copt=-fsanitize=address --test_env=ASAN_OPTIONS=detect_leaks=0 --platform_suffix=asan --test_timeout=600 centipede/puzzles:all + bazelisk test --disk_cache=~/.cache/bazel --platform_suffix=asan ` + --extra_toolchains=@local_config_cc//:cc-toolchain-x64_windows-clang-cl ` + --extra_execution_platforms=//:x64_windows-clang-cl ` + --//fuzztest:use_riegeli=false ` + --copt=-fsanitize=address ` + --copt=-fno-sanitize-blacklist <# https://github.com/llvm/llvm-project/issues/34691 #> ` + --linkopt=-wholearchive:clang_rt.asan_dynamic-x86_64.lib ` + --linkopt=-Wholearchive:clang_rt.asan_dynamic_runtime_thunk-x86_64.lib ` + --linkopt=/opt:nolldtailmerge <# https://github.com/llvm/llvm-project/issues/62078 #> ` + --test_output=errors --enable_runfiles --local_test_jobs=1 ` + "--test_env=PATH=C:\Program Files\LLVM\lib\clang\20\lib\windows;%PATH%" ` + --test_env=ASAN_OPTIONS=quarantine_size_mb=16 ` + --test_tag_filters=-no-windows --build_tests_only=true ` + centipede/puzzles:all - name: Save new cache based on main - if: github.ref == 'refs/heads/main' - uses: actions/cache/save@v4 + # if: github.ref == 'refs/heads/main' + uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0 with: path: "~/.cache/bazel" - key: bazel-centipede-cache-mac-${{ github.run_id }} + key: bazel-centipede-cache-win-${{ github.run_id }}
diff --git a/BUILD b/BUILD new file mode 100644 index 0000000..4f91cb3 --- /dev/null +++ b/BUILD
@@ -0,0 +1,8 @@ +platform( + name = "x64_windows-clang-cl", + constraint_values = [ + "@platforms//cpu:x86_64", + "@platforms//os:windows", + "@bazel_tools//tools/cpp:clang-cl", + ], +)
diff --git a/MODULE.bazel b/MODULE.bazel index 4610482..741fdab 100644 --- a/MODULE.bazel +++ b/MODULE.bazel
@@ -23,6 +23,10 @@ name = "rules_cc", version = "0.2.17", ) + +cc_configure = use_extension("@rules_cc//cc:extensions.bzl", "cc_configure_extension") +use_repo(cc_configure, "local_config_cc") + bazel_dep( name = "rules_shell", version = "0.6.1",
diff --git a/centipede/BUILD b/centipede/BUILD index 3ad6fae..b3716bf 100644 --- a/centipede/BUILD +++ b/centipede/BUILD
@@ -215,6 +215,10 @@ name = "util", srcs = ["util.cc"], hdrs = ["util.h"], + linkopts = select({ + "@platforms//os:windows": ["-DEFAULTLIB:ws2_32.lib"], + "//conditions:default": [], + }), deps = [ ":feature", "@abseil-cpp//absl/base:core_headers", @@ -246,10 +250,12 @@ srcs = ["config_init.cc"], hdrs = ["config_init.h"], deps = [ + ":centipede_flags", ":config_util", "@abseil-cpp//absl/base:core_headers", "@abseil-cpp//absl/base:log_severity", "@abseil-cpp//absl/flags:config", + "@abseil-cpp//absl/flags:flag", "@abseil-cpp//absl/flags:parse", "@abseil-cpp//absl/log:globals", "@abseil-cpp//absl/log:initialize", @@ -340,7 +346,11 @@ "@abseil-cpp//absl/functional:any_invocable", "@abseil-cpp//absl/synchronization", "@abseil-cpp//absl/time", - ], + ] + select({ + "@platforms//os:windows": [], + "//conditions:default": [ + ], + }), ) cc_library( @@ -349,6 +359,7 @@ hdrs = ["stats.h"], linkopts = select({ "@platforms//os:macos": [], + "@platforms//os:windows": [], "//conditions:default": [ "-latomic", # for std::atomic::load()/store(). ], @@ -417,6 +428,7 @@ copts = DISABLE_SANCOV_COPTS, linkopts = select({ "@platforms//os:macos": [], + "@platforms//os:windows": [], "//conditions:default": [ "-lrt", # for shm_open ], @@ -605,6 +617,7 @@ "@abseil-cpp//absl/synchronization", "@abseil-cpp//absl/time", "@com_google_fuzztest//common:logging", + "@com_google_fuzztest//fuzztest/internal:escaping", ], ) @@ -629,6 +642,10 @@ name = "centipede_callbacks", srcs = ["centipede_callbacks.cc"], hdrs = ["centipede_callbacks.h"], + linkopts = select({ + "@platforms//os:windows": ["-DEFAULTLIB:ws2_32.lib"], + "//conditions:default": [], + }), deps = [ ":binary_info", ":byte_array_mutator", @@ -683,6 +700,7 @@ hdrs = ["stop.h"], linkopts = select({ "@platforms//os:macos": [], + "@platforms//os:windows": [], "//conditions:default": [ "-latomic", # for std::atomic::load()/store(). ], @@ -1007,6 +1025,10 @@ "runner_utils.h", ], hdrs = ["engine_worker_abi.h"], + linkopts = select({ + "@platforms//os:windows": ["-DEFAULTLIB:ws2_32.lib"], + "//conditions:default": [], + }), deps = [ ":engine_abi", ":execution_metadata", @@ -1014,6 +1036,7 @@ ":runner_request", ":runner_result", ":shared_memory_blob_sequence", + ":util", "@abseil-cpp//absl/base:nullability", "@com_google_fuzztest//common:defs", ], @@ -1024,6 +1047,7 @@ srcs = ["engine_controller_with_subprocess.cc"], hdrs = ["engine_controller_abi.h"], deps = [ + ":command", "@com_google_fuzztest//centipede:engine_abi", "@com_google_fuzztest//fuzztest/internal:escaping", ], @@ -1065,22 +1089,29 @@ # Need to allow MSAN to avoid false positives when interacting with dependencies. RUNNER_COPTS = [ "-fsanitize-coverage=0", - "-fno-sanitize=address,hwaddress,thread,undefined", -] +] + select({ + "@platforms//os:windows": [], + "//conditions:default": ["-fno-sanitize=address,hwaddress,thread,undefined"], +}) RUNNER_SANITIZED_COPTS = [ "-fsanitize-coverage=0", ] -RUNNER_LINKOPTS = [ - "-ldl", # for dlsym - "-lpthread", # for pthread_once -] + select({ +RUNNER_LINKOPTS = select({ + "@platforms//os:windows": [ + "-DEFAULTLIB:psapi.lib", + "-DEFAULTLIB:ws2_32.lib", + ], "@platforms//os:macos": [ + "-ldl", # for dlsym + "-lpthread", # for pthread_once # For compatible behavior on weak symbols. "-Wl,-undefined,dynamic_lookup", ], "//conditions:default": [ + "-ldl", # for dlsym + "-lpthread", # for pthread_once "-lrt", # for shm_open ], }) @@ -1997,6 +2028,7 @@ ":centipede_callbacks", ":centipede_default_callbacks", ":centipede_interface", + ":command", ":environment", ":feature", ":mutation_data",
diff --git a/centipede/binary_info.cc b/centipede/binary_info.cc index 0d02c53..246613b 100644 --- a/centipede/binary_info.cc +++ b/centipede/binary_info.cc
@@ -64,7 +64,7 @@ ":arg2=", cf_table_path.path(), ":arg3=", dso_table_path.path(), ":")); Command::Options cmd_options; cmd_options.env_diff = std::move(env_diff); - cmd_options.stdout_file_prefix = log_prefix; + cmd_options.stdout_file_prefix = log_prefix.string(); Command cmd{binary_path_with_args, std::move(cmd_options)}; int exit_code = cmd.Execute(); if (exit_code != EXIT_SUCCESS) { @@ -129,20 +129,20 @@ std::string symbol_table_contents; // TODO(b/295978603): move calculation of paths into WorkDir class. FUZZTEST_CHECK_OK(RemoteFileGetContents( - (std::filesystem::path(dir) / kSymbolTableFileName).c_str(), + (std::filesystem::path(dir) / kSymbolTableFileName).string(), symbol_table_contents)); std::istringstream symbol_table_stream(symbol_table_contents); symbols.ReadFromLLVMSymbolizer(symbol_table_stream); std::string pc_table_contents; FUZZTEST_CHECK_OK(RemoteFileGetContents( - (std::filesystem::path(dir) / kPCTableFileName).c_str(), + (std::filesystem::path(dir) / kPCTableFileName).string(), pc_table_contents)); std::istringstream pc_table_stream(pc_table_contents); pc_table = ReadPcTable(pc_table_stream); cf_table = - ReadCfTable((std::filesystem::path(dir) / kCfTableFileName).c_str()); + ReadCfTable((std::filesystem::path(dir) / kCfTableFileName).string()); } void BinaryInfo::Write(std::string_view dir) { @@ -150,19 +150,19 @@ symbols.WriteToLLVMSymbolizer(symbol_table_stream); // TODO(b/295978603): move calculation of paths into WorkDir class. FUZZTEST_CHECK_OK(RemoteFileSetContents( - (std::filesystem::path(dir) / kSymbolTableFileName).c_str(), + (std::filesystem::path(dir) / kSymbolTableFileName).string(), symbol_table_stream.str())); std::ostringstream pc_table_stream; WritePcTable(pc_table, pc_table_stream); FUZZTEST_CHECK_OK(RemoteFileSetContents( - (std::filesystem::path(dir) / kPCTableFileName).c_str(), + (std::filesystem::path(dir) / kPCTableFileName).string(), pc_table_stream.str())); std::ostringstream cf_table_stream; WriteCfTable(cf_table, cf_table_stream); FUZZTEST_CHECK_OK(RemoteFileSetContents( - (std::filesystem::path(dir) / kCfTableFileName).c_str(), + (std::filesystem::path(dir) / kCfTableFileName).string(), cf_table_stream.str())); }
diff --git a/centipede/byte_array_mutator.cc b/centipede/byte_array_mutator.cc index fc2597d..bd64a84 100644 --- a/centipede/byte_array_mutator.cc +++ b/centipede/byte_array_mutator.cc
@@ -292,7 +292,7 @@ const ByteArray &other) { // Overwrite data[pos:pos+size] with other[first:first+size]. // Overwrite no more than half of data. - size_t max_size = std::max(1UL, data.size() / 2); + size_t max_size = std::max(size_t{1}, data.size() / 2); size_t first = rng_() % other.size(); max_size = std::min(max_size, other.size() - first); size_t size = 1 + rng_() % max_size;
diff --git a/centipede/centipede.cc b/centipede/centipede.cc index e2a0ee3..6328d87 100644 --- a/centipede/centipede.cc +++ b/centipede/centipede.cc
@@ -137,14 +137,18 @@ function_filter_(env_.function_filter, symbols_), coverage_logger_(coverage_logger), stats_(stats), - input_filter_path_(std::filesystem::path(TemporaryLocalDirPath()) - .append("filter-input")), + input_filter_path_{[&] { + auto dir = TemporaryLocalDirPath(); + CreateLocalDirRemovedAtExit(dir); + return (std::filesystem::path(dir) / "filter-input").string(); + }()}, input_filter_cmd_{[&] { Command::Options cmd_options; cmd_options.args = {input_filter_path_}; cmd_options.stdout_file_prefix = std::filesystem::path(TemporaryLocalDirPath()) - .append("filter-input.log"); + .append("filter-input.log") + .string(); cmd_options.stderr_file_prefix = cmd_options.stdout_file_prefix; return Command{env_.input_filter, std::move(cmd_options)}; }()}, @@ -243,7 +247,8 @@ std::vector<std::string> reproducer_paths, RemoteListFiles(reproducer_dir, /*recursively=*/false)); for (const auto &reproducer_path : reproducer_paths) { - std::string id = std::filesystem::path{reproducer_path}.filename(); + std::string id = + std::filesystem::path{reproducer_path}.filename().string(); if (auto [_it, inserted] = crash_ids.insert(id); !inserted) { continue; } @@ -1086,11 +1091,12 @@ auto hash = Hash(one_input); auto crash_dir = wd_.CrashReproducerDirPaths().MyShard(); FUZZTEST_CHECK_OK(RemoteMkdir(crash_dir)); - std::string input_file_path = std::filesystem::path(crash_dir) / hash; + std::string input_file_path = + (std::filesystem::path(crash_dir) / hash).string(); auto crash_metadata_dir = wd_.CrashMetadataDirPaths().MyShard(); FUZZTEST_CHECK_OK(RemoteMkdir(crash_metadata_dir)); std::string crash_metadata_path_prefix = - std::filesystem::path(crash_metadata_dir) / hash; + (std::filesystem::path(crash_metadata_dir) / hash).string(); FUZZTEST_LOG(INFO) << log_prefix << "Detected crash-reproducing input:" << "\nInput index : " << input_idx << "\nInput bytes : " @@ -1139,21 +1145,25 @@ FUZZTEST_CHECK_OK(RemoteMkdir(crash_dir)); std::string crashing_batch_name = absl::StrCat("crashing_batch-", suspect_hash); - std::string save_dir = std::filesystem::path(crash_dir) / crashing_batch_name; + std::string save_dir = + (std::filesystem::path(crash_dir) / crashing_batch_name).string(); FUZZTEST_CHECK_OK(RemoteMkdir(save_dir)); FUZZTEST_LOG(INFO) << log_prefix << "Saving used inputs from batch to: " << save_dir; for (int i = 0; i <= suspect_input_idx; ++i) { const auto &one_input = input_vec[i]; auto hash = Hash(one_input); - std::string file_path = std::filesystem::path(save_dir).append( - absl::StrFormat("input-%010d-%s", i, hash)); + std::string file_path = + std::filesystem::path(save_dir) + .append(absl::StrFormat("input-%010d-%s", i, hash)) + .string(); FUZZTEST_CHECK_OK(RemoteFileSetContents(file_path, one_input)); } auto crash_metadata_dir = wd_.CrashMetadataDirPaths().MyShard(); FUZZTEST_CHECK_OK(RemoteMkdir(crash_metadata_dir)); std::string crash_metadata_file_path = - std::filesystem::path(crash_metadata_dir) / crashing_batch_name; + (std::filesystem::path(crash_metadata_dir) / crashing_batch_name) + .string(); FUZZTEST_LOG(INFO) << log_prefix << "Saving crash metadata to: " << crash_metadata_file_path; FUZZTEST_CHECK_OK(RemoteFileSetContents(crash_metadata_file_path,
diff --git a/centipede/centipede_callbacks.cc b/centipede/centipede_callbacks.cc index c615992..440203f 100644 --- a/centipede/centipede_callbacks.cc +++ b/centipede/centipede_callbacks.cc
@@ -14,12 +14,26 @@ #include "./centipede/centipede_callbacks.h" +#if !defined(_WIN32) #include <fcntl.h> #include <sys/poll.h> #include <sys/socket.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/un.h> +#include <unistd.h> +#else +#define WIN32_LEAN_AND_MEAN +#define NOGDI +// clang-format off +// Must be before <afunix.h> +#include <ws2tcpip.h> +// clang-format on +#include <afunix.h> +#include <io.h> +#include <windows.h> +#include <winsock2.h> +#endif #include <algorithm> #include <cerrno> @@ -41,6 +55,7 @@ #include "absl/strings/ascii.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_join.h" +#include "absl/strings/str_replace.h" #include "absl/strings/str_split.h" #include "absl/synchronization/mutex.h" #include "absl/time/clock.h" @@ -52,6 +67,7 @@ #include "./centipede/mutation_data.h" #include "./centipede/runner_request.h" #include "./centipede/runner_result.h" +#include "./centipede/shared_memory_blob_sequence.h" #include "./centipede/stop.h" #include "./centipede/util.h" #include "./centipede/workdir.h" @@ -62,14 +78,36 @@ namespace fuzztest::internal { +namespace { + +#if defined(_WIN32) +using socket_t = SOCKET; +constexpr socket_t kInvalidSocket = INVALID_SOCKET; +#else +using socket_t = int; +constexpr socket_t kInvalidSocket = -1; +#endif + +std::string RunnerFlagEscape(std::string_view value) { + return absl::StrReplaceAll(value, { + {":", "\\:"}, + {"\\", "\\\\"}, + }); +} + +} // namespace + class CentipedeCallbacks::PersistentModeServer { public: explicit PersistentModeServer(std::string server_path) : server_path_(std::move(server_path)) { FUZZTEST_CHECK(!server_path_.empty()); +#if defined(_WIN32) + EnsureWinsockInitialized(); +#endif server_socket_ = socket(AF_UNIX, SOCK_STREAM, 0); - FUZZTEST_PCHECK(server_socket_ != -1); + FUZZTEST_PCHECK(server_socket_ != kInvalidSocket); SetCloseOnExec(server_socket_); SetNonBlocking(server_socket_); @@ -84,13 +122,23 @@ << " is too long. Truncating it to " << new_server_path; server_path_ = std::move(new_server_path); } - server_path_.copy(server_addr.sun_path, sizeof(server_addr.sun_path)); + std::string win_sock_path = server_path_; + std::memset(server_addr.sun_path, 0, sizeof(server_addr.sun_path)); + std::strncpy(server_addr.sun_path, win_sock_path.c_str(), + sizeof(server_addr.sun_path) - 1); +#if !defined(_WIN32) static constexpr int kEnable = 1; FUZZTEST_PCHECK(setsockopt(server_socket_, SOL_SOCKET, SO_REUSEADDR, - &kEnable, sizeof(kEnable)) != -1); - FUZZTEST_PCHECK(bind(server_socket_, (struct sockaddr*)&server_addr, - sizeof(server_addr)) != -1); + reinterpret_cast<const char*>(&kEnable), + sizeof(kEnable)) != -1); +#endif + std::error_code ec; + std::filesystem::remove(server_path_, ec); + std::filesystem::remove(win_sock_path, ec); + int bind_ret = bind(server_socket_, (struct sockaddr*)&server_addr, + sizeof(server_addr)); + FUZZTEST_PCHECK(bind_ret != -1); // Expect one child process to connect to the server. static constexpr int kBacklog = 1; @@ -105,7 +153,7 @@ if (!EnsureConnection(deadline)) { return false; } - FUZZTEST_CHECK_NE(conn_socket_, -1); + FUZZTEST_CHECK(conn_socket_ != kInvalidSocket); FUZZTEST_CHECK(!in_batch_); if (!WriteFd(conn_socket_, deadline, PersistentModeRequest::kRunBatch)) { FUZZTEST_LOG(ERROR) @@ -123,7 +171,7 @@ // code if running without persistent mode, hence the name). Returns false // otherwise. bool WaitBatch(absl::Time deadline, int& exit_code) { - FUZZTEST_CHECK_NE(conn_socket_, -1); + FUZZTEST_CHECK(conn_socket_ != kInvalidSocket); FUZZTEST_CHECK(in_batch_); if (!ReadFd(conn_socket_, deadline, exit_code)) { FUZZTEST_LOG(ERROR) @@ -139,7 +187,7 @@ void RequestExit(absl::Time deadline) { if (!EnsureConnection(deadline)) return; - FUZZTEST_CHECK_NE(conn_socket_, -1); + FUZZTEST_CHECK(conn_socket_ != kInvalidSocket); if (!WriteFd(conn_socket_, deadline, PersistentModeRequest::kExit)) { FUZZTEST_LOG(ERROR) << "Failed to request the persistent mode client to exit - " @@ -149,13 +197,17 @@ } ~PersistentModeServer() { - if (conn_socket_ != -1) { + if (conn_socket_ != kInvalidSocket) { Disconnect(); } - FUZZTEST_CHECK_NE(server_socket_, -1); + FUZZTEST_CHECK(server_socket_ != kInvalidSocket); +#if defined(_WIN32) + closesocket(server_socket_); +#else FUZZTEST_PCHECK(close(server_socket_) != -1); - server_socket_ = -1; +#endif + server_socket_ = kInvalidSocket; std::error_code ec; FUZZTEST_CHECK(!server_path_.empty()); @@ -167,27 +219,51 @@ } private: - static void SetCloseOnExec(int fd) { + static void SetCloseOnExec(socket_t fd) { +#if defined(_WIN32) + SetHandleInformation(reinterpret_cast<HANDLE>(fd), HANDLE_FLAG_INHERIT, 0); +#else int flags = fcntl(fd, F_GETFD); FUZZTEST_PCHECK(flags != -1); flags |= FD_CLOEXEC; FUZZTEST_PCHECK(fcntl(fd, F_SETFD, flags) != -1); +#endif } - static void SetNonBlocking(int fd) { + static void SetNonBlocking(socket_t fd) { +#if defined(_WIN32) + u_long mode = 1; + ioctlsocket(fd, FIONBIO, &mode); +#else int flags = fcntl(fd, F_GETFL); FUZZTEST_PCHECK(flags != -1); flags |= O_NONBLOCK; FUZZTEST_PCHECK(fcntl(fd, F_SETFL, flags) != -1); +#endif } - std::string_view FdName(int fd) { + std::string_view FdName(socket_t fd) { return fd == server_socket_ ? "server" : "connection"; } - bool PollFd(int fd, int event, absl::Time deadline) { + bool PollFd(socket_t fd, int event, absl::Time deadline) { FUZZTEST_CHECK(event == POLLIN || event == POLLOUT) << "`event` must be POLLIN or POLLOUT"; +#if defined(_WIN32) + fd_set fds; + FD_ZERO(&fds); + FD_SET(fd, &fds); + timeval tv{}; + int timeout_ms = PollTimeoutMs(deadline - absl::Now()); + if (timeout_ms >= 0) { + tv.tv_sec = timeout_ms / 1000; + tv.tv_usec = (timeout_ms % 1000) * 1000; + } + int select_ret = + select(0, event == POLLIN ? &fds : NULL, event == POLLOUT ? &fds : NULL, + NULL, timeout_ms >= 0 ? &tv : NULL); + return select_ret > 0; +#else struct pollfd poll_fd{}; int poll_ret = -1; do { @@ -196,18 +272,13 @@ } while (poll_ret < 0 && errno == EINTR); if (poll_ret == 1 && (poll_fd.revents & (event | POLLHUP)) == event) { return true; - } else if (poll_ret < 0) { - FUZZTEST_PLOG(ERROR) << "Persistent mode: poll() failed on " - << FdName(fd); - } else if (poll_ret == 0) { - FUZZTEST_LOG(ERROR) << "Persistent mode: poll() timed out on " - << FdName(fd); } +#endif return false; } template <typename T> - bool ReadFd(int fd, absl::Time deadline, T& data) { + bool ReadFd(socket_t fd, absl::Time deadline, T& data) { static_assert( std::is_trivial_v<T> && std::is_standard_layout_v<T>, "ReadFd() must be used on a trivial type with standard layout"); @@ -217,7 +288,11 @@ if (!PollFd(fd, POLLIN, deadline)) { return false; } +#if defined(_WIN32) + auto r = recv(fd, cursor, static_cast<int>(end - cursor), 0); +#else ssize_t r = read(fd, cursor, end - cursor); +#endif if (r > 0) { cursor += r; FUZZTEST_CHECK(cursor <= end) @@ -228,7 +303,12 @@ FUZZTEST_LOG(ERROR) << "read() returned 0 early on " << FdName(fd); return false; } +#if defined(_WIN32) + if (WSAGetLastError() == WSAEWOULDBLOCK || WSAGetLastError() == WSAEINTR) + continue; +#else if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) continue; +#endif FUZZTEST_PLOG(ERROR) << "read() fails on " << FdName(fd); return false; } @@ -236,7 +316,7 @@ } template <typename T> - bool WriteFd(int fd, absl::Time deadline, const T& data) { + bool WriteFd(socket_t fd, absl::Time deadline, const T& data) { static_assert( std::is_trivial_v<T> && std::is_standard_layout_v<T>, "WriteFd() must be used on a trivial type with standard layout"); @@ -246,7 +326,11 @@ if (!PollFd(fd, POLLOUT, deadline)) { return false; } +#if defined(_WIN32) + auto r = send(fd, cursor, static_cast<int>(end - cursor), 0); +#else ssize_t r = write(fd, cursor, end - cursor); +#endif if (r > 0) { cursor += r; FUZZTEST_CHECK(cursor <= end) @@ -257,7 +341,12 @@ FUZZTEST_LOG(ERROR) << "write() returned 0 early on " << FdName(fd); return false; } +#if defined(_WIN32) + if (WSAGetLastError() == WSAEWOULDBLOCK || WSAGetLastError() == WSAEINTR) + continue; +#else if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) continue; +#endif FUZZTEST_PLOG(ERROR) << "write() fails on " << FdName(fd); return false; } @@ -265,22 +354,27 @@ } bool EnsureConnection(absl::Time deadline) { - if (conn_socket_ != -1) return true; + if (conn_socket_ != kInvalidSocket) return true; // Since the runner always tries to connect to the persistent mode // socket at the beginning of the execution, waiting for the connection // should be fast if the the runner is able to connect at all. But we // need to give enough time for the binary to load and reach the runner // logic (120s should be enough). deadline = std::min(deadline, absl::Now() + absl::Seconds(120)); - FUZZTEST_CHECK_NE(server_socket_, -1); + FUZZTEST_CHECK(server_socket_ != kInvalidSocket); do { if (!PollFd(server_socket_, POLLIN, deadline)) { return false; } conn_socket_ = accept(server_socket_, nullptr, 0); - } while (conn_socket_ == -1 && - (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)); - FUZZTEST_PCHECK(conn_socket_ != -1); + } while ( + conn_socket_ == kInvalidSocket && +#if defined(_WIN32) + (WSAGetLastError() == WSAEINTR || WSAGetLastError() == WSAEWOULDBLOCK)); +#else + (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)); +#endif + FUZZTEST_CHECK(conn_socket_ != kInvalidSocket); SetCloseOnExec(conn_socket_); SetNonBlocking(conn_socket_); @@ -288,14 +382,18 @@ } void Disconnect() { - FUZZTEST_CHECK_NE(conn_socket_, -1); + FUZZTEST_CHECK(conn_socket_ != kInvalidSocket); +#if defined(_WIN32) + closesocket(conn_socket_); +#else FUZZTEST_PCHECK(close(conn_socket_) != -1); - conn_socket_ = -1; +#endif + conn_socket_ = kInvalidSocket; } std::string server_path_; - int server_socket_ = -1; - int conn_socket_ = -1; + socket_t server_socket_ = kInvalidSocket; + socket_t conn_socket_ = kInvalidSocket; bool in_batch_ = false; }; @@ -334,6 +432,26 @@ } } +CentipedeCallbacks::CentipedeCallbacks(const Environment& env, + StopCondition& stop_condition) + : env_(env), + stop_condition_(stop_condition), + byte_array_mutator_(env.knobs, GetRandomSeed(env.seed)), + fuzztest_mutator_(env.knobs, GetRandomSeed(env.seed)) { + inputs_blobseq_ = std::make_unique<SharedMemoryBlobSequence>( + shmem_name1_.c_str(), env.shmem_size_mb << 20, env.use_posix_shmem); + outputs_blobseq_ = std::make_unique<SharedMemoryBlobSequence>( + shmem_name2_.c_str(), env.shmem_size_mb << 20, env.use_posix_shmem); + CreateLocalDirRemovedAtExit(temp_dir_); + if (env.use_legacy_default_mutator) { + bool r = byte_array_mutator_.set_max_len(env.max_len); + FUZZTEST_CHECK(r); + } else { + bool r = fuzztest_mutator_.set_max_len(env.max_len); + FUZZTEST_CHECK(r); + } +} + std::string CentipedeCallbacks::ConstructRunnerFlags( std::string_view extra_flags, bool disable_coverage) { std::vector<std::string> flags = { @@ -358,11 +476,12 @@ if (env_.use_dataflow_features) flags.emplace_back("use_dataflow_features"); } if (!env_.runner_dl_path_suffix.empty()) { - flags.emplace_back( - absl::StrCat("dl_path_suffix=", env_.runner_dl_path_suffix)); + flags.emplace_back(absl::StrCat( + "dl_path_suffix=", RunnerFlagEscape(env_.runner_dl_path_suffix))); } if (!env_.pcs_file_path.empty()) - flags.emplace_back(absl::StrCat("pcs_file_path=", env_.pcs_file_path)); + flags.emplace_back( + absl::StrCat("pcs_file_path=", RunnerFlagEscape(env_.pcs_file_path))); if (!extra_flags.empty()) flags.emplace_back(extra_flags); flags.emplace_back(""); return absl::StrJoin(flags, ":"); @@ -388,24 +507,34 @@ // The current construction seems to be fine (usually below 100 bytes) for // the Linux limit (108 bytes), but we put the descriptive part to the end // make it still likely meaningful when truncated. + std::string tmp_prefix = getenv("TMPDIR") ? getenv("TMPDIR") + : getenv("TEMP") ? getenv("TEMP") + : getenv("TMP") ? getenv("TMP") + : "/tmp"; std::string server_path = - absl::StrCat(ProcessAndThreadUniqueID("/tmp/centipede-"), "-", - Hash(binary), "-persistent-mode"); + (std::filesystem::path(tmp_prefix) / + absl::StrCat(ProcessAndThreadUniqueID("centipede-"), "-", Hash(binary), + "-pm.sock")) + .string(); persistent_mode_server = std::make_unique<CentipedeCallbacks::PersistentModeServer>( std::move(server_path)); } std::vector<std::string> env_diff = env_.env_diff_for_binaries; env_diff.push_back(ConstructRunnerFlags( - absl::StrCat(":shmem:test=", env_.test_name, ":arg1=", - inputs_blobseq_.path(), ":arg2=", outputs_blobseq_.path(), - ":failure_description_path=", failure_description_path_, - ":failure_signature_path=", failure_signature_path_, - persistent_mode_server == nullptr - ? "" - : absl::StrCat(":persistent_mode_socket=", - persistent_mode_server->server_path()), - ":"), + absl::StrCat( + ":shmem:test=", RunnerFlagEscape(env_.test_name), + ":arg1=", RunnerFlagEscape(inputs_blobseq_->path()), + ":arg2=", RunnerFlagEscape(outputs_blobseq_->path()), + ":failure_description_path=", + RunnerFlagEscape(failure_description_path_), + ":failure_signature_path=", RunnerFlagEscape(failure_signature_path_), + persistent_mode_server == nullptr + ? "" + : absl::StrCat( + ":persistent_mode_socket=", + RunnerFlagEscape(persistent_mode_server->server_path())), + ":"), disable_coverage)); if (env_.clang_coverage_binary == binary) { @@ -539,8 +668,8 @@ batch_result.ClearAndResize(inputs.size()); // Reset the blobseqs. - inputs_blobseq_.Reset(); - outputs_blobseq_.Reset(); + inputs_blobseq_->Reset(); + outputs_blobseq_->Reset(); size_t num_inputs_written = 0; @@ -550,7 +679,7 @@ num_inputs_written = 1; } else { // Feed the inputs to inputs_blobseq_. - num_inputs_written = RequestExecution(inputs, inputs_blobseq_); + num_inputs_written = RequestExecution(inputs, *inputs_blobseq_); } if (num_inputs_written != inputs.size()) { @@ -562,16 +691,16 @@ // Run. const auto batch_start_time = absl::Now(); const int exit_code = RunBatchForBinary(binary); - inputs_blobseq_.ReleaseSharedMemory(); // Inputs are already consumed. + inputs_blobseq_->ReleaseSharedMemory(); // Inputs are already consumed. const bool batch_timed_out = env_.timeout_per_batch > 0 && absl::Now() - batch_start_time > absl::Seconds(env_.timeout_per_batch); // Get results. batch_result.exit_code() = exit_code; - const bool read_success = batch_result.Read(outputs_blobseq_); + const bool read_success = batch_result.Read(*outputs_blobseq_); FUZZTEST_LOG_IF(ERROR, !read_success) << "Failed to read batch result!"; - outputs_blobseq_.ReleaseSharedMemory(); // Outputs are already consumed. + outputs_blobseq_->ReleaseSharedMemory(); // Outputs are already consumed. // We may have fewer feature blobs than inputs if // * some inputs were not written (i.e. num_inputs_written < inputs.size). @@ -602,7 +731,8 @@ // TODO: b/467103298 - Handle failures when the exit code is zero, e.g., when // the target exits via `std::_Exit(0)`. - if (exit_code != EXIT_SUCCESS) { + if (exit_code != EXIT_SUCCESS || + std::filesystem::exists(failure_description_path_)) { ReadFromLocalFile(last_execute_log_path_, batch_result.log()); if (std::filesystem::exists(failure_description_path_)) { @@ -649,7 +779,7 @@ std::string centipede_runner_flags = absl::StrCat( "CENTIPEDE_RUNNER_FLAGS=:dump_seed_inputs:test=", env_.test_name, - ":arg1=", output_dir.string(), ":"); + ":arg1=", RunnerFlagEscape(output_dir.string()), ":"); if (!env_.runner_dl_path_suffix.empty()) { absl::StrAppend(¢ipede_runner_flags, "dl_path_suffix=", env_.runner_dl_path_suffix, ":"); @@ -687,7 +817,7 @@ std::vector<std::string> seed_input_filenames; for (const auto& dir_ent : std::filesystem::directory_iterator(output_dir)) { - seed_input_filenames.push_back(dir_ent.path().filename()); + seed_input_filenames.push_back(dir_ent.path().filename().string()); } std::sort(seed_input_filenames.begin(), seed_input_filenames.end()); num_avail_seeds = seed_input_filenames.size(); @@ -711,14 +841,13 @@ // See also: `DumpSerializedTargetConfigToFile()`. bool CentipedeCallbacks::GetSerializedTargetConfigViaExternalBinary( std::string_view binary, std::string& serialized_config) { - const auto config_file_path = - std::filesystem::path{temp_dir_} / "configuration"; + const std::string config_file_path = temp_dir_ + "/configuration"; std::string centipede_runner_flags = absl::StrCat("CENTIPEDE_RUNNER_FLAGS=:dump_configuration:arg1=", - config_file_path.string(), ":"); + RunnerFlagEscape(config_file_path), ":"); if (!env_.runner_dl_path_suffix.empty()) { - absl::StrAppend(¢ipede_runner_flags, - "dl_path_suffix=", env_.runner_dl_path_suffix, ":"); + absl::StrAppend(¢ipede_runner_flags, "dl_path_suffix=", + RunnerFlagEscape(env_.runner_dl_path_suffix), ":"); } Command::Options cmd_options; cmd_options.env_diff = env_.env_diff_for_binaries; @@ -732,7 +861,7 @@ if (is_success) { if (std::filesystem::exists(config_file_path)) { - ReadFromLocalFile(config_file_path.string(), serialized_config); + ReadFromLocalFile(config_file_path, serialized_config); } else { serialized_config = ""; } @@ -740,9 +869,11 @@ if (env_.print_runner_log || !is_success) { PrintExecutionLog(); } - std::error_code error; - std::filesystem::remove(config_file_path, error); - FUZZTEST_CHECK(!error); + if (std::filesystem::exists(config_file_path)) { + std::error_code error; + std::filesystem::remove(config_file_path, error); + FUZZTEST_CHECK(!error); + } return is_success; } @@ -755,17 +886,17 @@ << "Standalone binary does not support custom mutator"; auto start_time = absl::Now(); - inputs_blobseq_.Reset(); - outputs_blobseq_.Reset(); + inputs_blobseq_->Reset(); + outputs_blobseq_->Reset(); size_t num_inputs_written = - RequestMutation(num_mutants, inputs, inputs_blobseq_); + RequestMutation(num_mutants, inputs, *inputs_blobseq_); FUZZTEST_LOG_IF(INFO, num_inputs_written != inputs.size()) << VV(num_inputs_written) << VV(inputs.size()); // Execute. const int exit_code = RunBatchForBinary(binary); - inputs_blobseq_.ReleaseSharedMemory(); // Inputs are already consumed. + inputs_blobseq_->ReleaseSharedMemory(); // Inputs are already consumed. if (exit_code != EXIT_SUCCESS) { FUZZTEST_LOG(WARNING) << "Custom mutator failed with exit code: " @@ -777,8 +908,8 @@ MutationResult result; result.exit_code() = exit_code; - result.Read(num_mutants, outputs_blobseq_); - outputs_blobseq_.ReleaseSharedMemory(); // Outputs are already consumed. + result.Read(num_mutants, *outputs_blobseq_); + outputs_blobseq_->ReleaseSharedMemory(); // Outputs are already consumed. FUZZTEST_VLOG(1) << __FUNCTION__ << " took " << (absl::Now() - start_time); return result; @@ -834,7 +965,7 @@ absl::MutexLock lock(GetExecutionLoggingMutex()); for (const auto& log_line : absl::StrSplit(absl::StripAsciiWhitespace(log_text), '\n')) { - FUZZTEST_LOG(INFO).NoPrefix() << "LOG: " << log_line; + FUZZTEST_LOG(INFO).NoPrefix() << "LOG: " << log_line << "\n"; } }
diff --git a/centipede/centipede_callbacks.h b/centipede/centipede_callbacks.h index 38d6bc3..17e16ed 100644 --- a/centipede/centipede_callbacks.h +++ b/centipede/centipede_callbacks.h
@@ -48,20 +48,7 @@ // Note: the interface is not yet stable and may change w/o a notice. class CentipedeCallbacks { public: - CentipedeCallbacks(const Environment& env, StopCondition& stop_condition) - : env_(env), - stop_condition_(stop_condition), - byte_array_mutator_(env.knobs, GetRandomSeed(env.seed)), - fuzztest_mutator_(env.knobs, GetRandomSeed(env.seed)), - inputs_blobseq_(shmem_name1_.c_str(), env.shmem_size_mb << 20, - env.use_posix_shmem), - outputs_blobseq_(shmem_name2_.c_str(), env.shmem_size_mb << 20, - env.use_posix_shmem) { - if (env.use_legacy_default_mutator) - FUZZTEST_CHECK(byte_array_mutator_.set_max_len(env.max_len)); - else - FUZZTEST_CHECK(fuzztest_mutator_.set_max_len(env.max_len)); - } + CentipedeCallbacks(const Environment& env, StopCondition& stop_condition); virtual ~CentipedeCallbacks() {} // Feeds `inputs` into the `binary`, for every input populates `batch_result`. @@ -188,23 +175,23 @@ // They are computed in CTOR, to avoid extra computation in the hot loop. std::string temp_dir_ = TemporaryLocalDirPath(); std::string temp_input_file_path_ = - std::filesystem::path(temp_dir_).append("temp_input_file"); + std::filesystem::path(temp_dir_).append("temp_input_file").string(); const std::string execute_log_prefix_ = - std::filesystem::path(temp_dir_).append("log"); + std::filesystem::path(temp_dir_).append("log").string(); // An owned file path to save the last execution log before it goes out of // scope. const std::string saved_execute_log_path_ = - std::filesystem::path(temp_dir_).append("saved_log"); + std::filesystem::path(temp_dir_).append("saved_log").string(); std::string last_execute_log_path_; std::string failure_description_path_ = - std::filesystem::path(temp_dir_).append("failure_description"); + std::filesystem::path(temp_dir_).append("failure_description").string(); std::string failure_signature_path_ = - std::filesystem::path(temp_dir_).append("failure_signature"); + std::filesystem::path(temp_dir_).append("failure_signature").string(); const std::string shmem_name1_ = ProcessAndThreadUniqueID("/ctpd-shm1-"); const std::string shmem_name2_ = ProcessAndThreadUniqueID("/ctpd-shm2-"); - SharedMemoryBlobSequence inputs_blobseq_; - SharedMemoryBlobSequence outputs_blobseq_; + std::unique_ptr<SharedMemoryBlobSequence> inputs_blobseq_; + std::unique_ptr<SharedMemoryBlobSequence> outputs_blobseq_; // Need unique_ptr indirection because CommandContext is not movable/copyable // due to Command.
diff --git a/centipede/centipede_interface.cc b/centipede/centipede_interface.cc index 489bd97..30681e7 100644 --- a/centipede/centipede_interface.cc +++ b/centipede/centipede_interface.cc
@@ -14,7 +14,14 @@ #include "./centipede/centipede_interface.h" +#if !defined(_WIN32) #include <unistd.h> +#else +#define WIN32_LEAN_AND_MEAN +#define NOGDI +#include <process.h> +#include <windows.h> +#endif #include <algorithm> #include <atomic> @@ -81,7 +88,7 @@ void ForEachBlob(const Environment& env, StopCondition& stop_condition) { auto tmpdir = TemporaryLocalDirPath(); CreateLocalDirRemovedAtExit(tmpdir); - std::string tmpfile = std::filesystem::path(tmpdir).append("t"); + std::string tmpfile = (std::filesystem::path(tmpdir) / "t").string(); for (const auto& arg : env.args) { FUZZTEST_LOG(INFO) << "Running '" << env.for_each_blob << "' on " << arg; @@ -153,7 +160,8 @@ binary_info.Write(binary_info_dir); } if (binary_info.uses_legacy_trace_pc_instrumentation) { - pcs_file_path = std::filesystem::path(TemporaryLocalDirPath()) / "pcs"; + pcs_file_path = + (std::filesystem::path(TemporaryLocalDirPath()) / "pcs").string(); SavePCTableToFile(binary_info.pc_table, pcs_file_path); } if (env.use_pcpair_features) { @@ -194,7 +202,7 @@ PeriodicAction::Options{ /*sleep_before_each=*/ [](size_t iteration) { - return absl::Minutes(std::clamp(iteration, 0UL, 10UL)); + return absl::Minutes(std::clamp(iteration, size_t{0}, size_t{10})); }, }); if (!envs.front().experiment.empty() || FUZZTEST_VLOG_IS_ON(1)) { @@ -205,7 +213,8 @@ PeriodicAction::Options{ /*sleep_before_each=*/ [](size_t iteration) { - return absl::Seconds(std::clamp(iteration, 5UL, 600UL)); + return absl::Seconds( + std::clamp(iteration, size_t{5}, size_t{600})); }, }); } @@ -290,7 +299,8 @@ coverage.shard_rel_glob = std::filesystem::path{ workdir.DistilledCorpusFilePaths().AllShardsGlob()} - .filename(); + .filename() + .string(); coverage.individual_input_rel_glob = "*"; coverage.sampled_fraction_or_count = 1.0f; sources.push_back(std::move(coverage)); @@ -300,7 +310,8 @@ // We're seeding the current corpus files. destination.shard_rel_glob = std::filesystem::path{workdir.CorpusFilePaths().AllShardsGlob()} - .filename(); + .filename() + .string(); destination.shard_index_digits = WorkDir::kDigitsInShardIndex; destination.num_shards = static_cast<uint32_t>(env.num_threads); return { @@ -391,7 +402,7 @@ stop_condition.SetStopTime(absl::InfiniteFuture()); if (!is_workdir_specified) { - env.workdir = base_workdir_path / env.test_name; + env.workdir = (base_workdir_path / env.test_name).string(); } const auto execution_id_path = (base_workdir_path / absl::StrCat(env.test_name, ".execution_id")) @@ -465,8 +476,8 @@ if (!is_resuming) { FUZZTEST_CHECK_OK(GenerateSeedCorpusFromConfig( GetSeedCorpusConfig( - env, regression_dir.c_str(), - env.fuzztest_replay_coverage_inputs ? coverage_dir.c_str() : ""), + env, regression_dir.string(), + env.fuzztest_replay_coverage_inputs ? coverage_dir.string() : ""), env.binary_name, env.binary_hash)) << "while generating the seed corpus"; } @@ -474,7 +485,7 @@ absl::Duration time_limit = env.fuzztest_time_limit_per_test; absl::Duration time_spent = absl::ZeroDuration(); const std::string fuzzing_time_file = - std::filesystem::path(env.workdir) / "fuzzing_time"; + (std::filesystem::path(env.workdir) / "fuzzing_time").string(); if (is_resuming && RemotePathExists(fuzzing_time_file)) { time_spent = ReadFuzzingTime(fuzzing_time_file); time_limit = std::max(time_limit - time_spent, absl::ZeroDuration()); @@ -507,10 +518,11 @@ if (!stats_root_path.empty()) { const auto stats_dir = stats_root_path / env.test_name; - FUZZTEST_CHECK_OK(RemoteMkdir(stats_dir.c_str())); + FUZZTEST_CHECK_OK(RemoteMkdir(stats_dir.string())); FUZZTEST_CHECK_OK(RemoteFileRename( workdir.FuzzingStatsPath(), - (stats_dir / absl::StrCat("fuzzing_stats_", execution_stamp)).c_str())); + (stats_dir / absl::StrCat("fuzzing_stats_", execution_stamp)) + .string())); } if (stop_condition.EarlyStopRequested()) { @@ -559,21 +571,22 @@ // Distill and store the coverage corpus. Distill(env); - if (RemotePathExists(coverage_dir.c_str())) { + if (RemotePathExists(coverage_dir.string())) { // In the future, we will store k latest coverage corpora for some k, but // for now we only keep the latest one. FUZZTEST_CHECK_OK( - RemotePathDelete(coverage_dir.c_str(), /*recursively=*/true)); + RemotePathDelete(coverage_dir.string(), /*recursively=*/true)); } - FUZZTEST_CHECK_OK(RemoteMkdir(coverage_dir.c_str())); + FUZZTEST_CHECK_OK(RemoteMkdir(coverage_dir.string())); std::vector<std::string> distilled_corpus_files; FUZZTEST_CHECK_OK( RemoteGlobMatch(workdir.DistilledCorpusFilePaths().AllShardsGlob(), distilled_corpus_files)); for (const std::string& corpus_file : distilled_corpus_files) { - const std::string file_name = std::filesystem::path(corpus_file).filename(); + const std::string file_name = + std::filesystem::path(corpus_file).filename().string(); FUZZTEST_CHECK_OK( - RemoteFileRename(corpus_file, (coverage_dir / file_name).c_str())); + RemoteFileRename(corpus_file, (coverage_dir / file_name).string())); } } @@ -596,7 +609,8 @@ std::vector<std::string> results; results.reserve(crash_paths.size()); for (const auto& crash_path : crash_paths) { - std::string crash_id = std::filesystem::path{crash_path}.filename(); + std::string crash_id = + std::filesystem::path{crash_path}.filename().string(); results.push_back(std::move(crash_id)); } FUZZTEST_CHECK_OK(RemoteFileSetContents(env.list_crash_ids_file, @@ -617,7 +631,7 @@ "crashing"; const WorkDir workdir{env}; SeedCorpusSource crash_corpus_source; - crash_corpus_source.dir_glob = crash_dir; + crash_corpus_source.dir_glob = crash_dir.string(); crash_corpus_source.num_recent_dirs = 1; crash_corpus_source.individual_input_rel_glob = env.crash_id; crash_corpus_source.sampled_fraction_or_count = 1.0f; @@ -627,7 +641,8 @@ /*dir_path=*/env.workdir, /*shard_rel_glob=*/ std::filesystem::path{workdir.CorpusFilePaths().AllShardsGlob()} - .filename(), + .filename() + .string(), /*shard_index_digits=*/WorkDir::kDigitsInShardIndex, /*num_shards=*/1}}; FUZZTEST_CHECK_OK(GenerateSeedCorpusFromConfig( @@ -661,11 +676,12 @@ env.fuzztest_binary_identifier / env.test_name / "crashing"; std::string crash_contents; - const auto read_status = - RemoteFileGetContents((crash_dir / env.crash_id).c_str(), crash_contents); + const auto read_status = RemoteFileGetContents( + (crash_dir / env.crash_id).string(), crash_contents); if (!read_status.ok()) { FUZZTEST_LOG(ERROR) << "Failed reading the crash " << env.crash_id - << " from " << crash_dir.c_str() << ": " << read_status; + << " from " << crash_dir.string() << ": " + << read_status; return EXIT_FAILURE; } const auto write_status =
diff --git a/centipede/centipede_main.cc b/centipede/centipede_main.cc index e38dcaf..d8aa3ba 100644 --- a/centipede/centipede_main.cc +++ b/centipede/centipede_main.cc
@@ -12,7 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if !defined(_WIN32) #include <unistd.h> +#else +#include <io.h> +#endif #include <csignal> #include <cstdlib> @@ -29,6 +33,11 @@ fuzztest::internal::StopCondition global_stop_condition; void SetSignalHandlers() { +#if defined(_WIN32) + std::signal(SIGINT, [](int received_signum) { + global_stop_condition.RequestEarlyStop(EXIT_FAILURE); + }); +#else struct sigaction sigact = {}; sigact.sa_flags = SA_ONSTACK; sigact.sa_handler = [](int received_signum) { @@ -39,6 +48,7 @@ global_stop_condition.RequestEarlyStop(EXIT_FAILURE); }; sigaction(SIGINT, &sigact, nullptr); +#endif } } // namespace
diff --git a/centipede/centipede_test.cc b/centipede/centipede_test.cc index 58539ff..e2e0b14 100644 --- a/centipede/centipede_test.cc +++ b/centipede/centipede_test.cc
@@ -37,6 +37,7 @@ #include "./centipede/centipede_callbacks.h" #include "./centipede/centipede_default_callbacks.h" #include "./centipede/centipede_interface.h" +#include "./centipede/command.h" #include "./centipede/environment.h" #include "./centipede/feature.h" #include "./centipede/mutation_data.h" @@ -64,6 +65,10 @@ using ::testing::Not; using ::testing::SizeIs; +#if defined(_WIN32) && !defined(WEXITSTATUS) +#define WEXITSTATUS(s) (s) +#endif + inline std::vector<ByteSpan> AsByteSpans(const std::vector<ByteArray>& inputs) { std::vector<ByteSpan> results; results.reserve(inputs.size()); @@ -152,7 +157,7 @@ TempCorpusDir tmp_dir{test_info_->name()}; Environment env; env.log_level = 0; // Disable most of the logging in the test. - env.workdir = tmp_dir.path(); + env.workdir = tmp_dir.path().string(); env.num_runs = 100000; // Enough to run through all 1- and 2-byte inputs. env.batch_size = 7; // Just some small number. env.require_pc_table = false; // No PC table here. @@ -180,11 +185,11 @@ TempDir corpus_dir{test_info_->name(), "corpus"}; Environment env; env.log_level = 0; // Disable most of the logging in the test. - env.workdir = workdir_1.path(); + env.workdir = workdir_1.path().string(); env.num_runs = 100000; // Enough to run through all 1- and 2-byte inputs. env.batch_size = 7; // Just some small number. env.require_pc_table = false; // No PC table here. - env.corpus_dir.push_back(corpus_dir.path()); + env.corpus_dir.push_back(corpus_dir.path().string()); // 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. @@ -201,7 +206,7 @@ { // Second, run without fuzzing using the same corpus_dir. - env.workdir = workdir_2.path(); + env.workdir = workdir_2.path().string(); env.num_runs = 0; CentipedeMock mock_2(env); NonOwningCallbacksFactory factory_2(mock_2); @@ -217,11 +222,11 @@ TempDir corpus_dir{test_info_->name(), "corpus"}; Environment env; env.log_level = 0; // Disable most of the logging in the test. - env.workdir = workdir_1.path(); + env.workdir = workdir_1.path().string(); env.num_runs = 100000; // Enough to run through all 1- and 2-byte inputs. env.batch_size = 7; // Just some small number. env.require_pc_table = false; // No PC table here. - env.corpus_dir.push_back(corpus_dir.path()); + env.corpus_dir.push_back(corpus_dir.path().string()); { // First, generate corpus files in corpus_dir. @@ -237,7 +242,7 @@ { // Second, run without fuzzing using the same corpus_dir, but as // output-only. - env.workdir = workdir_2.path(); + env.workdir = workdir_2.path().string(); env.num_runs = 0; env.first_corpus_dir_output_only = true; CentipedeMock mock_2(env); @@ -252,7 +257,7 @@ TempCorpusDir tmp_dir{test_info_->name()}; Environment env; env.log_level = 0; // Disable most of the logging in the test. - env.workdir = tmp_dir.path(); + env.workdir = tmp_dir.path().string(); env.num_runs = 100000; // Enough to run through all 1- and 2-byte inputs. env.batch_size = 7; // Just some small number. env.require_pc_table = false; // No PC table here. @@ -273,7 +278,7 @@ TEST(Centipede, ShardsAndDistillTest) { TempCorpusDir tmp_dir{test_info_->name()}; Environment env; - env.workdir = tmp_dir.path(); + env.workdir = tmp_dir.path().string(); env.log_level = 0; // Disable most of the logging in the test. size_t combined_num_runs = 100000; // Enough to run through all inputs. env.total_shards = 20; @@ -336,13 +341,14 @@ TEST(Centipede, InputFilter) { TempCorpusDir tmp_dir{test_info_->name()}; Environment env; - env.workdir = tmp_dir.path(); + env.workdir = tmp_dir.path().string(); env.num_runs = 256; // Enough to run through all 1- byte inputs. env.log_level = 0; // Disable most of the logging in the test. env.require_pc_table = false; // No PC table here. // Add %f so that test_input_filter doesn't need to be linked with forkserver. - env.input_filter = "%f" + std::string{GetDataDependencyFilepath( - "centipede/testing/test_input_filter")}; + env.input_filter = + "%f" + + GetDataDependencyFilepath("centipede/testing/test_input_filter").string(); CentipedeMock mock(env); NonOwningCallbacksFactory factory(mock); CentipedeMain(env, factory); // Run fuzzing. @@ -404,10 +410,10 @@ TEST_F(CentipedeWithTemporaryLocalDir, MutateViaExternalBinary) { // This binary contains a test-friendly custom mutator. const std::string binary_with_custom_mutator = - GetDataDependencyFilepath("centipede/testing/test_fuzz_target"); + GetDataDependencyFilepath("centipede/testing/test_fuzz_target").string(); // This binary does not contain a custom mutator. const std::string binary_without_custom_mutator = - GetDataDependencyFilepath("centipede/testing/abort_fuzz_target"); + GetDataDependencyFilepath("centipede/testing/abort_fuzz_target").string(); // Mutate a couple of different inputs. std::vector<ByteArray> inputs = {{0, 1, 2}, {3, 4}}; // The custom mutator in the test binary will revert the order of bytes @@ -547,7 +553,7 @@ // each. TempCorpusDir work_tmp_dir{test_info_->name(), "workdir"}; Environment env; - env.workdir = work_tmp_dir.path(); + env.workdir = work_tmp_dir.path().string(); env.num_runs = 3; // Just a few runs. env.require_pc_table = false; // No PC table here. MergeMock mock(env); @@ -563,7 +569,7 @@ // the seed {0} each. TempCorpusDir merge_tmp_dir(test_info_->name(), "merge_from"); Environment merge_env; - merge_env.workdir = merge_tmp_dir.path(); + merge_env.workdir = merge_tmp_dir.path().string(); merge_env.num_runs = 4; merge_env.require_pc_table = false; // No PC table here. mock.Reset(); @@ -578,7 +584,7 @@ // Merge shards of `merge_env` into shards of `env`. // Shard 0 will receive one extra input: {4} // Shard 1 will receive two extra inputs: {7}, {8} - env.merge_from = merge_tmp_dir.path(); + env.merge_from = merge_tmp_dir.path().string(); env.num_runs = 0; for (env.my_shard_index = 0; env.my_shard_index < 2; ++env.my_shard_index) { CentipedeMain(env, factory); @@ -648,12 +654,13 @@ static std::vector<ByteArray> RunWithFunctionFilter( std::string_view function_filter, const TempDir& tmp_dir) { Environment env; - env.workdir = tmp_dir.path(); + env.workdir = tmp_dir.path().string(); env.seed = 1; // make the runs predictable. env.exec_time_weight_scaling = false; env.num_runs = 100; env.batch_size = 10; - env.binary = GetDataDependencyFilepath("centipede/testing/test_fuzz_target"); + env.binary = + GetDataDependencyFilepath("centipede/testing/test_fuzz_target").string(); env.coverage_binary = env.binary; // Must symbolize in order for the filter to work. env.symbolizer_path = GetLLVMSymbolizerPath(); @@ -768,8 +775,9 @@ const std::string& dir_path = arg; std::vector<FileAndContents> files_and_contents; for (const auto& dir_ent : std::filesystem::directory_iterator(dir_path)) { - auto file_and_contents = FileAndContents{dir_ent.path().filename()}; - ReadFromLocalFile(dir_ent.path().c_str(), file_and_contents.contents); + auto file_and_contents = + FileAndContents{dir_ent.path().filename().string()}; + ReadFromLocalFile(dir_ent.path().string(), file_and_contents.contents); files_and_contents.push_back(std::move(file_and_contents)); } return ExplainMatchResult(expected_files_and_contents, files_and_contents, @@ -782,7 +790,7 @@ TEST(Centipede, ExtraBinaries) { TempDir tmp_dir{test_info_->name()}; Environment env; - env.workdir = tmp_dir.path(); + env.workdir = tmp_dir.path().string(); env.num_runs = 100; env.batch_size = 10; env.log_level = 1; @@ -910,7 +918,7 @@ TempDir temp_dir{test_info_->name()}; Environment env; - env.workdir = temp_dir.path(); + env.workdir = temp_dir.path().string(); env.num_runs = kBatchSize * kNumBatches; env.batch_size = kBatchSize; // No real binary: prevent attempts by Centipede to read a PCtable from it. @@ -935,7 +943,7 @@ 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()); + found_crash_file_names.push_back(dir_ent.path().filename().string()); } // TODO(ussuri): Verify exact names/contents of the files, not just count. EXPECT_EQ(found_crash_file_names.size(), kCrashingInputIdxInBatch + 1); @@ -947,7 +955,7 @@ // Verify that when `env.batch_triage_suspect_only` is set, only triage the // suspect. TempDir suspect_only_temp_dir{test_info_->name()}; - env.workdir = suspect_only_temp_dir.path(); + env.workdir = suspect_only_temp_dir.path().string(); env.batch_triage_suspect_only = true; UndetectedCrashingInputMock suspect_only_mock(env, kCrashingInputIdx); NonOwningCallbacksFactory suspect_only_factory(suspect_only_mock); @@ -958,8 +966,8 @@ TEST_F(CentipedeWithTemporaryLocalDir, GetsSeedInputs) { Environment env; - env.binary = - GetDataDependencyFilepath("centipede/testing/seeded_fuzz_target"); + env.binary = GetDataDependencyFilepath("centipede/testing/seeded_fuzz_target") + .string(); CentipedeDefaultCallbacks callbacks(env, stop_condition); std::vector<ByteArray> seeds; @@ -971,7 +979,8 @@ TEST_F(CentipedeWithTemporaryLocalDir, GetsSerializedTargetConfig) { Environment env; env.binary = - GetDataDependencyFilepath("centipede/testing/fuzz_target_with_config"); + GetDataDependencyFilepath("centipede/testing/fuzz_target_with_config") + .string(); CentipedeDefaultCallbacks callbacks(env, stop_condition); const auto serialized_config = callbacks.GetSerializedTargetConfig(); @@ -984,7 +993,7 @@ Environment env; env.binary = absl::StrCat( GetDataDependencyFilepath("centipede/testing/fuzz_target_with_config") - .c_str(), + .string(), " --simulate_failure"); CentipedeDefaultCallbacks callbacks(env, stop_condition); @@ -995,7 +1004,8 @@ TEST_F(CentipedeWithTemporaryLocalDir, CleansUpMetadataAfterStartup) { Environment env; env.binary = GetDataDependencyFilepath( - "centipede/testing/expensive_startup_fuzz_target"); + "centipede/testing/expensive_startup_fuzz_target") + .string(); CentipedeDefaultCallbacks callbacks(env, stop_condition); BatchResult batch_result; @@ -1042,7 +1052,7 @@ TEST(Centipede, RunsExecuteCallbackInTheCurrentThreadWhenFuzzingWithOneThread) { TempDir temp_dir{test_info_->name()}; Environment env; - env.workdir = temp_dir.path(); + env.workdir = temp_dir.path().string(); env.require_pc_table = false; ASSERT_EQ(env.num_threads, 1); FakeCentipedeCallbacksForThreadChecking callbacks(env, @@ -1057,7 +1067,8 @@ TEST_F(CentipedeWithTemporaryLocalDir, DetectsStackOverflow) { Environment env; - env.binary = GetDataDependencyFilepath("centipede/testing/test_fuzz_target"); + env.binary = + GetDataDependencyFilepath("centipede/testing/test_fuzz_target").string(); env.stack_limit_kb = 64; CentipedeDefaultCallbacks callbacks(env, stop_condition); @@ -1100,7 +1111,7 @@ TempDir temp_dir{test_info_->name()}; Environment env; env.log_level = 0; // Disable most of the logging in the test. - env.workdir = temp_dir.path(); + env.workdir = temp_dir.path().string(); env.batch_size = 7; // Just some small number. env.require_pc_table = false; // No PC table here. SetupFailureCallbacks mock(env); @@ -1140,7 +1151,7 @@ TempDir temp_dir{test_info_->name()}; Environment env; env.log_level = 0; // Disable most of the logging in the test. - env.workdir = temp_dir.path(); + env.workdir = temp_dir.path().string(); env.batch_size = 7; // Just some small number. env.require_pc_table = false; // No PC table here. SkippedTestCallbacks mock(env); @@ -1180,7 +1191,7 @@ TempDir temp_dir{test_info_->name()}; Environment env; env.log_level = 0; // Disable most of the logging in the test. - env.workdir = temp_dir.path(); + env.workdir = temp_dir.path().string(); env.batch_size = 7; // Just some small number. env.num_runs = 100; env.require_pc_table = false; // No PC table here. @@ -1226,7 +1237,7 @@ TEST(Centipede, DoesNotReduceInputWhenTheOptionIsUnset) { TempCorpusDir tmp_dir{test_info_->name()}; Environment env; - env.workdir = tmp_dir.path(); + env.workdir = tmp_dir.path().string(); env.num_runs = 1000000; // Should be enough env.batch_size = 7; // Just some small number. env.require_pc_table = false; @@ -1247,7 +1258,7 @@ TEST(Centipede, ReducesInputWhenTheOptionIsSet) { TempCorpusDir tmp_dir{test_info_->name()}; Environment env; - env.workdir = tmp_dir.path(); + env.workdir = tmp_dir.path().string(); env.num_runs = 1000000; // Should be enough env.batch_size = 7; // Just some small number. env.require_pc_table = false; @@ -1268,7 +1279,8 @@ TEST_F(CentipedeWithTemporaryLocalDir, UsesProvidedCustomMutator) { Environment env; env.binary = GetDataDependencyFilepath( - "centipede/testing/fuzz_target_with_custom_mutator"); + "centipede/testing/fuzz_target_with_custom_mutator") + .string(); CentipedeDefaultCallbacks callbacks(env, stop_condition); const std::vector<ByteArray> inputs = {{99}}; @@ -1285,7 +1297,7 @@ env.binary = absl::StrCat(GetDataDependencyFilepath( "centipede/testing/fuzz_target_with_custom_mutator") - .c_str(), + .string(), " --simulate_failure"); StopCondition stop_condition; CentipedeDefaultCallbacks callbacks(env, stop_condition); @@ -1301,7 +1313,8 @@ TEST_F(CentipedeWithTemporaryLocalDir, FallsBackToBuiltInMutatorWhenCustomMutatorNotProvided) { Environment env; - env.binary = GetDataDependencyFilepath("centipede/testing/abort_fuzz_target"); + env.binary = + GetDataDependencyFilepath("centipede/testing/abort_fuzz_target").string(); CentipedeDefaultCallbacks callbacks(env, stop_condition); const std::vector<ByteArray> inputs = {{1}, {2}, {3}, {4}, {5}, {6}}; @@ -1330,7 +1343,8 @@ TEST_F(CentipedeWithTemporaryLocalDir, HangingFuzzTargetExitsAfterTimeout) { Environment env; env.binary = - GetDataDependencyFilepath("centipede/testing/hanging_fuzz_target"); + GetDataDependencyFilepath("centipede/testing/hanging_fuzz_target") + .string(); BatchResult batch_result; const std::vector<ByteArray> inputs = {{0}}; CentipedeDefaultCallbacks callbacks(env, stop_condition); @@ -1345,7 +1359,8 @@ TEST_F(CentipedeWithTemporaryLocalDir, ExecuteEndsAfterCustomFailure) { Environment env; - env.binary = GetDataDependencyFilepath("centipede/testing/test_fuzz_target"); + env.binary = + GetDataDependencyFilepath("centipede/testing/test_fuzz_target").string(); CentipedeDefaultCallbacks callbacks(env, stop_condition); BatchResult result; std::vector<ByteArray> inputs = { @@ -1365,7 +1380,8 @@ TEST_F(CentipedeWithTemporaryLocalDir, ToleratesAsyncFailureInMutation) { Environment env; env.binary = - GetDataDependencyFilepath("centipede/testing/async_failing_target"); + GetDataDependencyFilepath("centipede/testing/async_failing_target") + .string(); StopCondition stop_condition; CentipedeDefaultCallbacks callbacks(env, stop_condition); BatchResult result; @@ -1389,14 +1405,16 @@ TEST_F(CentipedeWithTemporaryLocalDir, EngineWorksInWorkerMode) { TempCorpusDir tmp_dir{test_info_->name()}; Environment env; - env.workdir = tmp_dir.path(); + env.workdir = tmp_dir.path().string(); env.binary = GetDataDependencyFilepath( - "centipede/testing/test_binary_for_engine_testing"); + "centipede/testing/test_binary_for_engine_testing") + .string(); env.test_name = "some_test"; env.populate_binary_info = false; env.fork_server = false; env.persistent_mode = true; env.exit_on_crash = true; + env.print_runner_log = true; env.stop_at = absl::Now() + absl::Seconds(10); fuzztest::internal::DefaultCallbacksFactory< fuzztest::internal::CentipedeDefaultCallbacks> @@ -1406,21 +1424,30 @@ } TEST_F(CentipedeWithTemporaryLocalDir, EngineWorksInStandaloneMode) { - const std::string centipede_path = - GetDataDependencyFilepath("centipede/centipede"); - const std::string test_binary_path = GetDataDependencyFilepath( - "centipede/testing/test_binary_for_engine_testing"); - // Create a temporary dir and enter it for running the test binary, because - // the test binary uses CWD as the engine workdir. - TempCorpusDir tmp_dir{test_info_->name()}; - const auto test_command = - absl::StrCat("cd ", tmp_dir.path().string(), - " && env FUZZTEST_CENTIPEDE_BINARY_PATH=", centipede_path, - " ", test_binary_path); EXPECT_DEATH( [&] { - const int status = std::system(test_command.c_str()); - std::exit(WEXITSTATUS(status)); + const std::string centipede_path = + GetDataDependencyFilepath("centipede/centipede").string(); + std::string test_binary_path = + GetDataDependencyFilepath( + "centipede/testing/test_binary_for_engine_testing") + .string(); + // Create a temporary dir and enter it for running the test binary, + // because the test binary uses CWD as the engine workdir. + TempDir tmp_dir{test_info_->name()}; +#if defined(_WIN32) + _chdir(tmp_dir.path().string().c_str()); +#else + chdir(tmp_dir.path().string().c_str()); +#endif + + Command::Options cmd_options; + cmd_options.env_diff.push_back( + absl::StrCat("FUZZTEST_CENTIPEDE_BINARY_PATH=", centipede_path)); + Command cmd(test_binary_path, cmd_options); + cmd.ExecuteAsync(); + auto result = cmd.Wait(absl::Now() + absl::Seconds(10)); + std::exit(result.value_or(EXIT_FAILURE)); }(), ContainsRegex("Failure *: some_failure_description")); }
diff --git a/centipede/command.cc b/centipede/command.cc index 972a056..4c70c32 100644 --- a/centipede/command.cc +++ b/centipede/command.cc
@@ -14,6 +14,14 @@ #include "./centipede/command.h" +#if defined(_WIN32) +#define WIN32_LEAN_AND_MEAN +#define NOGDI +#include <io.h> +#include <process.h> +#include <windows.h> +using pid_t = int; +#else #include <errno.h> #include <fcntl.h> #include <spawn.h> @@ -22,6 +30,7 @@ #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> +#endif #ifdef __APPLE__ #include <inttypes.h> @@ -35,6 +44,7 @@ #include <cstdlib> #include <filesystem> // NOLINT #include <fstream> +#include <map> // NOLINT #include <optional> #include <string> #include <string_view> @@ -45,6 +55,7 @@ #include "absl/base/const_init.h" #include "absl/status/status.h" #include "absl/status/statusor.h" +#include "absl/strings/ascii.h" // NOLINT #include "absl/strings/match.h" #include "absl/strings/numbers.h" #include "absl/strings/str_cat.h" @@ -58,6 +69,7 @@ #include "./centipede/stop.h" #include "./centipede/util.h" #include "./common/logging.h" +#include "./fuzztest/internal/escaping.h" #if !defined(_MSC_VER) // Needed to pass the current environment to posix_spawn, which needs an @@ -73,7 +85,9 @@ constexpr std::string_view kNoForkServerRequestPrefix("%f"); absl::StatusOr<std::string> GetProcessCreationStamp(pid_t pid) { -#ifdef __APPLE__ +#if defined(_WIN32) + return "0"; +#elif defined(__APPLE__) struct proc_bsdinfo info = {}; if (proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, &info, PROC_PIDTBSDINFO_SIZE) != PROC_PIDTBSDINFO_SIZE) { @@ -122,6 +136,11 @@ // TODO(ussuri): Encapsulate as much of the fork server functionality from // this source as possible in this struct, and make it a class. +#if defined(_WIN32) +struct Command::ForkServerProps { + pid_t pid_ = -1; +}; +#else struct Command::ForkServerProps { // The file paths of the comms pipes. std::string fifo_path_[2]; @@ -176,6 +195,7 @@ } } }; +#endif // NOTE: Because std::unique_ptr<T> requires T to be a complete type wherever // the deleter is instantiated, the special member functions must be defined @@ -201,6 +221,41 @@ Command::Command(std::string_view path) : Command{path, {}} {} std::string Command::ToString() const { +#if defined(_WIN32) + std::string path = path_; + if (absl::StartsWith(path, kNoForkServerRequestPrefix)) { + path = path.substr(kNoForkServerRequestPrefix.size()); + } + constexpr std::string_view kTempFileWildCard = "@@"; + if (absl::StrContains(path, kTempFileWildCard)) { + FUZZTEST_CHECK(!options_.temp_file_path.empty()); + std::string temp_file = options_.temp_file_path; + path = absl::StrReplaceAll(path, {{kTempFileWildCard, temp_file}}); + } + std::string binary_cmd = path; + auto Escape = [](std::string s) { + std::string r = "\""; + size_t num_bs = 0; + for (size_t i = 0; i < s.size(); ++i) { + if (s[i] == '"') { + r.append(std::string(num_bs + 1, '\\')); + num_bs = 0; + } else if (s[i] == '\\') { + ++num_bs; + } else { + num_bs = 0; + } + r += s[i]; + } + r.append(num_bs, '\\'); + r += '"'; + return r; + }; + for (const auto& arg : options_.args) { + absl::StrAppend(&binary_cmd, " ", Escape(arg)); + } + return binary_cmd; +#else std::vector<std::string> ss; ss.reserve(/*env*/ 1 + options_.env_diff.size() + /*path*/ 1 + /*args*/ options_.args.size() + /*out/err*/ 2); @@ -235,7 +290,7 @@ ss.push_back(std::move(path)); // args. for (const auto& arg : options_.args) { - ss.push_back(arg); + ss.push_back(ShellEscape(arg)); } // out/err. if (!stdout_file_.empty()) { @@ -250,10 +305,14 @@ } // Trim trailing space and return. return absl::StrJoin(ss, kCommandLineSeparator); +#endif } bool Command::StartForkServer(std::string_view temp_dir_path, std::string_view prefix) { +#if defined(_WIN32) + return false; +#else if (absl::StartsWith(path_, kNoForkServerRequestPrefix)) { FUZZTEST_VLOG(2) << "Fork server disabled for " << path(); return false; @@ -340,6 +399,7 @@ } fork_server_->creation_stamp = *std::move(creation_stamp); return true; +#endif // _WIN32 } void Command::ResetRedirectionFiles(std::string_view new_suffix) { @@ -364,6 +424,9 @@ } absl::Status Command::VerifyForkServerIsHealthy() { +#if defined(_WIN32) + return absl::UnimplementedError("Fork server not supported on Windows"); +#else // Preconditions: the callers (`Execute()`) should call us only when the fork // server is presumed to be running (`fork_server_pid_` >= 0). If it is, the // comms pipes are guaranteed to be opened by `StartForkServer()`. @@ -389,11 +452,123 @@ fork_server_->creation_stamp, ", but got ", *creation_stamp)); } return absl::OkStatus(); +#endif } bool Command::ExecuteAsync() { FUZZTEST_CHECK(!is_executing()); +#if defined(_WIN32) + FUZZTEST_CHECK_EQ(pid_, -1); + ResetRedirectionFiles(GetUniqueSuffix()); + command_line_ = ToString(); + + struct CaseInsensitiveLess { + bool operator()(std::string_view a, std::string_view b) const { + if (absl::EqualsIgnoreCase(a, b)) return false; + return absl::AsciiStrToLower(a) < absl::AsciiStrToLower(b); + } + }; + + std::map<std::string, std::string, CaseInsensitiveLess> env_map; + + LPCH env_strings = GetEnvironmentStringsA(); + if (env_strings != nullptr) { + const char* ptr = env_strings; + while (*ptr != '\0') { + std::string_view entry(ptr); + ptr += entry.size() + 1; + size_t eq_pos = entry.find('=', 1); + if (eq_pos != std::string_view::npos) { + env_map[std::string(entry.substr(0, eq_pos))] = + std::string(entry.substr(eq_pos + 1)); + } else { + env_map[std::string(entry)] = ""; + } + } + FreeEnvironmentStringsA(env_strings); + } + + for (std::string_view env_var : options_.env_diff) { + if (absl::StartsWith(env_var, "-")) { + std::string_view key = env_var.substr(1); + if (absl::EndsWith(key, "=")) { + key = key.substr(0, key.size() - 1); + } + env_map.erase(std::string(key)); + } else { + auto pos = env_var.find('='); + if (pos != std::string_view::npos) { + std::string key(env_var.substr(0, pos)); + std::string val(env_var.substr(pos + 1)); + env_map[key] = val; + } + } + } + + std::vector<char> env_block; + for (const auto& [key, val] : env_map) { + std::string entry = absl::StrCat(key, "=", val); + env_block.insert(env_block.end(), entry.begin(), entry.end()); + env_block.push_back('\0'); + } + env_block.push_back('\0'); + + STARTUPINFOA si = {sizeof(si)}; + PROCESS_INFORMATION pi = {}; + si.dwFlags = STARTF_USESTDHANDLES; + si.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + + SECURITY_ATTRIBUTES sa = {sizeof(sa), NULL, TRUE}; + HANDLE hOut = INVALID_HANDLE_VALUE; + HANDLE hErr = INVALID_HANDLE_VALUE; + + if (!stdout_file_.empty()) { + hOut = CreateFileA(stdout_file_.c_str(), FILE_WRITE_DATA, FILE_SHARE_READ, + &sa, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + si.hStdOutput = + (hOut != INVALID_HANDLE_VALUE) ? hOut : GetStdHandle(STD_OUTPUT_HANDLE); + } else { + si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); + } + + if (!stderr_file_.empty()) { + if (stderr_file_ == stdout_file_) { + si.hStdError = si.hStdOutput; + } else { + hErr = CreateFileA(stderr_file_.c_str(), FILE_WRITE_DATA, FILE_SHARE_READ, + &sa, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + si.hStdError = (hErr != INVALID_HANDLE_VALUE) + ? hErr + : GetStdHandle(STD_ERROR_HANDLE); + } + } else { + si.hStdError = GetStdHandle(STD_ERROR_HANDLE); + } + + std::string cmd = command_line_; + const BOOL cp_res = CreateProcessA( + NULL, cmd.data(), NULL, NULL, TRUE, 0, + env_block.empty() ? NULL : env_block.data(), NULL, &si, &pi); + + if (!cp_res) { + DWORD err = GetLastError(); + FUZZTEST_LOG(ERROR) << "CreateProcessA failed for '" << cmd + << "': error=" << err; + if (hOut != INVALID_HANDLE_VALUE) CloseHandle(hOut); + if (hErr != INVALID_HANDLE_VALUE && hErr != hOut) CloseHandle(hErr); + return false; + } + + if (hOut != INVALID_HANDLE_VALUE) CloseHandle(hOut); + if (hErr != INVALID_HANDLE_VALUE && hErr != hOut) CloseHandle(hErr); + + win_process_handle_ = pi.hProcess; + CloseHandle(pi.hThread); + pid_ = static_cast<int>(pi.dwProcessId); + is_executing_ = true; + return true; +#else if (fork_server_ != nullptr) { FUZZTEST_VLOG(1) << "Sending execution request to fork server"; @@ -430,11 +605,39 @@ is_executing_ = true; return true; +#endif } std::optional<int> Command::Wait(absl::Time deadline, StopCondition* stop_condition) { FUZZTEST_CHECK(is_executing()); +#if defined(_WIN32) + DWORD timeout_ms = INFINITE; + if (deadline != absl::InfiniteFuture()) { + auto dur = deadline - absl::Now(); + if (dur <= absl::ZeroDuration()) { + timeout_ms = 0; + } else { + timeout_ms = static_cast<DWORD>(absl::ToInt64Milliseconds(dur)); + } + } + DWORD res = + WaitForSingleObject(static_cast<HANDLE>(win_process_handle_), timeout_ms); + if (res == WAIT_TIMEOUT) { + VlogProblemInfo( + absl::StrCat("Timeout while waiting for command process: deadline is ", + deadline), + /*vlog_level=*/1); + return std::nullopt; + } + DWORD exit_code = 0; + GetExitCodeProcess(static_cast<HANDLE>(win_process_handle_), &exit_code); + CloseHandle(static_cast<HANDLE>(win_process_handle_)); + win_process_handle_ = nullptr; + pid_ = -1; + is_executing_ = false; + return static_cast<int>(exit_code); +#else int exit_code = EXIT_SUCCESS; if (fork_server_ != nullptr) { @@ -527,10 +730,16 @@ } return exit_code; +#endif } void Command::RequestStop(bool force) { FUZZTEST_CHECK(is_executing()); +#if defined(_WIN32) + if (win_process_handle_ != nullptr) { + TerminateProcess(static_cast<HANDLE>(win_process_handle_), 1); + } +#else if (fork_server_) { FUZZTEST_CHECK_NE(fork_server_->pid_, -1); // Cannot send SIGKILL to the fork server as it kills only the parent @@ -541,6 +750,7 @@ } FUZZTEST_CHECK_NE(pid_, -1); kill(pid_, force ? SIGKILL : SIGTERM); +#endif } std::string Command::ReadRedirectedStdout() const {
diff --git a/centipede/command.h b/centipede/command.h index b574a84..fd84b8a 100644 --- a/centipede/command.h +++ b/centipede/command.h
@@ -128,6 +128,9 @@ struct ForkServerProps; int pid_ = -1; +#if defined(_WIN32) + void* win_process_handle_ = nullptr; +#endif bool is_executing_ = false; // Derived from Options::{stdout,stderr}_file_prefix, with the realized suffix
diff --git a/centipede/config_file.cc b/centipede/config_file.cc index 89c0613..e0d59e0 100644 --- a/centipede/config_file.cc +++ b/centipede/config_file.cc
@@ -162,7 +162,7 @@ if (!path.empty() && !std::filesystem::exists(path)) { // assume remote // Read the remote file. std::string contents; - FUZZTEST_CHECK_OK(RemoteFileGetContents(path.c_str(), contents)); + FUZZTEST_CHECK_OK(RemoteFileGetContents(path.string(), contents)); // Save a temporary local copy. const std::filesystem::path tmp_dir = TemporaryLocalDirPath(); @@ -170,11 +170,11 @@ FUZZTEST_LOG(INFO) << "Localizing remote config: " << VV(path) << VV(local_path); // NOTE: Ignore "Remote" in the API names here: the paths are always local. - FUZZTEST_CHECK_OK(RemoteMkdir(tmp_dir.c_str())); - FUZZTEST_CHECK_OK(RemoteFileSetContents(local_path.c_str(), contents)); + FUZZTEST_CHECK_OK(RemoteMkdir(tmp_dir.string())); + FUZZTEST_CHECK_OK(RemoteFileSetContents(local_path.string(), contents)); // Augment the argv to point at the local copy and ensure it is cleaned up. - replacements.emplace_back(path.c_str(), local_path.c_str()); + replacements.emplace_back(path.string(), local_path.string()); cleanup = [local_path]() { std::filesystem::remove(local_path); }; } @@ -245,7 +245,7 @@ } else { file_contents = flags_str; } - FUZZTEST_CHECK_OK(RemoteFileSetContents(path.c_str(), file_contents)); + FUZZTEST_CHECK_OK(RemoteFileSetContents(path.string(), file_contents)); } return path;
diff --git a/centipede/corpus.cc b/centipede/corpus.cc index 5c194b5..b13b15b 100644 --- a/centipede/corpus.cc +++ b/centipede/corpus.cc
@@ -204,7 +204,7 @@ // The corpus must not be empty, hence target_size is at least 1. // It should also be <= max_corpus_size. size_t target_size = std::min( - max_corpus_size, std::max(1UL, records_.size() - num_zero_weights)); + max_corpus_size, std::max(size_t{1}, records_.size() - num_zero_weights)); auto subset_to_remove = weighted_distribution_.RemoveRandomWeightedSubset(target_size, rng); if (subset_to_remove.size() == records_.size()) {
diff --git a/centipede/crash_deduplication.cc b/centipede/crash_deduplication.cc index b5ae094..37592c2 100644 --- a/centipede/crash_deduplication.cc +++ b/centipede/crash_deduplication.cc
@@ -94,7 +94,7 @@ std::vector<ExistingCrash> existing_crashes; ASSIGN_OR_RETURN_IF_NOT_OK( const std::vector<std::string> input_files, - RemoteListFiles(crashing_dir.c_str(), /*recursively=*/false)); + RemoteListFiles(crashing_dir.string(), /*recursively=*/false)); existing_crashes.reserve(input_files.size()); for (const std::string& input_file : input_files) { @@ -121,14 +121,14 @@ std::vector<IncubatingCrash> incubating_crashes; ASSIGN_OR_RETURN_IF_NOT_OK( const std::vector<std::string> input_files, - RemoteListFiles(incubating_dir.c_str(), /*recursively=*/false)); + RemoteListFiles(incubating_dir.string(), /*recursively=*/false)); incubating_crashes.reserve(input_files.size()); for (const std::string& input_file : input_files) { IncubatingCrash incubating; incubating.details.input_path = input_file; incubating.details.input_signature = - std::filesystem::path(input_file).filename().c_str(); + std::filesystem::path(input_file).filename().string(); incubating_crashes.push_back(std::move(incubating)); } return incubating_crashes; @@ -274,9 +274,9 @@ GetInputFileName(bug_id, crash_signature, details.input_signature); std::filesystem::path new_input_path = crashing_dir / new_input_file_name; - if (details.input_path != new_input_path.c_str()) { + if (details.input_path != new_input_path.string()) { RETURN_IF_NOT_OK( - RemoteFileCopy(details.input_path, new_input_path.c_str())); + RemoteFileCopy(details.input_path, new_input_path.string())); } crash_summary.AddCrash({/*id=*/new_input_file_name, @@ -321,7 +321,7 @@ crash_summary.AddCrash({ /*id=*/std::filesystem::path(existing.crash_report.details.input_path) .filename() - .c_str(), + .string(), /*category=*/description, existing.crash_report.signature, description, @@ -375,7 +375,7 @@ std::filesystem::path dest_path = incubating_dir / existing.crash_report.details.input_signature; RETURN_IF_NOT_OK(RemoteFileRename( - existing.crash_report.details.input_path, dest_path.c_str())); + existing.crash_report.details.input_path, dest_path.string())); break; } @@ -424,12 +424,12 @@ const std::filesystem::path& source_dir, const std::filesystem::path& regression_dir, absl::Duration ttl, absl::Clock& clock) { - if (!RemotePathExists(source_dir.c_str())) { + if (!RemotePathExists(source_dir.string())) { return absl::OkStatus(); } ASSIGN_OR_RETURN_IF_NOT_OK( const std::vector<std::string> active_crash_files, - RemoteListFiles(source_dir.c_str(), /*recursively=*/false)); + RemoteListFiles(source_dir.string(), /*recursively=*/false)); absl::Time now = clock.TimeNow(); @@ -442,11 +442,11 @@ if (input_file_components.ok()) { dest_filename = input_file_components->input_signature; } else { - dest_filename = std::filesystem::path(file_path).filename().c_str(); + dest_filename = std::filesystem::path(file_path).filename().string(); } std::filesystem::path dest_path = regression_dir / dest_filename; - RETURN_IF_NOT_OK(RemoteFileRename(file_path, dest_path.c_str())); + RETURN_IF_NOT_OK(RemoteFileRename(file_path, dest_path.string())); } } return absl::OkStatus(); @@ -472,9 +472,10 @@ for (std::string& crashing_input_path : crashing_input_paths) { std::string crashing_input_file_name = - std::filesystem::path(crashing_input_path).filename(); + std::filesystem::path(crashing_input_path).filename().string(); const std::string crash_signature_path = - crash_metadata_dir / absl::StrCat(crashing_input_file_name, ".sig"); + (crash_metadata_dir / absl::StrCat(crashing_input_file_name, ".sig")) + .string(); std::string crash_signature; const absl::Status status = RemoteFileGetContents(crash_signature_path, crash_signature); @@ -496,7 +497,8 @@ if (crashes.contains(crash_signature)) continue; const std::string crash_description_path = - crash_metadata_dir / absl::StrCat(crashing_input_file_name, ".desc"); + (crash_metadata_dir / absl::StrCat(crashing_input_file_name, ".desc")) + .string(); std::string crash_description; const absl::Status description_status = RemoteFileGetContents(crash_description_path, crash_description); @@ -537,9 +539,9 @@ new_crashes_by_signature, CrashSummary& crash_summary, StopCondition& stop_condition, absl::Duration regression_ttl, absl::Clock& clock) { - RETURN_IF_NOT_OK(RemoteMkdir(crashing_dir.c_str())); - RETURN_IF_NOT_OK(RemoteMkdir(regression_dir.c_str())); - RETURN_IF_NOT_OK(RemoteMkdir(incubating_dir.c_str())); + RETURN_IF_NOT_OK(RemoteMkdir(crashing_dir.string())); + RETURN_IF_NOT_OK(RemoteMkdir(regression_dir.string())); + RETURN_IF_NOT_OK(RemoteMkdir(incubating_dir.string())); ASSIGN_OR_RETURN_IF_NOT_OK(auto existing_crashes, ReadExistingCrashes(crashing_dir));
diff --git a/centipede/dispatcher_flag_helper.h b/centipede/dispatcher_flag_helper.h index d143831..7f553e0 100644 --- a/centipede/dispatcher_flag_helper.h +++ b/centipede/dispatcher_flag_helper.h
@@ -20,6 +20,18 @@ #include <cstdint> #include <cstring> +#if defined(_WIN32) +inline static char* strndup(const char* s, size_t n) { + size_t len = strnlen(s, n); + char* ret = static_cast<char*>(malloc(len + 1)); + if (ret) { + memcpy(ret, s, len); + ret[len] = '\0'; + } + return ret; +} +#endif + #include "absl/base/nullability.h" namespace fuzztest::internal { @@ -62,12 +74,39 @@ const char *absl_nullable GetStringFlag(const char *absl_nonnull flag) const { if (!flags) return nullptr; // Extract "value" from ":flag=value:" inside centipede_runner_flags. - const char *beg = strstr(flags, flag); + const size_t flag_len = strlen(flag); + const char* beg = flags; + while (true) { + beg = strstr(beg, flag); + if (beg == nullptr) return nullptr; + // Find the closest non-backslash before the candiate position. + const char* cur = beg - 1; + while (cur >= flags && *cur == '\\') --cur; + // There are even number of backslashes before the flag, meaning it's not + // escaped. + if (((beg - cur) & 1) == 1) break; + beg = beg + flag_len; + } if (!beg) return nullptr; - const char *value_beg = beg + strlen(flag); - const char *end = strstr(value_beg, ":"); - if (!end) return nullptr; - return strndup(value_beg, end - value_beg); + const char* value_beg = beg + flag_len; + const char* end = value_beg; + while (*end) { + if (*end == '\\' && *(end + 1) != 0) { + end += 2; + continue; + } + if (*end == ':') break; + ++end; + } + if (!end || !*end) return nullptr; + char* r = strndup(value_beg, end - value_beg); + size_t d = 0; + for (const char* c = r; *c != 0; ++c) { + if (*c == '\\' && *(c + 1) != 0) ++c; + r[d++] = *c; + } + r[d] = 0; + return r; } };
diff --git a/centipede/engine_controller_with_subprocess.cc b/centipede/engine_controller_with_subprocess.cc index 2ffc89d..ad9c795 100644 --- a/centipede/engine_controller_with_subprocess.cc +++ b/centipede/engine_controller_with_subprocess.cc
@@ -12,17 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. +#if !defined(_WIN32) #include <sys/wait.h> +#endif #include <cstdlib> #include <cstring> #include <string> +#include "./centipede/command.h" #include "./centipede/engine_abi.h" #include "./centipede/engine_controller_abi.h" -#include "./fuzztest/internal/escaping.h" -using fuzztest::internal::ShellEscape; +using fuzztest::internal::Command; FuzzTestControllerStatus FuzzTestControllerRun( const FuzzTestAdapterManager* manager, const FuzzTestBytesViews* flags) { @@ -35,17 +37,19 @@ if (centipede_binary_path == nullptr) { return kFuzzTestControllerFailure; } - std::string command; - command.append(ShellEscape(centipede_binary_path)); + Command::Options cmd_options; for (size_t flag_index = 0; flag_index < flags->count; ++flag_index) { const FuzzTestBytesView flag = flags->views[flag_index]; - command.append(" "); - command.append( - ShellEscape({reinterpret_cast<const char*>(flag.data), flag.size})); + cmd_options.args.push_back( + {reinterpret_cast<const char*>(flag.data), flag.size}); } - int ret = system(command.c_str()); - if (ret == -1) return kFuzzTestControllerFailure; + Command cmd(centipede_binary_path, cmd_options); + int ret = cmd.Execute(); +#if defined(_WIN32) + return ret == 0 ? kFuzzTestControllerSuccess : kFuzzTestControllerFailure; +#else return WIFEXITED(ret) && WEXITSTATUS(ret) == EXIT_SUCCESS ? kFuzzTestControllerSuccess : kFuzzTestControllerFailure; +#endif }
diff --git a/centipede/engine_worker.cc b/centipede/engine_worker.cc index a1febf0..92c3eff 100644 --- a/centipede/engine_worker.cc +++ b/centipede/engine_worker.cc
@@ -12,11 +12,37 @@ // See the License for the specific language governing permissions and // limitations under the License. #include <fcntl.h> + +#if !defined(_WIN32) #include <sys/resource.h> #include <sys/socket.h> #include <sys/time.h> #include <sys/un.h> #include <unistd.h> +#else +#define WIN32_LEAN_AND_MEAN +#define NOGDI +// clang-format off +// Must be before <afunix.h> +#include <ws2tcpip.h> +// clang-format on +#include <afunix.h> +#include <io.h> +#include <process.h> +#include <psapi.h> +#include <windows.h> +#include <winsock2.h> +#endif + +#if defined(_WIN32) +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif +inline static int rand_r(unsigned int* seed) { + *seed = *seed * 1103515245 + 12345; + return static_cast<int>((*seed / 65536) % 32768); +} +#endif #include <algorithm> #include <array> @@ -63,6 +89,12 @@ char err_buf[80]; const auto err_str = [&]() -> std::string_view { static constexpr std::string_view kFallbackMsg = "[strerror_r failed]"; +#if defined(_WIN32) + if (strerror_s(err_buf, sizeof(err_buf), first.saved_errno) != 0) { + return kFallbackMsg; + } + return err_buf; +#else auto result = strerror_r(first.saved_errno, err_buf, sizeof(err_buf)); constexpr bool xsi_strerror_r = std::is_same_v<decltype(result), int>; constexpr bool gnu_strerror_r = std::is_same_v<decltype(result), char*>; @@ -75,15 +107,25 @@ if (result == nullptr) return kFallbackMsg; return result; } +#endif }(); WorkerLog(err_str); } else if constexpr (std::is_same_v<LogLnSync, T>) { +#if defined(_WIN32) + _write(2, "\n", 1); + _commit(2); +#else write(STDERR_FILENO, "\n", 1); fsync(STDERR_FILENO); +#endif } else { std::string_view sv = first; while (!sv.empty()) { +#if defined(_WIN32) + const int r = _write(2, sv.data(), static_cast<unsigned int>(sv.size())); +#else const int r = write(STDERR_FILENO, sv.data(), sv.size()); +#endif if (r <= 0) break; sv = sv.substr(r); } @@ -111,7 +153,7 @@ // allocates memory (enforced by `WorkerInitEarly`). After that it would be // signal-safe. // -// The worker flags format is `:(NAME=VALUE|SWITCH:)+`. `GetWorkerFlags` +// The worker flags format is `:(NAME=VALUE:|SWITCH:)+`. `GetWorkerFlags` // replaces `:` with '\0' so that we can get null-terminated strings of VALUE // without copying them, which is important for signal-safety. const WorkerFlags& GetWorkerFlags() { @@ -127,15 +169,23 @@ WorkerLog("Cannot allocate the worker flags", LogLnSync{}); std::_Exit(1); } - memcpy(str, env_flags, len); - str[len] = 0; - WorkerLog("Got worker flags ", std::string_view{str, len}, LogLnSync{}); - // Post-processing to make '\0' as the separator, making each item as a - // null-terminating string to be used without copying it. - for (size_t i = 0; i < len; ++i) { - if (str[i] == ':') str[i] = 0; + size_t src = 0; + size_t dst = 0; + while (src < len) { + if (env_flags[src] == ':') { + str[dst] = '\0'; + } else { + if (env_flags[src] == '\\' && src + 1 < len) { + ++src; + } + str[dst] = env_flags[src]; + } + ++src; + ++dst; } - return WorkerFlags{true, len, str}; + str[dst] = 0; + WorkerLog("Got worker flags ", std::string_view{str, dst}, LogLnSync{}); + return WorkerFlags{true, dst, str}; }(); return worker_flags; } @@ -181,8 +231,14 @@ void TrySetFileContents(const char* absl_nonnull path, bool append, C... contents) { // Needs to be signal-safe. +#if defined(_WIN32) + int f = _open( + path, _O_CREAT | _O_WRONLY | _O_BINARY | (append ? _O_APPEND : _O_TRUNC), + _S_IREAD | _S_IWRITE); +#else int f = open(path, O_CREAT | O_WRONLY | (append ? O_APPEND : O_TRUNC), /*mode=*/0660); +#endif if (f == -1) { WorkerLog("cannot open path ", path, ": ", LogErrNo{}, LogLnSync{}); return; @@ -190,7 +246,11 @@ ([&] { std::string_view sv = contents; while (!sv.empty()) { +#if defined(_WIN32) + const int r = _write(f, sv.data(), static_cast<unsigned int>(sv.size())); +#else const int r = write(f, sv.data(), sv.size()); +#endif if (r < 0) { WorkerLog("write() failed on ", path, ": ", LogErrNo{}, LogLnSync{}); return false; @@ -205,12 +265,21 @@ return true; }() && ...); // NOLINT - stop fighting with auto-fomatting. +#if defined(_WIN32) + if (_commit(f) != 0) { + WorkerLog("_commit() failed on ", path, ": ", LogErrNo{}, LogLnSync{}); + } + if (_close(f) != 0) { + WorkerLog("_close() failed on ", path, ": ", LogErrNo{}, LogLnSync{}); + } +#else if (fsync(f) != 0) { WorkerLog("fsync() failed on ", path, ": ", LogErrNo{}, LogLnSync{}); } if (close(f) != 0) { WorkerLog("close() failed on ", path, ": ", LogErrNo{}, LogLnSync{}); } +#endif } enum class WorkerAction { @@ -346,15 +415,45 @@ return {reinterpret_cast<const char*>(bytes.data()), bytes.size()}; } -// Zero initialized. -static int persistent_mode_socket; +#if defined(_WIN32) +using worker_socket_t = SOCKET; +constexpr worker_socket_t kInvalidWorkerSocket = INVALID_SOCKET; +inline static void CloseWorkerSocket(worker_socket_t s) { closesocket(s); } +#else +using worker_socket_t = int; +constexpr worker_socket_t kInvalidWorkerSocket = -1; +inline static void CloseWorkerSocket(worker_socket_t s) { close(s); } +#endif +static worker_socket_t persistent_mode_socket = kInvalidWorkerSocket; + +#if defined(_WIN32) +void WorkerInitEarly(); +static int init_worker_early_fn() { + WorkerInitEarly(); + return 0; +} +#pragma section(".CRT$XCU", read) +extern "C" __declspec(allocate(".CRT$XCU")) int (*p_init_worker_early)() = + init_worker_early_fn; +#pragma comment(linker, "/include:p_init_worker_early") +void WorkerInitEarly() { +#else __attribute__((constructor(200))) void WorkerInitEarly() { +#endif const char* persistent_mode_socket_path = GetWorkerFlag(kWorkerPersistentModeSocketPathFlagHeader); if (persistent_mode_socket_path == nullptr) return; + +#if defined(_WIN32) + [[maybe_unused]] static bool init_wsa = []() { + WSADATA wsa_data; + return WSAStartup(MAKEWORD(2, 2), &wsa_data) == 0; + }(); +#endif + persistent_mode_socket = socket(AF_UNIX, SOCK_STREAM, 0); - if (persistent_mode_socket < 0) { + if (persistent_mode_socket == kInvalidWorkerSocket) { WorkerLog( "Failed to create persistent mode socket - not running persistent " "mode.", @@ -370,27 +469,42 @@ "persistent mode socket path string must be fit in sockaddr_un.sun_path"); std::memcpy(addr.sun_path, persistent_mode_socket_path, socket_path_len); - int connect_ret = 0; - do { + int connect_ret = -1; + for (int retry = 0; retry < 200; ++retry) { connect_ret = connect(persistent_mode_socket, (struct sockaddr*)&addr, sizeof(addr)); - } while (connect_ret == -1 && errno == EINTR); + if (connect_ret == 0) break; +#if defined(_WIN32) + int err = WSAGetLastError(); + if (err == WSAEINTR || err == WSAEWOULDBLOCK || err == WSAECONNREFUSED) { + Sleep(10); + continue; + } +#else + if (errno == EINTR) continue; +#endif + break; + } if (connect_ret == -1) { WorkerLog("Failed to connect the persistent mode socket to ", persistent_mode_socket_path, LogLnSync{}); - (void)close(persistent_mode_socket); - persistent_mode_socket = -1; + CloseWorkerSocket(persistent_mode_socket); + persistent_mode_socket = kInvalidWorkerSocket; return; } +#if defined(_WIN32) + SetHandleInformation(reinterpret_cast<HANDLE>(persistent_mode_socket), + HANDLE_FLAG_INHERIT, 0); +#else int flags = fcntl(persistent_mode_socket, F_GETFD); if (flags == -1) { WorkerLog( "fcntl(F_GETFD) failed on the persistent mode socket - exiting " "persistent mode", LogLnSync{}); - (void)close(persistent_mode_socket); - persistent_mode_socket = -1; + CloseWorkerSocket(persistent_mode_socket); + persistent_mode_socket = kInvalidWorkerSocket; return; } flags |= FD_CLOEXEC; @@ -399,10 +513,11 @@ "fcntl(F_SETFD) failed on the persistent mode socket - exiting " "persistent mode", LogLnSync{}); - (void)close(persistent_mode_socket); - persistent_mode_socket = -1; + CloseWorkerSocket(persistent_mode_socket); + persistent_mode_socket = kInvalidWorkerSocket; return; } +#endif WorkerLog("Persistent mode: connected to ", persistent_mode_socket_path, LogLnSync{}); } @@ -542,7 +657,7 @@ for (size_t i = 0; i < seed_handles.size(); ++i) { char seed_path_buf[PATH_MAX]; const size_t num_path_chars = - snprintf(seed_path_buf, PATH_MAX, "%s/%09lu", output_dir, i); + snprintf(seed_path_buf, PATH_MAX, "%s/%09zu", output_dir, i); WorkerCheck(num_path_chars < PATH_MAX, "seed path reaches PATH_MAX"); std::vector<uint8_t> serialized_input; const auto sink = GetBytesSinkTo(serialized_input); @@ -560,10 +675,19 @@ // Returns the current time in microseconds. uint64_t TimeInUsec() { + [[maybe_unused]] constexpr size_t kUsecInSec = 1000000; +#if defined(_WIN32) + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + ULARGE_INTEGER uli; + uli.LowPart = ft.dwLowDateTime; + uli.HighPart = ft.dwHighDateTime; + return static_cast<uint64_t>(uli.QuadPart / 10); +#else struct timeval tv = {}; - constexpr size_t kUsecInSec = 1000000; gettimeofday(&tv, nullptr); return tv.tv_sec * kUsecInSec + tv.tv_usec; +#endif } void WorkerDoMutate(const FuzzTestAdapter& adapter) { @@ -723,6 +847,14 @@ } timer; auto GetPeakRSSMb = []() -> size_t { +#if defined(_WIN32) + PROCESS_MEMORY_COUNTERS pmc = {}; + pmc.cb = sizeof(pmc); + if (GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) { + return pmc.PeakWorkingSetSize >> 20; + } + return 0; +#else struct rusage usage = {}; if (getrusage(RUSAGE_SELF, &usage) != 0) return 0; #ifdef __APPLE__ @@ -733,6 +865,7 @@ // On Linux, ru_maxrss is in KiB return usage.ru_maxrss >> 10; #endif // __APPLE__ +#endif }; // In-loop variables declared outside to save allocations. @@ -869,10 +1002,13 @@ } else { // Reset stdout/stderr. for (int fd = 1; fd <= 2; fd++) { +#if defined(_WIN32) + _lseek(fd, 0, SEEK_SET); + (void)_chsize(fd, 0); +#else lseek(fd, 0, SEEK_SET); - // NOTE: Allow ftruncate() to fail by ignoring its return; that's okay - // to happen when the stdout/stderr are not redirected to a file. (void)ftruncate(fd, 0); +#endif } WorkerLog( "FuzzTest engine worker (", @@ -993,7 +1129,7 @@ WorkerCheck(adapter.FreeInput != nullptr, "FreeInput must be defined"); WorkerCheck(adapter.FreeCtx != nullptr, "FreeCtx must be defined"); - if (persistent_mode_socket > 0) { + if (persistent_mode_socket != kInvalidWorkerSocket) { HandlePersistentMode(adapter); } else if (action == WorkerAction::kTestGetSeeds) { WorkerDoGetSeeds(adapter);
diff --git a/centipede/fuzztest_mutator.cc b/centipede/fuzztest_mutator.cc index 1af4e17..df039ce 100644 --- a/centipede/fuzztest_mutator.cc +++ b/centipede/fuzztest_mutator.cc
@@ -115,11 +115,11 @@ ~MutatorDomain() {} }; -FuzzTestMutator::FuzzTestMutator(const Knobs &knobs, uint64_t seed) +FuzzTestMutator::FuzzTestMutator(const Knobs& knobs, uint64_t seed) : knobs_(knobs), prng_(seed), - mutation_metadata_(std::make_unique<MutationMetadata>()), - domain_(std::make_unique<MutatorDomain>()) { + mutation_metadata_(std::make_unique<MutationMetadata>()) { + domain_ = std::make_unique<MutatorDomain>(); domain_->WithMinSize(1).WithMaxSize(max_len_); } @@ -139,7 +139,7 @@ const ByteArray &other) { // Overwrite data[pos:pos+size] with other[first:first+size]. // Overwrite no more than half of data. - size_t max_size = std::max(1UL, data.size() / 2); + size_t max_size = std::max(size_t{1}, data.size() / 2); const auto first = absl::Uniform<size_t>(prng_, 0, other.size()); max_size = std::min(max_size, other.size() - first); const auto size = absl::Uniform<size_t>(prng_, 1, max_size + 1);
diff --git a/centipede/minimize_crash.cc b/centipede/minimize_crash.cc index 3a75160..4a812d7 100644 --- a/centipede/minimize_crash.cc +++ b/centipede/minimize_crash.cc
@@ -68,7 +68,7 @@ // Write the crasher to disk. auto hash = Hash(crasher); auto dir = crash_dir_path_; - std::string file_path = dir.append(hash); + std::string file_path = dir.append(hash).string(); WriteToLocalFile(file_path, crasher); }
diff --git a/centipede/periodic_action.cc b/centipede/periodic_action.cc index d1f42fe..c0e84db 100644 --- a/centipede/periodic_action.cc +++ b/centipede/periodic_action.cc
@@ -23,6 +23,10 @@ #include "absl/functional/any_invocable.h" #include "absl/synchronization/mutex.h" #include "absl/time/time.h" +#if defined(_WIN32) +#include <thread> // NOLINT +#else +#endif namespace fuzztest::internal {
diff --git a/centipede/puzzles/BUILD b/centipede/puzzles/BUILD index b87aad0..3f62e6b 100644 --- a/centipede/puzzles/BUILD +++ b/centipede/puzzles/BUILD
@@ -26,7 +26,6 @@ package(default_visibility = ["@com_google_fuzztest//centipede/puzzles:__subpackages__"]) -# The puzzles use a simple configuration language, see run_puzzle.sh. [puzzle(name = n) for n in [ "byte_cmp_4", "callstack", @@ -35,7 +34,6 @@ "memcmp_4_may_inline", "memcmp_3", "strcmp", - "strcasecmp", "strncmp", "switch", "uint32_cmp_1", @@ -46,5 +44,12 @@ "autodictionary_stress", "paths", "thread_uint32_cmp_1", +]] + +[puzzle( + name = n, + tags = ["no-windows"], +) for n in [ + "strcasecmp", "pthread_exit_uint32_cmp_1", ]]
diff --git a/centipede/puzzles/autodictionary_stress.cc b/centipede/puzzles/autodictionary_stress.cc index 297d5ee..40a415d 100644 --- a/centipede/puzzles/autodictionary_stress.cc +++ b/centipede/puzzles/autodictionary_stress.cc
@@ -12,9 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Centipede puzzle: stress test for --use_auto_dictionary=1. -// RUN: Run --use_auto_dictionary=1 --use_cmp_features=0 -j 5 -// RUN: ExpectInLog "Input bytes.*: abcdxyzVeryLongStringKeyword" +// CASE main: ARG: --use_auto_dictionary=1 +// CASE main: ARG: --use_cmp_features=0 +// CASE main: ARG: --j=5 +// CASE main: MATCH: Input bytes.*: abcdxyzVeryLongStringKeyword // TODO(kcc): we currently use --use_cmp_features=0 because otherwise // the corpus gets too large and the puzzle does not get solved quickly.
diff --git a/centipede/puzzles/byte_cmp_4.cc b/centipede/puzzles/byte_cmp_4.cc index 8fb9c4c..9bdcf96 100644 --- a/centipede/puzzles/byte_cmp_4.cc +++ b/centipede/puzzles/byte_cmp_4.cc
@@ -13,7 +13,7 @@ // limitations under the License. // Centipede puzzle: sequence of 4 1-byte comparisons. -// RUN: Run && SolutionIs fuzZ +// CASE main: MATCH: Input bytes *: fuzZ #include <cstddef> #include <cstdint> #include <cstdlib>
diff --git a/centipede/puzzles/callstack.cc b/centipede/puzzles/callstack.cc index 4fc9a8b..7fd1601 100644 --- a/centipede/puzzles/callstack.cc +++ b/centipede/puzzles/callstack.cc
@@ -18,10 +18,10 @@ // All functions but F0 also modify `g_result`. // The puzzle can be solved if the call sequence is FA->FB->FC->FD->FE. -// clang-format off -// RUN: Run --callstack_level=10 --use_cmp_features=0 --use_dataflow_features=0 // NOLINT -// RUN: SolutionIs ABCDE -// clang-format on +// CASE main: ARG: --callstack_level=10 +// CASE main: ARG: --use_cmp_features=0 +// CASE main: ARG: --use_dataflow_features=0 +// CASE main: MATCH: Input bytes *: ABCDE #include <cstddef> #include <cstdint> #include <cstdlib>
diff --git a/centipede/puzzles/deep_recursion.cc b/centipede/puzzles/deep_recursion.cc index 6e7572d..9440cbd 100644 --- a/centipede/puzzles/deep_recursion.cc +++ b/centipede/puzzles/deep_recursion.cc
@@ -13,11 +13,11 @@ // limitations under the License. // 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 -// clang-format on -#include <sys/resource.h> +// CASE main: ARG: --callstack_level=10 +// CASE main: ARG: --use_cmp_features=0 +// CASE main: ARG: --max_len=10 +// CASE main: ARG: --num_runs=10000000 +// CASE main: MATCH: Input bytes *: ABCDEF #include <cstddef> #include <cstdint>
diff --git a/centipede/puzzles/independent_compares.cc b/centipede/puzzles/independent_compares.cc index 07294f9..0679482 100644 --- a/centipede/puzzles/independent_compares.cc +++ b/centipede/puzzles/independent_compares.cc
@@ -13,7 +13,7 @@ // limitations under the License. // Centipede puzzle: 4 independent compares setting a mask. -// RUN: Run && SolutionIs FUZZ +// CASE main: MATCH: Input bytes *: FUZZ #include <cstddef> #include <cstdint> #include <cstdlib>
diff --git a/centipede/puzzles/memcmp_3.cc b/centipede/puzzles/memcmp_3.cc index 36244e3..c12ffe0 100644 --- a/centipede/puzzles/memcmp_3.cc +++ b/centipede/puzzles/memcmp_3.cc
@@ -14,9 +14,10 @@ // Centipede puzzle: one 3-byte memcmp. Check the output in the log. // Disable use_auto_dictionary so that we test other functionality. -// RUN: Run --use_auto_dictionary=false && SolutionIs fUz -// RUN: ExpectInLog "TEXT IN STDOUT" -// RUN: ExpectInLog "TEXT IN STDERR" +// CASE main: ARG: --use_auto_dictionary=false +// CASE main: MATCH: Input bytes *: fUz +// CASE main: MATCH: TEXT IN STDOUT +// CASE main: MATCH: TEXT IN STDERR #include <cstdint> #include <cstdio>
diff --git a/centipede/puzzles/memcmp_4.cc b/centipede/puzzles/memcmp_4.cc index 97bbbd5..deefe4a 100644 --- a/centipede/puzzles/memcmp_4.cc +++ b/centipede/puzzles/memcmp_4.cc
@@ -14,7 +14,9 @@ // Centipede puzzle: one 4-byte memcmp. // Disable use_auto_dictionary so that we test other functionality. -// RUN: Run --use_auto_dictionary=false --max_len=10 && SolutionIs fuZz +// CASE main: ARG: --use_auto_dictionary=false +// CASE main: ARG: --max_len=10 +// CASE main: MATCH: Input bytes *: fuZz #include <cstdint> #include <cstdlib>
diff --git a/centipede/puzzles/memcmp_4_may_inline.cc b/centipede/puzzles/memcmp_4_may_inline.cc index bc2f062..746796d 100644 --- a/centipede/puzzles/memcmp_4_may_inline.cc +++ b/centipede/puzzles/memcmp_4_may_inline.cc
@@ -14,7 +14,8 @@ // Centipede puzzle: one 4-byte memcmp, which may get inlined. // Disable use_auto_dictionary so that we test other functionality. -// RUN: Run --use_auto_dictionary=false && SolutionIs FUZz +// CASE main: ARG: --use_auto_dictionary=false +// CASE main: MATCH: Input bytes *: FUZz #include <cstdint> #include <cstdlib>
diff --git a/centipede/puzzles/oom.cc b/centipede/puzzles/oom.cc index bb20e5d..9515a3c 100644 --- a/centipede/puzzles/oom.cc +++ b/centipede/puzzles/oom.cc
@@ -12,8 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Centipede puzzle: easy-to-reach OOM. -// RUN: Run --rss_limit_mb=1000 && SolutionIs OOM && ExpectOOM +// CASE main: ARG: --rss_limit_mb=1000 +// CASE main: MATCH: Input bytes *: OOM +// CASE main: MATCH: Failure.*: rss-limit-exceeded #include <cstddef> #include <cstdint> #include <cstdio>
diff --git a/centipede/puzzles/paths.cc b/centipede/puzzles/paths.cc index ab8de1a..654300b 100644 --- a/centipede/puzzles/paths.cc +++ b/centipede/puzzles/paths.cc
@@ -16,8 +16,10 @@ // We disable use_dataflow_features because on this puzzle // it is also effective. -// RUN: Run --use_dataflow_features=0 --use_cmp_features=0 --path_level=10 -// RUN: ExpectInLog "Input bytes.*: .x1.x2.x3" +// CASE main: ARG: --use_dataflow_features=0 +// CASE main: ARG: --use_cmp_features=0 +// CASE main: ARG: --path_level=10 +// CASE main: MATCH: Input bytes.*: .x1.x2.x3 // This puzzle aborts on input "\x1\x2\x3" // The code here has very little control flow, but an exponential number of
diff --git a/centipede/puzzles/per_batch_timeout.cc b/centipede/puzzles/per_batch_timeout.cc index f64f009..f278597 100644 --- a/centipede/puzzles/per_batch_timeout.cc +++ b/centipede/puzzles/per_batch_timeout.cc
@@ -13,12 +13,16 @@ // limitations under the License. // Centipede puzzle: easy-to-reach per-batch timeout. -// clang-format off -// NOLINTNEXTLINE -// RUN: Run --batch_size=10 --timeout_per_input=2 --timeout_per_batch=7 && ExpectPerBatchTimeout -// clang-format on +// CASE main: ARG: --batch_size=10 +// CASE main: ARG: --timeout_per_input=2 +// CASE main: ARG: --timeout_per_batch=7 +// CASE main: MATCH: Failure.*: per-batch-timeout-exceeded +#if defined(_WIN32) +#include <windows.h> +#else #include <unistd.h> +#endif #include <cstddef> #include <cstdint> @@ -27,6 +31,10 @@ // Within the --timeout_per_input, but we have no solution, so the runner // should keep running us until it exceeds --timeout_per_batch, then report a // failure back to the engine. - sleep(1); +#if defined(_WIN32) + Sleep(1000); +#else + sleep(1); // NOLINT +#endif return 0; }
diff --git a/centipede/puzzles/per_input_timeout.cc b/centipede/puzzles/per_input_timeout.cc index d286543..26a1f82 100644 --- a/centipede/puzzles/per_input_timeout.cc +++ b/centipede/puzzles/per_input_timeout.cc
@@ -12,17 +12,27 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Centipede puzzle: easy-to-reach per-input timeout. -// RUN: Run --timeout_per_input=2 && SolutionIs SLO && ExpectPerInputTimeout +// CASE main: ARG: --timeout_per_input=2 +// CASE main: MATCH: Input bytes *: SLO +// CASE main: MATCH: Failure.*: per-input-timeout-exceeded +#if defined(_WIN32) +#include <windows.h> +#else #include <unistd.h> +#endif #include <cstddef> #include <cstdint> extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { if (size == 3 && data[0] == 'S' && data[1] == 'L' && data[2] == 'O') { - sleep(1000); // Dies with timeout. + // Dies with timeout. +#if defined(_WIN32) + Sleep(1000000); +#else + sleep(1000); // NOLINT +#endif } return 0; }
diff --git a/centipede/puzzles/pthread_exit_uint32_cmp_1.cc b/centipede/puzzles/pthread_exit_uint32_cmp_1.cc index 1e5bade..f132c08 100644 --- a/centipede/puzzles/pthread_exit_uint32_cmp_1.cc +++ b/centipede/puzzles/pthread_exit_uint32_cmp_1.cc
@@ -15,10 +15,11 @@ // Centipede puzzle: one 4-byte cmp, in a separate thread using pthread // interface. We should be able to solve it w/o cmp features *or* w/o auto // dictionary. -// -// RUN: Run && SolutionIs Fuzz -// RUN: Run --use_auto_dictionary=0 && SolutionIs Fuzz -// RUN: Run --use_cmp_features=0 && SolutionIs Fuzz +// CASE main: MATCH: Input bytes *: Fuzz +// CASE no_cmp: ARG: --use_cmp_features=0 +// CASE no_cmp: MATCH: Input bytes *: Fuzz +// CASE no_autodict: ARG: --use_auto_dictionary=0 +// CASE no_autodict: MATCH: Input bytes *: Fuzz #include <pthread.h>
diff --git a/centipede/puzzles/puzzle.bzl b/centipede/puzzles/puzzle.bzl index a446b96..1e87757 100644 --- a/centipede/puzzles/puzzle.bzl +++ b/centipede/puzzles/puzzle.bzl
@@ -14,14 +14,15 @@ """BUILD rule for Centipede puzzles""" -load("@rules_shell//shell:sh_test.bzl", "sh_test") +load("@rules_cc//cc:cc_test.bzl", "cc_test") load("@com_google_fuzztest//centipede/testing:build_defs.bzl", "centipede_fuzz_target") -def puzzle(name): +def puzzle(name, tags = []): """Generates a cc_fuzz_target target instrumented with sancov and a sh script to run it. Args: name: A unique name for this target + tags: Tags for this target """ centipede_fuzz_target( @@ -36,13 +37,24 @@ # repeatability. Each sh_test performs a single run with a single seed, so # that the log is minimal. for seed in ["1", "2"]: - sh_test( + cc_test( name = "run_" + seed + "_" + name, - srcs = ["run_puzzle.sh"], + srcs = ["run_puzzle.cc"], + args = ["--seed=" + seed, "--puzzle=" + name], data = [ ":" + name, name + ".cc", "@com_google_fuzztest//centipede:centipede_uninstrumented", - "@com_google_fuzztest//centipede:test_util_sh", ], + deps = [ + "@googletest//:gtest", + "@abseil-cpp//absl/flags:flag", + "@abseil-cpp//absl/flags:parse", + "@abseil-cpp//absl/strings", + "@abseil-cpp//absl/time", + "@com_google_fuzztest//centipede:command", + "@com_google_fuzztest//common:logging", + "@com_google_fuzztest//common:test_util", + ], + tags = tags, )
diff --git a/centipede/puzzles/run_puzzle.cc b/centipede/puzzles/run_puzzle.cc new file mode 100644 index 0000000..939675a --- /dev/null +++ b/centipede/puzzles/run_puzzle.cc
@@ -0,0 +1,179 @@ +// Copyright 2026 The FuzzTest Authors. +// +// 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 +// +// https://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. + +#include <filesystem> // NOLINT +#include <fstream> +#include <set> +#include <sstream> +#include <string> +#include <string_view> +#include <system_error> // NOLINT +#include <utility> +#include <vector> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/flags/flag.h" +#include "absl/flags/parse.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_split.h" +#include "absl/time/time.h" +#include "./centipede/command.h" +#include "./common/logging.h" +#include "./common/test_util.h" + +ABSL_FLAG(int, seed, 0, "The random seed to use"); +ABSL_FLAG(std::string, puzzle, "", "The name of the puzzle to run"); + +namespace fuzztest::internal { +namespace { + +using testing::ContainsRegex; +using testing::Value; + +std::string ReadFile(std::filesystem::path path) { + std::ifstream file(path); + std::stringstream ss; + ss << file.rdbuf(); + return ss.str(); +} + +constexpr std::string_view kCasePrefix = "// CASE "; +constexpr std::string_view kArgPrefix = "ARG:"; +constexpr std::string_view kMatchPrefix = "MATCH:"; + +std::vector<std::string> GetCasesInPuzzle(std::string_view puzzle) { + const std::string puzzle_source_path = + GetDataDependencyFilepath( + absl::StrCat("centipede/puzzles/", puzzle, ".cc")) + .string(); + std::error_code ec; + FUZZTEST_CHECK(std::filesystem::exists(puzzle_source_path, ec)) + << "Puzzle source " << puzzle_source_path << " does not exist"; + const std::string puzzle_source = ReadFile(puzzle_source_path); + std::set<std::string> found_cases; + for (std::string_view line : absl::StrSplit(puzzle_source, '\n')) { + const auto case_start_pos = line.find(kCasePrefix); + if (case_start_pos == line.npos) continue; + const auto case_end_pos = + line.find(":", case_start_pos + kCasePrefix.size()); + if (case_end_pos == line.npos) continue; + if (line.find(kMatchPrefix, case_end_pos) == line.npos) continue; + found_cases.insert(std::string{ + line.substr(case_start_pos + kCasePrefix.size(), + case_end_pos - case_start_pos - kCasePrefix.size())}); + } + return {found_cases.begin(), found_cases.end()}; +} + +class PuzzleTest : public testing::Test { + public: + PuzzleTest(std::string_view puzzle, std::string_view case_name) + : puzzle_{puzzle}, case_name_{case_name} {} + + void TestBody() { + const std::string centipede_path = + GetDataDependencyFilepath("centipede/centipede_uninstrumented") + .string(); + const std::string puzzle_binary_path = + GetDataDependencyFilepath(absl::StrCat("centipede/puzzles/", puzzle_)) + .string(); + const std::string puzzle_source_path = + GetDataDependencyFilepath( + absl::StrCat("centipede/puzzles/", puzzle_, ".cc")) + .string(); + std::error_code ec; + FUZZTEST_CHECK(std::filesystem::exists(puzzle_binary_path, ec)) + << "Puzzle binary " << puzzle_binary_path << " does not exist"; + FUZZTEST_CHECK(std::filesystem::exists(puzzle_source_path, ec)) + << "Puzzle source " << puzzle_source_path << " does not exist"; + TempDir tmp_dir{absl::StrCat(puzzle_, "_", case_name_)}; + + const std::string case_prefix = absl::StrCat(kCasePrefix, case_name_, ":"); + std::vector<std::string> extra_args; + std::vector<std::string> output_matches; + const std::string puzzle_source = ReadFile(puzzle_source_path); + for (std::string_view line : absl::StrSplit(puzzle_source, '\n')) { + const auto case_pos = line.find(case_prefix); + if (case_pos == line.npos) continue; + if (const auto arg_pos = + line.find(kArgPrefix, case_pos + case_prefix.size()); + arg_pos != line.npos) { + auto arg = std::string{absl::StripAsciiWhitespace( + line.substr(arg_pos + kArgPrefix.size()))}; + FUZZTEST_LOG(INFO) << "Using arg: " << arg; + extra_args.push_back(std::move(arg)); + continue; + } + if (const auto output_match_pos = + line.find(kMatchPrefix, case_pos + case_prefix.size()); + output_match_pos != line.npos) { + auto output_match = std::string{absl::StripAsciiWhitespace( + line.substr(output_match_pos + kMatchPrefix.size()))}; + FUZZTEST_LOG(INFO) << "Using match: " << output_match; + output_matches.push_back(std::move(output_match)); + continue; + } + } + FUZZTEST_CHECK(!output_matches.empty()) + << "Need at least one output match for case " << case_name_; + + Command::Options cmd_options; + cmd_options.args = { + absl::StrCat("--workdir=", (tmp_dir.path() / "workdir").string()), + absl::StrCat("--binary=", puzzle_binary_path), + "--populate_binary_info=0", + absl::StrCat("--seed=", absl::GetFlag(FLAGS_seed)), + "--num_runs=2000000", + "--shmem_size_mb=100", + "--exit_on_crash", + }; + cmd_options.args.insert(cmd_options.args.end(), extra_args.begin(), + extra_args.end()); + + const std::string output_prefix = (tmp_dir.path() / "out_").string(); + cmd_options.stdout_file_prefix = output_prefix; + cmd_options.stderr_file_prefix = output_prefix; + Command cmd(centipede_path, cmd_options); + FUZZTEST_CHECK(cmd.ExecuteAsync()); + (void)cmd.Wait(absl::Now() + absl::Seconds(30)); + const std::string output = ReadFile(cmd.stdout_file()); + for (const auto& output_match : output_matches) { + EXPECT_TRUE(Value(output, ContainsRegex(output_match))) << output; + } + } + + private: + std::string puzzle_; + std::string case_name_; +}; + +} // namespace +} // namespace fuzztest::internal + +int main(int argc, char** argv) { + absl::ParseCommandLine(argc, argv); + testing::InitGoogleTest(&argc, argv); + const std::string puzzle = absl::GetFlag(FLAGS_puzzle); + FUZZTEST_CHECK(!puzzle.empty()); + const auto cases = fuzztest::internal::GetCasesInPuzzle(puzzle); + for (const auto& case_name : cases) { + testing::RegisterTest( + "Puzzle", case_name.c_str(), nullptr, nullptr, __FILE__, __LINE__, + [puzzle, case_name]() -> testing::Test* { + return new fuzztest::internal::PuzzleTest{puzzle, case_name}; + }); + } + return RUN_ALL_TESTS(); +}
diff --git a/centipede/puzzles/run_puzzle.sh b/centipede/puzzles/run_puzzle.sh deleted file mode 100755 index d6d12c8..0000000 --- a/centipede/puzzles/run_puzzle.sh +++ /dev/null
@@ -1,122 +0,0 @@ -#!/bin/bash - -# Copyright 2022 The Centipede Authors. -# -# 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 -# -# https://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. - -# Run a short fuzzing session for one puzzle and check the outcome. -# This script is executed under the name run_S_PUZZLE_NAME, where S is a single -# digit representing the seed, so we get the seed and puzzle name from $0. -# Every puzzle must have one or more lines containing "RUN:<program-text>" -# This script will execute <program-text> directly in the current context. -# <program-text> can use the functions defined in this file, see USER_FUNCTIONS. - -set -eu -o pipefail - -ls -la "$(dirname "$0")" - -source "$(dirname "$0")/../test_util.sh" - -readonly centipede_dir="$(fuzztest::internal::get_centipede_test_srcdir)" -fuzztest::internal::maybe_set_var_to_executable_path centipede "${centipede_dir}/centipede_uninstrumented" -readonly centipede -fuzztest::internal::maybe_set_var_to_executable_path llvm_symbolizer "$(fuzztest::internal::get_llvm_symbolizer_path)" -readonly llvm_symbolizer -fuzztest::internal::maybe_set_var_to_executable_path objdump "$(fuzztest::internal::get_objdump_path)" -readonly objdump - -readonly target_name="$(basename "$0")" -readonly seed_and_puzzle_name="${target_name#run_}" -readonly seed="${seed_and_puzzle_name:0:1}" -readonly puzzle_name="${seed_and_puzzle_name:2}" -readonly puzzle_source_name="${puzzle_name}.cc" -readonly puzzle_path="${centipede_dir}/puzzles/${puzzle_name}" -readonly puzzle_source_path="${centipede_dir}/puzzles/${puzzle_source_name}" - -readonly workdir="${TEST_TMPDIR}/workdir" -readonly log="${TEST_TMPDIR}/log" -readonly script="${TEST_TMPDIR}/script" - -# Read the configuration from the puzzle source. -grep 'RUN:' "${puzzle_source_path}" | sed 's/^.*RUN://' > "${script}" -echo "======== SCRIPT" -cat "${script}" -echo "======== END SCRIPT" - -##################################### USER_FUNCTIONS - -# Runs Centipede with additional parameters in $@, saves the result in log, cats -# the log. Expects Centipede to exit with failure. -function Run() { - echo "======== Run $*" - rm -rf "${workdir}" - mkdir "${workdir}" - if "${centipede}" \ - --workdir "${workdir}" \ - --binary "${puzzle_path}" \ - --symbolizer_path="${llvm_symbolizer}" \ - --objdump_path="${objdump}$" \ - --seed="${seed}" \ - --num_runs=2000000 \ - --timeout_per_input=10 \ - --exit_on_crash \ - "$@" \ - 2>&1 | tee "${log}" - then - # Centipede must exit with failure. - return 1 - fi -} - -# Checks that $1 is the solution for the puzzle. -function SolutionIs() { - echo "====== ${FUNCNAME[0]}: $1" - fuzztest::internal::assert_regex_in_file "Input bytes.*: $1" "${log}" -} - -# Expects that Centipede found a per-input timeout. -function ExpectPerInputTimeout() { - echo "======= ${FUNCNAME[0]}" - fuzztest::internal::assert_regex_in_file "Per-input timeout exceeded" "${log}" - fuzztest::internal::assert_regex_in_file "Failure.*: per-input-timeout-exceeded" "${log}" -} - -# Expects that Centipede found a per-batch timeout. -function ExpectPerBatchTimeout() { - echo "======= ${FUNCNAME[0]}" - fuzztest::internal::assert_regex_in_file "Failure.*: per-batch-timeout-exceeded" "${log}" - fuzztest::internal::assert_regex_in_file \ - "Failure applies to entire batch: not executing inputs one-by-one" "${log}" - fuzztest::internal::assert_regex_not_in_file \ - "Executing inputs one-by-one, trying to find the reproducer" "${log}" -} - -# Expects that Centipede found a OOM. -function ExpectOOM() { - echo "======= ${FUNCNAME[0]}" - fuzztest::internal::assert_regex_in_file "RSS limit exceeded" "${log}" - fuzztest::internal::assert_regex_in_file "Failure.*: rss-limit-exceeded" "${log}" -} - -# Expects that $1 is found in the log. -function ExpectInLog() { - echo "======= ${FUNCNAME[0]}: $1" - fuzztest::internal::assert_regex_in_file "$1" "${log}" -} - -##################################### end USER_FUNCTIONS - -# shellcheck disable=SC1090 -source "${script}" - -echo PASS
diff --git a/centipede/puzzles/strcasecmp.cc b/centipede/puzzles/strcasecmp.cc index c9aaadd..670a9f5 100644 --- a/centipede/puzzles/strcasecmp.cc +++ b/centipede/puzzles/strcasecmp.cc
@@ -15,9 +15,10 @@ // Centipede puzzle: one 3-byte strncasecmp and one 3-byte strcasecmp. Check // the output in the log. Disable use_auto_dictionary so that we test other // functionality. -// RUN: Run --use_auto_dictionary=false && SolutionIs 123456 -// RUN: ExpectInLog "TEXT IN STDOUT" -// RUN: ExpectInLog "TEXT IN STDERR" +// CASE main: ARG: --use_auto_dictionary=false +// CASE main: MATCH: Input bytes *: 123456 +// CASE main: MATCH: TEXT IN STDOUT +// CASE main: MATCH: TEXT IN STDERR #include <strings.h>
diff --git a/centipede/puzzles/strcmp.cc b/centipede/puzzles/strcmp.cc index c61bb17..338d338 100644 --- a/centipede/puzzles/strcmp.cc +++ b/centipede/puzzles/strcmp.cc
@@ -14,9 +14,10 @@ // Centipede puzzle: one 4-byte strcmp. Check the output in the log. // Disable use_auto_dictionary so that we test other functionality. -// RUN: Run --use_auto_dictionary=false && SolutionIs fUzZ -// RUN: ExpectInLog "TEXT IN STDOUT" -// RUN: ExpectInLog "TEXT IN STDERR" +// CASE main: ARG: --use_auto_dictionary=false +// CASE main: MATCH: Input bytes *: fUzZ +// CASE main: MATCH: TEXT IN STDOUT +// CASE main: MATCH: TEXT IN STDERR #include <cstdint> #include <cstdio>
diff --git a/centipede/puzzles/strncmp.cc b/centipede/puzzles/strncmp.cc index 4668e11..74f622d 100644 --- a/centipede/puzzles/strncmp.cc +++ b/centipede/puzzles/strncmp.cc
@@ -14,9 +14,10 @@ // Centipede puzzle: one 4-byte strncmp. Check the output in the log. // Disable use_auto_dictionary so that we test other functionality. -// RUN: Run --use_auto_dictionary=false && SolutionIs fUzZ -// RUN: ExpectInLog "TEXT IN STDOUT" -// RUN: ExpectInLog "TEXT IN STDERR" +// CASE main: ARG: --use_auto_dictionary=false +// CASE main: MATCH: Input bytes *: fUzZ +// CASE main: MATCH: TEXT IN STDOUT +// CASE main: MATCH: TEXT IN STDERR #include <cstdint> #include <cstdio>
diff --git a/centipede/puzzles/switch.cc b/centipede/puzzles/switch.cc index 40a61a2..8e7f09e 100644 --- a/centipede/puzzles/switch.cc +++ b/centipede/puzzles/switch.cc
@@ -13,7 +13,9 @@ // limitations under the License. // Centipede puzzle: one 4-byte switch. -// RUN: Run --max_len=10 --use_cmp_features=0 && ExpectInLog "deadbeef found!" +// CASE main: ARG: --max_len=10 +// CASE main: ARG: --use_cmp_features=0 +// CASE main: MATCH: deadbeef found! #include <cstdint> #include <cstdio>
diff --git a/centipede/puzzles/thread_uint32_cmp_1.cc b/centipede/puzzles/thread_uint32_cmp_1.cc index 0665815..962fe3e 100644 --- a/centipede/puzzles/thread_uint32_cmp_1.cc +++ b/centipede/puzzles/thread_uint32_cmp_1.cc
@@ -14,9 +14,11 @@ // Centipede puzzle: one 4-byte cmp, in a separate thread. // We should be able to solve it w/o cmp features *or* w/o auto dictionary. -// RUN: Run && SolutionIs Fuzz -// RUN: Run --use_auto_dictionary=0 && SolutionIs Fuzz -// RUN: Run --use_cmp_features=0 && SolutionIs Fuzz +// CASE main: MATCH: Input bytes *: Fuzz +// CASE no_cmp: ARG: --use_cmp_features=0 +// CASE no_cmp: MATCH: Input bytes *: Fuzz +// CASE no_autodict: ARG: --use_auto_dictionary=0 +// CASE no_autodict: MATCH: Input bytes *: Fuzz #include <cstdint> #include <cstdlib>
diff --git a/centipede/puzzles/uint32_cmp_1.cc b/centipede/puzzles/uint32_cmp_1.cc index f41879b..3b4df01 100644 --- a/centipede/puzzles/uint32_cmp_1.cc +++ b/centipede/puzzles/uint32_cmp_1.cc
@@ -14,9 +14,11 @@ // Centipede puzzle: one 4-byte cmp. // We should be able to solve it w/o cmp features *or* w/o auto dictionary. -// RUN: Run && SolutionIs Fuzz -// RUN: Run --use_cmp_features=0 && SolutionIs Fuzz -// RUN: Run --use_auto_dictionary=0 && SolutionIs Fuzz +// CASE main: MATCH: Input bytes *: Fuzz +// CASE no_cmp: ARG: --use_cmp_features=0 +// CASE no_cmp: MATCH: Input bytes *: Fuzz +// CASE no_autodict: ARG: --use_auto_dictionary=0 +// CASE no_autodict: MATCH: Input bytes *: Fuzz #include <cstdint> #include <cstdlib>
diff --git a/centipede/resource_pool.h b/centipede/resource_pool.h index c33a8ff..2c1ea91 100644 --- a/centipede/resource_pool.h +++ b/centipede/resource_pool.h
@@ -15,9 +15,15 @@ #ifndef FUZZTEST_CENTIPEDE_RESOURCE_RESOURCE_POOL_H_ #define FUZZTEST_CENTIPEDE_RESOURCE_RESOURCE_POOL_H_ +#if defined(_WIN32) +#define WIN32_LEAN_AND_MEAN +#define NOGDI +#include <windows.h> +#else #include <sys/syscall.h> #include <sys/types.h> #include <unistd.h> +#endif #include <ostream> #include <string>
diff --git a/centipede/runner.cc b/centipede/runner.cc index e734e6d..00b58fd 100644 --- a/centipede/runner.cc +++ b/centipede/runner.cc
@@ -23,12 +23,39 @@ #include "./centipede/runner.h" #include <fcntl.h> +#if !defined(_WIN32) #include <pthread.h> // NOLINT: use pthread to avoid extra dependencies. #include <sys/resource.h> #include <sys/socket.h> #include <sys/time.h> #include <sys/un.h> #include <unistd.h> +#else +#define WIN32_LEAN_AND_MEAN +#define NOGDI +// clang-format off +// Must be before <afunix.h> +#include <ws2tcpip.h> +// clang-format on +#include <afunix.h> +#include <io.h> +#include <process.h> +#include <psapi.h> +#include <windows.h> +#include <winsock2.h> + +#include <thread> // NOLINT +inline static void EnsureWinsockInitialized() { + static WSADATA wsa_data; + [[maybe_unused]] static int init = WSAStartup(MAKEWORD(2, 2), &wsa_data); +} +inline static int rand_r(unsigned int* seed) { + *seed = *seed * 1103515245 + 12345; + return static_cast<int>((*seed / 65536) % 32768); +} +inline static void sleep(unsigned int seconds) { Sleep(seconds * 1000); } +inline static int ftruncate(int fd, size_t size) { return _chsize(fd, size); } +#endif #include <algorithm> #include <atomic> @@ -81,6 +108,14 @@ } // namespace static size_t GetPeakRSSMb() { +#if defined(_WIN32) + PROCESS_MEMORY_COUNTERS pmc = {}; + pmc.cb = sizeof(pmc); + if (GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) { + return pmc.PeakWorkingSetSize >> 20; + } + return 0; +#else struct rusage usage = {}; if (getrusage(RUSAGE_SELF, &usage) != 0) return 0; #ifdef __APPLE__ @@ -91,14 +126,24 @@ // On Linux, ru_maxrss is in KiB return usage.ru_maxrss >> 10; #endif // __APPLE__ +#endif } // Returns the current time in microseconds. static uint64_t TimeInUsec() { +#if defined(_WIN32) + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + ULARGE_INTEGER uli; + uli.LowPart = ft.dwLowDateTime; + uli.HighPart = ft.dwHighDateTime; + return static_cast<uint64_t>(uli.QuadPart / 10); +#else struct timeval tv = {}; constexpr size_t kUsecInSec = 1000000; gettimeofday(&tv, nullptr); return tv.tv_sec * kUsecInSec + tv.tv_usec; +#endif } // Atomic flags to make sure that (a) watchdog failure is reported only for @@ -195,7 +240,11 @@ tls.ignore = true; state->watchdog_thread_started = true; while (true) { +#if defined(_WIN32) + Sleep(1000); +#else sleep(1); +#endif // No calls to ResetInputTimer() yet: input execution hasn't started. if (state->input_start_time == 0) continue; @@ -235,12 +284,20 @@ run_time_flags.timeout_per_input.load(), run_time_flags.rss_limit_mb.load(), state->run_time_flags.stack_limit_kb.load()); +#if defined(_WIN32) + std::thread(WatchdogThread, nullptr).detach(); +#else pthread_t watchdog_thread; pthread_create(&watchdog_thread, nullptr, WatchdogThread, nullptr); pthread_detach(watchdog_thread); +#endif // Wait until the watchdog actually starts and initializes itself. while (!state->watchdog_thread_started) { +#if defined(_WIN32) + Sleep(0); +#else sleep(0); +#endif } } @@ -559,7 +616,7 @@ if (seed_index >= 1000000000) return; char seed_path_buf[PATH_MAX]; const size_t num_path_chars = - snprintf(seed_path_buf, PATH_MAX, "%s/%09lu", output_dir, seed_index); + snprintf(seed_path_buf, PATH_MAX, "%s/%09zu", output_dir, seed_index); PrintErrorAndExitIf(num_path_chars >= PATH_MAX, "seed path reaches PATH_MAX"); FILE* output_file = fopen(seed_path_buf, "w"); @@ -787,6 +844,9 @@ // Returns the current process VmSize, in bytes. static size_t GetVmSizeInBytes() { +#if defined(_WIN32) + return 0; +#else FILE* f = fopen("/proc/self/statm", "r"); // man proc if (!f) return 0; size_t vm_size = 0; @@ -794,10 +854,12 @@ (void)fscanf(f, "%zd", &vm_size); fclose(f); return vm_size * getpagesize(); // proc gives VmSize in pages. +#endif } // Sets RLIMIT_CORE, RLIMIT_AS static void SetLimits() { +#if !defined(_WIN32) // Disable core dumping. struct rlimit core_limits; getrlimit(RLIMIT_CORE, &core_limits); @@ -826,6 +888,7 @@ "VmSize is %zdGb, suspecting ASAN/MSAN/TSAN\n", vm_size_in_bytes >> 30); } +#endif } // Create a fake reference to ForkServerCallMeVeryEarly() here so that the @@ -845,9 +908,13 @@ if (state->persistent_mode_socket_path == nullptr) { return; } +#if defined(_WIN32) + EnsureWinsockInitialized(); +#endif state->persistent_mode_socket = socket(AF_UNIX, SOCK_STREAM, 0); if (state->persistent_mode_socket < 0) { fprintf(stderr, "Failed to create persistent mode socket\n"); + return; } struct sockaddr_un addr{}; @@ -863,26 +930,44 @@ do { connect_ret = connect(state->persistent_mode_socket, (struct sockaddr*)&addr, sizeof(addr)); - } while (connect_ret == -1 && errno == EINTR); + } while (connect_ret == -1 && +#if defined(_WIN32) + WSAGetLastError() == WSAEINTR +#else + errno == EINTR +#endif + ); if (connect_ret == -1) { fprintf(stderr, "Failed to connect the persistent mode socket to %s\n", state->persistent_mode_socket_path); +#if defined(_WIN32) + (void)closesocket(state->persistent_mode_socket); +#else (void)close(state->persistent_mode_socket); +#endif state->persistent_mode_socket = -1; + return; } +#if defined(_WIN32) + SetHandleInformation(reinterpret_cast<HANDLE>(state->persistent_mode_socket), + HANDLE_FLAG_INHERIT, 0); +#else int flags = fcntl(state->persistent_mode_socket, F_GETFD); if (flags == -1) { fprintf(stderr, "fcntl(F_GETFD) failed\n"); (void)close(state->persistent_mode_socket); state->persistent_mode_socket = -1; + return; } flags |= FD_CLOEXEC; if (fcntl(state->persistent_mode_socket, F_SETFD, flags) == -1) { fprintf(stderr, "fcntl(F_SETFD) failed\n"); (void)close(state->persistent_mode_socket); state->persistent_mode_socket = -1; + return; } +#endif } GlobalRunnerState::GlobalRunnerState() {
diff --git a/centipede/runner.h b/centipede/runner.h index 5ddc3b3..85894b0 100644 --- a/centipede/runner.h +++ b/centipede/runner.h
@@ -15,7 +15,6 @@ #ifndef THIRD_PARTY_CENTIPEDE_RUNNER_H_ #define THIRD_PARTY_CENTIPEDE_RUNNER_H_ -#include <pthread.h> // NOLINT: use pthread to avoid extra dependencies. #include <time.h> #include <atomic> @@ -27,6 +26,7 @@ #include "./centipede/runner_interface.h" #include "./centipede/runner_result.h" #include "./centipede/runner_utils.h" +#include "./centipede/sancov_state.h" namespace fuzztest::internal { @@ -84,7 +84,7 @@ flag_helper.GetStringFlag(":persistent_mode_socket="); int persistent_mode_socket = 0; - pthread_mutex_t execution_result_override_mu = PTHREAD_MUTEX_INITIALIZER; + MutexType execution_result_override_mu = MUTEX_INITIALIZER; // If not nullptr, it points to a batch result with either zero or one // execution. When an execution result present, it will be passed as the // execution result of the current test input. The object is owned and cleaned
diff --git a/centipede/runner_cmp_trace.h b/centipede/runner_cmp_trace.h index 53d15c6..fcd7405 100644 --- a/centipede/runner_cmp_trace.h +++ b/centipede/runner_cmp_trace.h
@@ -18,7 +18,13 @@ // Capturing arguments of CMP instructions, memcmp, and similar. // WARNING: this code needs to have minimal dependencies. +#if !defined(_WIN32) #include <sys/time.h> +#else +#define WIN32_LEAN_AND_MEAN +#define NOGDI +#include <windows.h> +#endif #include <cstddef> #include <cstdint> @@ -63,12 +69,21 @@ to_clear = false; if (rand_seed_ == 0) { // Initialize the random seed (likely) once. +#if defined(_WIN32) + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + ULARGE_INTEGER uli; + uli.LowPart = ft.dwLowDateTime; + uli.HighPart = ft.dwHighDateTime; + rand_seed_ = static_cast<uint64_t>(uli.QuadPart / 10); +#else struct timeval tv = {}; constexpr size_t kUsecInSec = 1000000; // There is a chance that `gettimeofday()` triggers `Capture()` // recursively, but this should be fine as we unset `to_clear` before. gettimeofday(&tv, nullptr); rand_seed_ = tv.tv_sec * kUsecInSec + tv.tv_usec; +#endif } } if (size > kNumBytesPerValue) size = kNumBytesPerValue;
diff --git a/centipede/runner_dl_info.cc b/centipede/runner_dl_info.cc index 95e2286..ba8c3e3 100644 --- a/centipede/runner_dl_info.cc +++ b/centipede/runner_dl_info.cc
@@ -17,11 +17,19 @@ #ifdef __APPLE__ #include <dlfcn.h> #include <mach-o/dyld.h> +#elif defined(_WIN32) +#define WIN32_LEAN_AND_MEAN +#define NOGDI +#include <windows.h> +// clang-format off +// Must be after <windows.h> +#include <psapi.h> +// clang-format on #else // __APPLE__ #include <elf.h> #include <link.h> // dl_iterate_phdr -#endif // __APPLE__ #include <unistd.h> +#endif // __APPLE__ #include <cinttypes> #include <cstddef> @@ -184,6 +192,46 @@ }); } +#elif defined(_WIN32) + +namespace { + +DlInfo GetDlInfoFromModule(HMODULE hModule) { + DlInfo result; + result.Clear(); + if (hModule == nullptr) return result; + + MODULEINFO modInfo = {}; + if (GetModuleInformation(GetCurrentProcess(), hModule, &modInfo, + sizeof(modInfo))) { + result.start_address = reinterpret_cast<uintptr_t>(modInfo.lpBaseOfDll); + result.size = modInfo.SizeOfImage; + result.link_offset = result.start_address; + GetModuleFileNameA(hModule, result.path, sizeof(result.path)); + } + return result; +} + +} // namespace + +DlInfo GetDlInfo(const char* absl_nullable dl_path_suffix) { + HMODULE hModule = nullptr; + if (dl_path_suffix == nullptr) { + hModule = GetModuleHandleA(nullptr); + } else { + hModule = GetModuleHandleA(dl_path_suffix); + } + return GetDlInfoFromModule(hModule); +} + +DlInfo GetDlInfo(uintptr_t pc) { + HMODULE hModule = nullptr; + GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast<LPCSTR>(pc), &hModule); + return GetDlInfoFromModule(hModule); +} + #else // __APPLE__ namespace {
diff --git a/centipede/runner_fork_server.cc b/centipede/runner_fork_server.cc index 237f95f..ae45812 100644 --- a/centipede/runner_fork_server.cc +++ b/centipede/runner_fork_server.cc
@@ -51,6 +51,12 @@ // We try to avoid any high-level code here, even most of libc because this code // works too early in the process. E.g. getenv() will not work yet. +#if defined(_WIN32) +namespace fuzztest::internal { +void ForkServerCallMeVeryEarly() {} +} // namespace fuzztest::internal +#else + #include <fcntl.h> #ifdef __APPLE__ #include <sys/sysctl.h> @@ -422,3 +428,5 @@ #endif // __APPLE__ } // namespace fuzztest::internal + +#endif // !defined(_WIN32)
diff --git a/centipede/runner_interface.h b/centipede/runner_interface.h index 9b560bd..cdb0d1a 100644 --- a/centipede/runner_interface.h +++ b/centipede/runner_interface.h
@@ -45,16 +45,23 @@ const uint8_t* data1, size_t size1, const uint8_t* data2, size_t size2, uint8_t* out, size_t max_out_size, unsigned int seed); +#if !defined(_WIN32) +#define CENTIPEDE_WEAK_ATTRIBUTE __attribute__((weak)) +#else +#define CENTIPEDE_WEAK_ATTRIBUTE __attribute__((weak_import)) +#endif + // This is the header-less interface of libFuzzer, see // https://llvm.org/docs/LibFuzzer.html. extern "C" { int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size); -__attribute__((weak)) int LLVMFuzzerInitialize(int* absl_nonnull argc, - char*** absl_nonnull argv); -__attribute__((weak)) size_t LLVMFuzzerCustomMutator(uint8_t* data, size_t size, - size_t max_size, - unsigned int seed); -__attribute__((weak)) size_t LLVMFuzzerCustomCrossOver( +CENTIPEDE_WEAK_ATTRIBUTE int LLVMFuzzerInitialize(int* absl_nonnull argc, + char*** absl_nonnull argv); +CENTIPEDE_WEAK_ATTRIBUTE size_t LLVMFuzzerCustomMutator(uint8_t* data, + size_t size, + size_t max_size, + unsigned int seed); +CENTIPEDE_WEAK_ATTRIBUTE size_t LLVMFuzzerCustomCrossOver( const uint8_t* data1, size_t size1, const uint8_t* data2, size_t size2, uint8_t* out, size_t max_out_size, unsigned int seed); } // extern "C"
diff --git a/centipede/runner_utils.cc b/centipede/runner_utils.cc index 8010574..807cf0d 100644 --- a/centipede/runner_utils.cc +++ b/centipede/runner_utils.cc
@@ -14,8 +14,17 @@ #include "./centipede/runner_utils.h" +#if !defined(_WIN32) #include <pthread.h> #include <unistd.h> +#else +#define WIN32_LEAN_AND_MEAN +#define NOGDI +#include <io.h> +#include <process.h> +#include <windows.h> +#include <winsock2.h> +#endif #include <cerrno> #include <cstdint> @@ -39,7 +48,13 @@ } uintptr_t GetCurrentThreadStackRegionLow() { -#ifdef __APPLE__ +#if defined(_WIN32) + MEMORY_BASIC_INFORMATION mbi = {}; + if (VirtualQuery(&mbi, &mbi, sizeof(mbi)) != 0) { + return reinterpret_cast<uintptr_t>(mbi.AllocationBase); + } + return 0; +#elif defined(__APPLE__) pthread_t self = pthread_self(); const auto stack_addr = reinterpret_cast<uintptr_t>(pthread_get_stackaddr_np(self)); @@ -63,34 +78,54 @@ RunnerCheck(stack_region_low != 0, "the current thread stack region starts from 0 - unexpected!"); return stack_region_low; -#endif // __APPLE__ +#endif } -bool ReadAll(int fd, char* data, size_t size) { +bool ReadAll(intptr_t fd, char* data, size_t size) { while (size > 0) { +#if defined(_WIN32) + int r = recv(static_cast<SOCKET>(fd), data, static_cast<int>(size), 0); + if (r <= 0 && WSAGetLastError() != 0) { + r = _read(static_cast<int>(fd), data, static_cast<unsigned int>(size)); + } +#else ssize_t r = read(fd, data, size); +#endif if (r > 0) { - // read() guarantees r <= size data += r; size -= r; continue; } +#if defined(_WIN32) + if (r == -1 && (WSAGetLastError() == WSAEINTR || errno == EINTR)) continue; +#else if (r == -1 && errno == EINTR) continue; +#endif return false; } return true; } -bool WriteAll(int fd, const char* data, size_t size) { +bool WriteAll(intptr_t fd, const char* data, size_t size) { while (size > 0) { +#if defined(_WIN32) + int r = send(static_cast<SOCKET>(fd), data, static_cast<int>(size), 0); + if (r <= 0 && WSAGetLastError() != 0) { + r = _write(static_cast<int>(fd), data, static_cast<unsigned int>(size)); + } +#else ssize_t r = write(fd, data, size); +#endif if (r > 0) { - // write() guarantees r <= size data += r; size -= r; continue; } +#if defined(_WIN32) + if (r == -1 && (WSAGetLastError() == WSAEINTR || errno == EINTR)) continue; +#else if (r == -1 && errno == EINTR) continue; +#endif return false; } return true;
diff --git a/centipede/runner_utils.h b/centipede/runner_utils.h index 1722bfa..5327667 100644 --- a/centipede/runner_utils.h +++ b/centipede/runner_utils.h
@@ -64,12 +64,12 @@ // Reads `size` bytes to `data` from `fd` with retires (assuming `fd` is // blocking so there is no busy-spinning). Returns true if all bytes are // written, false otherwise due to errors. -bool ReadAll(int fd, char* data, size_t size); +bool ReadAll(intptr_t fd, char* data, size_t size); // Writes `size` bytes from `data` to `fd` with retires (assuming `fd` is // blocking so there is no busy-spinning). Returns true if all bytes are // written, false otherwise due to errors. -bool WriteAll(int fd, const char* data, size_t size); +bool WriteAll(intptr_t fd, const char* data, size_t size); // Leak sanitizer interface functions. extern "C" void __lsan_register_root_region(const void* p, size_t size);
diff --git a/centipede/rusage_stats.cc b/centipede/rusage_stats.cc index c5bf850..af8582f 100644 --- a/centipede/rusage_stats.cc +++ b/centipede/rusage_stats.cc
@@ -14,14 +14,20 @@ #include "./centipede/rusage_stats.h" -#ifdef __APPLE__ +#if defined(_WIN32) +#define WIN32_LEAN_AND_MEAN +#define NOGDI +#include <process.h> +#include <windows.h> +#elif defined(__APPLE__) #include <libproc.h> #include <sys/proc.h> #include <sys/sysctl.h> -#endif // __APPLE__ +#else #include <sys/syscall.h> #include <sys/types.h> #include <unistd.h> +#endif #include <array> #include <cinttypes> @@ -52,28 +58,52 @@ //------------------------------------------------------------------------------ ProcessTimer::ProcessTimer() : start_time_{absl::Now()}, start_rusage_{} { +#if !defined(_WIN32) getrusage(RUSAGE_SELF, &start_rusage_); +#endif } void ProcessTimer::Get(double& user, double& sys, double& wall) const { +#if !defined(_WIN32) struct rusage curr_rusage = {}; getrusage(RUSAGE_SELF, &curr_rusage); - // clang-format off user = absl::ToDoubleSeconds( absl::DurationFromTimeval(curr_rusage.ru_utime) - absl::DurationFromTimeval(start_rusage_.ru_utime)); sys = absl::ToDoubleSeconds( absl::DurationFromTimeval(curr_rusage.ru_stime) - absl::DurationFromTimeval(start_rusage_.ru_stime)); +#else + user = 0; + sys = 0; +#endif wall = absl::ToDoubleSeconds(absl::Now() - start_time_); - // clang-format on } //------------------------------------------------------------------------------ // RUsageScope //------------------------------------------------------------------------------ -#ifdef __APPLE__ +#if defined(_WIN32) +class RUsageScope::PlatformInfo { + public: + enum ProcFile : size_t { + kSched = 0, + kStatm = 1, + kStatus = 2, + kNumDoNotUseDirectly = 3 + }; + PlatformInfo(pid_t pid) : pid_(pid) {} + pid_t pid() const { return pid_; } + const std::string& GetProcFilePath(ProcFile file) const { + static const std::string empty; + return empty; + } + + private: + pid_t pid_; +}; +#elif defined(__APPLE__) class RUsageScope::PlatformInfo { public: PlatformInfo(pid_t pid) : pid_(pid) {} @@ -112,7 +142,11 @@ #endif RUsageScope RUsageScope::ThisProcess() { // +#if defined(_WIN32) + return RUsageScope{static_cast<int>(GetCurrentProcessId())}; +#else return RUsageScope{getpid()}; +#endif } RUsageScope RUsageScope::Process(pid_t pid) { // @@ -517,7 +551,9 @@ 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 defined(_WIN32) + // Win32 stub +#elif defined(__APPLE__) if (scope.info().pid() != getpid()) return {}; struct proc_taskinfo pti = {}; FUZZTEST_CHECK(proc_pidinfo(scope.info().pid(), PROC_PIDTASKINFO, 0, &pti,
diff --git a/centipede/rusage_stats.h b/centipede/rusage_stats.h index d0d1cde..125033e 100644 --- a/centipede/rusage_stats.h +++ b/centipede/rusage_stats.h
@@ -21,7 +21,23 @@ #ifndef THIRD_PARTY_CENTIPEDE_RUSAGE_STATS_H_ #define THIRD_PARTY_CENTIPEDE_RUSAGE_STATS_H_ +#if defined(_WIN32) +#define WIN32_LEAN_AND_MEAN +#define NOGDI +#include <windows.h> +#include <winsock2.h> +using pid_t = int; +struct rusage { + struct timeval ru_utime; + struct timeval ru_stime; + size_t ru_maxrss; +}; +#ifndef RUSAGE_SELF +#define RUSAGE_SELF 0 +#endif +#else #include <sys/resource.h> +#endif #include <array> #include <cstddef>
diff --git a/centipede/sancov_callbacks.cc b/centipede/sancov_callbacks.cc index 3e5d0c5..5e6019c 100644 --- a/centipede/sancov_callbacks.cc +++ b/centipede/sancov_callbacks.cc
@@ -15,7 +15,13 @@ // Instrumentation callbacks for SanitizerCoverage (sancov). // https://clang.llvm.org/docs/SanitizerCoverage.html +#if !defined(_WIN32) #include <pthread.h> +#else +#define WIN32_LEAN_AND_MEAN +#define NOGDI +#include <windows.h> +#endif #include <cstddef> #include <cstdint> @@ -278,9 +284,11 @@ sp >= tls.stack_region_low && tls.stack_region_low > 0)) { tls.lowest_sp = sp; +#if !defined(_WIN32) if (fuzztest::internal::CheckStackLimit == nullptr) { return; } +#endif fuzztest::internal::CheckStackLimit(tls.top_frame_sp - sp, /*is_current_stack=*/true); } @@ -353,7 +361,6 @@ // __sanitizer_cov_trace_pc_guard() because // a) there is not use case for that currently and // b) it will slowdown the hot function. -static pthread_once_t main_object_lazy_init_once = PTHREAD_ONCE_INIT; static void MainObjectLazyInitOnceCallback() { sancov_state->main_object = fuzztest::internal::GetDlInfo( sancov_state->flag_helper.GetStringFlag(":dl_path_suffix=")); @@ -362,9 +369,22 @@ UpdatePcCounterSetSizeAligned(sancov_state->reverse_pc_table.NumPcs()); } +#if defined(_WIN32) +static INIT_ONCE main_object_lazy_init_once = INIT_ONCE_STATIC_INIT; +static BOOL CALLBACK MainObjectLazyInitOnceWin32(PINIT_ONCE, PVOID, PVOID*) { + MainObjectLazyInitOnceCallback(); + return TRUE; +} +__attribute__((noinline)) static void MainObjectLazyInit() { + InitOnceExecuteOnce(&main_object_lazy_init_once, MainObjectLazyInitOnceWin32, + nullptr, nullptr); +} +#else +static pthread_once_t main_object_lazy_init_once = PTHREAD_ONCE_INIT; __attribute__((noinline)) static void MainObjectLazyInit() { pthread_once(&main_object_lazy_init_once, MainObjectLazyInitOnceCallback); } +#endif // TODO(kcc): [impl] add proper testing for this callback. // TODO(kcc): make sure the pc_table in the engine understands the raw PCs.
diff --git a/centipede/sancov_interceptors.cc b/centipede/sancov_interceptors.cc index cf1e9ac..14ce957 100644 --- a/centipede/sancov_interceptors.cc +++ b/centipede/sancov_interceptors.cc
@@ -14,6 +14,93 @@ // Function interceptors for Centipede. +#if defined(_WIN32) +#include "./centipede/runner_utils.h" +#include "./centipede/sancov_state.h" + +namespace fuzztest::internal { +void SancovInterceptor() {} +} // namespace fuzztest::internal + +using fuzztest::internal::tls; + +extern "C" { +FUZZTEST_NO_SANITIZE +void __sanitizer_weak_hook_strcasecmp(void*, const char* s1, const char* s2, + int result) { + if (ABSL_PREDICT_FALSE(!tls.traced)) return; + if (s1 == nullptr || s2 == nullptr) return; + size_t len = 0; + while (s1[len] && s2[len]) ++len; + tls.TraceMemCmp(reinterpret_cast<uintptr_t>(__builtin_return_address(0)), + reinterpret_cast<const uint8_t*>(s1), + reinterpret_cast<const uint8_t*>(s2), len, result == 0); +} + +FUZZTEST_NO_SANITIZE +void __sanitizer_weak_hook_memcmp(void*, const void* s1, const void* s2, + size_t n, int result) { + if (ABSL_PREDICT_FALSE(!tls.traced)) return; + if (s1 == nullptr || s2 == nullptr) return; + tls.TraceMemCmp(reinterpret_cast<uintptr_t>(__builtin_return_address(0)), + reinterpret_cast<const uint8_t*>(s1), + reinterpret_cast<const uint8_t*>(s2), n, result == 0); +} + +FUZZTEST_NO_SANITIZE +void __sanitizer_weak_hook_strncmp(void*, const char* s1, const char* s2, + size_t n, int result) { + if (ABSL_PREDICT_FALSE(!tls.traced)) return; + if (s1 == nullptr || s2 == nullptr) return; + size_t len = 0; + while (len < n && s1[len] && s2[len]) ++len; + tls.TraceMemCmp(reinterpret_cast<uintptr_t>(__builtin_return_address(0)), + reinterpret_cast<const uint8_t*>(s1), + reinterpret_cast<const uint8_t*>(s2), len, result == 0); +} + +FUZZTEST_NO_SANITIZE +void __sanitizer_weak_hook_strcmp(void*, const char* s1, const char* s2, + int result) { + if (ABSL_PREDICT_FALSE(!tls.traced)) return; + if (s1 == nullptr || s2 == nullptr) return; + size_t len = 0; + while (s1[len] && s2[len]) ++len; + tls.TraceMemCmp(reinterpret_cast<uintptr_t>(__builtin_return_address(0)), + reinterpret_cast<const uint8_t*>(s1), + reinterpret_cast<const uint8_t*>(s2), len, result == 0); +} + +FUZZTEST_NO_SANITIZE +void __sanitizer_weak_hook_strncasecmp(void* caller_pc, const char* s1, + const char* s2, size_t n, int result) { + if (ABSL_PREDICT_FALSE(!tls.traced)) return; + if (s1 == nullptr || s2 == nullptr) return; + size_t len = 0; + while (len < n && s1[len] && s2[len]) ++len; + tls.TraceMemCmp(reinterpret_cast<uintptr_t>(__builtin_return_address(0)), + reinterpret_cast<const uint8_t*>(s1), + reinterpret_cast<const uint8_t*>(s2), len, result == 0); +} +} + +namespace { + +void NTAPI tls_callback(PVOID, DWORD, PVOID) { + // May be calling multiple times, but it's fine as `OnThreadStart` is + // idempotent. + tls.OnThreadStart(); +} + +} // namespace + +#pragma data_seg(".CRT$XLB") +// Must be globally visible. +PIMAGE_TLS_CALLBACK centipede_sancov_thread_callback = tls_callback; +#pragma data_seg() + +#else + #include <dlfcn.h> // for dlsym() #include <pthread.h> @@ -290,3 +377,5 @@ // Run the actual pthread_create. return REAL(pthread_create)(thread, attr, MyThreadStart, wrapped_args); } + +#endif // !defined(_WIN32)
diff --git a/centipede/sancov_state.cc b/centipede/sancov_state.cc index c5bbaf3..50cb4f1 100644 --- a/centipede/sancov_state.cc +++ b/centipede/sancov_state.cc
@@ -14,7 +14,9 @@ #include "./centipede/sancov_state.h" +#if !defined(_WIN32) #include <unistd.h> +#endif #include <cstdint> #include <cstdio> @@ -122,6 +124,7 @@ } void ThreadLocalSancovState::OnThreadStart() { + if (tls.started) return; termination_detector.EnsureAlive(); tls.started = true; // Always trace threads by default. Internal threads that do not want tracing @@ -146,6 +149,7 @@ } void ThreadLocalSancovState::OnThreadStop() { + if (!tls.started) return; tls.traced = false; LockGuard lock(sancov_state->tls_list_mu); const size_t sancov_lowest_sp = *tls.sancov_lowest_sp; @@ -469,9 +473,13 @@ } } const size_t sp_diff = tls.top_frame_sp - lowest_sp; +#if !defined(_WIN32) if (CheckStackLimit != nullptr) { CheckStackLimit(sp_diff, /*is_current_stack=*/false); } +#else + CheckStackLimit(sp_diff, /*is_current_stack=*/false); +#endif if (sancov_state->flags.callstack_level != 0) { feature_handler(feature_domains::kCallStack.ConvertToMe(sp_diff)); }
diff --git a/centipede/sancov_state.h b/centipede/sancov_state.h index bde30e1..baf34b8 100644 --- a/centipede/sancov_state.h +++ b/centipede/sancov_state.h
@@ -15,7 +15,13 @@ #ifndef FUZZTEST_CENTIPEDE_SANCOV_STATE_H_ #define FUZZTEST_CENTIPEDE_SANCOV_STATE_H_ +#if !defined(_WIN32) #include <pthread.h> +#else +#define WIN32_LEAN_AND_MEAN +#define NOGDI +#include <windows.h> +#endif #include <algorithm> #include <cstddef> @@ -49,14 +55,26 @@ // An arbitrarily large size. constexpr size_t kCmpFeatureSetSize = 1 << 18; -// Like std::lock_guard, but for pthread_mutex_t. +#if defined(_WIN32) +using MutexType = SRWLOCK; +#define MUTEX_INITIALIZER SRWLOCK_INIT +inline void MutexLock(MutexType* mu) { AcquireSRWLockExclusive(mu); } +inline void MutexUnlock(MutexType* mu) { ReleaseSRWLockExclusive(mu); } +#else +using MutexType = pthread_mutex_t; +#define MUTEX_INITIALIZER PTHREAD_MUTEX_INITIALIZER +inline void MutexLock(MutexType* mu) { pthread_mutex_lock(mu); } +inline void MutexUnlock(MutexType* mu) { pthread_mutex_unlock(mu); } +#endif + +// Like std::lock_guard, but for MutexType. class LockGuard { public: - explicit LockGuard(pthread_mutex_t &mu) : mu_(mu) { pthread_mutex_lock(&mu); } - ~LockGuard() { pthread_mutex_unlock(&mu_); } + explicit LockGuard(MutexType& mu) : mu_(mu) { MutexLock(&mu_); } + ~LockGuard() { MutexUnlock(&mu_); } private: - pthread_mutex_t &mu_; + MutexType& mu_; }; // Flags derived from CENTIPEDE_RUNNER_FLAGS. @@ -192,7 +210,7 @@ // Doubly linked list of detached TLSs. ThreadLocalSancovState *detached_tls_list; // Guards `tls_list` and `detached_tls_list`. - pthread_mutex_t tls_list_mu = PTHREAD_MUTEX_INITIALIZER; + MutexType tls_list_mu = MUTEX_INITIALIZER; // Iterates all TLS objects under tls_list_mu, except those with `ignore` set. // Calls `callback()` on every TLS. template <typename Callback> @@ -319,8 +337,12 @@ // Check for stack limit for `stack_usage`, with `is_current_stack` set if it // is for the current calling stack. +#if !defined(_WIN32) __attribute__((weak)) void CheckStackLimit(size_t stack_usage, bool is_current_stack); +#else +void CheckStackLimit(size_t stack_usage, bool is_current_stack); +#endif extern ExplicitLifetime<SancovState> sancov_state; extern __thread ThreadLocalSancovState tls;
diff --git a/centipede/seed_corpus_maker_lib.cc b/centipede/seed_corpus_maker_lib.cc index d9171c7..cd47839 100644 --- a/centipede/seed_corpus_maker_lib.cc +++ b/centipede/seed_corpus_maker_lib.cc
@@ -127,7 +127,7 @@ absl::flat_hash_set<std::string> current_corpus_shard_fnames; if (!source.shard_rel_glob.empty()) { std::vector<std::string> matched_fnames; - const std::string glob = fs::path{dir} / source.shard_rel_glob; + const std::string glob = (fs::path{dir} / source.shard_rel_glob).string(); const auto match_status = RemoteGlobMatch(glob, matched_fnames); if (!match_status.ok() && !absl::IsNotFound(match_status)) { FUZZTEST_LOG(ERROR) << "Got error when glob-matching in " << dir << ": " @@ -144,7 +144,8 @@ } if (!source.individual_input_rel_glob.empty()) { std::vector<std::string> matched_fnames; - const std::string glob = fs::path{dir} / source.individual_input_rel_glob; + const std::string glob = + (fs::path{dir} / source.individual_input_rel_glob).string(); const auto match_status = RemoteGlobMatch(glob, matched_fnames); if (!match_status.ok() && !absl::IsNotFound(match_status)) { FUZZTEST_LOG(ERROR) << "Got error when glob-matching in " << dir << ": " @@ -426,7 +427,7 @@ const std::string corpus_rel_fname = absl::StrReplaceAll(destination.shard_rel_glob, {{"*", shard_idx}}); const std::string corpus_fname = - fs::path{destination.dir_path} / corpus_rel_fname; + (fs::path{destination.dir_path} / corpus_rel_fname).string(); const auto work_dir = WorkDir::FromCorpusShardPath( // corpus_fname, coverage_binary_name, coverage_binary_hash);
diff --git a/centipede/shared_memory_blob_sequence.cc b/centipede/shared_memory_blob_sequence.cc index 3a24126..7026004 100644 --- a/centipede/shared_memory_blob_sequence.cc +++ b/centipede/shared_memory_blob_sequence.cc
@@ -14,15 +14,23 @@ #include "./centipede/shared_memory_blob_sequence.h" +#if defined(_WIN32) +#define WIN32_LEAN_AND_MEAN +#define NOGDI +#include <windows.h> +#else #include <fcntl.h> #include <stdlib.h> #include <string.h> #include <sys/mman.h> #include <sys/stat.h> #include <unistd.h> +#endif #include <cstdint> #include <cstdio> +#include <cstdlib> +#include <cstring> #include "absl/base/nullability.h" @@ -104,6 +112,59 @@ had_writes_after_reset_ = false; } +#if defined(_WIN32) + +SharedMemoryBlobSequence::SharedMemoryBlobSequence(const char* name, + size_t size, + bool use_posix_shmem) { + ErrorOnFailure(size < sizeof(Blob::size), "Size too small"); + size_ = size; + strncpy(path_, name, PATH_MAX - 1); + path_[PATH_MAX - 1] = '\0'; + path_is_owned_ = true; + HANDLE hMap = + CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, + static_cast<DWORD>(size_ >> 32), + static_cast<DWORD>(size_ & 0xFFFFFFFF), path_); + ErrorOnFailure(hMap == NULL, "CreateFileMappingA() failed"); + mapping_handle_ = hMap; + data_ = static_cast<uint8_t*>( + MapViewOfFile(hMap, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, size_)); + ErrorOnFailure(data_ == NULL, "MapViewOfFile() failed"); +} + +SharedMemoryBlobSequence::SharedMemoryBlobSequence(const char* path) { + strncpy(path_, path, PATH_MAX - 1); + path_[PATH_MAX - 1] = '\0'; + HANDLE hMap = OpenFileMappingA(FILE_MAP_READ | FILE_MAP_WRITE, FALSE, path_); + ErrorOnFailure(hMap == NULL, "OpenFileMappingA() failed"); + mapping_handle_ = hMap; + data_ = static_cast<uint8_t*>( + MapViewOfFile(hMap, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0)); + ErrorOnFailure(data_ == NULL, "MapViewOfFile() failed"); + MEMORY_BASIC_INFORMATION mbi = {}; + if (VirtualQuery(data_, &mbi, sizeof(mbi)) != 0) { + size_ = mbi.RegionSize; + } +} + +void SharedMemoryBlobSequence::MmapData() {} + +SharedMemoryBlobSequence::~SharedMemoryBlobSequence() { + if (data_ != nullptr) { + UnmapViewOfFile(data_); + } + if (mapping_handle_ != nullptr) { + CloseHandle(static_cast<HANDLE>(mapping_handle_)); + } +} + +void SharedMemoryBlobSequence::ReleaseSharedMemory() {} + +size_t SharedMemoryBlobSequence::NumBytesUsed() const { return size_; } + +#else // !_WIN32 + SharedMemoryBlobSequence::SharedMemoryBlobSequence(const char *name, size_t size, bool use_posix_shmem) { @@ -185,4 +246,6 @@ return statbuf.st_blocks * S_BLKSIZE; } +#endif // _WIN32 + } // namespace fuzztest::internal
diff --git a/centipede/shared_memory_blob_sequence.h b/centipede/shared_memory_blob_sequence.h index fc69c10..8d91119 100644 --- a/centipede/shared_memory_blob_sequence.h +++ b/centipede/shared_memory_blob_sequence.h
@@ -175,6 +175,10 @@ const char *absl_nonnull path() const { return path_; } private: +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif + // mmaps `size_` bytes from `fd_`, assigns to `data_`. Crashes if mmap failed. void MmapData(); @@ -182,6 +186,9 @@ // passed in. char path_[PATH_MAX] = {0}; int fd_ = -1; // file descriptor used to mmap the shared memory region. +#if defined(_WIN32) + void* mapping_handle_ = nullptr; +#endif // Whether the file pointed to by path_ is owned by this and needs to be // deallocated on destruction. bool path_is_owned_ = false;
diff --git a/centipede/shared_memory_blob_sequence_test.cc b/centipede/shared_memory_blob_sequence_test.cc index 2b9f557..4e8210f 100644 --- a/centipede/shared_memory_blob_sequence_test.cc +++ b/centipede/shared_memory_blob_sequence_test.cc
@@ -14,7 +14,14 @@ #include "./centipede/shared_memory_blob_sequence.h" +#if !defined(_WIN32) #include <unistd.h> +#else +#define WIN32_LEAN_AND_MEAN +#define NOGDI +#include <process.h> +#include <windows.h> +#endif #include <cstdint> #include <cstdlib> @@ -30,7 +37,12 @@ std::string ShmemName() { std::ostringstream oss; +#if defined(_WIN32) + oss << "/shm_test-" << GetCurrentProcessId() << "-" + << std::this_thread::get_id(); +#else oss << "/shm_test-" << getpid() << "-" << std::this_thread::get_id(); +#endif return oss.str(); } @@ -237,8 +249,8 @@ EXPECT_FALSE(blob2.IsValid()); } -// MacOS does not support releasing the shm memory. -#ifndef __APPLE__ +// MacOS and Windows do not support releasing the shm memory. +#if !defined(__APPLE__) && !defined(_WIN32) // Test ReleaseSharedMemory and NumBytesUsed. TEST_P(SharedMemoryBlobSequenceTest, ReleaseSharedMemory) { // Allocate a blob sequence with 1M bytes of storage.
diff --git a/centipede/symbol_table.cc b/centipede/symbol_table.cc index e693b48..f347775 100644 --- a/centipede/symbol_table.cc +++ b/centipede/symbol_table.cc
@@ -99,7 +99,8 @@ std::string_view tmp_dir_path) { static std::atomic_size_t unique_id = 0; size_t unique_id_value = unique_id.fetch_add(1); - const std::string dso_basename = std::filesystem::path{dso_path}.filename(); + const std::string dso_basename = + std::filesystem::path{dso_path}.filename().string(); const ScopedFile pcs_file{ tmp_dir_path, absl::StrCat(dso_basename, ".pcs.", unique_id_value)}; const auto symbols_file_prefix = std::filesystem::path{tmp_dir_path} / @@ -123,7 +124,7 @@ "<", std::string(pcs_file.path()), }; - cmd_options.stdout_file_prefix = symbols_file_prefix; + cmd_options.stdout_file_prefix = symbols_file_prefix.string(); Command cmd{symbolizer_path, std::move(cmd_options)}; int exit_code = cmd.Execute(); if (exit_code != EXIT_SUCCESS) { @@ -172,7 +173,7 @@ { // Symbolization is quite IO-bound so we arbitrarily run 30 at once // even if we have few CPUs. - const size_t num_threads = std::min(dso_table.size(), 30UL); + const size_t num_threads = std::min(dso_table.size(), size_t{30}); fuzztest::internal::ThreadPool thread_pool(num_threads); for (size_t dso_id = 0; dso_id < dso_table.size(); ++dso_id) { const auto &dso_info = dso_table[dso_id];
diff --git a/centipede/testing/test_fuzz_target.cc b/centipede/testing/test_fuzz_target.cc index 37ad23d..6b5edaf 100644 --- a/centipede/testing/test_fuzz_target.cc +++ b/centipede/testing/test_fuzz_target.cc
@@ -20,7 +20,12 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#if !defined(_WIN32) #include <unistd.h> +#else +#include <windows.h> +inline void sleep(unsigned int seconds) { Sleep(seconds * 1000); } +#endif #include <utility> @@ -112,6 +117,7 @@ CallThisRecursively(100); } +#if !defined(_WIN32) // Stack overflow that triggers an instrumented SIGABRT handler in the current // stack. if (size == 4 && data[0] == 's' && data[1] == 't' && data[2] == 'k' && @@ -169,6 +175,7 @@ } free(sigstk.ss_sp); } +#endif // Call SingleEdgeFunc() if input is "func1". if (size == 5 && data[0] == 'f' && data[1] == 'u' && data[2] == 'n' &&
diff --git a/centipede/util.cc b/centipede/util.cc index ab2eb19..dd0153f 100644 --- a/centipede/util.cc +++ b/centipede/util.cc
@@ -17,8 +17,43 @@ #include "./centipede/util.h" +#if defined(_WIN32) +#define WIN32_LEAN_AND_MEAN +#define NOGDI +#include <process.h> +#include <windows.h> +#include <winsock2.h> +extern "C" { +void win_atomic_store(size_t size, void* ptr, void* val, + int model) __asm__("__atomic_store"); +void win_atomic_store(size_t size, void* ptr, void* val, int model) { + static CRITICAL_SECTION cs; + [[maybe_unused]] static bool init = []() { + InitializeCriticalSection(&cs); + return true; + }(); + EnterCriticalSection(&cs); + std::memcpy(ptr, val, size); + LeaveCriticalSection(&cs); +} + +void win_atomic_load(size_t size, const void* ptr, void* val, + int model) __asm__("__atomic_load"); +void win_atomic_load(size_t size, const void* ptr, void* val, int model) { + static CRITICAL_SECTION cs; + [[maybe_unused]] static bool init = []() { + InitializeCriticalSection(&cs); + return true; + }(); + EnterCriticalSection(&cs); + std::memcpy(val, ptr, size); + LeaveCriticalSection(&cs); +} +} +#else #include <sys/mman.h> #include <unistd.h> +#endif #include <algorithm> #include <cctype> @@ -47,6 +82,7 @@ #include "absl/base/const_init.h" #include "absl/base/nullability.h" #include "absl/base/thread_annotations.h" +#include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/strings/str_replace.h" #include "absl/strings/str_split.h" @@ -61,10 +97,24 @@ namespace fuzztest::internal { +void EnsureWinsockInitialized() { +#if defined(_WIN32) + [[maybe_unused]] static bool init = []() { + WSADATA wsa_data; + return WSAStartup(MAKEWORD(2, 2), &wsa_data) == 0; + }(); +#endif +} + size_t GetRandomSeed(size_t seed) { if (seed != 0) return seed; +#if defined(_WIN32) + return time(nullptr) + GetCurrentProcessId() + + std::hash<std::thread::id>{}(std::this_thread::get_id()); +#else return time(nullptr) + getpid() + std::hash<std::thread::id>{}(std::this_thread::get_id()); +#endif } std::string AsPrintableString(ByteSpan data, size_t max_len) { @@ -83,7 +133,7 @@ template <typename Container> void ReadFromLocalFile(std::string_view file_path, Container &data) { - std::ifstream f(std::string{file_path}); + std::ifstream f(std::string{file_path}, std::ios::in | std::ios::binary); if (!f) return; f.seekg(0, std::ios_base::end); auto size = f.tellg(); @@ -112,12 +162,13 @@ } void ClearLocalFileContents(std::string_view file_path) { - std::ofstream f(std::string{file_path}, std::ios::out | std::ios::trunc); + std::ofstream f(std::string{file_path}, + std::ios::out | std::ios::trunc | std::ios::binary); FUZZTEST_CHECK(f) << "Failed to clear the file: " << file_path; } void WriteToLocalFile(std::string_view file_path, ByteSpan data) { - std::ofstream f(std::string{file_path}); + std::ofstream f(std::string{file_path}, std::ios::out | std::ios::binary); FUZZTEST_CHECK(f) << "Failed to open local file: " << file_path; f.write(reinterpret_cast<const char *>(data.data()), static_cast<int64_t>(data.size())); @@ -136,13 +187,15 @@ void WriteToLocalHashedFileInDir(std::string_view dir_path, ByteSpan data) { if (dir_path.empty()) return; - std::string file_path = std::filesystem::path(dir_path).append(Hash(data)); + std::string file_path = + std::filesystem::path(dir_path).append(Hash(data)).string(); WriteToLocalFile(file_path, data); } void WriteToRemoteHashedFileInDir(std::string_view dir_path, ByteSpan data) { if (dir_path.empty()) return; - std::string file_path = std::filesystem::path(dir_path).append(Hash(data)); + std::string file_path = + std::filesystem::path(dir_path).append(Hash(data)).string(); FUZZTEST_CHECK_OK( RemoteFileSetContents(file_path, std::string(data.begin(), data.end()))); } @@ -155,17 +208,25 @@ } std::string ProcessAndThreadUniqueID(std::string_view prefix) { - // operator << is the only way to serialize std::this_thread::get_id(). - std::ostringstream oss; - oss << prefix << getpid() << "-" << std::this_thread::get_id(); - return oss.str(); + static std::atomic<size_t> counter{0}; + std::string res; +#if defined(_WIN32) + res = absl::StrCat(prefix, GetCurrentProcessId(), "-", GetCurrentThreadId(), + "-", counter.fetch_add(1)); +#else + res = absl::StrCat(prefix, getpid(), "-", counter.fetch_add(1)); +#endif + return res; } std::string TemporaryLocalDirPath() { const char *TMPDIR = getenv("TMPDIR"); + if (!TMPDIR) TMPDIR = getenv("TEMP"); + if (!TMPDIR) TMPDIR = getenv("TMP"); std::string tmp = TMPDIR ? TMPDIR : "/tmp"; - return std::filesystem::path(tmp).append( - ProcessAndThreadUniqueID("centipede-")); + return std::filesystem::path(tmp) + .append(ProcessAndThreadUniqueID("centipede-")) + .string(); } // We need to maintain a global set of dirs that CreateLocalDirRemovedAtExit() @@ -189,13 +250,15 @@ void CreateLocalDirRemovedAtExit(std::string_view path) { // Safeguard against removing dirs not created by TemporaryLocalDirPath(). - FUZZTEST_CHECK_NE(path.find("/centipede-"), std::string::npos); + FUZZTEST_CHECK(path.find("/centipede-") != std::string::npos || + path.find("\\centipede-") != std::string::npos); // Create the dir. std::error_code error; - std::filesystem::remove_all(path, error); - FUZZTEST_LOG_IF(ERROR, error) - << "Unable to clean up existing dir " << path << ": " << error.message(); - std::filesystem::create_directories(path); + std::filesystem::path p(path); + if (std::filesystem::exists(p, error)) { + std::filesystem::remove_all(p, error); + } + std::filesystem::create_directories(p, error); // Add to dirs_to_delete_at_exit. absl::MutexLock lock(dirs_to_delete_at_exit_mutex); if (!dirs_to_delete_at_exit) { @@ -206,7 +269,7 @@ } ScopedFile::ScopedFile(std::string_view dir_path, std::string_view name) - : my_path_(std::filesystem::path(dir_path) / name) {} + : my_path_((std::filesystem::path(dir_path) / name).string()) {} ScopedFile::~ScopedFile() { std::error_code error; @@ -359,16 +422,53 @@ return res; } +#if defined(_WIN32) +static LONG CALLBACK +AutoCommitPageFaultHandler(PEXCEPTION_POINTERS ExceptionInfo) { + if (ExceptionInfo->ExceptionRecord->ExceptionCode == + EXCEPTION_ACCESS_VIOLATION) { + ULONG_PTR fault_addr = + ExceptionInfo->ExceptionRecord->ExceptionInformation[1]; + if (VirtualAlloc(reinterpret_cast<void*>(fault_addr), 1, MEM_COMMIT, + PAGE_READWRITE) != nullptr) { + return EXCEPTION_CONTINUE_EXECUTION; + } + } + return EXCEPTION_CONTINUE_SEARCH; +} +#endif + uint8_t *MmapNoReserve(size_t size) { +#if defined(_WIN32) + static bool installed_veh = []() { + AddVectoredExceptionHandler(1, AutoCommitPageFaultHandler); + return true; + }(); + (void)installed_veh; + auto result = + VirtualAlloc(nullptr, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + if (result == nullptr) { + result = VirtualAlloc(nullptr, size, MEM_RESERVE, PAGE_READWRITE); + } + FUZZTEST_CHECK(result != nullptr) + << "VirtualAlloc failed for size " << size << " err=" << GetLastError(); + return reinterpret_cast<uint8_t*>(result); +#else auto result = mmap(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0); FUZZTEST_CHECK(result != MAP_FAILED); return reinterpret_cast<uint8_t *>(result); +#endif } void Munmap(uint8_t *ptr, size_t size) { +#if defined(_WIN32) + BOOL result = VirtualFree(ptr, 0, MEM_RELEASE); + FUZZTEST_CHECK(result != 0); +#else auto result = munmap(ptr, size); FUZZTEST_CHECK_EQ(result, 0); +#endif } int PollTimeoutMs(absl::Duration timeout) {
diff --git a/centipede/util.h b/centipede/util.h index 4905c68..0cf8e12 100644 --- a/centipede/util.h +++ b/centipede/util.h
@@ -31,6 +31,9 @@ namespace fuzztest::internal { +// Ensures WSAStartup is called on Windows before any socket operations. +void EnsureWinsockInitialized(); + // Returns the hash of the contents of the file `file_path`. Supports the file // being remote. Returns an empty string if the `file_path` is empty. std::string HashOfFileContents(std::string_view file_path);
diff --git a/centipede/workdir.cc b/centipede/workdir.cc index c0f4090..676f8b6 100644 --- a/centipede/workdir.cc +++ b/centipede/workdir.cc
@@ -58,7 +58,7 @@ WorkDir::PathShards::PathShards(std::string_view base_dir, std::string_view rel_prefix, size_t my_shard_index) - : prefix_{std::filesystem::path(base_dir) / rel_prefix}, + : prefix_{(std::filesystem::path(base_dir) / rel_prefix).string()}, my_shard_index_{my_shard_index} {} std::string WorkDir::PathShards::Shard(size_t shard_index) const { @@ -97,12 +97,12 @@ std::string_view binary_name, // std::string_view binary_hash) { const std::filesystem::path path{corpus_shard_path}; - const std::string dir = path.parent_path(); - const std::string stem = path.stem(); + const std::string dir = path.parent_path().string(); + const std::string stem = path.stem().string(); FUZZTEST_CHECK(stem == kCorpusShardStem || absl::StartsWith(stem, kDistilledCorpusShardStemPrefix)) << VV(corpus_shard_path); - const std::string dot_ext = path.extension(); + const std::string dot_ext = path.extension().string(); FUZZTEST_CHECK(!dot_ext.empty() && dot_ext[0] == '.') << VV(corpus_shard_path); const std::string ext = dot_ext.substr(1); @@ -142,8 +142,9 @@ } std::string WorkDir::CoverageDirPath() const { - return std::filesystem::path(workdir_) / - absl::StrCat(binary_name_, "-", binary_hash_); + return (std::filesystem::path(workdir_) / + absl::StrCat(binary_name_, "-", binary_hash_)) + .string(); } WorkDir::PathShards WorkDir::CrashReproducerDirPaths() const { @@ -155,11 +156,11 @@ } std::string WorkDir::BinaryInfoDirPath() const { - return std::filesystem::path(CoverageDirPath()) / "binary-info"; + return (std::filesystem::path(CoverageDirPath()) / "binary-info").string(); } std::string WorkDir::DebugInfoDirPath() const { - return std::filesystem::path(workdir_) / "debug"; + return (std::filesystem::path(workdir_) / "debug").string(); } WorkDir::PathShards WorkDir::DistilledCorpusFilePaths() const { @@ -179,53 +180,60 @@ } std::string WorkDir::CoverageReportPath(std::string_view annotation) const { - return std::filesystem::path(workdir_) / - absl::StrFormat("coverage-report-%s.%0*d%s.txt", binary_name_, - kDigitsInShardIndex, my_shard_index_, - NormalizeAnnotation(annotation)); + return (std::filesystem::path(workdir_) / + absl::StrFormat("coverage-report-%s.%0*d%s.txt", binary_name_, + kDigitsInShardIndex, my_shard_index_, + NormalizeAnnotation(annotation))) + .string(); } std::string WorkDir::CorpusStatsPath(std::string_view annotation) const { - return std::filesystem::path(workdir_) / - absl::StrFormat("corpus-stats-%s.%0*d%s.json", binary_name_, - kDigitsInShardIndex, my_shard_index_, - NormalizeAnnotation(annotation)); + return (std::filesystem::path(workdir_) / + absl::StrFormat("corpus-stats-%s.%0*d%s.json", binary_name_, + kDigitsInShardIndex, my_shard_index_, + NormalizeAnnotation(annotation))) + .string(); } std::string WorkDir::FuzzingStatsPath(std::string_view annotation) const { - return std::filesystem::path(workdir_) / - absl::StrFormat("fuzzing-stats-%s.%0*d%s.csv", binary_name_, - kDigitsInShardIndex, my_shard_index_, - NormalizeAnnotation(annotation)); + return (std::filesystem::path(workdir_) / + absl::StrFormat("fuzzing-stats-%s.%0*d%s.csv", binary_name_, + kDigitsInShardIndex, my_shard_index_, + NormalizeAnnotation(annotation))) + .string(); } std::string WorkDir::SourceBasedCoverageRawProfilePath() const { // Pass %m to enable online merge mode: updates file in place instead of // replacing it %m is replaced by lprofGetLoadModuleSignature(void) which // should be consistent for a fixed binary - return std::filesystem::path(CoverageDirPath()) / - absl::StrFormat("clang_coverage.%0*d.%s.profraw", kDigitsInShardIndex, - my_shard_index_, "%m"); + return (std::filesystem::path(CoverageDirPath()) / + absl::StrFormat("clang_coverage.%0*d.%s.profraw", kDigitsInShardIndex, + my_shard_index_, "%m")) + .string(); } std::string WorkDir::SourceBasedCoverageIndexedProfilePath() const { - return std::filesystem::path(CoverageDirPath()) / - absl::StrFormat("clang_coverage.profdata"); + return (std::filesystem::path(CoverageDirPath()) / + absl::StrFormat("clang_coverage.profdata")) + .string(); } std::string WorkDir::SourceBasedCoverageReportPath( std::string_view annotation) const { - return std::filesystem::path(workdir_) / - absl::StrFormat("source-coverage-report-%s.%0*d%s", binary_name_, - kDigitsInShardIndex, my_shard_index_, - NormalizeAnnotation(annotation)); + return (std::filesystem::path(workdir_) / + absl::StrFormat("source-coverage-report-%s.%0*d%s", binary_name_, + kDigitsInShardIndex, my_shard_index_, + NormalizeAnnotation(annotation))) + .string(); } std::string WorkDir::RUsageReportPath(std::string_view annotation) const { - return std::filesystem::path(workdir_) / - (absl::StrFormat("rusage-report-%s.%0*d%s.txt", binary_name_, - kDigitsInShardIndex, my_shard_index_, - NormalizeAnnotation(annotation))); + return (std::filesystem::path(workdir_) / + (absl::StrFormat("rusage-report-%s.%0*d%s.txt", binary_name_, + kDigitsInShardIndex, my_shard_index_, + NormalizeAnnotation(annotation)))) + .string(); } std::vector<std::string> WorkDir::EnumerateRawCoverageProfiles() const { @@ -247,7 +255,7 @@ std::vector<std::string> raw_profiles; for (const auto &entry : dir_iter) { if (entry.is_regular_file() && entry.path().extension() == ".profraw") - raw_profiles.push_back(entry.path()); + raw_profiles.push_back(entry.path().string()); } return raw_profiles; }
diff --git a/common/remote_file_oss.cc b/common/remote_file_oss.cc index d5ccbff..dc3e3dd 100644 --- a/common/remote_file_oss.cc +++ b/common/remote_file_oss.cc
@@ -62,7 +62,13 @@ public: static absl::StatusOr<LocalRemoteFile *> Create(std::string path, std::string_view mode) { - FILE *file = std::fopen(path.c_str(), mode.data()); + std::string mode_str(mode); +#if defined(_WIN32) + if (mode_str.find('b') == std::string::npos) { + mode_str += "b"; + } +#endif + FILE* file = std::fopen(path.c_str(), mode_str.c_str()); if (file == nullptr) { return absl::UnknownError(absl::StrCat( "fopen() failed, path: ", path, ", errno: ", std::strerror(errno))); @@ -344,7 +350,7 @@ } absl::StatusOr<int64_t> RemoteFileGetSize(std::string_view path) { - FILE *f = std::fopen(path.data(), "r"); + FILE* f = std::fopen(path.data(), "rb"); if (f == nullptr) { return absl::UnknownError( absl::StrCat("fopen() failed, path: ", std::string(path),
diff --git a/common/test_util.cc b/common/test_util.cc index 3232d55..fac7238 100644 --- a/common/test_util.cc +++ b/common/test_util.cc
@@ -18,6 +18,17 @@ #include <string_view> #include <system_error> // NOLINT +#if !defined(_WIN32) +#include <unistd.h> +#else +#include <process.h> +#include <windows.h> +inline static int getpid() { return static_cast<int>(GetCurrentProcessId()); } +inline static void setenv(const char* name, const char* value, int) { + _putenv_s(name, value); +} +#endif + #include "gtest/gtest.h" #include "absl/strings/str_cat.h" #include "./common/logging.h" @@ -35,11 +46,12 @@ FUZZTEST_CHECK(!error) << "Failed to create dir: " VV(dir) << error.message(); } - return std::filesystem::canonical(dir); + return std::filesystem::absolute(dir); } std::string GetTempFilePath(std::string_view subdir, size_t i) { - return GetTestTempDir(subdir) / absl::StrCat("tmp.", getpid(), ".", i); + return (GetTestTempDir(subdir) / absl::StrCat("tmp.", getpid(), ".", i)) + .string(); } std::filesystem::path GetTestRunfilesDir() {
diff --git a/common/test_util.h b/common/test_util.h index c859bff..e363f18 100644 --- a/common/test_util.h +++ b/common/test_util.h
@@ -79,7 +79,7 @@ const std::filesystem::path& path() const { return path_; } std::string GetFilePath(std::string_view file_name) const { - return path_ / file_name; + return (path_ / file_name).string(); } std::string CreateSubdir(std::string_view name) const {