If the plugin .pread method did not fill the buffer with data then the
contents of the heap could be leaked back to the client.  To avoid
this create a thread-local data buffer which is initialized to zero
and expanded (with zeroes) as required.

This buffer is shared between pread and pwrite which makes the code a
little bit simpler.  Also this may improve locality by reusing the
same memory for all pread and pwrite calls in the same thread.

I have checked plugins shipped with nbdkit and none of them are
vulnerable in this way.  They all do one of these things:

 - They fully set the buffer with a single call to something like
   memcpy, memset, etc.

 - They use a loop like ‘while (count > 0)’ and correctly update count
   and the buffer pointer on each iteration.

 - For language plugins (except OCaml), they check that the string
   length returned from the language plugin is at least as long as the
   requested data, before memcpy-ing the data to the return buffer.

 - For OCaml, see the previous commit.

Of course I cannot check plugins which may be supplied by others.

Credit: Eric Blake for finding the bug.
---
 server/internal.h    |  1 +
 server/protocol.c    | 16 +++++++++-------
 server/threadlocal.c | 37 +++++++++++++++++++++++++++++++++++++
 3 files changed, 47 insertions(+), 7 deletions(-)

diff --git a/server/internal.h b/server/internal.h
index 837cd4a..817f022 100644
--- a/server/internal.h
+++ b/server/internal.h
@@ -350,6 +350,7 @@ extern void threadlocal_set_sockaddr (const struct sockaddr 
*addr,
 /*extern void threadlocal_get_sockaddr ();*/
 extern void threadlocal_set_error (int err);
 extern int threadlocal_get_error (void);
+extern void *threadlocal_buffer (size_t size);
 
 /* Declare program_name. */
 #if HAVE_DECL_PROGRAM_INVOCATION_SHORT_NAME == 1
diff --git a/server/protocol.c b/server/protocol.c
index 3f89f6d..9e8eea5 100644
--- a/server/protocol.c
+++ b/server/protocol.c
@@ -611,7 +611,7 @@ protocol_recv_request_send_reply (struct connection *conn)
   uint16_t cmd, flags;
   uint32_t magic, count, error = 0;
   uint64_t offset;
-  CLEANUP_FREE char *buf = NULL;
+  char *buf;
   CLEANUP_EXTENTS_FREE struct nbdkit_extents *extents = NULL;
 
   /* Read the request packet. */
@@ -656,12 +656,12 @@ protocol_recv_request_send_reply (struct connection *conn)
       goto send_reply;
     }
 
-    /* Allocate the data buffer used for either read or write requests. */
+    /* Get the data buffer used for either read or write requests.
+     * This is a common per-thread data buffer, it must not be freed.
+     */
     if (cmd == NBD_CMD_READ || cmd == NBD_CMD_WRITE) {
-      buf = malloc (count);
+      buf = threadlocal_buffer ((size_t) count);
       if (buf == NULL) {
-      out_of_memory:
-        perror ("malloc");
         error = ENOMEM;
         if (cmd == NBD_CMD_WRITE &&
             skip_over_write_buffer (conn->sockin, count) < 0)
@@ -673,8 +673,10 @@ protocol_recv_request_send_reply (struct connection *conn)
     /* Allocate the extents list for block status only. */
     if (cmd == NBD_CMD_BLOCK_STATUS) {
       extents = nbdkit_extents_new (offset, conn->exportsize);
-      if (extents == NULL)
-        goto out_of_memory;
+      if (extents == NULL) {
+        error = ENOMEM;
+        goto send_reply;
+      }
     }
 
     /* Receive the write data buffer. */
diff --git a/server/threadlocal.c b/server/threadlocal.c
index e556dbc..49ae1ac 100644
--- a/server/threadlocal.c
+++ b/server/threadlocal.c
@@ -58,6 +58,8 @@ struct threadlocal {
   struct sockaddr *addr;
   socklen_t addrlen;
   int err;
+  void *buffer;
+  size_t buffer_size;
 };
 
 static pthread_key_t threadlocal_key;
@@ -69,6 +71,7 @@ free_threadlocal (void *threadlocalv)
 
   free (threadlocal->name);
   free (threadlocal->addr);
+  free (threadlocal->buffer);
   free (threadlocal);
 }
 
@@ -189,3 +192,37 @@ threadlocal_get_error (void)
   errno = err;
   return threadlocal ? threadlocal->err : 0;
 }
+
+/* Return the single pread/pwrite buffer for this thread.  The buffer
+ * size is increased to ‘size’ bytes if required.
+ *
+ * The buffer starts out as zeroes but after use may contain data from
+ * previous requests.  This is fine because: (a) Correctly written
+ * plugins should overwrite the whole buffer on each request so no
+ * leak should occur.  (b) The aim of this buffer is to avoid leaking
+ * random heap data from the core server; previous request data from
+ * the plugin is not considered sensitive.
+ */
+extern void *
+threadlocal_buffer (size_t size)
+{
+  struct threadlocal *threadlocal = pthread_getspecific (threadlocal_key);
+
+  if (!threadlocal)
+    abort ();
+
+  if (threadlocal->buffer_size < size) {
+    void *ptr;
+
+    ptr = realloc (threadlocal->buffer, size);
+    if (ptr == NULL) {
+      nbdkit_error ("threadlocal_buffer: realloc: %m");
+      return NULL;
+    }
+    memset (ptr, 0, size);
+    threadlocal->buffer = ptr;
+    threadlocal->buffer_size = size;
+  }
+
+  return threadlocal->buffer;
+}
-- 
2.20.1

_______________________________________________
Libguestfs mailing list
[email protected]
https://www.redhat.com/mailman/listinfo/libguestfs

Reply via email to