Blame SOURCES/0002-server-Wait-until-handshake-complete-before-calling-.patch

dd8626
From 611f931d75feaf516da8097e6a69ff084b4ca38c Mon Sep 17 00:00:00 2001
dd8626
From: "Richard W.M. Jones" <rjones@redhat.com>
dd8626
Date: Wed, 11 Sep 2019 09:47:30 +0100
dd8626
Subject: [PATCH 2/2] server: Wait until handshake complete before calling
dd8626
 .open callback
dd8626
MIME-Version: 1.0
dd8626
Content-Type: text/plain; charset=UTF-8
dd8626
Content-Transfer-Encoding: 8bit
dd8626
dd8626
Currently we call the plugin .open callback as soon as we receive a
dd8626
TCP connection:
dd8626
dd8626
  $ nbdkit -fv --tls=require --tls-certificates=tests/pki null \
dd8626
           --run "telnet localhost 10809"
dd8626
  [...]
dd8626
  Trying ::1...
dd8626
  Connected to localhost.
dd8626
  Escape character is '^]'.
dd8626
  nbdkit: debug: accepted connection
dd8626
  nbdkit: debug: null: open readonly=0       ◀ NOTE
dd8626
  nbdkit: null[1]: debug: newstyle negotiation: flags: global 0x3
dd8626
  NBDMAGICIHAVEOPT
dd8626
dd8626
In plugins such as curl, guestfs, ssh, vddk and others we do a
dd8626
considerable amount of work in the .open callback (such as making a
dd8626
remote connection or launching an appliance).  Therefore we are
dd8626
providing an easy Denial of Service / Amplification Attack for
dd8626
unauthorized clients to cause a lot of work to be done for only the
dd8626
cost of a simple TCP 3 way handshake.
dd8626
dd8626
This commit moves the call to the .open callback after the NBD
dd8626
handshake has mostly completed.  In particular TLS authentication must
dd8626
be complete before we will call into the plugin.
dd8626
dd8626
It is unlikely that there are plugins which really depend on the
dd8626
current behaviour of .open (which I found surprising even though I
dd8626
guess I must have written it).  If there are then we could add a new
dd8626
.connect callback or similar to allow plugins to get control at the
dd8626
earlier point in the connection.
dd8626
dd8626
After this change you can see that the .open callback is not called
dd8626
from just a simple TCP connection:
dd8626
dd8626
  $ ./nbdkit -fv --tls=require --tls-certificates=tests/pki null \
dd8626
             --run "telnet localhost 10809"
dd8626
  [...]
dd8626
  Trying ::1...
dd8626
  Connected to localhost.
dd8626
  Escape character is '^]'.
dd8626
  nbdkit: debug: accepted connection
dd8626
  nbdkit: null[1]: debug: newstyle negotiation: flags: global 0x3
dd8626
  NBDMAGICIHAVEOPT
dd8626
  xx
dd8626
  nbdkit: null[1]: debug: newstyle negotiation: client flags: 0xd0a7878
dd8626
  nbdkit: null[1]: error: client requested unknown flags 0xd0a7878
dd8626
  Connection closed by foreign host.
dd8626
  nbdkit: debug: null: unload plugin
dd8626
dd8626
Signed-off-by: Richard W.M. Jones <rjones@redhat.com>
dd8626
(cherry picked from commit c05686f9577fa91b6a3a4d8c065954ca6fc3fd62)
dd8626
(cherry picked from commit e06cde00659ff97182173d0e33fff784041bcb4a)
dd8626
---
dd8626
 src/connections.c | 83 +++++++++++++++++++++++++++----------------------------
dd8626
 1 file changed, 40 insertions(+), 43 deletions(-)
dd8626
dd8626
diff --git a/src/connections.c b/src/connections.c
dd8626
index e1ffeff..6e7c080 100644
dd8626
--- a/src/connections.c
dd8626
+++ b/src/connections.c
dd8626
@@ -246,12 +246,6 @@ _handle_single_connection (int sockin, int sockout)
dd8626
   if (!conn)
dd8626
     goto done;
dd8626
 
dd8626
-  lock_request (conn);
dd8626
-  r = backend->open (backend, conn, readonly);
dd8626
-  unlock_request (conn);
dd8626
-  if (r == -1)
dd8626
-    goto done;
dd8626
-
dd8626
   /* NB: because of an asynchronous exit backend can be set to NULL at
dd8626
    * just about any time.
dd8626
    */
dd8626
@@ -261,17 +255,11 @@ _handle_single_connection (int sockin, int sockout)
dd8626
     plugin_name = "(unknown)";
dd8626
   threadlocal_set_name (plugin_name);
dd8626
 
dd8626
-  /* Prepare (for filters), called just after open. */
dd8626
-  lock_request (conn);
dd8626
-  if (backend)
dd8626
-    r = backend->prepare (backend, conn);
dd8626
-  else
dd8626
-    r = 0;
dd8626
-  unlock_request (conn);
dd8626
-  if (r == -1)
dd8626
-    goto done;
dd8626
-
dd8626
-  /* Handshake. */
dd8626
+  /* NBD handshake.
dd8626
+   *
dd8626
+   * Note that this calls the backend .open callback when it is safe
dd8626
+   * to do so (eg. after TLS authentication).
dd8626
+   */
dd8626
   if (negotiate_handshake (conn) == -1)
dd8626
     goto done;
dd8626
 
dd8626
@@ -408,12 +396,43 @@ free_connection (struct connection *conn)
dd8626
   free (conn);
dd8626
 }
dd8626
 
dd8626
+/* Common code used by oldstyle and newstyle protocols to:
dd8626
+ *
dd8626
+ * - call the backend .open method
dd8626
+ *
dd8626
+ * - get the export size
dd8626
+ *
dd8626
+ * - compute the eflags (same between oldstyle and newstyle
dd8626
+ *   protocols)
dd8626
+ *
dd8626
+ * The protocols must defer this as late as possible so that
dd8626
+ * unauthorized clients can't cause unnecessary work in .open by
dd8626
+ * simply opening a TCP connection.
dd8626
+ */
dd8626
 static int
dd8626
-compute_eflags (struct connection *conn, uint16_t *flags)
dd8626
+protocol_common_open (struct connection *conn,
dd8626
+                      uint64_t *exportsize, uint16_t *flags)
dd8626
 {
dd8626
+  int64_t size;
dd8626
   uint16_t eflags = NBD_FLAG_HAS_FLAGS;
dd8626
   int fl;
dd8626
 
dd8626
+  if (backend->open (backend, conn, readonly) == -1)
dd8626
+    return -1;
dd8626
+
dd8626
+  /* Prepare (for filters), called just after open. */
dd8626
+  if (backend->prepare (backend, conn) == -1)
dd8626
+    return -1;
dd8626
+
dd8626
+  size = backend->get_size (backend, conn);
dd8626
+  if (size == -1)
dd8626
+    return -1;
dd8626
+  if (size < 0) {
dd8626
+    nbdkit_error (".get_size function returned invalid value "
dd8626
+                  "(%" PRIi64 ")", size);
dd8626
+    return -1;
dd8626
+  }
dd8626
+
dd8626
   fl = backend->can_write (backend, conn);
dd8626
   if (fl == -1)
dd8626
     return -1;
dd8626
@@ -463,6 +482,7 @@ compute_eflags (struct connection *conn, uint16_t *flags)
dd8626
     conn->is_rotational = 1;
dd8626
   }
dd8626
 
dd8626
+  *exportsize = size;
dd8626
   *flags = eflags;
dd8626
   return 0;
dd8626
 }
dd8626
@@ -471,7 +491,6 @@ static int
dd8626
 _negotiate_handshake_oldstyle (struct connection *conn)
dd8626
 {
dd8626
   struct old_handshake handshake;
dd8626
-  int64_t r;
dd8626
   uint64_t exportsize;
dd8626
   uint16_t gflags, eflags;
dd8626
 
dd8626
@@ -483,21 +502,11 @@ _negotiate_handshake_oldstyle (struct connection *conn)
dd8626
     return -1;
dd8626
   }
dd8626
 
dd8626
-  r = backend->get_size (backend, conn);
dd8626
-  if (r == -1)
dd8626
+  if (protocol_common_open (conn, &exportsize, &eflags) == -1)
dd8626
     return -1;
dd8626
-  if (r < 0) {
dd8626
-    nbdkit_error (".get_size function returned invalid value "
dd8626
-                  "(%" PRIi64 ")", r);
dd8626
-    return -1;
dd8626
-  }
dd8626
-  exportsize = (uint64_t) r;
dd8626
   conn->exportsize = exportsize;
dd8626
 
dd8626
   gflags = 0;
dd8626
-  if (compute_eflags (conn, &eflags) < 0)
dd8626
-    return -1;
dd8626
-
dd8626
   debug ("oldstyle negotiation: flags: global 0x%x export 0x%x",
dd8626
          gflags, eflags);
dd8626
 
dd8626
@@ -607,19 +616,7 @@ send_newstyle_option_reply_info_export (struct connection *conn,
dd8626
 static int
dd8626
 finish_newstyle_options (struct connection *conn)
dd8626
 {
dd8626
-  int64_t r;
dd8626
-
dd8626
-  r = backend->get_size (backend, conn);
dd8626
-  if (r == -1)
dd8626
-    return -1;
dd8626
-  if (r < 0) {
dd8626
-    nbdkit_error (".get_size function returned invalid value "
dd8626
-                  "(%" PRIi64 ")", r);
dd8626
-    return -1;
dd8626
-  }
dd8626
-  conn->exportsize = (uint64_t) r;
dd8626
-
dd8626
-  if (compute_eflags (conn, &conn->eflags) < 0)
dd8626
+  if (protocol_common_open (conn, &conn->exportsize, &conn->eflags) == -1)
dd8626
     return -1;
dd8626
 
dd8626
   debug ("newstyle negotiation: flags: export 0x%x", conn->eflags);
dd8626
-- 
dd8626
1.8.3.1
dd8626