|
|
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);
|