Index: lib/fuzzer/CMakeLists.txt =================================================================== --- lib/fuzzer/CMakeLists.txt +++ lib/fuzzer/CMakeLists.txt @@ -1,5 +1,6 @@ set(LIBFUZZER_SOURCES FuzzerClangCounters.cpp + FuzzerCommand.cpp FuzzerCrossOver.cpp FuzzerDriver.cpp FuzzerExtFunctionsDlsym.cpp Index: lib/fuzzer/FuzzerCommand.h =================================================================== --- /dev/null +++ lib/fuzzer/FuzzerCommand.h @@ -0,0 +1,77 @@ +//===- FuzzerCommand.h - Interface representing a process -------*- C++ -* ===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// FuzzerCommand represents a command to run in a subprocess. It allows callers +// to manage command line arguments and output and error streams. +//===----------------------------------------------------------------------===// + +#ifndef LLVM_FUZZER_COMMAND_H +#define LLVM_FUZZER_COMMAND_H + +#include "FuzzerDefs.h" +#include +#include + +namespace fuzzer { +class Command final { +public: + Command(); + explicit Command(const Vector &Args); + explicit Command(const Command &other); + Command &operator=(const Command &other); + ~Command(); + + // Returns a string representation of the command. On most systems this will + // be the equivalent command line. + std::string ToString() const; + + // Returns true if the given Arg is present in Args. Only checks up to + // "-ignore_remaining_args=1". + bool HasArgument(const std::string &Arg); + + // Adds the given argument before "-ignore_remaining_args=1", or at the end + // if that flag isn't present. + void AddArgument(const std::string &Arg); + + // Adds all given arguments before "-ignore_remaining_args=1", or at the end + // if that flag isn't present. + void AddArguments(const Vector &ArgsToAdd); + + // Like AddArgument, but adds "-[Flag]=[Value]". + void AddFlag(const std::string &Flag, const std::string &Value); + + // Removes the given argument from the command argument list. Ignores any + // occurrences after "-ignore_remaining_args=1", if present. + void RemoveArgument(const std::string &Arg); + + // Like RemoveArgument, but removes "-[Flag]=...". + void RemoveFlag(const std::string &Flag); + + // Configures the command to redirect its output to the name file. + void SetOutputFile(const std::string &FileName); + + // Redirects the command's stderr to its stdout. + void CombineOutAndErr(); + + // Invokes the command as appropriate for the given platform. + int Execute(); + +private: + // The command arguments. Args[0] is the command name. + Vector Args; + + // True indicates stderr is redirected to stdout. + bool CombinedOutAndErr; + + // If not empty, stdout is redirected to the named file. + std::string OutputFile; +}; + +} // namespace fuzzer + +#endif // LLVM_FUZZER_COMMAND_H Index: lib/fuzzer/FuzzerCommand.cpp =================================================================== --- /dev/null +++ lib/fuzzer/FuzzerCommand.cpp @@ -0,0 +1,113 @@ +//===- FuzzerCommand.cpp - Command implementation ---------------*- C++ -* ===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// FuzzerCommand represents a command to run in a subprocess. It allows callers +// to manage command line arguments and output and error streams. +//===----------------------------------------------------------------------===// + +#include "FuzzerCommand.h" +#include "FuzzerIO.h" +#include "FuzzerUtil.h" + +#include +#include +#include +#include + +namespace fuzzer { + +namespace { + +// This command line flag is used to indicate that the remaining command line is +// immutable, meaning this flag effectively marks the end of the mutable +// argument list. +const char *kIgnoreRemaining = "-ignore_remaining_args=1"; + +} // namespace + +Command::Command() : CombinedOutAndErr(false) {} + +Command::Command(const Vector &ArgsToAdd) + : Args(ArgsToAdd), CombinedOutAndErr(false) {} + +Command::Command(const Command &other) + : Args(other.Args), CombinedOutAndErr(other.CombinedOutAndErr), + OutputFile(other.OutputFile) {} + +Command &Command::operator=(const Command &other) { + Args = other.Args; + CombinedOutAndErr = other.CombinedOutAndErr; + OutputFile = other.OutputFile; + return *this; +} + +Command::~Command() {} + +std::string Command::ToString() const { + std::stringstream SS; + for (auto arg : Args) { + SS << arg << " "; + } + if (!OutputFile.empty()) { + SS << "> " << OutputFile << " "; + } + if (CombinedOutAndErr) { + SS << "2>&1 "; + } + std::string result = SS.str(); + if (!result.empty()) { + result = result.substr(0, result.length() - 1); + } + return result; +} + +bool Command::HasArgument(const std::string &Arg) { + auto i = std::find(Args.begin(), Args.end(), kIgnoreRemaining); + return std::find(Args.begin(), i, Arg) != i; +} + +void Command::AddArgument(const std::string &Arg) { + auto i = std::find(Args.begin(), Args.end(), kIgnoreRemaining); + Args.insert(i, Arg); +} + +void Command::AddArguments(const Vector &ArgsToAdd) { + auto i = std::find(Args.begin(), Args.end(), kIgnoreRemaining); + Args.insert(i, ArgsToAdd.begin(), ArgsToAdd.end()); +} + +void Command::AddFlag(const std::string &Flag, const std::string &Value) { + AddArgument("-" + Flag + "=" + Value); +} + +void Command::RemoveArgument(const std::string &Arg) { + auto i = std::find(Args.begin(), Args.end(), kIgnoreRemaining); + Args.erase(std::remove(Args.begin(), i, Arg), i); +} + +void Command::RemoveFlag(const std::string &Flag) { + std::string Arg("-" + Flag + "="); + auto i = std::find(Args.begin(), Args.end(), kIgnoreRemaining); + auto IsMatch = [&](const std::string &Other) { + return Arg.compare(0, std::string::npos, Other, 0, Arg.length()) == 0; + }; + Args.erase(std::remove_if(Args.begin(), i, IsMatch), i); +} + +void Command::SetOutputFile(const std::string &FileName) { + OutputFile = FileName; +} + +void Command::CombineOutAndErr() { CombinedOutAndErr = true; } + +int Command::Execute() { + // TODO(aarongreen): Replace this with platform-specific routines. + return ExecuteCommand(ToString()); +} + +} // namespace fuzzer Index: lib/fuzzer/FuzzerDriver.cpp =================================================================== --- lib/fuzzer/FuzzerDriver.cpp +++ lib/fuzzer/FuzzerDriver.cpp @@ -9,6 +9,7 @@ // FuzzerDriver and flag parsing. //===----------------------------------------------------------------------===// +#include "FuzzerCommand.h" #include "FuzzerCorpus.h" #include "FuzzerIO.h" #include "FuzzerInterface.h" @@ -206,16 +207,20 @@ } } -static void WorkerThread(const std::string &Cmd, std::atomic *Counter, +static void WorkerThread(const Command &BaseCmd, std::atomic *Counter, unsigned NumJobs, std::atomic *HasErrors) { while (true) { unsigned C = (*Counter)++; if (C >= NumJobs) break; std::string Log = "fuzz-" + std::to_string(C) + ".log"; - std::string ToRun = Cmd + " > " + Log + " 2>&1\n"; - if (Flags.verbosity) - Printf("%s", ToRun.c_str()); - int ExitCode = ExecuteCommand(ToRun); + Command Cmd(BaseCmd); + Cmd.SetOutputFile(Log); + Cmd.CombineOutAndErr(); + if (Flags.verbosity) { + std::string CommandLine = Cmd.ToString(); + Printf("%s", CommandLine.c_str()); + } + int ExitCode = Cmd.Execute(); if (ExitCode != 0) *HasErrors = true; std::lock_guard Lock(Mu); @@ -240,7 +245,9 @@ unsigned NumWorkers, unsigned NumJobs) { std::atomic Counter(0); std::atomic HasErrors(false); - std::string Cmd = CloneArgsWithoutX(Args, "jobs", "workers"); + Command Cmd(Args); + Cmd.RemoveFlag("jobs"); + Cmd.RemoveFlag("workers"); Vector V; std::thread Pulse(PulseThread); Pulse.detach(); @@ -303,20 +310,19 @@ } std::string InputFilePath = Inputs->at(0); std::string OutputFilePath = Flags.exact_artifact_path; - std::string BaseCmd = - CloneArgsWithoutX(Args, "cleanse_crash", "cleanse_crash"); + Command Cmd(Args); + Cmd.RemoveFlag("cleanse_crash"); - auto InputPos = BaseCmd.find(" " + InputFilePath + " "); - assert(InputPos != std::string::npos); - BaseCmd.erase(InputPos, InputFilePath.size() + 1); + assert(Cmd.HasArgument(InputFilePath)); + Cmd.RemoveArgument(InputFilePath); auto LogFilePath = DirPlusFile( TmpDir(), "libFuzzerTemp." + std::to_string(GetPid()) + ".txt"); auto TmpFilePath = DirPlusFile( TmpDir(), "libFuzzerTemp." + std::to_string(GetPid()) + ".repro"); - auto LogFileRedirect = " > " + LogFilePath + " 2>&1 "; - - auto Cmd = BaseCmd + " " + TmpFilePath + LogFileRedirect; + Cmd.AddArgument(TmpFilePath); + Cmd.SetOutputFile(LogFilePath); + Cmd.CombineOutAndErr(); std::string CurrentFilePath = InputFilePath; auto U = FileToVector(CurrentFilePath); @@ -336,7 +342,7 @@ for (auto NewByte : ReplacementBytes) { U[Idx] = NewByte; WriteToFile(U, TmpFilePath); - auto ExitCode = ExecuteCommand(Cmd); + auto ExitCode = Cmd.Execute(); RemoveFile(TmpFilePath); if (!ExitCode) { U[Idx] = OriginalByte; @@ -361,22 +367,22 @@ exit(1); } std::string InputFilePath = Inputs->at(0); - auto BaseCmd = SplitBefore( - "-ignore_remaining_args=1", - CloneArgsWithoutX(Args, "minimize_crash", "exact_artifact_path")); - auto InputPos = BaseCmd.first.find(" " + InputFilePath + " "); - assert(InputPos != std::string::npos); - BaseCmd.first.erase(InputPos, InputFilePath.size() + 1); + Command BaseCmd(Args); + BaseCmd.RemoveFlag("minimize_crash"); + BaseCmd.RemoveFlag("exact_artifact_path"); + assert(BaseCmd.HasArgument(InputFilePath)); + BaseCmd.RemoveArgument(InputFilePath); if (Flags.runs <= 0 && Flags.max_total_time == 0) { Printf("INFO: you need to specify -runs=N or " "-max_total_time=N with -minimize_crash=1\n" "INFO: defaulting to -max_total_time=600\n"); - BaseCmd.first += " -max_total_time=600"; + BaseCmd.AddFlag("max_total_time", "600"); } auto LogFilePath = DirPlusFile( TmpDir(), "libFuzzerTemp." + std::to_string(GetPid()) + ".txt"); - auto LogFileRedirect = " > " + LogFilePath + " 2>&1 "; + BaseCmd.SetOutputFile(LogFilePath); + BaseCmd.CombineOutAndErr(); std::string CurrentFilePath = InputFilePath; while (true) { @@ -384,11 +390,12 @@ Printf("CRASH_MIN: minimizing crash input: '%s' (%zd bytes)\n", CurrentFilePath.c_str(), U.size()); - auto Cmd = BaseCmd.first + " " + CurrentFilePath + LogFileRedirect + " " + - BaseCmd.second; + Command Cmd(BaseCmd); + Cmd.AddArgument(CurrentFilePath); - Printf("CRASH_MIN: executing: %s\n", Cmd.c_str()); - int ExitCode = ExecuteCommand(Cmd); + std::string CommandLine = Cmd.ToString(); + Printf("CRASH_MIN: executing: %s\n", CommandLine.c_str()); + int ExitCode = Cmd.Execute(); if (ExitCode == 0) { Printf("ERROR: the input %s did not crash\n", CurrentFilePath.c_str()); exit(1); @@ -404,10 +411,11 @@ Flags.exact_artifact_path ? Flags.exact_artifact_path : Options.ArtifactPrefix + "minimized-from-" + Hash(U); - Cmd += " -minimize_crash_internal_step=1 -exact_artifact_path=" + - ArtifactPath; - Printf("CRASH_MIN: executing: %s\n", Cmd.c_str()); - ExitCode = ExecuteCommand(Cmd); + Cmd.AddFlag("minimize_crash_internal_step", "1"); + Cmd.AddFlag("exact_artifact_path", ArtifactPath); + CommandLine = Cmd.ToString(); + Printf("CRASH_MIN: executing: %s\n", CommandLine.c_str()); + ExitCode = Cmd.Execute(); CopyFileToErr(LogFilePath); if (ExitCode == 0) { if (Flags.exact_artifact_path) { Index: lib/fuzzer/FuzzerMerge.cpp =================================================================== --- lib/fuzzer/FuzzerMerge.cpp +++ lib/fuzzer/FuzzerMerge.cpp @@ -9,6 +9,7 @@ // Merging corpora. //===----------------------------------------------------------------------===// +#include "FuzzerCommand.h" #include "FuzzerMerge.h" #include "FuzzerIO.h" #include "FuzzerInternal.h" @@ -232,7 +233,7 @@ std::ostringstream StartedLine; // Write the pre-run marker. OF << "STARTED " << std::dec << i << " " << U.size() << "\n"; - OF.flush(); // Flush is important since ExecuteCommand may crash. + OF.flush(); // Flush is important since Command::Execute may crash. // Run. TPC.ResetMaps(); ExecuteCallback(U.data(), U.size()); @@ -332,15 +333,16 @@ // Execute the inner process until it passes. // Every inner process should execute at least one input. - auto BaseCmd = SplitBefore("-ignore_remaining_args=1", - CloneArgsWithoutX(Args, "merge")); + Command BaseCmd(Args); + BaseCmd.RemoveFlag("merge"); bool Success = false; for (size_t Attempt = 1; Attempt <= NumAttempts; Attempt++) { MaybeExitGracefully(); Printf("MERGE-OUTER: attempt %zd\n", Attempt); - auto ExitCode = - ExecuteCommand(BaseCmd.first + " -merge_control_file=" + CFPath + - " -merge_inner=1 " + BaseCmd.second); + Command Cmd(BaseCmd); + Cmd.AddFlag("merge_control_file", CFPath); + Cmd.AddFlag("merge_inner", "1"); + auto ExitCode = Cmd.Execute(); if (!ExitCode) { Printf("MERGE-OUTER: succesfull in %zd attempt(s)\n", Attempt); Success = true;