---
 fuzzing/Makefile.am             |   6 +-
 fuzzing/libnbd-fuzz-wrapper.c   | 398 ++++++++++++++++++++------------
 fuzzing/libnbd-libfuzzer-test.c |   4 +
 3 files changed, 253 insertions(+), 155 deletions(-)

diff --git a/fuzzing/Makefile.am b/fuzzing/Makefile.am
index 2d46cd0..928cdc9 100644
--- a/fuzzing/Makefile.am
+++ b/fuzzing/Makefile.am
@@ -38,10 +38,14 @@ libnbd_fuzz_wrapper_CPPFLAGS = \
        -I$(top_srcdir)/common/include \
        -I$(top_srcdir)/common/utils \
        $(NULL)
-libnbd_fuzz_wrapper_CFLAGS = $(WARNINGS_CFLAGS)
+libnbd_fuzz_wrapper_CFLAGS = \
+       $(WARNINGS_CFLAGS) \
+       $(PTHREAD_CFLAGS) \
+       $(NULL)
 libnbd_fuzz_wrapper_LDADD = \
        $(top_builddir)/common/utils/libutils.la \
        $(top_builddir)/lib/libnbd.la \
+       $(PTHREAD_LIBS) \
        $(NULL)
 
 libnbd_libfuzzer_test_SOURCES = libnbd-libfuzzer-test.c
diff --git a/fuzzing/libnbd-fuzz-wrapper.c b/fuzzing/libnbd-fuzz-wrapper.c
index 338adc0..10f10e4 100644
--- a/fuzzing/libnbd-fuzz-wrapper.c
+++ b/fuzzing/libnbd-fuzz-wrapper.c
@@ -17,10 +17,38 @@
  */
 
 /* This is a wrapper allowing libnbd to be tested using common fuzzers
- * such as afl.  It takes the fuzzer test case as a filename on the
- * command line.  This is fed to the libnbd socket.  Any output to the
- * socket from libnbd is sent to /dev/null.  This is basically the
- * same way we fuzz nbdkit, but in reverse (see nbdkit.git/fuzzing).
+ * such as AFL++.  It takes the fuzzer test case as a filename on the
+ * command line.
+ *
+ * It uses fuzzed-data-provider.h to parse the input allowing a choice
+ * of APIs to be called in any order under control of the fuzzer.  The
+ * test cases therefore do not correspond very closely to raw NBD
+ * protocol.
+ *
+ * The fuzzer input is parsed as:
+ *
+ *   <initial server buffer> (includes implicit length)
+ *   zero or more <command>s
+ *
+ * The <initial server buffer> field is the data that is written back
+ * to libnbd over the socket.  The first buffer will contain the
+ * initial NBD handshake.  (Any data sent by libnbd over the socket is
+ * discarded.)
+ *
+ * The series of <command>s directs the program to execute different
+ * libnbd APIs.  When the input is exhausted we stop the test.  Each
+ * <command> is:
+ *
+ *   <enum of API to call>
+ *   <offset>
+ *   <flags>
+ *   <data buffer> (includes implicit length)
+ *   <server buffer> (includes implicit length)
+ *
+ * The <data buffer> field is the buffer used by the libnbd API.  The
+ * actual data in this buffer is only used by nbd_aio_pwrite.  For
+ * other APIs only the length is used and the content is ignored.  The
+ * <server buffer> is more data to send to libnbd if required.
  */
 
 #include <config.h>
@@ -31,86 +59,84 @@
 #include <stdint.h>
 #include <inttypes.h>
 #include <string.h>
-#include <fcntl.h>
 #include <unistd.h>
-#include <poll.h>
+#include <fcntl.h>
 #include <errno.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/wait.h>
+#include <sys/stat.h>
+
+#include <pthread.h>
 
 #include <libnbd.h>
 
+#include "minmax.h"
+#include "vector.h"
+
+#include "fuzzed-data-provider.h"
+
 #ifndef SOCK_CLOEXEC
 #define SOCK_CLOEXEC 0 /* This file doesn't use exec */
 #endif
 
-static void client (int s);
-static void server (int fd, int s);
+/* The test case from the fuzzer.  This is loaded from the input file
+ * given on the command line.
+ */
+byte_vector fuzzed_data;
+
+static void *writer_thread (void *);
+static void send_to_writer_thread (byte_vector v);
+static void end_writer_thread (void);
+static void do_test (int sock);
 
 int
 main (int argc, char *argv[])
 {
-  int fd;
-  pid_t pid;
-  int sv[2], r, status;
+  int fd, err;
+  struct stat statbuf;
+  int sv[2];
+  pthread_t thread;
 
-  if (argc == 2) {
-    /* Open the test case before we fork so we know the file exists. */
-    fd = open (argv[1], O_RDONLY);
-    if (fd == -1) {
-      perror (argv[1]);
-      exit (EXIT_FAILURE);
-    }
-  }
-  else {
+  if (argc != 2) {
     fprintf (stderr, "libnbd-fuzz-wrapper testcase\n");
     exit (EXIT_FAILURE);
   }
 
+  /* Load the test case fully into memory. */
+  fd = open (argv[1], O_RDONLY);
+  if (fd == -1) {
+    perror (argv[1]);
+    exit (EXIT_FAILURE);
+  }
+  if (fstat (fd, &statbuf) == -1) {
+    perror ("fstat");
+    abort ();
+  }
+  if (byte_vector_reserve_exactly (&fuzzed_data, statbuf.st_size) == -1) {
+    perror ("malloc");
+    abort ();
+  }
+  if (read (fd, fuzzed_data.ptr, statbuf.st_size) != statbuf.st_size) {
+    fprintf (stderr, "%s: incomplete read\n", argv[0]);
+    abort ();
+  }
+  close (fd);
+
   /* Create a connected socket. */
   if (socketpair (AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0, sv) == -1) {
     perror ("socketpair");
-    exit (EXIT_FAILURE);
+    abort ();
   }
 
-  /* Fork: The parent will be the libnbd process (client).  The child
-   * will be the (usually phony) NBD server listening on the socket.
-   */
-  pid = fork ();
-  if (pid == -1) {
-    perror ("fork");
-    exit (EXIT_FAILURE);
+  err = pthread_create (&thread, NULL, writer_thread, &sv[1]);
+  if (err != 0) {
+    errno = err;
+    perror ("pthread_create");
+    abort ();
   }
 
-  if (pid > 0) {
-    /* Parent: libnbd client. */
-    close (sv[1]);
-    close (fd);
+  /* Start running the test. */
+  do_test (sv[0]);
 
-    client (sv[0]);
-
-    close (sv[0]);
-
-    r = wait (&status);
-    if (r == -1) {
-      perror ("wait");
-      exit (EXIT_FAILURE);
-    }
-    if (!WIFEXITED (status) || WEXITSTATUS (status) != 0)
-      exit (EXIT_FAILURE);
-    else
-      exit (EXIT_SUCCESS);
-  }
-
-  /* Child: NBD server. */
-  close (sv[0]);
-
-  server (fd, sv[1]);
-
-  close (sv[1]);
-
-  _exit (EXIT_SUCCESS);
+  exit (EXIT_SUCCESS);
 }
 
 /* Structured reads callback, does nothing. */
@@ -146,73 +172,133 @@ extent64_callback (void *user_data,
   return 0;
 }
 
-/* This is the client (parent process) running libnbd. */
-static char buf[512];
-static char prbuf[65536];
+enum api_type {
+  PREAD,
+  PWRITE,
+  FLUSH,
+  TRIM,
+  ZERO,
+  CACHE,
+  PREAD_STRUCTURED,
+  BLOCK_STATUS,
+  BLOCK_STATUS_64,
+  API_TYPE_MAX = BLOCK_STATUS_64
+};
 
 static void
-client (int sock)
+do_test (int sock)
 {
   struct nbd_handle *nbd;
+  byte_vector data, from_server;
   int64_t length;
+  int api;
+  uint64_t offset;
+  uint32_t flags;
 
   nbd = nbd_create ();
   if (nbd == NULL) {
     fprintf (stderr, "%s\n", nbd_get_error ());
-    exit (EXIT_FAILURE);
+    abort ();
   }
 
   /* Note we ignore errors in these calls because we are only
-   * interested in whether the process crashes.  Likewise, we don't
-   * want to accidentally avoid sending traffic to the server merely
-   * because client side strictness sees a problem.
+   * interested in whether the process crashes.
+   */
+
+  /* We don't want to accidentally avoid sending traffic to the server
+   * merely because client side strictness sees a problem.
    */
   nbd_set_strict_mode (nbd, 0);
 
-  /* Enable a metadata context, for block status below. */
+  /* Enable a metadata context, for block status. */
   nbd_add_meta_context (nbd, LIBNBD_CONTEXT_BASE_ALLOCATION);
 
   /* This tests the handshake phase. */
   nbd_set_opt_mode (nbd, true);
+
+  /* Consume the initial server buffer and get ready to write it to
+   * the server.
+   */
+  from_server = fuzzed_data_consume_buffer ();
+  send_to_writer_thread (from_server);
+
+  /* Connect to the socket. */
   nbd_connect_socket (nbd, sock);
+
   nbd_opt_info (nbd);
   nbd_opt_go (nbd);
 
   length = nbd_get_size (nbd);
 
-  /* Test common asynchronous I/O calls. */
-  nbd_aio_pread (nbd, buf, sizeof buf, 0, NBD_NULL_COMPLETION, 0);
-  nbd_aio_pwrite (nbd, buf, sizeof buf, 0, NBD_NULL_COMPLETION, 0);
-  nbd_aio_flush (nbd, NBD_NULL_COMPLETION, 0);
-  nbd_aio_trim (nbd, 8192, 8192, NBD_NULL_COMPLETION, 0);
-  nbd_aio_zero (nbd, 8192, 65536, NBD_NULL_COMPLETION, 0);
-  nbd_aio_cache (nbd, 8192, 0, NBD_NULL_COMPLETION, 0);
+  /* Main loop: Consume fuzzer data to decide which calls we will make. */
+  while (more_fuzzed_data ()) {
+    api = fuzzed_data_consume_enum (API_TYPE_MAX);
+    offset = fuzzed_data_consume_uint64_t (0, length);
+    flags = fuzzed_data_consume_unsigned (0, 65535);
+    data = fuzzed_data_consume_buffer ();
+    from_server = fuzzed_data_consume_buffer ();
+    send_to_writer_thread (from_server);
 
-  /* Test structured reads. */
-  nbd_aio_pread_structured (nbd, prbuf, sizeof prbuf, 8192,
-                            (nbd_chunk_callback) {
-                              .callback = chunk_callback,
+    switch (api) {
+    case PREAD:
+      nbd_aio_pread (nbd, data.ptr, data.len, offset,
+                     NBD_NULL_COMPLETION, flags);
+      break;
+    case PWRITE:
+      nbd_aio_pwrite (nbd, data.ptr, data.len, offset,
+                      NBD_NULL_COMPLETION, flags);
+      break;
+    case FLUSH:
+      nbd_aio_flush (nbd, NBD_NULL_COMPLETION, flags);
+      break;
+    case TRIM:
+      nbd_aio_trim (nbd, data.len, offset, NBD_NULL_COMPLETION, flags);
+      break;
+    case ZERO:
+      nbd_aio_zero (nbd, data.len, offset, NBD_NULL_COMPLETION, flags);
+      break;
+    case CACHE:
+      nbd_aio_cache (nbd, data.len, offset, NBD_NULL_COMPLETION, flags);
+      break;
+    case PREAD_STRUCTURED:
+      nbd_aio_pread_structured (nbd, data.ptr, data.len, offset,
+                                (nbd_chunk_callback) {
+                                  .callback = chunk_callback,
+                                },
+                                NBD_NULL_COMPLETION,
+                                flags);
+      break;
+    case BLOCK_STATUS:
+      nbd_aio_block_status (nbd, data.len, offset,
+                            (nbd_extent_callback) {
+                              .callback = extent_callback,
                             },
                             NBD_NULL_COMPLETION,
-                            0);
+                            flags);
+      break;
+    case BLOCK_STATUS_64:
+      nbd_aio_block_status_64 (nbd, data.len, offset,
+                               (nbd_extent64_callback) {
+                                 .callback = extent64_callback,
+                               },
+                               NBD_NULL_COMPLETION,
+                               flags);
+      break;
+    default:
+      abort ();
+    }
 
-  /* Test both sizes of block status. */
-  nbd_aio_block_status (nbd, length, 0,
-                        (nbd_extent_callback) {
-                          .callback = extent_callback,
-                        },
-                        NBD_NULL_COMPLETION,
-                        0);
-  nbd_aio_block_status_64 (nbd, length, 0,
-                           (nbd_extent64_callback) {
-                             .callback = extent64_callback,
-                           },
-                           NBD_NULL_COMPLETION,
-                           0);
+    /* Run the state machine until it blocks. */
+    while (nbd_poll (nbd, 0) == 1)
+      ;
+  }
 
-  /* Run the commands until there are no more in flight or there is an
-   * error caused by the server side disconnecting.
+  /* No more data from the "server", tell the writer thread to shut
+   * down once it has finished writing all its data.
    */
+  end_writer_thread ();
+
+  /* Keep running until all commands have finished. */
   while (nbd_aio_in_flight (nbd) > 0) {
     if (nbd_poll (nbd, -1) == -1)
       break;
@@ -222,71 +308,75 @@ client (int sock)
   nbd_shutdown (nbd, 0);
 }
 
-/* This is the server (child process) acting like an NBD server. */
-static void
-server (int fd, int sock)
+/* The writer thread. */
+static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
+static byte_vector to_write = empty_vector;
+static bool writer_done = false;
+
+static void *
+writer_thread (void *vp)
 {
-  struct pollfd pfds[1];
-  char rbuf[512], wbuf[512];
-  size_t wsize = 0;
-  ssize_t r;
+  int sock = *(int *)vp;
+  uint8_t buf[128];
+  size_t n;
 
   for (;;) {
-    pfds[0].fd = sock;
-    pfds[0].events = POLLIN;
-    if (wsize > 0 || fd >= 0) pfds[0].events |= POLLOUT;
-    pfds[0].revents = 0;
-
-    if (poll (pfds, 1, -1) == -1) {
-      if (errno == EINTR)
-        continue;
-      perror ("poll");
-      /* This is not an error. */
-      return;
+    /* Get next data to write.  Take a copy because as soon as we
+     * release the lock the to_write.ptr buffer might be reallocated.
+     */
+    pthread_mutex_lock (&lock);
+  again:
+    if (writer_done) {
+      /* Indicate no more data below. */
+      n = 0;
     }
-
-    /* We can read from the client socket.  Just throw away anything sent. */
-    if ((pfds[0].revents & POLLIN) != 0) {
-      r = read (sock, rbuf, sizeof rbuf);
-      if (r == -1 && errno != EINTR) {
-        perror ("read");
-        return;
-      }
-      else if (r == 0)          /* end of input from the server */
-        return;
+    else if (to_write.len > 0) {
+      /* Copy some data out of to_write, ready to write it below. */
+      n = MIN (sizeof buf, to_write.len);
+      memcpy (buf, to_write.ptr, n);
+      memmove (to_write.ptr, &to_write.ptr[n], to_write.len - n);
+      to_write.len -= n;
     }
+    else {
+      /* Not done and waiting on more data to write. */
+      pthread_cond_wait (&cond, &lock);
+      goto again;
+    }
+    pthread_mutex_unlock (&lock);
 
-    /* We can write to the client socket. */
-    if ((pfds[0].revents & POLLOUT) != 0) {
-      /* Write more data from the wbuf. */
-      if (wsize > 0) {
-      morewrite:
-        r = write (sock, wbuf, wsize);
-        if (r == -1 && errno != EAGAIN && errno != EWOULDBLOCK) {
-          perror ("write");
-          return;
-        }
-        else if (r > 0) {
-          memmove (wbuf, &wbuf[r], wsize-r);
-          wsize -= r;
-        }
-      }
-      /* Write more data from the file. */
-      else if (fd >= 0) {
-        r = read (fd, wbuf, sizeof wbuf);
-        if (r == -1) {
-          perror ("read");
-          _exit (EXIT_FAILURE);
-        }
-        else if (r == 0) {
-          fd = -1;              /* ignore the file from now on */
-          shutdown (sock, SHUT_WR);
-        }
-        else {
-          wsize = r;
-          goto morewrite;
-        }
-      }
+    if (n == 0) { /* no more data */
+      close (sock);
+      pthread_exit (NULL);
     }
-  } /* for (;;) */
+
+    /* Write the data.  Assumes we can always write 128 bytes
+     * atomically to a Unix domain socket, which is likely always true
+     * in Linux.
+     */
+    if (write (sock, buf, n) != n)
+      perror ("write");
+  }
+}
+
+static void
+send_to_writer_thread (byte_vector v)
+{
+  pthread_mutex_lock (&lock);
+  if (byte_vector_reserve (&to_write, v.len) == -1)
+    abort ();
+  memcpy (&to_write.ptr[to_write.len], v.ptr, v.len);
+  to_write.len += v.len;
+  byte_vector_reset (&v);
+  pthread_cond_signal (&cond);
+  pthread_mutex_unlock (&lock);
+}
+
+static void
+end_writer_thread (void)
+{
+  pthread_mutex_lock (&lock);
+  writer_done = true;
+  pthread_cond_signal (&cond);
+  pthread_mutex_unlock (&lock);
 }
diff --git a/fuzzing/libnbd-libfuzzer-test.c b/fuzzing/libnbd-libfuzzer-test.c
index 1721b74..cf6c1cf 100644
--- a/fuzzing/libnbd-libfuzzer-test.c
+++ b/fuzzing/libnbd-libfuzzer-test.c
@@ -22,6 +22,10 @@
  *
  * - This case is mostly unmaintained.  The maintainers use AFL++ for
  *   fuzzing (see libnbd-fuzz-wrapper.c).
+ *
+ * - This test needs to be updated to use the new method of reading
+ *   input via the fuzzed data provider (see again
+ *   libnbd-fuzz-wrapper.c).
  */
 
 #include <config.h>
-- 
2.43.1
_______________________________________________
Libguestfs mailing list -- guestfs@lists.libguestfs.org
To unsubscribe send an email to guestfs-le...@lists.libguestfs.org

Reply via email to