Allow subprocess API to inherit the current process `environ`. This is to be used by the Centipede adaptor. PiperOrigin-RevId: 763133244
diff --git a/fuzztest/internal/subprocess.cc b/fuzztest/internal/subprocess.cc index a6951a8..a670bd7 100644 --- a/fuzztest/internal/subprocess.cc +++ b/fuzztest/internal/subprocess.cc
@@ -29,6 +29,7 @@ #endif // !defined(_MSC_VER) #include <future> +#include <optional> #include <string> #include <utility> #include <vector> @@ -42,6 +43,10 @@ #include "absl/types/span.h" #include "./fuzztest/internal/logging.h" +// Needed to pass the current environment to posix_spawn, which needs an +// explicit envp without an option to inherit implicitly. +extern char** environ; + namespace fuzztest::internal { #if !defined(_MSC_VER) && !(defined(__ANDROID_MIN_SDK_VERSION__) && \ @@ -66,7 +71,8 @@ absl::Span<const std::string> command_line, absl::FunctionRef<void(absl::string_view)> on_stdout_output, absl::FunctionRef<void(absl::string_view)> on_stderr_output, - const absl::flat_hash_map<std::string, std::string>& environment, + const std::optional<absl::flat_hash_map<std::string, std::string>>& + environment, absl::Duration timeout); private: @@ -77,13 +83,13 @@ void StartWatchdog(absl::Duration timeout); pid_t StartChild( absl::Span<const std::string> command_line, - const absl::flat_hash_map<std::string, std::string>& environment); + const std::optional<absl::flat_hash_map<std::string, std::string>>& + environment); void ReadChildOutput( absl::FunctionRef<void(absl::string_view)> on_stdout_output, absl::FunctionRef<void(absl::string_view)> on_stderr_output); - // Pipe file descriptors pairs. Index 0 is for stdout, index 1 is for - // stderr. + // Pipe file descriptors pairs. Index 0 is for stdout, index 1 is for stderr. static constexpr int kStdOutIdx = 0; static constexpr int kStdErrIdx = 1; int parent_pipe_[2]; @@ -157,7 +163,8 @@ // Do fork() and exec() in one step, using posix_spawnp(). pid_t SubProcess::StartChild( absl::Span<const std::string> command_line, - const absl::flat_hash_map<std::string, std::string>& environment) { + const std::optional<absl::flat_hash_map<std::string, std::string>>& + environment) { posix_spawn_file_actions_t actions = CreateChildFileActions(); // Create `argv` and `envp` parameters for exec(). @@ -168,18 +175,21 @@ } argv[argc] = nullptr; - size_t envc = environment.size(); - std::vector<char*> envp(envc + 1); - int i = 0; - for (const auto& [key, value] : environment) { - envp[i++] = strdup(absl::StrCat(key, "=", value).c_str()); + std::vector<char*> envp; + if (environment.has_value()) { + size_t envc = environment->size(); + envp.resize(envc + 1); + int i = 0; + for (const auto& [key, value] : *environment) { + envp[i++] = strdup(absl::StrCat(key, "=", value).c_str()); + } + envp[envc] = nullptr; } - envp[envc] = nullptr; pid_t child_pid; int err; err = posix_spawnp(&child_pid, argv[0], &actions, nullptr, argv.data(), - envp.data()); + environment.has_value() ? envp.data() : environ); FUZZTEST_INTERNAL_CHECK(err == 0, "Cannot spawn child process: ", strerror(err)); @@ -212,6 +222,7 @@ // Loop reading stdout and stderr from the child process. int fd_remain = fd_count; + char buf[4096]; while (fd_remain > 0) { int ret = poll(pfd, fd_count, -1); @@ -294,7 +305,8 @@ absl::Span<const std::string> command_line, absl::FunctionRef<void(absl::string_view)> on_stdout_output, absl::FunctionRef<void(absl::string_view)> on_stderr_output, - const absl::flat_hash_map<std::string, std::string>& environment, + const std::optional<absl::flat_hash_map<std::string, std::string>>& + environment, absl::Duration timeout) { CreatePipes(); pid_t child_pid = StartChild(command_line, environment); @@ -313,7 +325,8 @@ absl::Span<const std::string> command_line, absl::FunctionRef<void(absl::string_view)> on_stdout_output, absl::FunctionRef<void(absl::string_view)> on_stderr_output, - const absl::flat_hash_map<std::string, std::string>& environment, + const std::optional<absl::flat_hash_map<std::string, std::string>>& + environment, absl::Duration timeout) { #if defined(_MSC_VER) FUZZTEST_INTERNAL_CHECK(false, @@ -331,7 +344,8 @@ RunResults RunCommand( absl::Span<const std::string> command_line, - const absl::flat_hash_map<std::string, std::string>& environment, + const std::optional<absl::flat_hash_map<std::string, std::string>>& + environment, absl::Duration timeout) { std::string stdout; std::string stderr;
diff --git a/fuzztest/internal/subprocess.h b/fuzztest/internal/subprocess.h index 8d70020..142f308 100644 --- a/fuzztest/internal/subprocess.h +++ b/fuzztest/internal/subprocess.h
@@ -16,6 +16,7 @@ #define FUZZTEST_FUZZTEST_INTERNAL_SUBPROCESS_H_ #include <iostream> +#include <optional> #include <string> #include <variant> #include <vector> @@ -98,23 +99,28 @@ // Runs `command_line` in a subprocess and passes through its stdout/stderr to // `on_stdout_output` and `on_stderr_output` callbacks. Environment variables -// can be set via `environment`. If optional `timeout` is provided, the process -// is terminated after the given timeout. The timeout will be rounded up to -// seconds. +// can be set via `environment` if it is not std::nullopt, otherwise environment +// will be inherited from the current process (`environ`). If optional `timeout` +// is provided, the process is terminated after the given timeout. The timeout +// will be rounded up to seconds. TerminationStatus RunCommandWithOutputCallbacks( absl::Span<const std::string> command_line, absl::FunctionRef<void(absl::string_view)> on_stdout_output, absl::FunctionRef<void(absl::string_view)> on_stderr_output, - const absl::flat_hash_map<std::string, std::string>& environment = {}, + const std::optional<absl::flat_hash_map<std::string, std::string>>& + environment = {{}}, absl::Duration timeout = absl::InfiniteDuration()); // Runs `command_line` in a subprocess and returns the run results that captures -// the stdout/stderr as strings. Environment variables can be set via -// `environment`. If optional `timeout` is provided, the process is terminated -// after the given timeout. The timeout will be rounded up to seconds. +// the stdout/stderr as strings. Environment variables +// can be set via `environment` if it is not std::nullopt, otherwise environment +// will be inherited from the current process (`environ`). `environment`. If +// optional `timeout` is provided, the process is terminated after the given +// timeout. The timeout will be rounded up to seconds. RunResults RunCommand( absl::Span<const std::string> command_line, - const absl::flat_hash_map<std::string, std::string>& environment = {}, + const std::optional<absl::flat_hash_map<std::string, std::string>>& + environment = {{}}, absl::Duration timeout = absl::InfiniteDuration()); } // namespace fuzztest::internal
diff --git a/fuzztest/internal/subprocess_test.cc b/fuzztest/internal/subprocess_test.cc index 844c0bc..f5d10bd 100644 --- a/fuzztest/internal/subprocess_test.cc +++ b/fuzztest/internal/subprocess_test.cc
@@ -15,6 +15,7 @@ #include "./fuzztest/internal/subprocess.h" #include <csignal> +#include <optional> #include <sstream> #include <string> @@ -27,6 +28,7 @@ namespace { using ::testing::HasSubstr; +using ::testing::Not; template <typename T> std::string ToString(T v) { @@ -71,8 +73,27 @@ EXPECT_DEATH(RunCommand({"not-a-binary"}), "Cannot spawn child process"); } +TEST(SubProcessTest, EnvironmentIsNotInheritedByDefault) { + setenv("THING", "1", /*overwrite=*/1); + auto [status, std_out, std_err] = RunCommand({"env"}); + EXPECT_TRUE(status.Exited()); + EXPECT_EQ(status, ExitCode(0)); + EXPECT_THAT(std_out, Not(HasSubstr("THING=1"))); + EXPECT_EQ(std_err, ""); +} + +TEST(SubProcessTest, EnvironmentIsInheritedIfRequested) { + setenv("THING", "1", /*overwrite=*/1); + auto [status, std_out, std_err] = + RunCommand({"env"}, /*environment=*/std::nullopt); + EXPECT_TRUE(status.Exited()); + EXPECT_EQ(status, ExitCode(0)); + EXPECT_THAT(std_out, HasSubstr("THING=1")) << std_out; + EXPECT_EQ(std_err, ""); +} + TEST(SubProcessTest, PassedEnvironmentIsSet) { - auto [status, std_out, std_err] = RunCommand({"env"}, {{"THING", "42"}}); + auto [status, std_out, std_err] = RunCommand({"env"}, {{{"THING", "42"}}}); EXPECT_TRUE(status.Exited()); EXPECT_EQ(status, ExitCode(0)); EXPECT_EQ(std_out, "THING=42\n");