Summary:
Add close_fd_mask functionality to AFL driver.

Summary:
Add support for env var AFL_DRIVER_CLOSE_FD_MASK which behaves
the same as libFuzzer's -close_fd_mask=1.

Also add tests.

Reviewers: kcc, vitalybuka, morehouse

Reviewed By: morehouse

Subscribers: #sanitizers, llvm-commits

Tags: #sanitizers, #llvm

Differential Revision: https://reviews.llvm.org/D60334

llvm-svn: 358703
GitOrigin-RevId: 139e216e6610091b7ee3c30bc11114f5d73cbd3e
diff --git a/afl/afl_driver.cpp b/afl/afl_driver.cpp
index 18101aa..f21dfc5 100644
--- a/afl/afl_driver.cpp
+++ b/afl/afl_driver.cpp
@@ -31,10 +31,14 @@
 rm -rf IN OUT; mkdir IN OUT; echo z > IN/z;
 $AFL_HOME/afl-fuzz -i IN -o OUT ./a.out
 ################################################################################
-AFL_DRIVER_STDERR_DUPLICATE_FILENAME: Setting this environment variable
-*appends* stderr to the file specified. If the file does not exist, it is
-created. This is useful for getting stack traces (when using ASAN for example)
-or original error messages on hard to reproduce bugs.
+AFL_DRIVER_STDERR_DUPLICATE_FILENAME: Setting this *appends* stderr to the file
+specified. If the file does not exist, it is created. This is useful for getting
+stack traces (when using ASAN for example) or original error messages on hard
+to reproduce bugs. Note that any content written to stderr will be written to
+this file instead of stderr's usual location.
+
+AFL_DRIVER_CLOSE_FD_MASK: Similar to libFuzzer's -close_fd_mask behavior option.
+If 1, close stdout at startup. If 2 close stderr; if 3 close both.
 
 */
 #include <assert.h>
@@ -97,16 +101,24 @@
 
 // Notify AFL about deferred forkserver.
 static volatile char AFL_DEFER_FORKSVR[] = "##SIG_AFL_DEFER_FORKSRV##";
-extern "C" void  __afl_manual_init();
+extern "C" void __afl_manual_init();
 static volatile char suppress_warning1 = AFL_DEFER_FORKSVR[0];
 
 // Input buffer.
 static const size_t kMaxAflInputSize = 1 << 20;
 static uint8_t AflInputBuf[kMaxAflInputSize];
 
+// Use this optionally defined function to output sanitizer messages even if
+// user asks to close stderr.
+__attribute__((weak)) extern "C" void __sanitizer_set_report_fd(void *);
+
+// Keep track of where stderr content is being written to, so that
+// dup_and_close_stderr can use the correct one.
+static FILE *output_file = stderr;
+
 // Experimental feature to use afl_driver without AFL's deferred mode.
 // Needs to run before __afl_auto_init.
-__attribute__((constructor(0))) void __decide_deferred_forkserver(void) {
+__attribute__((constructor(0))) static void __decide_deferred_forkserver(void) {
   if (getenv("AFL_DRIVER_DONT_DEFER")) {
     if (unsetenv("__AFL_DEFER_FORKSRV")) {
       perror("Failed to unset __AFL_DEFER_FORKSRV");
@@ -117,13 +129,13 @@
 
 // If the user asks us to duplicate stderr, then do it.
 static void maybe_duplicate_stderr() {
-  char* stderr_duplicate_filename =
+  char *stderr_duplicate_filename =
       getenv("AFL_DRIVER_STDERR_DUPLICATE_FILENAME");
 
   if (!stderr_duplicate_filename)
     return;
 
-  FILE* stderr_duplicate_stream =
+  FILE *stderr_duplicate_stream =
       freopen(stderr_duplicate_filename, "a+", stderr);
 
   if (!stderr_duplicate_stream) {
@@ -132,6 +144,54 @@
         "Failed to duplicate stderr to AFL_DRIVER_STDERR_DUPLICATE_FILENAME");
     abort();
   }
+  output_file = stderr_duplicate_stream;
+}
+
+// Most of these I/O functions were inspired by/copied from libFuzzer's code.
+static void discard_output(int fd) {
+  FILE *temp = fopen("/dev/null", "w");
+  if (!temp)
+    abort();
+  dup2(fileno(temp), fd);
+  fclose(temp);
+}
+
+static void close_stdout() { discard_output(STDOUT_FILENO); }
+
+// Prevent the targeted code from writing to "stderr" but allow sanitizers and
+// this driver to do so.
+static void dup_and_close_stderr() {
+  int output_fileno = fileno(output_file);
+  int output_fd = dup(output_fileno);
+  if (output_fd <= 0)
+    abort();
+  FILE *new_output_file = fdopen(output_fd, "w");
+  if (!new_output_file)
+    abort();
+  if (!__sanitizer_set_report_fd)
+    return;
+  __sanitizer_set_report_fd(reinterpret_cast<void *>(output_fd));
+  discard_output(output_fileno);
+}
+
+static void Printf(const char *Fmt, ...) {
+  va_list ap;
+  va_start(ap, Fmt);
+  vfprintf(output_file, Fmt, ap);
+  va_end(ap);
+  fflush(output_file);
+}
+
+// Close stdout and/or stderr if user asks for it.
+static void maybe_close_fd_mask() {
+  char *fd_mask_str = getenv("AFL_DRIVER_CLOSE_FD_MASK");
+  if (!fd_mask_str)
+    return;
+  int fd_mask = atoi(fd_mask_str);
+  if (fd_mask & 2)
+    dup_and_close_stderr();
+  if (fd_mask & 1)
+    close_stdout();
 }
 
 // Define LLVMFuzzerMutate to avoid link failures for targets that use it
@@ -142,7 +202,7 @@
 }
 
 // Execute any files provided as parameters.
-int ExecuteFilesOnyByOne(int argc, char **argv) {
+static int ExecuteFilesOnyByOne(int argc, char **argv) {
   for (int i = 1; i < argc; i++) {
     std::ifstream in(argv[i], std::ios::binary);
     in.seekg(0, in.end);
@@ -161,7 +221,7 @@
 }
 
 int main(int argc, char **argv) {
-  fprintf(stderr,
+  Printf(
       "======================= INFO =========================\n"
       "This binary is built for AFL-fuzz.\n"
       "To run the target function on individual input(s) execute this:\n"
@@ -174,12 +234,13 @@
       "re-spawning the process (default: 1000)\n"
       "======================================================\n",
           argv[0], argv[0], argv[0]);
+
+  maybe_duplicate_stderr();
+  maybe_close_fd_mask();
   if (LLVMFuzzerInitialize)
     LLVMFuzzerInitialize(&argc, &argv);
   // Do any other expensive one-time initialization here.
 
-  maybe_duplicate_stderr();
-
   if (!getenv("AFL_DRIVER_DONT_DEFER"))
     __afl_manual_init();
 
@@ -187,8 +248,7 @@
   if (argc == 2 && argv[1][0] == '-')
       N = atoi(argv[1] + 1);
   else if(argc == 2 && (N = atoi(argv[1])) > 0)
-      fprintf(stderr, "WARNING: using the deprecated call style `%s %d`\n",
-              argv[0], N);
+      Printf("WARNING: using the deprecated call style `%s %d`\n", argv[0], N);
   else if (argc > 1)
     return ExecuteFilesOnyByOne(argc, argv);
 
@@ -212,5 +272,5 @@
       delete[] copy;
     }
   }
-  fprintf(stderr, "%s: successfully executed %d input(s)\n", argv[0], num_runs);
+  Printf("%s: successfully executed %d input(s)\n", argv[0], num_runs);
 }