[glib: 1/2] gsocket: Fix credentials error-handling on Apple OSes




commit 9ac3a27f0360feb6eba78ba64270e1db827eb26d
Author: Ole André Vadla Ravnås <oleavr gmail com>
Date:   Tue Dec 29 21:41:34 2020 +0100

    gsocket: Fix credentials error-handling on Apple OSes
    
    - When querying a TCP socket, getsockopt() may succeed but the resulting
      `optlen` will be zero. This means we'd previously be reading
      uninitialized stack memory in such cases.
    - After a file-descriptor has gone through FD-passing, getsockopt() may
      fail with EINVAL. At least this is the case with TCP sockets.
    - While at it also use SOL_LOCAL instead of hard-coding its value.

 gio/gsocket.c      |  14 +++-
 gio/tests/socket.c | 218 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 230 insertions(+), 2 deletions(-)
---
diff --git a/gio/gsocket.c b/gio/gsocket.c
index e911de781..5d8ecfb9c 100644
--- a/gio/gsocket.c
+++ b/gio/gsocket.c
@@ -5965,10 +5965,11 @@ g_socket_get_credentials (GSocket   *socket,
     socklen_t optlen = sizeof (cred);
 
     if (getsockopt (socket->priv->fd,
-                    0,
+                    SOL_LOCAL,
                     LOCAL_PEERCRED,
                     &cred,
-                    &optlen) == 0)
+                    &optlen) == 0
+        && optlen != 0)
       {
         if (cred.cr_version == XUCRED_VERSION)
           {
@@ -5993,6 +5994,15 @@ g_socket_get_credentials (GSocket   *socket,
             return NULL;
           }
       }
+    else if (optlen == 0 || errno == EINVAL)
+      {
+        g_set_error (error,
+                     G_IO_ERROR,
+                     G_IO_ERROR_NOT_SUPPORTED,
+                     _("Unable to read socket credentials: %s"),
+                     "unsupported socket type");
+        return NULL;
+      }
   }
 #elif G_CREDENTIALS_USE_NETBSD_UNPCBID
   {
diff --git a/gio/tests/socket.c b/gio/tests/socket.c
index d6eecdc38..683866ede 100644
--- a/gio/tests/socket.c
+++ b/gio/tests/socket.c
@@ -18,6 +18,7 @@
 
 #include <gio/gio.h>
 
+#include <gio/gcredentialsprivate.h>
 #ifdef G_OS_UNIX
 #include <errno.h>
 #include <sys/wait.h>
@@ -1898,6 +1899,216 @@ test_read_write (gconstpointer user_data)
   g_object_unref (client);
 }
 
+#if G_CREDENTIALS_SUPPORTED
+static gpointer client_setup_thread (gpointer user_data);
+
+static void
+test_credentials_tcp_client (void)
+{
+  const GSocketFamily family = G_SOCKET_FAMILY_IPV4;
+  IPTestData *data;
+  GError *error = NULL;
+  GSocket *client;
+  GSocketAddress *addr;
+  GCredentials *creds;
+
+  data = create_server (family, echo_server_thread, FALSE, &error);
+  if (error != NULL)
+    {
+      gchar *message = g_strdup_printf ("Failed to create server: %s", error->message);
+      g_test_skip (message);
+      g_free (message);
+      g_clear_error (&error);
+      return;
+    }
+
+  addr = g_socket_get_local_address (data->server, &error);
+  g_assert_no_error (error);
+
+  client = g_socket_new (family,
+                        G_SOCKET_TYPE_STREAM,
+                        G_SOCKET_PROTOCOL_DEFAULT,
+                        &error);
+  g_assert_no_error (error);
+
+  g_socket_set_blocking (client, TRUE);
+  g_socket_set_timeout (client, 1);
+
+  g_socket_connect (client, addr, NULL, &error);
+  g_assert_no_error (error);
+  g_object_unref (addr);
+
+  creds = g_socket_get_credentials (client, &error);
+  if (creds != NULL)
+    {
+      gchar *str = g_credentials_to_string (creds);
+      g_print ("Supported on this OS: %s\n", str);
+      g_free (str);
+      g_clear_object (&creds);
+    }
+  else
+    {
+      g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED);
+      g_print ("Unsupported on this OS: %s\n", error->message);
+      g_clear_error (&error);
+    }
+
+  g_socket_close (client, &error);
+  g_assert_no_error (error);
+
+  g_thread_join (data->thread);
+
+  g_socket_close (data->server, &error);
+  g_assert_no_error (error);
+
+  g_object_unref (data->server);
+  g_object_unref (client);
+
+  g_slice_free (IPTestData, data);
+}
+
+static void
+test_credentials_tcp_server (void)
+{
+  const GSocketFamily family = G_SOCKET_FAMILY_IPV4;
+  IPTestData *data;
+  GSocket *server;
+  GError *error = NULL;
+  GSocketAddress *addr = NULL;
+  GInetAddress *iaddr = NULL;
+  GSocket *sock = NULL;
+  GCredentials *creds;
+
+  data = g_slice_new0 (IPTestData);
+  data->family = family;
+  data->server = server = g_socket_new (family,
+                                       G_SOCKET_TYPE_STREAM,
+                                       G_SOCKET_PROTOCOL_DEFAULT,
+                                       &error);
+  if (error != NULL)
+    goto skip;
+
+  g_socket_set_blocking (server, TRUE);
+
+  iaddr = g_inet_address_new_loopback (family);
+  addr = g_inet_socket_address_new (iaddr, 0);
+
+  if (!g_socket_bind (server, addr, TRUE, &error))
+    goto skip;
+
+  if (!g_socket_listen (server, &error))
+    goto skip;
+
+  data->thread = g_thread_new ("client", client_setup_thread, data);
+
+  sock = g_socket_accept (server, NULL, &error);
+  g_assert_no_error (error);
+
+  creds = g_socket_get_credentials (sock, &error);
+  if (creds != NULL)
+    {
+      gchar *str = g_credentials_to_string (creds);
+      g_print ("Supported on this OS: %s\n", str);
+      g_free (str);
+      g_clear_object (&creds);
+    }
+  else
+    {
+      g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED);
+      g_print ("Unsupported on this OS: %s\n", error->message);
+      g_clear_error (&error);
+    }
+
+  goto beach;
+
+skip:
+  {
+    gchar *message = g_strdup_printf ("Failed to create server: %s", error->message);
+    g_test_skip (message);
+    g_free (message);
+
+    goto beach;
+  }
+beach:
+  {
+    g_clear_error (&error);
+
+    g_clear_object (&sock);
+    g_clear_object (&addr);
+    g_clear_object (&iaddr);
+
+    g_clear_pointer (&data->thread, g_thread_join);
+    g_clear_object (&data->server);
+    g_clear_object (&data->client);
+
+    g_slice_free (IPTestData, data);
+  }
+}
+
+static gpointer
+client_setup_thread (gpointer user_data)
+{
+  IPTestData *data = user_data;
+  GSocketAddress *addr;
+  GSocket *client;
+  GError *error = NULL;
+
+  addr = g_socket_get_local_address (data->server, &error);
+  g_assert_no_error (error);
+
+  data->client = client = g_socket_new (data->family,
+                                       G_SOCKET_TYPE_STREAM,
+                                       G_SOCKET_PROTOCOL_DEFAULT,
+                                       &error);
+  g_assert_no_error (error);
+
+  g_socket_set_blocking (client, TRUE);
+  g_socket_set_timeout (client, 1);
+
+  g_socket_connect (client, addr, NULL, &error);
+  g_assert_no_error (error);
+
+  g_object_unref (addr);
+
+  return NULL;
+}
+
+#ifdef G_OS_UNIX
+static void
+test_credentials_unix_socketpair (void)
+{
+  gint fds[2];
+  gint status;
+  GSocket *sock;
+  GError *error = NULL;
+  GCredentials *creds;
+
+  status = socketpair (PF_UNIX, SOCK_STREAM, 0, fds);
+  g_assert_cmpint (status, ==, 0);
+
+  sock = g_socket_new_from_fd (fds[0], &error);
+
+  creds = g_socket_get_credentials (sock, &error);
+  if (creds != NULL)
+    {
+      gchar *str = g_credentials_to_string (creds);
+      g_print ("Supported on this OS: %s\n", str);
+      g_free (str);
+      g_clear_object (&creds);
+    }
+  else
+    {
+      g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED);
+      g_print ("Unsupported on this OS: %s\n", error->message);
+      g_clear_error (&error);
+    }
+
+  g_object_unref (sock);
+  close (fds[1]);
+}
+#endif
+#endif
+
 int
 main (int   argc,
       char *argv[])
@@ -1954,6 +2165,13 @@ main (int   argc,
                         test_read_write);
   g_test_add_data_func ("/socket/read_writev", GUINT_TO_POINTER (TRUE),
                         test_read_write);
+#if G_CREDENTIALS_SUPPORTED
+  g_test_add_func ("/socket/credentials/tcp_client", test_credentials_tcp_client);
+  g_test_add_func ("/socket/credentials/tcp_server", test_credentials_tcp_server);
+#ifdef G_OS_UNIX
+  g_test_add_func ("/socket/credentials/unix_socketpair", test_credentials_unix_socketpair);
+#endif
+#endif
 
   return g_test_run();
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]