From 9da49d4eb6982c659fec988231baef8cd1b05be2 Mon Sep 17 00:00:00 2001
From: Simon McVittie <simon.mcvittie@collabora.co.uk>
Date: Wed, 11 Feb 2015 13:19:15 +0000
Subject: [PATCH 3/8] Add LSM-agnostic support for LinuxSecurityLabel
 credential

Bug: https://bugs.freedesktop.org/show_bug.cgi?id=89041
Change-Id: I70512843d1a7661c87461b1b6d86fbfbda934ad5
Reviewed-by: Philip Withnall <philip.withnall@collabora.co.uk>
Acked-by: Stephen Smalley <sds@tycho.nsa.gov> (for SELinux)
Acked-by: John Johansen <john.johansen@canonical.com> (for AppArmor)
Acked-by: Casey Schaufler <casey@schaufler-ca.com> (for Smack)
Tested-by: Tyler Hicks <tyhicks@canonical.com>
---
 bus/driver.c                    |  19 ++++++++
 dbus/dbus-auth.c                |  11 +++--
 dbus/dbus-connection-internal.h |   3 ++
 dbus/dbus-connection.c          |  26 ++++++++++
 dbus/dbus-credentials.c         |  68 ++++++++++++++++++++++++++
 dbus/dbus-credentials.h         |   4 ++
 dbus/dbus-sysdeps-unix.c        | 105 ++++++++++++++++++++++++++++++++++++++++
 dbus/dbus-transport.c           |  27 +++++++++++
 dbus/dbus-transport.h           |   3 ++
 9 files changed, 262 insertions(+), 4 deletions(-)

diff --git a/bus/driver.c b/bus/driver.c
index 888c7ca..11706f8 100644
--- a/bus/driver.c
+++ b/bus/driver.c
@@ -34,6 +34,7 @@
 #include "utils.h"
 
 #include <dbus/dbus-asv-util.h>
+#include <dbus/dbus-connection-internal.h>
 #include <dbus/dbus-string.h>
 #include <dbus/dbus-internals.h>
 #include <dbus/dbus-message.h>
@@ -1567,6 +1568,7 @@ bus_driver_handle_get_connection_credentials (DBusConnection *connection,
   DBusMessageIter reply_iter;
   DBusMessageIter array_iter;
   unsigned long ulong_val;
+  char *s;
   const char *service;
 
   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
@@ -1601,6 +1603,23 @@ bus_driver_handle_get_connection_credentials (DBusConnection *connection,
         goto oom;
     }
 
+  if (_dbus_connection_get_linux_security_label (conn, &s))
+    {
+      if (s == NULL)
+        goto oom;
+
+      /* use the GVariant bytestring convention for strings of unknown
+       * encoding: include the \0 in the payload, for zero-copy reading */
+      if (!_dbus_asv_add_byte_array (&array_iter, "LinuxSecurityLabel",
+                                     s, strlen (s) + 1))
+        {
+          dbus_free (s);
+          goto oom;
+        }
+
+      dbus_free (s);
+    }
+
   if (!_dbus_asv_close (&reply_iter, &array_iter))
     goto oom;
 
diff --git a/dbus/dbus-auth.c b/dbus/dbus-auth.c
index 6a07665..aee877d 100644
--- a/dbus/dbus-auth.c
+++ b/dbus/dbus-auth.c
@@ -1102,20 +1102,23 @@ handle_server_data_external_mech (DBusAuth         *auth,
                                               auth->desired_identity))
         return FALSE;
 
-      /* also copy process ID from the socket credentials
+      /* also copy misc process info from the socket credentials
        */
       if (!_dbus_credentials_add_credential (auth->authorized_identity,
                                              DBUS_CREDENTIAL_UNIX_PROCESS_ID,
                                              auth->credentials))
         return FALSE;
 
-      /* also copy audit data from the socket credentials
-       */
       if (!_dbus_credentials_add_credential (auth->authorized_identity,
                                              DBUS_CREDENTIAL_ADT_AUDIT_DATA_ID,
                                              auth->credentials))
         return FALSE;
-      
+
+      if (!_dbus_credentials_add_credential (auth->authorized_identity,
+                                             DBUS_CREDENTIAL_LINUX_SECURITY_LABEL,
+                                             auth->credentials))
+        return FALSE;
+
       if (!send_ok (auth))
         return FALSE;
 
diff --git a/dbus/dbus-connection-internal.h b/dbus/dbus-connection-internal.h
index 2897404..64ef336 100644
--- a/dbus/dbus-connection-internal.h
+++ b/dbus/dbus-connection-internal.h
@@ -107,6 +107,9 @@ void              _dbus_connection_set_pending_fds_function       (DBusConnectio
                                                                    DBusPendingFdsChangeFunction callback,
                                                                    void *data);
 
+dbus_bool_t       _dbus_connection_get_linux_security_label       (DBusConnection  *connection,
+                                                                   char           **label_p);
+
 /* if DBUS_ENABLE_STATS */
 void _dbus_connection_get_stats (DBusConnection *connection,
                                  dbus_uint32_t  *in_messages,
diff --git a/dbus/dbus-connection.c b/dbus/dbus-connection.c
index b574207..8952b75 100644
--- a/dbus/dbus-connection.c
+++ b/dbus/dbus-connection.c
@@ -5322,6 +5322,32 @@ dbus_connection_set_unix_user_function (DBusConnection             *connection,
     (* old_free_function) (old_data);
 }
 
+/* Same calling convention as dbus_connection_get_windows_user */
+dbus_bool_t
+_dbus_connection_get_linux_security_label (DBusConnection  *connection,
+                                           char           **label_p)
+{
+  dbus_bool_t result;
+
+  _dbus_assert (connection != NULL);
+  _dbus_assert (label_p != NULL);
+
+  CONNECTION_LOCK (connection);
+
+  if (!_dbus_transport_try_to_authenticate (connection->transport))
+    result = FALSE;
+  else
+    result = _dbus_transport_get_linux_security_label (connection->transport,
+                                                       label_p);
+#ifndef __linux__
+  _dbus_assert (!result);
+#endif
+
+  CONNECTION_UNLOCK (connection);
+
+  return result;
+}
+
 /**
  * Gets the Windows user SID of the connection if known.  Returns
  * #TRUE if the ID is filled in.  Always returns #FALSE on non-Windows
diff --git a/dbus/dbus-credentials.c b/dbus/dbus-credentials.c
index 7325125..151bb00 100644
--- a/dbus/dbus-credentials.c
+++ b/dbus/dbus-credentials.c
@@ -50,6 +50,7 @@ struct DBusCredentials {
   dbus_uid_t unix_uid;
   dbus_pid_t pid;
   char *windows_sid;
+  char *linux_security_label;
   void *adt_audit_data;
   dbus_int32_t adt_audit_data_size;
 };
@@ -79,6 +80,7 @@ _dbus_credentials_new (void)
   creds->unix_uid = DBUS_UID_UNSET;
   creds->pid = DBUS_PID_UNSET;
   creds->windows_sid = NULL;
+  creds->linux_security_label = NULL;
   creds->adt_audit_data = NULL;
   creds->adt_audit_data_size = 0;
 
@@ -133,6 +135,7 @@ _dbus_credentials_unref (DBusCredentials    *credentials)
   if (credentials->refcount == 0)
     {
       dbus_free (credentials->windows_sid);
+      dbus_free (credentials->linux_security_label);
       dbus_free (credentials->adt_audit_data);
       dbus_free (credentials);
     }
@@ -193,6 +196,30 @@ _dbus_credentials_add_windows_sid (DBusCredentials    *credentials,
 }
 
 /**
+ * Add a Linux security label, as used by LSMs such as SELinux, Smack and
+ * AppArmor, to the credentials.
+ *
+ * @param credentials the object
+ * @param label the label
+ * @returns #FALSE if no memory
+ */
+dbus_bool_t
+_dbus_credentials_add_linux_security_label (DBusCredentials    *credentials,
+                                            const char         *label)
+{
+  char *copy;
+
+  copy = _dbus_strdup (label);
+  if (copy == NULL)
+    return FALSE;
+
+  dbus_free (credentials->linux_security_label);
+  credentials->linux_security_label = copy;
+
+  return TRUE;
+}
+
+/**
  * Add ADT audit data to the credentials.
  *
  * @param credentials the object
@@ -236,6 +263,8 @@ _dbus_credentials_include (DBusCredentials    *credentials,
       return credentials->unix_uid != DBUS_UID_UNSET;
     case DBUS_CREDENTIAL_WINDOWS_SID:
       return credentials->windows_sid != NULL;
+    case DBUS_CREDENTIAL_LINUX_SECURITY_LABEL:
+      return credentials->linux_security_label != NULL;
     case DBUS_CREDENTIAL_ADT_AUDIT_DATA_ID:
       return credentials->adt_audit_data != NULL;
     }
@@ -284,6 +313,19 @@ _dbus_credentials_get_windows_sid (DBusCredentials    *credentials)
 }
 
 /**
+ * Gets the Linux security label (as used by LSMs) from the credentials,
+ * or #NULL if the credentials object doesn't contain a security label.
+ *
+ * @param credentials the object
+ * @returns the security label
+ */
+const char *
+_dbus_credentials_get_linux_security_label (DBusCredentials *credentials)
+{
+  return credentials->linux_security_label;
+}
+
+/**
  * Gets the ADT audit data in the credentials, or #NULL if
  * the credentials object doesn't contain ADT audit data.
  *
@@ -329,6 +371,10 @@ _dbus_credentials_are_superset (DBusCredentials    *credentials,
     (possible_subset->windows_sid == NULL ||
      (credentials->windows_sid && strcmp (possible_subset->windows_sid,
                                           credentials->windows_sid) == 0)) &&
+    (possible_subset->linux_security_label == NULL ||
+     (credentials->linux_security_label != NULL &&
+      strcmp (possible_subset->linux_security_label,
+              credentials->linux_security_label) == 0)) &&
     (possible_subset->adt_audit_data == NULL ||
      (credentials->adt_audit_data && memcmp (possible_subset->adt_audit_data,
                                              credentials->adt_audit_data,
@@ -348,6 +394,7 @@ _dbus_credentials_are_empty (DBusCredentials    *credentials)
     credentials->pid == DBUS_PID_UNSET &&
     credentials->unix_uid == DBUS_UID_UNSET &&
     credentials->windows_sid == NULL &&
+    credentials->linux_security_label == NULL &&
     credentials->adt_audit_data == NULL;
 }
 
@@ -388,6 +435,9 @@ _dbus_credentials_add_credentials (DBusCredentials    *credentials,
                                       DBUS_CREDENTIAL_ADT_AUDIT_DATA_ID,
                                       other_credentials) &&
     _dbus_credentials_add_credential (credentials,
+                                      DBUS_CREDENTIAL_LINUX_SECURITY_LABEL,
+                                      other_credentials) &&
+    _dbus_credentials_add_credential (credentials,
                                       DBUS_CREDENTIAL_WINDOWS_SID,
                                       other_credentials);
 }
@@ -427,6 +477,13 @@ _dbus_credentials_add_credential (DBusCredentials    *credentials,
       if (!_dbus_credentials_add_windows_sid (credentials, other_credentials->windows_sid))
         return FALSE;
     } 
+  else if (which == DBUS_CREDENTIAL_LINUX_SECURITY_LABEL &&
+           other_credentials->linux_security_label != NULL)
+    {
+      if (!_dbus_credentials_add_linux_security_label (credentials,
+            other_credentials->linux_security_label))
+        return FALSE;
+    }
   else if (which == DBUS_CREDENTIAL_ADT_AUDIT_DATA_ID &&
            other_credentials->adt_audit_data != NULL) 
     {
@@ -449,6 +506,8 @@ _dbus_credentials_clear (DBusCredentials    *credentials)
   credentials->unix_uid = DBUS_UID_UNSET;
   dbus_free (credentials->windows_sid);
   credentials->windows_sid = NULL;
+  dbus_free (credentials->linux_security_label);
+  credentials->linux_security_label = NULL;
   dbus_free (credentials->adt_audit_data);
   credentials->adt_audit_data = NULL;
   credentials->adt_audit_data_size = 0;
@@ -540,6 +599,15 @@ _dbus_credentials_to_string_append (DBusCredentials    *credentials,
   else
     join = FALSE;
 
+  if (credentials->linux_security_label != NULL)
+    {
+      if (!_dbus_string_append_printf (string, "%slsm='%s'",
+                                       join ? " " : "",
+                                       credentials->linux_security_label))
+        goto oom;
+      join = TRUE;
+    }
+
   return TRUE;
 oom:
   return FALSE;
diff --git a/dbus/dbus-credentials.h b/dbus/dbus-credentials.h
index abcc4bb..ab74eac 100644
--- a/dbus/dbus-credentials.h
+++ b/dbus/dbus-credentials.h
@@ -34,6 +34,7 @@ typedef enum {
   DBUS_CREDENTIAL_UNIX_PROCESS_ID,
   DBUS_CREDENTIAL_UNIX_USER_ID,
   DBUS_CREDENTIAL_ADT_AUDIT_DATA_ID,
+  DBUS_CREDENTIAL_LINUX_SECURITY_LABEL,
   DBUS_CREDENTIAL_WINDOWS_SID
 } DBusCredentialType;
 
@@ -47,6 +48,8 @@ dbus_bool_t      _dbus_credentials_add_unix_uid             (DBusCredentials
                                                              dbus_uid_t          uid);
 dbus_bool_t      _dbus_credentials_add_windows_sid          (DBusCredentials    *credentials,
                                                              const char         *windows_sid);
+dbus_bool_t      _dbus_credentials_add_linux_security_label (DBusCredentials    *credentials,
+                                                             const char         *label);
 dbus_bool_t      _dbus_credentials_add_adt_audit_data       (DBusCredentials    *credentials,
                                                              void               *audit_data,
                                                              dbus_int32_t        size);
@@ -55,6 +58,7 @@ dbus_bool_t      _dbus_credentials_include                  (DBusCredentials
 dbus_pid_t       _dbus_credentials_get_pid                  (DBusCredentials    *credentials);
 dbus_uid_t       _dbus_credentials_get_unix_uid             (DBusCredentials    *credentials);
 const char*      _dbus_credentials_get_windows_sid          (DBusCredentials    *credentials);
+const char *     _dbus_credentials_get_linux_security_label (DBusCredentials    *credentials);
 void *           _dbus_credentials_get_adt_audit_data       (DBusCredentials    *credentials);
 dbus_int32_t     _dbus_credentials_get_adt_audit_data_size  (DBusCredentials    *credentials);
 dbus_bool_t      _dbus_credentials_are_superset             (DBusCredentials    *credentials,
diff --git a/dbus/dbus-sysdeps-unix.c b/dbus/dbus-sysdeps-unix.c
index fe891ab..61af423 100644
--- a/dbus/dbus-sysdeps-unix.c
+++ b/dbus/dbus-sysdeps-unix.c
@@ -1639,6 +1639,105 @@ write_credentials_byte (int             server_fd,
     }
 }
 
+/* return FALSE on OOM, TRUE otherwise, even if no credentials were found */
+static dbus_bool_t
+add_linux_security_label_to_credentials (int              client_fd,
+                                         DBusCredentials *credentials)
+{
+#if defined(__linux__) && defined(SO_PEERSEC)
+  DBusString buf;
+  socklen_t len = 1024;
+  dbus_bool_t oom = FALSE;
+
+  if (!_dbus_string_init_preallocated (&buf, len) ||
+      !_dbus_string_set_length (&buf, len))
+    return FALSE;
+
+  while (getsockopt (client_fd, SOL_SOCKET, SO_PEERSEC,
+         _dbus_string_get_data (&buf), &len) < 0)
+    {
+      int e = errno;
+
+      _dbus_verbose ("getsockopt failed with %s, len now %lu\n",
+                     _dbus_strerror (e), (unsigned long) len);
+
+      if (e != ERANGE || len <= _dbus_string_get_length (&buf))
+        {
+          _dbus_verbose ("Failed to getsockopt(SO_PEERSEC): %s\n",
+                         _dbus_strerror (e));
+          goto out;
+        }
+
+      /* If not enough space, len is updated to be enough.
+       * Try again with a large enough buffer. */
+      if (!_dbus_string_set_length (&buf, len))
+        {
+          oom = TRUE;
+          goto out;
+        }
+
+      _dbus_verbose ("will try again with %lu\n", (unsigned long) len);
+    }
+
+  if (len <= 0)
+    {
+      _dbus_verbose ("getsockopt(SO_PEERSEC) yielded <= 0 bytes: %lu\n",
+                     (unsigned long) len);
+      goto out;
+    }
+
+  if (len > _dbus_string_get_length (&buf))
+    {
+      _dbus_verbose ("%lu > %d", (unsigned long) len,
+                     _dbus_string_get_length (&buf));
+      _dbus_assert_not_reached ("getsockopt(SO_PEERSEC) overflowed");
+    }
+
+  if (_dbus_string_get_byte (&buf, len - 1) == 0)
+    {
+      /* the kernel included the trailing \0 in its count,
+       * but DBusString always has an extra \0 after the data anyway */
+      _dbus_verbose ("subtracting trailing \\0\n");
+      len--;
+    }
+
+  if (!_dbus_string_set_length (&buf, len))
+    {
+      _dbus_assert_not_reached ("shortening string should not lead to OOM");
+      oom = TRUE;
+      goto out;
+    }
+
+  if (strlen (_dbus_string_get_const_data (&buf)) != len)
+    {
+      /* LSM people on the linux-security-module@ mailing list say this
+       * should never happen: the label should be a bytestring with
+       * an optional trailing \0 */
+      _dbus_verbose ("security label from kernel had an embedded \\0, "
+                     "ignoring it\n");
+      goto out;
+    }
+
+  _dbus_verbose ("getsockopt(SO_PEERSEC): %lu bytes excluding \\0: %s\n",
+                 (unsigned long) len,
+                 _dbus_string_get_const_data (&buf));
+
+  if (!_dbus_credentials_add_linux_security_label (credentials,
+        _dbus_string_get_const_data (&buf)))
+    {
+      oom = TRUE;
+      goto out;
+    }
+
+out:
+  _dbus_string_free (&buf);
+  return !oom;
+#else
+  /* no error */
+  return TRUE;
+#endif
+}
+
 /**
  * Reads a single byte which must be nul (an error occurs otherwise),
  * and reads unix credentials if available. Clears the credentials
@@ -1922,6 +2021,12 @@ _dbus_read_credentials_socket  (int              client_fd,
         }
     }
 
+  if (!add_linux_security_label_to_credentials (client_fd, credentials))
+    {
+      _DBUS_SET_OOM (error);
+      return FALSE;
+    }
+
   return TRUE;
 }
 
diff --git a/dbus/dbus-transport.c b/dbus/dbus-transport.c
index e9dcc56..a43e7bb 100644
--- a/dbus/dbus-transport.c
+++ b/dbus/dbus-transport.c
@@ -1425,6 +1425,33 @@ _dbus_transport_set_unix_user_function (DBusTransport             *transport,
   transport->free_unix_user_data = free_data_function;
 }
 
+dbus_bool_t
+_dbus_transport_get_linux_security_label (DBusTransport  *transport,
+                                          char          **label_p)
+{
+  DBusCredentials *auth_identity;
+
+  *label_p = NULL;
+
+  if (!transport->authenticated)
+    return FALSE;
+
+  auth_identity = _dbus_auth_get_identity (transport->auth);
+
+  if (_dbus_credentials_include (auth_identity,
+                                 DBUS_CREDENTIAL_LINUX_SECURITY_LABEL))
+    {
+      /* If no memory, we are supposed to return TRUE and set NULL */
+      *label_p = _dbus_strdup (_dbus_credentials_get_linux_security_label (auth_identity));
+
+      return TRUE;
+    }
+  else
+    {
+      return FALSE;
+    }
+}
+
 /**
  * See dbus_connection_get_windows_user().
  *
diff --git a/dbus/dbus-transport.h b/dbus/dbus-transport.h
index 39c74c4..843f231 100644
--- a/dbus/dbus-transport.h
+++ b/dbus/dbus-transport.h
@@ -87,6 +87,9 @@ void               _dbus_transport_set_unix_user_function (DBusTransport
                                                            DBusFreeFunction           *old_free_data_function);
 dbus_bool_t        _dbus_transport_get_windows_user       (DBusTransport              *transport,
                                                            char                      **windows_sid_p);
+dbus_bool_t        _dbus_transport_get_linux_security_label (DBusTransport            *transport,
+                                                           char                      **label_p);
+
 void               _dbus_transport_set_windows_user_function (DBusTransport              *transport,
                                                               DBusAllowWindowsUserFunction   function,
                                                               void                       *data,
-- 
2.1.4

