[libFuzzer] Error and exit if user supplied fuzzer writeable directories don't exist

Currently, libFuzzer will exit with an error message if a non-existent
corpus directory is provided. However, if a user provides a non-existent
directory for the `artifact_prefix`, `exact_artifact_path`, or
`features_dir`, libFuzzer will continue execution but silently fail to
write artifacts/features.

To improve the user experience, this PR adds validation for the existence of
all user supplied directories before executing the main fuzzing loop. If they
don't exist, libFuzzer will exit with an error message.

Patch By: dgg5503

Reviewed By: morehouse

Differential Revision: https://reviews.llvm.org/D84808
This commit is contained in:
Matt Morehouse 2020-08-26 09:06:12 -07:00
parent 5b9c2b1bea
commit 2392ff093a
5 changed files with 60 additions and 10 deletions

View File

@ -250,6 +250,13 @@ static void WorkerThread(const Command &BaseCmd, std::atomic<unsigned> *Counter,
}
}
static void ValidateDirectoryExists(const std::string &Path) {
if (!Path.empty() && !IsDirectory(Path)) {
Printf("ERROR: The required directory \"%s\" does not exist\n", Path.c_str());
exit(1);
}
}
std::string CloneArgsWithoutX(const Vector<std::string> &Args,
const char *X1, const char *X2) {
std::string Cmd;
@ -678,13 +685,32 @@ int FuzzerDriver(int *argc, char ***argv, UserCallback Callback) {
Options.MallocLimitMb = Options.RssLimitMb;
if (Flags.runs >= 0)
Options.MaxNumberOfRuns = Flags.runs;
if (!Inputs->empty() && !Flags.minimize_crash_internal_step)
Options.OutputCorpus = (*Inputs)[0];
if (!Inputs->empty() && !Flags.minimize_crash_internal_step) {
// Ensure output corpus assumed to be the first arbitrary argument input
// is not a path to an existing file.
std::string OutputCorpusDir = (*Inputs)[0];
if (!IsFile(OutputCorpusDir)) {
Options.OutputCorpus = OutputCorpusDir;
ValidateDirectoryExists(Options.OutputCorpus);
}
}
Options.ReportSlowUnits = Flags.report_slow_units;
if (Flags.artifact_prefix)
if (Flags.artifact_prefix) {
Options.ArtifactPrefix = Flags.artifact_prefix;
if (Flags.exact_artifact_path)
// Since the prefix could be a full path to a file name prefix, assume
// that if the path ends with the platform's separator that a directory
// is desired
std::string ArtifactPathDir = Options.ArtifactPrefix;
if (!IsSeparator(ArtifactPathDir[ArtifactPathDir.length() - 1])) {
ArtifactPathDir = DirName(ArtifactPathDir);
}
ValidateDirectoryExists(ArtifactPathDir);
}
if (Flags.exact_artifact_path) {
Options.ExactArtifactPath = Flags.exact_artifact_path;
ValidateDirectoryExists(DirName(Options.ExactArtifactPath));
}
Vector<Unit> Dictionary;
if (Flags.dict)
if (!ParseDictionaryFile(FileToString(Flags.dict), &Dictionary))
@ -707,8 +733,10 @@ int FuzzerDriver(int *argc, char ***argv, UserCallback Callback) {
Options.FocusFunction = Flags.focus_function;
if (Flags.data_flow_trace)
Options.DataFlowTrace = Flags.data_flow_trace;
if (Flags.features_dir)
if (Flags.features_dir) {
Options.FeaturesDir = Flags.features_dir;
ValidateDirectoryExists(Options.FeaturesDir);
}
if (Flags.collect_data_flow)
Options.CollectDataFlow = Flags.collect_data_flow;
if (Flags.stop_file)

View File

@ -58,6 +58,7 @@ void RawPrint(const char *Str);
// Platform specific functions:
bool IsFile(const std::string &Path);
bool IsDirectory(const std::string &Path);
size_t FileSize(const std::string &Path);
void ListFilesInDirRecursive(const std::string &Dir, long *Epoch,
@ -82,6 +83,7 @@ struct SizedFile {
void GetSizedFilesFromDir(const std::string &Dir, Vector<SizedFile> *V);
char GetSeparator();
bool IsSeparator(char C);
// Similar to the basename utility: returns the file name w/o the dir prefix.
std::string Basename(const std::string &Path);

View File

@ -31,7 +31,7 @@ bool IsFile(const std::string &Path) {
return S_ISREG(St.st_mode);
}
static bool IsDirectory(const std::string &Path) {
bool IsDirectory(const std::string &Path) {
struct stat St;
if (stat(Path.c_str(), &St))
return false;
@ -104,6 +104,10 @@ char GetSeparator() {
return '/';
}
bool IsSeparator(char C) {
return C == '/';
}
FILE* OpenFile(int Fd, const char* Mode) {
return fdopen(Fd, Mode);
}

View File

@ -76,6 +76,18 @@ static bool IsDir(DWORD FileAttrs) {
return FileAttrs & FILE_ATTRIBUTE_DIRECTORY;
}
bool IsDirectory(const std::string &Path) {
DWORD Att = GetFileAttributesA(Path.c_str());
if (Att == INVALID_FILE_ATTRIBUTES) {
Printf("GetFileAttributesA() failed for \"%s\" (Error code: %lu).\n",
Path.c_str(), GetLastError());
return false;
}
return IsDir(Att);
}
std::string Basename(const std::string &Path) {
size_t Pos = Path.find_last_of("/\\");
if (Pos == std::string::npos) return Path;
@ -227,7 +239,7 @@ intptr_t GetHandleFromFd(int fd) {
return _get_osfhandle(fd);
}
static bool IsSeparator(char C) {
bool IsSeparator(char C) {
return C == '\\' || C == '/';
}

View File

@ -16,6 +16,10 @@ RUN: %run %t-SimpleTest %t/SUB1 -runs=0 2>&1 | FileCheck %s --check-prefix=LONG
LONG: INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 8192 bytes
RUN: rm -rf %t/SUB1
RUN: not %run %t-SimpleTest NONEXISTENT_DIR 2>&1 | FileCheck %s --check-prefix=NONEXISTENT_DIR
NONEXISTENT_DIR: No such file or directory: NONEXISTENT_DIR; exiting
RUN: rm -rf %t.dir && mkdir -p %t.dir
RUN: not %run %t-SimpleTest -artifact_prefix=%t.dir/NONEXISTENT_DIR/ 2>&1 | FileCheck %s --check-prefix=NONEXISTENT_DIR_RGX
RUN: not %run %t-SimpleTest -artifact_prefix=%t.dir/NONEXISTENT_DIR/myprefix 2>&1 | FileCheck %s --check-prefix=NONEXISTENT_DIR_RGX
RUN: not %run %t-SimpleTest -features_dir=%t.dir/NONEXISTENT_DIR/ 2>&1 | FileCheck %s --check-prefix=NONEXISTENT_DIR_RGX
RUN: not %run %t-SimpleTest %t.dir/NONEXISTENT_DIR 2>&1 | FileCheck %s --check-prefix=NONEXISTENT_DIR_RGX
RUN: not %run %t-SimpleTest -exact_artifact_path=%t.dir/NONEXISTENT_DIR/myprefix 2>&1 | FileCheck %s --check-prefix=NONEXISTENT_DIR_RGX
NONEXISTENT_DIR_RGX: ERROR: The required directory "{{.*/NONEXISTENT_DIR/?}}" does not exist