Blame SOURCES/flatpak-0.8.8-cve-2018-6560.patch

e929c9
From e00ded769dcdddea0169dd813c5878c915a63f6a Mon Sep 17 00:00:00 2001
e929c9
From: Alexander Larsson <alexl@redhat.com>
e929c9
Date: Sun, 28 Jan 2018 20:51:54 +0100
e929c9
Subject: [PATCH] Fix vulnerability in dbus proxy
e929c9
e929c9
During the authentication all client data is directly forwarded
e929c9
to the dbus daemon as is, until we detect the BEGIN command after
e929c9
which we start filtering the binary dbus protocol.
e929c9
e929c9
Unfortunately the detection of the BEGIN command in the proxy
e929c9
did not exactly match the detection in the dbus daemon. A BEGIN
e929c9
followed by a space or tab was considered ok in the daemon but
e929c9
not by the proxy. This could be exploited to send arbitrary
e929c9
dbus messages to the host, which can be used to break out of
e929c9
the sandbox.
e929c9
e929c9
This was noticed by Gabriel Campana of The Google Security Team.
e929c9
e929c9
This fix makes the detection of the authentication phase end
e929c9
match the dbus code. In addition we duplicate the authentication
e929c9
line validation from dbus, which includes ensuring all data is
e929c9
ASCII, and limiting the size of a line to 16k. In fact, we add
e929c9
some extra stringent checks, disallowing ASCII control chars and
e929c9
requiring that auth lines start with a capital letter.
e929c9
---
e929c9
 dbus-proxy/flatpak-proxy.c | 127 ++++++++++++++++++++++++++-----------
e929c9
 1 file changed, 89 insertions(+), 38 deletions(-)
e929c9
e929c9
diff --git a/dbus-proxy/flatpak-proxy.c b/dbus-proxy/flatpak-proxy.c
e929c9
index aee73993..ec90cba7 100644
e929c9
--- a/dbus-proxy/flatpak-proxy.c
e929c9
+++ b/dbus-proxy/flatpak-proxy.c
e929c9
@@ -173,10 +173,11 @@ typedef struct FlatpakProxyClient FlatpakProxyClient;
e929c9
 FlatpakPolicy flatpak_proxy_get_policy (FlatpakProxy *proxy,
e929c9
                                         const char   *name);
e929c9
 
e929c9
-/* We start looking ignoring the first cr-lf
e929c9
-   since there is no previous line initially */
e929c9
-#define AUTH_END_INIT_OFFSET 2
e929c9
-#define AUTH_END_STRING "\r\nBEGIN\r\n"
e929c9
+#define FIND_AUTH_END_CONTINUE -1
e929c9
+#define FIND_AUTH_END_ABORT -2
e929c9
+
e929c9
+#define AUTH_LINE_SENTINEL "\r\n"
e929c9
+#define AUTH_BEGIN "BEGIN"
e929c9
 
e929c9
 typedef enum {
e929c9
   EXPECTED_REPLY_NONE,
e929c9
@@ -251,7 +252,7 @@ struct FlatpakProxyClient
e929c9
   FlatpakProxy *proxy;
e929c9
 
e929c9
   gboolean      authenticated;
e929c9
-  int           auth_end_offset;
e929c9
+  GByteArray   *auth_buffer;
e929c9
 
e929c9
   ProxySide     client_side;
e929c9
   ProxySide     bus_side;
e929c9
@@ -363,6 +364,7 @@ flatpak_proxy_client_finalize (GObject *object)
e929c9
   client->proxy->clients = g_list_remove (client->proxy->clients, client);
e929c9
   g_clear_object (&client->proxy);
e929c9
 
e929c9
+  g_byte_array_free (client->auth_buffer, TRUE);
e929c9
   g_hash_table_destroy (client->rewrite_reply);
e929c9
   g_hash_table_destroy (client->get_owner_reply);
e929c9
   g_hash_table_destroy (client->unique_id_policy);
e929c9
@@ -398,7 +400,7 @@ flatpak_proxy_client_init (FlatpakProxyClient *client)
e929c9
   init_side (client, &client->client_side);
e929c9
   init_side (client, &client->bus_side);
e929c9
 
e929c9
-  client->auth_end_offset = AUTH_END_INIT_OFFSET;
e929c9
+  client->auth_buffer = g_byte_array_new ();
e929c9
   client->rewrite_reply = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref);
e929c9
   client->get_owner_reply = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
e929c9
   client->unique_id_policy = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
e929c9
@@ -2203,51 +2205,92 @@ got_buffer_from_side (ProxySide *side, Buffer *buffer)
e929c9
     got_buffer_from_bus (client, side, buffer);
e929c9
 }
e929c9
 
e929c9
+#define _DBUS_ISASCII(c) ((c) != '\0' && (((c) & ~0x7f) == 0))
e929c9
+
e929c9
+static gboolean
e929c9
+auth_line_is_valid (guint8 *line, guint8 *line_end)
e929c9
+{
e929c9
+  guint8 *p;
e929c9
+
e929c9
+  for (p = line; p < line_end; p++)
e929c9
+    {
e929c9
+      if (!_DBUS_ISASCII(*p))
e929c9
+        return FALSE;
e929c9
+
e929c9
+      /* Technically, the dbus spec allows all ASCII characters, but for robustness we also
e929c9
+         fail if we see any control characters. Such low values will appear in  potential attacks,
e929c9
+         but will never happen in real sasl (where all binary data is hex encoded). */
e929c9
+      if (*p < ' ')
e929c9
+        return FALSE;
e929c9
+    }
e929c9
+
e929c9
+  /* For robustness we require the first char of the line to be an upper case letter.
e929c9
+     This is not technically required by the dbus spec, but all commands are upper
e929c9
+     case, and there is no provisioning for whitespace before the command, so in practice
e929c9
+     this is true, and this means we're not confused by e.g. initial whitespace. */
e929c9
+  if (line[0] < 'A' || line[0] > 'Z')
e929c9
+    return FALSE;
e929c9
+
e929c9
+  return TRUE;
e929c9
+}
e929c9
+
e929c9
+static gboolean
e929c9
+auth_line_is_begin (guint8 *line)
e929c9
+{
e929c9
+  guint8 next_char;
e929c9
+
e929c9
+  if (!g_str_has_prefix ((char *)line, AUTH_BEGIN))
e929c9
+    return FALSE;
e929c9
+
e929c9
+  /* dbus-daemon accepts either nothing, or a whitespace followed by anything as end of auth */
e929c9
+  next_char = line[strlen (AUTH_BEGIN)];
e929c9
+  return (next_char == 0 ||
e929c9
+          next_char == ' ' ||
e929c9
+          next_char == '\t');
e929c9
+}
e929c9
+
e929c9
 static gssize
e929c9
 find_auth_end (FlatpakProxyClient *client, Buffer *buffer)
e929c9
 {
e929c9
-  guchar *match;
e929c9
-  int i;
e929c9
+  goffset offset = 0;
e929c9
+  gsize original_size = client->auth_buffer->len;
e929c9
+
e929c9
+  /* Add the new data to the remaining data from last iteration */
e929c9
+  g_byte_array_append (client->auth_buffer, buffer->data, buffer->pos);
e929c9
 
e929c9
-  /* First try to match any leftover at the start */
e929c9
-  if (client->auth_end_offset > 0)
e929c9
+  while (TRUE)
e929c9
     {
e929c9
-      gsize left = strlen (AUTH_END_STRING) - client->auth_end_offset;
e929c9
-      gsize to_match = MIN (left, buffer->pos);
e929c9
-      /* Matched at least up to to_match */
e929c9
-      if (memcmp (buffer->data, &AUTH_END_STRING[client->auth_end_offset], to_match) == 0)
e929c9
+      guint8 *line_start = client->auth_buffer->data + offset;
e929c9
+      gsize remaining_data = client->auth_buffer->len - offset;
e929c9
+      guint8 *line_end;
e929c9
+
e929c9
+      line_end = memmem (line_start, remaining_data,
e929c9
+                         AUTH_LINE_SENTINEL, strlen (AUTH_LINE_SENTINEL));
e929c9
+      if (line_end) /* Found end of line */
e929c9
         {
e929c9
-          client->auth_end_offset += to_match;
e929c9
+          offset = (line_end + strlen (AUTH_LINE_SENTINEL) - line_start);
e929c9
 
e929c9
-          /* Matched all */
e929c9
-          if (client->auth_end_offset == strlen (AUTH_END_STRING))
e929c9
-            return to_match;
e929c9
+          if (!auth_line_is_valid (line_start, line_end))
e929c9
+            return FIND_AUTH_END_ABORT;
e929c9
 
e929c9
-          /* Matched to end of buffer */
e929c9
-          return -1;
e929c9
-        }
e929c9
+          *line_end = 0;
e929c9
+          if (auth_line_is_begin (line_start))
e929c9
+            return offset - original_size;
e929c9
 
e929c9
-      /* Did not actually match at start */
e929c9
-      client->auth_end_offset = -1;
e929c9
-    }
e929c9
+          /* continue with next line */
e929c9
+        }
e929c9
+      else
e929c9
+        {
e929c9
+          /* No end-of-line in this buffer */
e929c9
+          g_byte_array_remove_range (client->auth_buffer, 0, offset);
e929c9
 
e929c9
-  /* Look for whole match inside buffer */
e929c9
-  match = memmem (buffer, buffer->pos,
e929c9
-                  AUTH_END_STRING, strlen (AUTH_END_STRING));
e929c9
-  if (match != NULL)
e929c9
-    return match - buffer->data + strlen (AUTH_END_STRING);
e929c9
+          /* Abort if more than 16k before newline, similar to what dbus-daemon does */
e929c9
+          if (client->auth_buffer->len >= 16*1024)
e929c9
+            return FIND_AUTH_END_ABORT;
e929c9
 
e929c9
-  /* Record longest prefix match at the end */
e929c9
-  for (i = MIN (strlen (AUTH_END_STRING) - 1, buffer->pos); i > 0; i--)
e929c9
-    {
e929c9
-      if (memcmp (buffer->data + buffer->pos - i, AUTH_END_STRING, i) == 0)
e929c9
-        {
e929c9
-          client->auth_end_offset = i;
e929c9
-          break;
e929c9
+          return FIND_AUTH_END_CONTINUE;
e929c9
         }
e929c9
     }
e929c9
-
e929c9
-  return -1;
e929c9
 }
e929c9
 
e929c9
 static gboolean
e929c9
@@ -2306,6 +2349,14 @@ side_in_cb (GSocket *socket, GIOCondition condition, gpointer user_data)
e929c9
                       if (extra_data > 0)
e929c9
                         side->extra_input_data = g_bytes_new (buffer->data + buffer->size, extra_data);
e929c9
                     }
e929c9
+                  else if (auth_end == FIND_AUTH_END_ABORT)
e929c9
+                    {
e929c9
+                      buffer_unref (buffer);
e929c9
+                      if (client->proxy->log_messages)
e929c9
+                        g_print ("Invalid AUTH line, aborting\n");
e929c9
+                      side_closed (side);
e929c9
+                      break;
e929c9
+                    }
e929c9
                 }
e929c9
 
e929c9
               got_buffer_from_side (side, buffer);