No public description

PiperOrigin-RevId: 917997132
diff --git a/centipede/BUILD b/centipede/BUILD
index 518d7a6..169c28d 100644
--- a/centipede/BUILD
+++ b/centipede/BUILD
@@ -31,6 +31,11 @@
 
 licenses(["notice"])
 
+exports_files([
+    "centipede_main.cc",
+    "seed_corpus_maker.cc",
+])
+
 ################################################################################
 #                                  Binaries
 ################################################################################
@@ -38,6 +43,7 @@
 cc_binary(
     name = "centipede",
     srcs = ["centipede_main.cc"],
+    compatible_with = [],
     deps = [
         ":centipede_callbacks",
         ":centipede_default_callbacks",
@@ -46,18 +52,21 @@
         ":environment_flags",
         ":stop",
         "@abseil-cpp//absl/base:nullability",
+        "@abseil-cpp//absl/log:flags",
     ],
 )
 
 cc_uninstrumented_binary(
     name = "centipede_uninstrumented",
     binary = ":centipede",
+    compatible_with = [],
 )
 
 # A standalone seed corpus generator.
 cc_binary(
     name = "seed_corpus_maker",
     srcs = ["seed_corpus_maker.cc"],
+    compatible_with = [],
     deps = [
         ":config_init",
         ":seed_corpus_maker_flags",
@@ -73,6 +82,7 @@
 cc_binary(
     name = "blob_file_converter",
     srcs = ["blob_file_converter.cc"],
+    compatible_with = [],
     deps = [
         ":config_init",
         ":rusage_profiler",
diff --git a/centipede/seed_corpus_maker.cc b/centipede/seed_corpus_maker.cc
index 01830d8..2724988 100644
--- a/centipede/seed_corpus_maker.cc
+++ b/centipede/seed_corpus_maker.cc
@@ -26,7 +26,7 @@
 #include "./common/remote_file.h"
 
 int main(int argc, char** absl_nonnull argv) {
-  (void)fuzztest::internal::InitRuntime(argc, argv);
+  const auto runtime_state = fuzztest::internal::InitRuntime(argc, argv);
 
   const std::string config = absl::GetFlag(FLAGS_config);
   FUZZTEST_QCHECK(!config.empty());
diff --git a/common/BUILD b/common/BUILD
index d64c480..2c0e084 100644
--- a/common/BUILD
+++ b/common/BUILD
@@ -14,8 +14,8 @@
 
 # The package contains libraries that are common to both FuzzTest and Centipede.
 
-load("@rules_cc//cc:cc_library.bzl", "cc_library")
 load("@rules_cc//cc:cc_test.bzl", "cc_test")
+load("//third_party/googlefuzztest/build_defs:cc_library.bzl", "cc_library")
 
 DEFAULT_VISIBILITY = ["//visibility:public"]
 
@@ -99,6 +99,16 @@
 )
 
 cc_library(
+    name = "env_util",
+    srcs = ["env_util.cc"],
+    hdrs = ["env_util.h"],
+    deps = [
+        "@abseil-cpp//absl/strings:str_format",
+        "@abseil-cpp//absl/time",
+    ],
+)
+
+cc_library(
     name = "hash",
     srcs = ["hash.cc"],
     hdrs = ["hash.h"],
@@ -126,23 +136,21 @@
         "//conditions:default": [],
     }),
     deps = [
-               ":defs",
-               ":logging",
-               ":status_macros",
-               "@abseil-cpp//absl/base:nullability",
-               "@abseil-cpp//absl/status",
-               "@abseil-cpp//absl/status:statusor",
-               "@abseil-cpp//absl/strings",
-           ] + select({
-               "//conditions:default": [":remote_file_oss"],
-           }) +
-           select({
-               "@com_google_fuzztest//fuzztest:disable_riegeli": [],
-               "//conditions:default": [
-                   "@com_google_riegeli//riegeli/bytes:reader",
-                   "@com_google_riegeli//riegeli/bytes:writer",
-               ],
-           }),
+        ":defs",
+        ":logging",
+        ":remote_file_oss",
+        ":status_macros",
+        "@abseil-cpp//absl/base:nullability",
+        "@abseil-cpp//absl/status",
+        "@abseil-cpp//absl/status:statusor",
+        "@abseil-cpp//absl/strings",
+    ] + select({
+        "@com_google_fuzztest//fuzztest:disable_riegeli": [],
+        "//conditions:default": [
+            "@com_google_riegeli//riegeli/bytes:reader",
+            "@com_google_riegeli//riegeli/bytes:writer",
+        ],
+    }),
 )
 
 cc_library(
@@ -155,7 +163,6 @@
         "@com_google_fuzztest//fuzztest:disable_riegeli": ["CENTIPEDE_DISABLE_RIEGELI"],
         "//conditions:default": [],
     }),
-    visibility = ["//visibility:private"],
     deps = [
         ":defs",
         ":logging",
@@ -252,14 +259,9 @@
     ],
 )
 
-# TODO(b/324462306): Merge this with remote_file_test once the bug is fixed.
-cc_library(
-    name = "remote_file_test_lib",
-    testonly = True,
+cc_test(
+    name = "remote_file_test",
     srcs = ["remote_file_test.cc"],
-    defines = select({
-        "//conditions:default": [],
-    }),
     deps = [
         ":logging",
         ":remote_file",
@@ -267,16 +269,6 @@
         "@abseil-cpp//absl/status",
         "@abseil-cpp//absl/time",
         "@googletest//:gtest",
-    ] + select({
-        "//conditions:default": [],
-    }),
-    alwayslink = True,
-)
-
-cc_test(
-    name = "remote_file_test",
-    deps = [
-        ":remote_file_test_lib",
         "@googletest//:gtest_main",
     ],
 )
diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index f451674..aaf302e 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -84,6 +84,19 @@
 
 fuzztest_cc_library(
   NAME
+    env_util
+  HDRS
+    "env_util.h"
+  SRCS
+    "env_util.cc"
+  DEPS
+    absl::strings
+    absl::str_format
+    absl::time
+)
+
+fuzztest_cc_library(
+  NAME
     hash
   HDRS
     "hash.h"
diff --git a/common/env_util.cc b/common/env_util.cc
new file mode 100644
index 0000000..5445ee7
--- /dev/null
+++ b/common/env_util.cc
@@ -0,0 +1,41 @@
+// Copyright 2026 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.
+
+#include "./common/env_util.h"
+
+#include <cstdlib>
+#include <string>
+
+#include "absl/strings/str_format.h"
+#include "absl/time/time.h"
+
+namespace fuzztest::internal {
+
+absl::Duration GetDurationFromEnv(const char* env_var_name,
+                                  absl::Duration default_value) {
+  const char* env_val = std::getenv(env_var_name);
+  if (env_val == nullptr) return default_value;
+
+  absl::Duration duration;
+  if (absl::ParseDuration(env_val, &duration)) {
+    return duration;
+  }
+
+  absl::FPrintF(
+      stderr, "[!] Cannot parse env %s=%s as duration. Using %v as default.\n",
+      env_var_name, env_val, default_value);
+  return default_value;
+}
+
+}  // namespace fuzztest::internal
diff --git a/common/env_util.h b/common/env_util.h
new file mode 100644
index 0000000..f61110f
--- /dev/null
+++ b/common/env_util.h
@@ -0,0 +1,29 @@
+// Copyright 2026 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.
+
+#ifndef FUZZTEST_COMMON_ENV_UTIL_H_
+#define FUZZTEST_COMMON_ENV_UTIL_H_
+
+#include "absl/time/time.h"
+
+namespace fuzztest::internal {
+
+// Returns the duration parsed from the environment variable `env_var_name`.
+// If the variable is unset or cannot be parsed, returns `default_value`.
+absl::Duration GetDurationFromEnv(const char* env_var_name,
+                                  absl::Duration default_value);
+
+}  // namespace fuzztest::internal
+
+#endif  // FUZZTEST_COMMON_ENV_UTIL_H_
diff --git a/common/remote_file.h b/common/remote_file.h
index 0b70444..bde7fb9 100644
--- a/common/remote_file.h
+++ b/common/remote_file.h
@@ -135,8 +135,8 @@
 
 // Lists all files within `path`, recursively expanding subdirectories if
 // `recursively` is true. Does not return any directories. Returns an empty
-// vector if `path` is an empty directory, or `path` does not exist. Returns
-// `{path}` if `path` is a non-directory.
+// vector if `path` is an empty directory, `path` does not exist, or `path`
+// is a non-directory.
 absl::StatusOr<std::vector<std::string>> RemoteListFiles(std::string_view path,
                                                          bool recursively);
 
diff --git a/common/remote_file_oss.cc b/common/remote_file_oss.cc
index 10299f6..d69b3b8 100644
--- a/common/remote_file_oss.cc
+++ b/common/remote_file_oss.cc
@@ -223,6 +223,7 @@
 absl::StatusOr<std::vector<std::string>> RemoteListFiles(std::string_view path,
                                                          bool recursively) {
   if (!std::filesystem::exists(path)) return std::vector<std::string>();
+  if (!std::filesystem::is_directory(path)) return std::vector<std::string>();
   auto list_files = [](auto dir_iter) {
     std::vector<std::string> ret;
     for (const auto &entry : dir_iter) {
diff --git a/common/remote_file_test.cc b/common/remote_file_test.cc
index 82fd85b..250f223 100644
--- a/common/remote_file_test.cc
+++ b/common/remote_file_test.cc
@@ -114,18 +114,6 @@
   EXPECT_THAT(*files, IsEmpty());
 }
 
-TEST(RemoteListFiles, ReturnsASingleFileWhenListingAFile) {
-  const fs::path temp_dir = GetTestTempDir(test_info_->name());
-
-  const std::string file1_path = temp_dir / "file_01";
-  CreateFileOrDie(file1_path);
-
-  const absl::StatusOr<std::vector<std::string>> files =
-      RemoteListFiles(temp_dir.string(), /*recursively=*/false);
-  ASSERT_TRUE(files.status().ok());
-  EXPECT_THAT(*files, UnorderedElementsAre(file1_path));
-}
-
 TEST(RemoteListFiles, ReturnsAnEmptyVectorWhenPathDoesNotExist) {
   const absl::StatusOr<std::vector<std::string>> files =
       RemoteListFiles("/this/file/path/does/not/exist", /*recursively=*/false);
@@ -133,6 +121,16 @@
   EXPECT_THAT(*files, IsEmpty());
 }
 
+TEST(RemoteListFiles, ReturnsAnEmptyVectorWhenPathIsNonDirectory) {
+  const fs::path temp_dir = GetTestTempDir(test_info_->name());
+  const std::string file_path = temp_dir / "file";
+  CreateFileOrDie(file_path);
+  const absl::StatusOr<std::vector<std::string>> files =
+      RemoteListFiles(file_path, /*recursively=*/false);
+  ASSERT_TRUE(files.status().ok());
+  EXPECT_THAT(*files, IsEmpty());
+}
+
 TEST(RemotePathDelete, RecursivelyDeletesAllFilesAndSubdirectories) {
   const fs::path temp_dir = GetTestTempDir(test_info_->name());
   const fs::path a_b_c = temp_dir / "a" / "b" / "c";
diff --git a/e2e_tests/BUILD b/e2e_tests/BUILD
index fc0e23f..63621bc 100644
--- a/e2e_tests/BUILD
+++ b/e2e_tests/BUILD
@@ -63,6 +63,7 @@
     ],
     deps = [
         ":test_binary_util",
+        "@abseil-cpp//absl/base:config",
         "@abseil-cpp//absl/container:flat_hash_map",
         "@abseil-cpp//absl/strings",
         "@abseil-cpp//absl/strings:str_format",
@@ -147,6 +148,7 @@
     ],
     deps = [
         ":test_binary_util",
+        "@abseil-cpp//absl/base:config",
         "@abseil-cpp//absl/base:no_destructor",
         "@abseil-cpp//absl/container:flat_hash_map",
         "@abseil-cpp//absl/status",
diff --git a/e2e_tests/corpus_database_test.cc b/e2e_tests/corpus_database_test.cc
index 4ce64b9..69ca49e 100644
--- a/e2e_tests/corpus_database_test.cc
+++ b/e2e_tests/corpus_database_test.cc
@@ -20,6 +20,7 @@
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
+#include "absl/base/config.h"
 #include "absl/base/no_destructor.h"
 #include "absl/container/flat_hash_map.h"
 #include "absl/status/status.h"
@@ -56,7 +57,7 @@
 
 absl::StatusOr<std::string> FindFile(absl::string_view root_path,
                                      absl::string_view file_name) {
-  for (const std::string &path : ListDirectoryRecursively(root_path)) {
+  for (const std::string& path : ListDirectoryRecursively(root_path)) {
     if (std::filesystem::path(path).filename() == file_name) return path;
   }
   return absl::NotFoundError(absl::StrCat("File ", file_name, " not found."));
@@ -91,12 +92,17 @@
 
   static void RunUpdateCorpusDatabase() {
     if (run_map_->contains(GetParam())) return;
-    auto &run = (*run_map_)[GetParam()];
+    auto& run = (*run_map_)[GetParam()];
     run.workspace = std::make_unique<TempDir>();
     RunOptions run_options;
+    std::string fuzz_for = "30s";
+#if defined(ABSL_HAVE_ADDRESS_SANITIZER) || \
+    defined(ABSL_HAVE_MEMORY_SANITIZER) || defined(ABSL_HAVE_THREAD_SANITIZER)
+    fuzz_for = "90s";
+#endif
     run_options.fuzztest_flags = {
         {"corpus_database", GetCorpusDatabasePath()},
-        {"fuzz_for", "30s"},
+        {"fuzz_for", fuzz_for},
         {"jobs", "2"},
     };
     auto [status_unused, std_out_unused, std_err] = RunBinaryMaybeWithCentipede(
diff --git a/e2e_tests/functional_test.cc b/e2e_tests/functional_test.cc
index 6133473..76270b7 100644
--- a/e2e_tests/functional_test.cc
+++ b/e2e_tests/functional_test.cc
@@ -26,6 +26,7 @@
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
+#include "absl/base/config.h"
 #include "absl/container/flat_hash_map.h"
 #include "absl/strings/str_cat.h"
 #include "absl/strings/str_format.h"
@@ -58,6 +59,15 @@
 namespace fuzztest::internal {
 namespace {
 
+#if defined(ABSL_HAVE_ADDRESS_SANITIZER) || \
+    defined(ABSL_HAVE_MEMORY_SANITIZER) || defined(ABSL_HAVE_THREAD_SANITIZER)
+constexpr const char* kShortFuzzFor = "30s";
+constexpr absl::Duration kShortTimeout = absl::Seconds(90);
+#else
+constexpr const char* kShortFuzzFor = "10s";
+constexpr absl::Duration kShortTimeout = absl::Seconds(30);
+#endif
+
 using ::fuzztest::domain_implementor::PrintMode;
 using ::testing::_;
 using ::testing::AllOf;
@@ -139,7 +149,7 @@
     fuzzer_flags["print_subprocess_log"] = "true";
     fuzzer_flags["unguided"] = "true";
     if (!fuzzer_flags.contains("fuzz_for")) {
-      fuzzer_flags["fuzz_for"] = "10s";
+      fuzzer_flags["fuzz_for"] = "60s";
     }
     return RunWithExactFuzzerFlags(test_filter, target_binary, env,
                                    std::move(fuzzer_flags));
@@ -533,7 +543,9 @@
 }
 
 TEST_F(UnitTestModeTest, DetectsRecursiveStructureIfOptionalsSetByDefault) {
-  auto [status, std_out, std_err] = Run("MySuite.FailsIfCantInitializeProto");
+  auto [status, std_out, std_err] =
+      Run("MySuite.FailsIfCantInitializeProto", kDefaultTargetBinary,
+          /*env=*/{}, {{"fuzz_for", "60s"}});
   ExpectTargetAbort(status, std_err);
   EXPECT_THAT_LOG(std_err, HasSubstr("recursive fields"));
 }
@@ -828,9 +840,9 @@
 TEST_F(FuzzingModeCommandLineInterfaceTest,
        IgnoresNegativeFuzzingRunsLimitInEnvVar) {
   auto [status, std_out, std_err] =
-      RunWith({{"fuzz", "MySuite.PassesWithPositiveInput"}},
+      RunWith({{"fuzz", "MySuite.PassesWithPositiveInput"}, {"fuzz_for", "1s"}},
               {{"FUZZTEST_MAX_FUZZING_RUNS", "-1"}},
-              /*timeout=*/absl::Seconds(10));
+              /*timeout=*/absl::Seconds(60));
   EXPECT_THAT_LOG(std_err, HasSubstr("will not limit fuzzing runs"));
 }
 
@@ -953,7 +965,7 @@
   // Although theoretically possible, it is extreme unlikely that the test would
   // find the crash without saving some corpus.
   auto [producer_status, producer_std_out, producer_std_err] =
-      RunWith({{"fuzz", "MySuite.String"}, {"fuzz_for", "10s"}},
+      RunWith({{"fuzz", "MySuite.String"}, {"fuzz_for", "60s"}},
               {{"FUZZTEST_TESTSUITE_OUT_DIR", corpus_dir.path()}});
 
   auto corpus_files = ReadFileOrDirectory(corpus_dir.path().c_str());
@@ -972,7 +984,7 @@
   // Although theoretically possible, it is extreme unlikely that the test would
   // find the crash without saving some corpus.
   auto [producer_status, producer_std_out, producer_std_err] =
-      RunWith({{"fuzz", "MySuite.String"}, {"fuzz_for", "10s"}},
+      RunWith({{"fuzz", "MySuite.String"}, {"fuzz_for", "60s"}},
               {{"FUZZTEST_TESTSUITE_OUT_DIR", corpus_dir.path()}});
 
   auto corpus_files = ReadFileOrDirectory(corpus_dir.path().c_str());
@@ -1012,7 +1024,7 @@
   // Although theoretically possible, it is extreme unlikely that the test would
   // find the crash without saving some corpus.
   auto [producer_status, producer_std_out, producer_std_err] =
-      RunWith({{"fuzz", "MySuite.String"}, {"fuzz_for", "10s"}},
+      RunWith({{"fuzz", "MySuite.String"}, {"fuzz_for", "60s"}},
               {{"FUZZTEST_TESTSUITE_OUT_DIR", corpus_dir.path()}});
 
   auto corpus_files = ReadFileOrDirectory(corpus_dir.path().c_str());
@@ -1212,10 +1224,11 @@
 
 TEST_F(FuzzingModeCommandLineInterfaceTest,
        DoesNotPrintWarningForDisabledLimitFlagsByDefault) {
-  auto [status, std_out, std_err] = RunWith(
-      {{"fuzz", "MySuite.PassesWithPositiveInput"}, {"fuzz_for", "10s"}},
-      /*env=*/{},
-      /*timeout=*/absl::Seconds(20));
+  auto [status, std_out, std_err] =
+      RunWith({{"fuzz", "MySuite.PassesWithPositiveInput"},
+               {"fuzz_for", kShortFuzzFor}},
+              /*env=*/{},
+              /*timeout=*/absl::Seconds(80));
   EXPECT_THAT_LOG(std_err,
                   Not(HasSubstr("limit is specified but will be ignored")));
   EXPECT_THAT(status, Eq(ExitCode(0)));
@@ -1224,7 +1237,7 @@
 TEST_F(FuzzingModeCommandLineInterfaceTest, RssLimitFlagWorks) {
   auto [status, std_out, std_err] = RunWith(
       {{"fuzz", "MySuite.LargeHeapAllocation"}, {"rss_limit_mb", "1024"}},
-      /*env=*/{}, /*timeout=*/absl::Seconds(10));
+      /*env=*/{}, /*timeout=*/absl::Seconds(60));
   EXPECT_THAT_LOG(std_err, HasSubstr("argument 0: "));
   EXPECT_THAT_LOG(std_err, ContainsRegex(absl::StrCat("RSS limit exceeded")));
   ExpectTargetAbort(status, std_err);
@@ -1243,7 +1256,7 @@
 // to restrict the filter to only fuzz tests.
 TEST_F(FuzzingModeCommandLineInterfaceTest, RunsOnlyFuzzTests) {
   auto [status, std_out, std_err] =
-      RunWith({{"fuzz_for", "1s"}}, /*env=*/{}, /*timeout=*/absl::Seconds(10),
+      RunWith({{"fuzz_for", "1s"}}, /*env=*/{}, /*timeout=*/absl::Seconds(60),
               "testdata/unit_test_and_fuzz_tests");
 
   EXPECT_THAT_LOG(std_out,
@@ -1257,7 +1270,7 @@
 TEST_F(FuzzingModeCommandLineInterfaceTest,
        AllowsSpecifyingFilterWithFuzzForDuration) {
   auto [status, std_out, std_err] =
-      RunWith({{"fuzz_for", "1s"}}, /*env=*/{}, /*timeout=*/absl::Seconds(10),
+      RunWith({{"fuzz_for", "1s"}}, /*env=*/{}, /*timeout=*/absl::Seconds(60),
               "testdata/unit_test_and_fuzz_tests",
               {{GTEST_FLAG_PREFIX_ "filter",
                 "UnitTest.AlwaysPasses:FuzzTest.AlwaysPasses"}});
@@ -1278,7 +1291,7 @@
   // Although theoretically possible, it is extreme unlikely that the test would
   // find the crash without saving some corpus.
   auto [producer_status, producer_std_out, producer_std_err] =
-      RunWith({{"fuzz", "MySuite.SkipInputs"}, {"fuzz_for", "10s"}},
+      RunWith({{"fuzz", "MySuite.SkipInputs"}, {"fuzz_for", kShortFuzzFor}},
               {{"FUZZTEST_TESTSUITE_OUT_DIR", corpus_dir.path()}});
 
   ASSERT_THAT_LOG(producer_std_err, HasSubstr("Skipped input"));
@@ -1338,10 +1351,10 @@
 #endif
   const auto [status_unused, std_out, std_err] = RunWith(
       {
-          {"fuzz_for", "10s"},
+          {"fuzz_for", "30s"},
           {"fuzz", "FaultySetupTest.NoOp"},
       },
-      {}, absl::Seconds(10), kDefaultTargetBinary);
+      {}, absl::Seconds(60), kDefaultTargetBinary);
   EXPECT_THAT_LOG(std_out, HasSubstr("[  FAILED  ] FaultySetupTest.NoOp"))
       << "\nstd_err:\n"
       << std_err;
@@ -1356,11 +1369,11 @@
   const auto [status_unused, std_out, std_err] = RunWith(
       {
           {"corpus_database", corpus_database.path()},
-          {"fuzz_for", "10s"},
+          {"fuzz_for", "30s"},
           {"fuzz", "FaultySetupTest.NoOp"},
           {"execution_id", "some_execution_id"},
       },
-      {}, absl::Seconds(10), kDefaultTargetBinary);
+      {}, absl::Seconds(60), kDefaultTargetBinary);
   EXPECT_THAT_LOG(std_out, HasSubstr("[  FAILED  ] FaultySetupTest.NoOp"))
       << "\nstd_err:\n"
       << std_err;
@@ -1854,7 +1867,7 @@
 TEST_P(FuzzingModeCrashFindingTest, FlatMapPassesWhenCorrect) {
   auto [status, std_out, std_err] =
       Run("MySuite.FlatMapPassesWhenCorrect", kDefaultTargetBinary,
-          /*env=*/{}, /*timeout=*/absl::Seconds(10));
+          /*env=*/{}, /*timeout=*/absl::Seconds(60));
   EXPECT_THAT(status, Eq(ExitCode(0)));
 }
 
@@ -2075,7 +2088,7 @@
           /*env=*/
           {
           },
-          /*timeout=*/absl::Seconds(30));
+          /*timeout=*/absl::Seconds(120));
   EXPECT_THAT_LOG(std_err, HasSubstr("argument 0: \"ahmfn\""));
   ExpectTargetAbort(status, std_err);
 }
diff --git a/e2e_tests/testdata/BUILD b/e2e_tests/testdata/BUILD
index 0db3a80..77ee7a1 100644
--- a/e2e_tests/testdata/BUILD
+++ b/e2e_tests/testdata/BUILD
@@ -93,6 +93,7 @@
         "@abseil-cpp//absl/strings:str_format",
         "@abseil-cpp//absl/time",
         "@abseil-cpp//absl/types:span",
+        "@com_google_fuzztest//common:env_util",
         "@com_google_fuzztest//common:logging",
         "@com_google_fuzztest//fuzztest",
         "@com_google_fuzztest//fuzztest:flatbuffers",
diff --git a/e2e_tests/testdata/CMakeLists.txt b/e2e_tests/testdata/CMakeLists.txt
index ec93a80..b3ff52d 100644
--- a/e2e_tests/testdata/CMakeLists.txt
+++ b/e2e_tests/testdata/CMakeLists.txt
@@ -43,6 +43,7 @@
   fuzztest_flatbuffers
   fuzztest_googletest_fixture_adapter
   fuzztest_common_logging
+  fuzztest_env_util
   fuzztest_test_flatbuffers_headers
 )
 link_fuzztest(fuzz_tests_for_functional_testing.stripped)
diff --git a/e2e_tests/testdata/fuzz_tests_for_functional_testing.cc b/e2e_tests/testdata/fuzz_tests_for_functional_testing.cc
index 0a066d0..f577b37 100644
--- a/e2e_tests/testdata/fuzz_tests_for_functional_testing.cc
+++ b/e2e_tests/testdata/fuzz_tests_for_functional_testing.cc
@@ -38,6 +38,7 @@
 #include "absl/strings/string_view.h"
 #include "absl/time/clock.h"
 #include "absl/time/time.h"
+#include "./common/env_util.h"
 #include "./common/logging.h"
 #include "./fuzztest/internal/test_flatbuffers_generated.h"
 #include "./fuzztest/internal/test_protobuf.pb.h"
@@ -819,7 +820,11 @@
     static bool first_input = true;
     if (first_input) {
       first_input = false;
-      absl::SleepFor(absl::Seconds(2));
+      absl::Duration sleep_duration = fuzztest::internal::GetDurationFromEnv(
+          "FUZZTEST_FIRST_INPUT_SLEEP_DURATION", absl::Seconds(2));
+      if (sleep_duration > absl::ZeroDuration()) {
+        absl::SleepFor(sleep_duration);
+      }
     }
   }
 }
diff --git a/fuzztest/internal/BUILD b/fuzztest/internal/BUILD
index 2bf470f..6181c55 100644
--- a/fuzztest/internal/BUILD
+++ b/fuzztest/internal/BUILD
@@ -103,6 +103,7 @@
         "@com_google_fuzztest//centipede:stop",
         "@com_google_fuzztest//centipede:workdir",
         "@com_google_fuzztest//common:defs",
+        "@com_google_fuzztest//common:env_util",
         "@com_google_fuzztest//common:logging",
         "@com_google_fuzztest//common:remote_file",
         "@com_google_fuzztest//common:temp_dir",
@@ -264,6 +265,7 @@
         "@abseil-cpp//absl/functional:function_ref",
         "@abseil-cpp//absl/hash",
         "@abseil-cpp//absl/status",
+        "@abseil-cpp//absl/strings",
         "@abseil-cpp//absl/strings:str_format",
         "@abseil-cpp//absl/strings:string_view",
         "@abseil-cpp//absl/time",
@@ -272,9 +274,7 @@
         "@com_google_fuzztest//common:defs",
         "@com_google_fuzztest//common:logging",
         "@com_google_fuzztest//common:remote_file",
-    ] + select({
-        "//conditions:default": [],
-    }),
+    ],
 )
 
 cc_test(
diff --git a/fuzztest/internal/centipede_adaptor.cc b/fuzztest/internal/centipede_adaptor.cc
index 276a331..f2c904d 100644
--- a/fuzztest/internal/centipede_adaptor.cc
+++ b/fuzztest/internal/centipede_adaptor.cc
@@ -75,6 +75,7 @@
 #include "./centipede/stop.h"
 #include "./centipede/workdir.h"
 #include "./common/defs.h"
+#include "./common/env_util.h"
 #include "./common/logging.h"
 #include "./common/remote_file.h"
 #include "./common/temp_dir.h"
@@ -219,7 +220,8 @@
     const Configuration& configuration, absl::string_view workdir,
     absl::string_view test_name, RunMode run_mode) {
   fuzztest::internal::Environment env = CreateDefaultCentipedeEnvironment();
-  constexpr absl::Duration kUnitTestDefaultDuration = absl::Seconds(3);
+  absl::Duration unit_test_default_duration = GetDurationFromEnv(
+      "FUZZTEST_UNIT_TEST_DEFAULT_DURATION", absl::Seconds(3));
   env.fuzztest_multi_test_mode_soon_to_be_removed = false;
   if (configuration.time_limit_per_input < absl::InfiniteDuration()) {
     const int64_t time_limit_seconds =
@@ -261,7 +263,7 @@
   // duration as the special value.
   if (total_time_limit == absl::ZeroDuration() &&
       run_mode == RunMode::kUnitTest) {
-    total_time_limit = kUnitTestDefaultDuration;
+    total_time_limit = unit_test_default_duration;
   }
   {
     Configuration single_test_configuration = configuration;
diff --git a/fuzztest/internal/io.cc b/fuzztest/internal/io.cc
index d9e1ef2..5b173ca 100644
--- a/fuzztest/internal/io.cc
+++ b/fuzztest/internal/io.cc
@@ -14,13 +14,8 @@
 
 #include "./fuzztest/internal/io.h"
 
-#include <cerrno>
-#include <cstring>
-#include <filesystem>  // NOLINT
-#include <fstream>
 #include <memory>
 #include <optional>
-#include <sstream>
 #include <string>
 #include <string_view>
 #include <tuple>
@@ -30,6 +25,7 @@
 #include "absl/functional/function_ref.h"
 #include "absl/hash/hash.h"
 #include "absl/status/status.h"
+#include "absl/strings/str_cat.h"
 #include "absl/strings/str_format.h"
 #include "absl/strings/string_view.h"
 #include "absl/time/clock.h"
@@ -43,118 +39,62 @@
 
 namespace fuzztest::internal {
 
-#if defined(FUZZTEST_STUB_STD_FILESYSTEM)
-
-// TODO(lszekeres): Return absl::Status instead of bool for these.
+absl::string_view Dirname(absl::string_view filename) {
+  auto last_slash_pos = filename.find_last_of("/\\");
+  if (last_slash_pos == absl::string_view::npos) return "";
+  if (last_slash_pos == 0) return filename.substr(0, 1);
+  return filename.substr(0, last_slash_pos);
+}
 
 bool WriteFile(absl::string_view path, absl::string_view contents) {
-  FUZZTEST_LOG(FATAL) << "Filesystem API not supported in iOS/MacOS";
-}
-
-std::optional<std::string> ReadFile(absl::string_view path) {
-  FUZZTEST_LOG(FATAL) << "Filesystem API not supported in iOS/MacOS";
-}
-
-bool IsDirectory(absl::string_view path) {
-  FUZZTEST_LOG(FATAL) << "Filesystem API not supported in iOS/MacOS";
-}
-
-bool CreateDirectory(absl::string_view path) {
-  FUZZTEST_LOG(FATAL) << "Filesystem API not supported in iOS/MacOS";
-}
-
-std::vector<std::string> ListDirectory(absl::string_view path) {
-  FUZZTEST_LOG(FATAL) << "Filesystem API not supported in iOS/MacOS";
-}
-
-std::vector<std::string> ListDirectoryRecursively(absl::string_view path) {
-  FUZZTEST_LOG(FATAL) << "Filesystem API not supported in iOS/MacOS";
-}
-
-#else
-
-bool WriteFile(absl::string_view path, absl::string_view contents) {
-  const std::filesystem::path fs_path{
-      std::string_view{path.data(), path.size()}};
-
-  // Just in case the directory does not currently exist.
-  // If it does, this is a noop.
-  CreateDirectory(fs_path.parent_path().string());
-
-  std::ofstream file(fs_path);
-  file << contents;
-  file.close();
-  if (!file.good()) {
-    absl::FPrintF(GetStderr(), "[!] %s:%d: Error writing %s: (%d) %s\n",
-                  __FILE__, __LINE__, path, errno, strerror(errno));
+  auto dirname = Dirname(path);
+  if (!dirname.empty() && dirname != "/") {
+    if (!RemoteMkdir(dirname).ok()) {
+      absl::FPrintF(GetStderr(), "[!] %s:%d: Couldn't create directory: %s\n",
+                    __FILE__, __LINE__, dirname);
+      return false;
+    }
   }
-  return !file.fail();
+  auto status = RemoteFileSetContents(path, std::string(contents));
+  if (!status.ok()) {
+    absl::FPrintF(GetStderr(), "[!] %s:%d: Error writing %s: %s\n", __FILE__,
+                  __LINE__, path, status.message());
+  }
+  return status.ok();
 }
 
 std::optional<std::string> ReadFile(absl::string_view path) {
-  const std::filesystem::path fs_path{
-      std::string_view{path.data(), path.size()}};
-
-  std::ifstream stream(fs_path);
-  if (!stream.good()) {
-    // Using stderr instead of GetStderr() to avoid initialization-order-fiasco
-    // when reading files at static init time with
-    // `.WithSeeds(fuzztest::ReadFilesFromDirectory(...))`.
-    absl::FPrintF(stderr, "[!] %s:%d: Error reading %s: (%d) %s\n", __FILE__,
-                  __LINE__, path, errno, strerror(errno));
+  std::string contents;
+  auto status = RemoteFileGetContents(path, contents);
+  if (!status.ok()) {
+    absl::FPrintF(stderr, "[!] %s:%d: Error reading %s: %s\n", __FILE__,
+                  __LINE__, path, status.message());
     return std::nullopt;
   }
-  std::stringstream buffer;
-  buffer << stream.rdbuf();
-  return buffer.str();
+  return contents;
 }
 
-bool IsDirectory(absl::string_view path) {
-  const std::filesystem::path fs_path{
-      std::string_view{path.data(), path.size()}};
+bool IsDirectory(absl::string_view path) { return RemotePathIsDirectory(path); }
 
-  return std::filesystem::is_directory(fs_path);
-}
-
-bool CreateDirectory(absl::string_view path) {
-  const std::filesystem::path fs_path{
-      std::string_view{path.data(), path.size()}};
-
-  return std::filesystem::create_directories(fs_path);
-}
+bool CreateDirectory(absl::string_view path) { return RemoteMkdir(path).ok(); }
 
 std::vector<std::string> ListDirectory(absl::string_view path) {
-  std::vector<std::string> output_paths;
-
-  const std::filesystem::path fs_path{
-      std::string_view{path.data(), path.size()}};
-  if (!std::filesystem::is_directory(fs_path)) return output_paths;
-  for (const auto& entry : std::filesystem::directory_iterator(fs_path)) {
-    output_paths.push_back(entry.path().string());
-  }
-  return output_paths;
+  auto files = RemoteListFiles(path, /*recursively=*/false);
+  if (!files.ok()) return {};
+  return *files;
 }
 
 std::vector<std::string> ListDirectoryRecursively(absl::string_view path) {
-  std::vector<std::string> output_paths;
-
-  const std::filesystem::path fs_path{
-      std::string_view{path.data(), path.size()}};
-  for (const auto& entry : std::filesystem::recursive_directory_iterator(
-           fs_path,
-           std::filesystem::directory_options::follow_directory_symlink)) {
-    output_paths.push_back(entry.path().string());
-  }
-  return output_paths;
+  auto files = RemoteListFiles(path, /*recursively=*/true);
+  if (!files.ok()) return {};
+  return *files;
 }
 
-#endif  // FUZZTEST_STUB_STD_FILESYSTEM
-
 std::string WriteDataToDir(absl::string_view data, absl::string_view outdir) {
-  std::string filename(outdir);
-  if (filename.back() != '/') filename += '/';
-  absl::StrAppendFormat(&filename, "%016x",
-                        absl::Hash<absl::string_view>{}(data));
+  std::string basename =
+      absl::StrFormat("%016x", absl::Hash<absl::string_view>{}(data));
+  const bool need_slash = !outdir.empty() && outdir.back() != '/';
+  std::string filename = absl::StrCat(outdir, need_slash ? "/" : "", basename);
   if (!WriteFile(filename, data)) return "";
   return filename;
 }