Merge "Optional autogen.sh flag --enable-kdbus-transport added allowing to compile...
[platform/upstream/dbus.git] / bus / selinux.c
index de68da3..c36c94e 100644 (file)
@@ -1,4 +1,5 @@
-/* selinux.c  SELinux security checks for D-BUS
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ * selinux.c  SELinux security checks for D-Bus
  *
  * Author: Matthew Rickard <mjricka@epoch.ncsc.mil>
  *
  * 
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  *
  */
+
+#include <config.h>
 #include <dbus/dbus-internals.h>
 #include <dbus/dbus-string.h>
+#ifndef DBUS_WIN
+#include <dbus/dbus-userdb.h>
+#endif
 #include "selinux.h"
 #include "services.h"
 #include "policy.h"
 #include "utils.h"
 #include "config-parser.h"
 
-#ifdef HAVE_SELINUX
+#ifdef HAVE_ERRNO_H
 #include <errno.h>
+#endif
+#ifdef HAVE_SELINUX
+#include <sys/types.h>
+#include <unistd.h>
+#include <limits.h>
 #include <pthread.h>
 #include <syslog.h>
 #include <selinux/selinux.h>
 #include <selinux/flask.h>
 #include <signal.h>
 #include <stdarg.h>
+#include <stdio.h>
+#include <grp.h>
 #endif /* HAVE_SELINUX */
+#ifdef HAVE_LIBAUDIT
+#include <cap-ng.h>
+#include <libaudit.h>
+#endif /* HAVE_LIBAUDIT */
 
 #define BUS_SID_FROM_SELINUX(sid)  ((BusSELinuxID*) (sid))
 #define SELINUX_SID_FROM_BUS(sid)  ((security_id_t) (sid))
@@ -57,6 +74,7 @@ static pthread_t avc_notify_thread;
 
 /* Prototypes for AVC callback functions.  */
 static void log_callback (const char *fmt, ...);
+static void log_audit_callback (void *data, security_class_t class, char *buf, size_t bufleft);
 static void *avc_create_thread (void (*run) (void));
 static void avc_stop_thread (void *thread);
 static void *avc_alloc_lock (void);
@@ -73,7 +91,7 @@ static const struct avc_memory_callback mem_cb =
 static const struct avc_log_callback log_cb =
 {
   .func_log = log_callback,
-  .func_audit = NULL
+  .func_audit = log_audit_callback
 };
 static const struct avc_thread_callback thread_cb =
 {
@@ -98,12 +116,55 @@ static const struct avc_lock_callback lock_cb =
  * @param variable argument list
  */
 #ifdef HAVE_SELINUX
+
+#ifdef HAVE_LIBAUDIT
+static int audit_fd = -1;
+#endif
+
+void
+bus_selinux_audit_init(void)
+{
+#ifdef HAVE_LIBAUDIT  
+  audit_fd = audit_open ();
+
+  if (audit_fd < 0)
+    {
+      /* If kernel doesn't support audit, bail out */
+      if (errno == EINVAL || errno == EPROTONOSUPPORT || errno == EAFNOSUPPORT)
+        return;
+      /* If user bus, bail out */
+      if (errno == EPERM && getuid() != 0)
+        return;
+      _dbus_warn ("Failed opening connection to the audit subsystem");
+    }
+#endif /* HAVE_LIBAUDIT */
+}
+
 static void 
 log_callback (const char *fmt, ...) 
 {
   va_list ap;
+
   va_start(ap, fmt);
-  vsyslog (LOG_INFO, fmt, ap);
+
+#ifdef HAVE_LIBAUDIT
+  if (audit_fd >= 0)
+  {
+    capng_get_caps_process();
+    if (capng_have_capability(CAPNG_EFFECTIVE, CAP_AUDIT_WRITE))
+    {
+      char buf[PATH_MAX*2];
+    
+      /* FIXME: need to change this to show real user */
+      vsnprintf(buf, sizeof(buf), fmt, ap);
+      audit_log_user_avc_message(audit_fd, AUDIT_USER_AVC, buf, NULL, NULL,
+                               NULL, getuid());
+      return;
+    }
+  }
+#endif /* HAVE_LIBAUDIT */
+  
+  vsyslog (LOG_USER | LOG_INFO, fmt, ap);
   va_end(ap);
 }
 
@@ -123,6 +184,29 @@ policy_reload_callback (u_int32_t event, security_id_t ssid,
 }
 
 /**
+ * Log any auxiliary data 
+ */
+static void
+log_audit_callback (void *data, security_class_t class, char *buf, size_t bufleft)
+{
+  DBusString *audmsg = data;
+
+  if (bufleft > (size_t) _dbus_string_get_length(audmsg))
+    {
+      _dbus_string_copy_to_buffer_with_nul (audmsg, buf, bufleft);
+    }
+  else
+    {
+      DBusString s;
+
+      _dbus_string_init_const(&s, "Buffer too small for audit message");
+
+      if (bufleft > (size_t) _dbus_string_get_length(&s))
+        _dbus_string_copy_to_buffer_with_nul (&s, buf, bufleft);
+    }
+}
+
+/**
  * Create thread to notify the AVC of enforcing and policy reload
  * changes via netlink.
  *
@@ -205,16 +289,13 @@ bus_selinux_enabled (void)
 }
 
 /**
- * Initialize the user space access vector cache (AVC) for D-BUS and set up
- * logging callbacks.
+ * Do early initialization; determine whether SELinux is enabled.
  */
 dbus_bool_t
-bus_selinux_init (void)
+bus_selinux_pre_init (void)
 {
 #ifdef HAVE_SELINUX
   int r;
-  char *bus_context;
-
   _dbus_assert (bus_sid == SECSID_WILD);
   
   /* Determine if we are running an SELinux kernel. */
@@ -227,7 +308,24 @@ bus_selinux_init (void)
     }
 
   selinux_enabled = r != 0;
+  return TRUE;
+#else
+  return TRUE;
+#endif
+}
+
+/**
+ * Initialize the user space access vector cache (AVC) for D-Bus and set up
+ * logging callbacks.
+ */
+dbus_bool_t
+bus_selinux_full_init (void)
+{
+#ifdef HAVE_SELINUX
+  char *bus_context;
 
+  _dbus_assert (bus_sid == SECSID_WILD);
+  
   if (!selinux_enabled)
     {
       _dbus_verbose ("SELinux not enabled in this kernel.\n");
@@ -244,7 +342,6 @@ bus_selinux_init (void)
     }
   else
     {
-      openlog ("dbus", LOG_PERROR, LOG_USER);
       _dbus_verbose ("Access Vector Cache (AVC) started.\n");
     }
 
@@ -277,10 +374,8 @@ bus_selinux_init (void)
 
   freecon (bus_context);
   
-  return TRUE;
-#else
-  return TRUE;
 #endif /* HAVE_SELINUX */
+  return TRUE;
 }
 
 /**
@@ -335,7 +430,8 @@ static dbus_bool_t
 bus_selinux_check (BusSELinuxID        *sender_sid,
                    BusSELinuxID        *override_sid,
                    security_class_t     target_class,
-                   access_vector_t      requested)
+                   access_vector_t      requested,
+                  DBusString          *auxdata)
 {
   if (!selinux_enabled)
     return TRUE;
@@ -345,10 +441,20 @@ bus_selinux_check (BusSELinuxID        *sender_sid,
                     override_sid ?
                     SELINUX_SID_FROM_BUS (override_sid) :
                     SELINUX_SID_FROM_BUS (bus_sid), 
-                    target_class, requested, &aeref, NULL) < 0)
+                    target_class, requested, &aeref, auxdata) < 0)
     {
-      _dbus_verbose ("SELinux denying due to security policy.\n");
-      return FALSE;
+    switch (errno)
+      {
+      case EACCES:
+        _dbus_verbose ("SELinux denying due to security policy.\n");
+        return FALSE;
+      case EINVAL:
+        _dbus_verbose ("SELinux denying due to invalid security context.\n");
+        return FALSE;
+      default:
+        _dbus_verbose ("SELinux denying due to: %s\n", _dbus_strerror (errno));
+        return FALSE;
+      }
     }
   else
     return TRUE;
@@ -365,20 +471,55 @@ bus_selinux_check (BusSELinuxID        *sender_sid,
  */
 dbus_bool_t
 bus_selinux_allows_acquire_service (DBusConnection     *connection,
-                                    BusSELinuxID       *service_sid)
+                                    BusSELinuxID       *service_sid,
+                                   const char         *service_name,
+                                   DBusError          *error)
 {
 #ifdef HAVE_SELINUX
   BusSELinuxID *connection_sid;
+  unsigned long spid;
+  DBusString auxdata;
+  dbus_bool_t ret;
   
   if (!selinux_enabled)
     return TRUE;
-
+  
   connection_sid = bus_connection_get_selinux_id (connection);
+  if (!dbus_connection_get_unix_process_id (connection, &spid))
+    spid = 0;
+
+  if (!_dbus_string_init (&auxdata))
+    goto oom;
+  if (!_dbus_string_append (&auxdata, "service="))
+    goto oom;
+
+  if (!_dbus_string_append (&auxdata, service_name))
+    goto oom;
+
+  if (spid)
+    {
+      if (!_dbus_string_append (&auxdata, " spid="))
+       goto oom;
+
+      if (!_dbus_string_append_uint (&auxdata, spid))
+       goto oom;
+    }
   
-  return bus_selinux_check (connection_sid,
-                            service_sid,
-                            SECCLASS_DBUS,
-                            DBUS__ACQUIRE_SVC);
+  ret = bus_selinux_check (connection_sid,
+                          service_sid,
+                          SECCLASS_DBUS,
+                          DBUS__ACQUIRE_SVC,
+                          &auxdata);
+
+  _dbus_string_free (&auxdata);
+  return ret;
+
+ oom:
+  _dbus_string_free (&auxdata);
+  BUS_SET_OOM (error);
+  return FALSE;
+
 #else
   return TRUE;
 #endif /* HAVE_SELINUX */
@@ -396,15 +537,91 @@ bus_selinux_allows_acquire_service (DBusConnection     *connection,
  */
 dbus_bool_t
 bus_selinux_allows_send (DBusConnection     *sender,
-                         DBusConnection     *proposed_recipient)
+                         DBusConnection     *proposed_recipient,
+                        const char         *msgtype,
+                        const char         *interface,
+                        const char         *member,
+                        const char         *error_name,
+                        const char         *destination,
+                        DBusError          *error)
 {
 #ifdef HAVE_SELINUX
   BusSELinuxID *recipient_sid;
   BusSELinuxID *sender_sid;
+  unsigned long spid, tpid;
+  DBusString auxdata;
+  dbus_bool_t ret;
+  dbus_bool_t string_alloced;
 
   if (!selinux_enabled)
     return TRUE;
 
+  if (!sender || !dbus_connection_get_unix_process_id (sender, &spid))
+    spid = 0;
+  if (!proposed_recipient || !dbus_connection_get_unix_process_id (proposed_recipient, &tpid))
+    tpid = 0;
+
+  string_alloced = FALSE;
+  if (!_dbus_string_init (&auxdata))
+    goto oom;
+  string_alloced = TRUE;
+
+  if (!_dbus_string_append (&auxdata, "msgtype="))
+    goto oom;
+
+  if (!_dbus_string_append (&auxdata, msgtype))
+    goto oom;
+
+  if (interface)
+    {
+      if (!_dbus_string_append (&auxdata, " interface="))
+       goto oom;
+      if (!_dbus_string_append (&auxdata, interface))
+       goto oom;
+    }
+
+  if (member)
+    {
+      if (!_dbus_string_append (&auxdata, " member="))
+       goto oom;
+      if (!_dbus_string_append (&auxdata, member))
+       goto oom;
+    }
+
+  if (error_name)
+    {
+      if (!_dbus_string_append (&auxdata, " error_name="))
+       goto oom;
+      if (!_dbus_string_append (&auxdata, error_name))
+       goto oom;
+    }
+
+  if (destination)
+    {
+      if (!_dbus_string_append (&auxdata, " dest="))
+       goto oom;
+      if (!_dbus_string_append (&auxdata, destination))
+       goto oom;
+    }
+
+  if (spid)
+    {
+      if (!_dbus_string_append (&auxdata, " spid="))
+       goto oom;
+
+      if (!_dbus_string_append_uint (&auxdata, spid))
+       goto oom;
+    }
+
+  if (tpid)
+    {
+      if (!_dbus_string_append (&auxdata, " tpid="))
+       goto oom;
+
+      if (!_dbus_string_append_uint (&auxdata, tpid))
+       goto oom;
+    }
+
   sender_sid = bus_connection_get_selinux_id (sender);
   /* A NULL proposed_recipient means the bus itself. */
   if (proposed_recipient)
@@ -412,13 +629,62 @@ bus_selinux_allows_send (DBusConnection     *sender,
   else
     recipient_sid = BUS_SID_FROM_SELINUX (bus_sid);
 
-  return bus_selinux_check (sender_sid, recipient_sid,
-                            SECCLASS_DBUS, DBUS__SEND_MSG);
+  ret = bus_selinux_check (sender_sid, 
+                          recipient_sid,
+                          SECCLASS_DBUS, 
+                          DBUS__SEND_MSG,
+                          &auxdata);
+
+  _dbus_string_free (&auxdata);
+
+  return ret;
+
+ oom:
+  if (string_alloced)
+    _dbus_string_free (&auxdata);
+  BUS_SET_OOM (error);
+  return FALSE;
+  
 #else
   return TRUE;
 #endif /* HAVE_SELINUX */
 }
 
+dbus_bool_t
+bus_selinux_append_context (DBusMessage    *message,
+                           BusSELinuxID   *sid,
+                           DBusError      *error)
+{
+#ifdef HAVE_SELINUX
+  char *context;
+
+  if (avc_sid_to_context (SELINUX_SID_FROM_BUS (sid), &context) < 0)
+    {
+      if (errno == ENOMEM)
+        BUS_SET_OOM (error);
+      else
+        dbus_set_error (error, DBUS_ERROR_FAILED,
+                        "Error getting context from SID: %s\n",
+                       _dbus_strerror (errno));
+      return FALSE;
+    }
+  if (!dbus_message_append_args (message,
+                                DBUS_TYPE_ARRAY,
+                                DBUS_TYPE_BYTE,
+                                &context,
+                                strlen (context),
+                                DBUS_TYPE_INVALID))
+    {
+      _DBUS_SET_OOM (error);
+      return FALSE;
+    }
+  freecon (context);
+  return TRUE;
+#else
+  return TRUE;
+#endif
+}
+
 /**
  * Gets the security context of a connection to the bus. It is up to
  * the caller to freecon() when they are done. 
@@ -490,11 +756,11 @@ bus_selinux_init_connection_id (DBusConnection *connection,
         BUS_SET_OOM (error);
       else
         dbus_set_error (error, DBUS_ERROR_FAILED,
-                        "Error getting SID from context: %s\n",
-                        _dbus_strerror (errno));
+                        "Error getting SID from context \"%s\": %s\n",
+                       con, _dbus_strerror (errno));
       
-      _dbus_warn ("Error getting SID from context: %s\n",
-                  _dbus_strerror (errno));
+      _dbus_warn ("Error getting SID from context \"%s\": %s\n",
+                 con, _dbus_strerror (errno));
       
       freecon (con);
       return NULL;
@@ -568,7 +834,15 @@ bus_selinux_id_table_insert (DBusHashTable *service_table,
   
   if (avc_context_to_sid ((char *) service_context, &sid) < 0)
     {
-      _dbus_assert (errno == ENOMEM);
+      if (errno == ENOMEM)
+        {
+         dbus_free (key);
+          return FALSE;
+       }
+
+      _dbus_warn ("Error getting SID from context \"%s\": %s\n",
+                 (char *) service_context,
+                  _dbus_strerror (errno));
       goto out;
     }
 
@@ -643,89 +917,7 @@ bus_selinux_id_table_lookup (DBusHashTable    *service_table,
 }
 
 /**
- * Copy security ID table mapping from one table into another.
- *
- * @param dest the table to copy into
- * @param override the table to copy from
- * @returns #FALSE if out of memory
- */
-#ifdef HAVE_SELINUX
-static dbus_bool_t
-bus_selinux_id_table_copy_over (DBusHashTable    *dest,
-                                DBusHashTable    *override)
-{
-  const char *key;
-  char *key_copy;
-  BusSELinuxID *sid;
-  DBusHashIter iter;
-  
-  _dbus_hash_iter_init (override, &iter);
-  while (_dbus_hash_iter_next (&iter))
-    {
-      key = _dbus_hash_iter_get_string_key (&iter);
-      sid = _dbus_hash_iter_get_value (&iter);
-
-      key_copy = _dbus_strdup (key);
-      if (key_copy == NULL)
-        return FALSE;
-
-      if (!_dbus_hash_table_insert_string (dest,
-                                           key_copy,
-                                           sid))
-        {
-          dbus_free (key_copy);
-          return FALSE;
-        }
-
-      bus_selinux_id_ref (sid);
-    }
-
-  return TRUE;
-}
-#endif /* HAVE_SELINUX */
-
-/**
- * Creates the union of the two tables (each table maps a service
- * name to a security ID). In case of the same service name in
- * both tables, the security ID from "override" will be used.
- *
- * @param base the base table
- * @param override the table that takes precedence in the merge
- * @returns the new table, or #NULL if out of memory
- */
-DBusHashTable*
-bus_selinux_id_table_union (DBusHashTable    *base,
-                            DBusHashTable    *override)
-{
-  DBusHashTable *combined_table;
-
-  combined_table = bus_selinux_id_table_new ();
-
-  if (combined_table == NULL)
-    return NULL;
-  
-#ifdef HAVE_SELINUX 
-  if (!selinux_enabled)
-    return combined_table;
-
-  if (!bus_selinux_id_table_copy_over (combined_table, base))
-    {
-      _dbus_hash_table_unref (combined_table);
-      return NULL;
-    }
-
-  if (!bus_selinux_id_table_copy_over (combined_table, override))
-    {
-      _dbus_hash_table_unref (combined_table);
-      return NULL;
-    }
-#endif /* HAVE_SELINUX */
-  
-  return combined_table;
-}
-
-/**
- * Get the SELinux policy root.  This is used to find the D-BUS
+ * Get the SELinux policy root.  This is used to find the D-Bus
  * specific config file within the policy.
  */
 const char *
@@ -744,8 +936,7 @@ bus_selinux_get_policy_root (void)
 void
 bus_selinux_id_table_print (DBusHashTable *service_table)
 {
-#ifdef DBUS_ENABLE_VERBOSE_MODE
-#ifdef HAVE_SELINUX
+#if defined (DBUS_ENABLE_VERBOSE_MODE) && defined (HAVE_SELINUX)
   DBusHashIter iter;
 
   if (!selinux_enabled)
@@ -761,19 +952,18 @@ bus_selinux_id_table_print (DBusHashTable *service_table)
       _dbus_verbose ("The context is %s\n", sid->ctx);
       _dbus_verbose ("The refcount is %d\n", sid->refcnt);
     }
-#endif /* HAVE_SELINUX */
-#endif /* DBUS_ENABLE_VERBOSE_MODE */
+#endif /* DBUS_ENABLE_VERBOSE_MODE && HAVE_SELINUX */
 }
 
 
-#ifdef DBUS_ENABLE_VERBOSE_MODE
-#ifdef HAVE_SELINUX
 /**
  * Print out some AVC statistics.
  */
+#ifdef HAVE_SELINUX
 static void
 bus_avc_print_stats (void)
 {
+#ifdef DBUS_ENABLE_VERBOSE_MODE
   struct avc_cache_stats cstats;
 
   if (!selinux_enabled)
@@ -791,10 +981,9 @@ bus_avc_print_stats (void)
   _dbus_verbose ("CAV hits: %d\n", cstats.cav_hits);
   _dbus_verbose ("CAV probes: %d\n", cstats.cav_probes);
   _dbus_verbose ("CAV misses: %d\n", cstats.cav_misses);
+#endif /* DBUS_ENABLE_VERBOSE_MODE */
 }
 #endif /* HAVE_SELINUX */
-#endif /* DBUS_ENABLE_VERBOSE_MODE */
-
 
 /**
  * Destroy the AVC before we terminate.
@@ -806,14 +995,91 @@ bus_selinux_shutdown (void)
   if (!selinux_enabled)
     return;
 
-  sidput (bus_sid);
-  bus_sid = SECSID_WILD;
-  
-#ifdef DBUS_ENABLE_VERBOSE_MODE
-  bus_avc_print_stats ();
-#endif /* DBUS_ENABLE_VERBOSE_MODE */
+  _dbus_verbose ("AVC shutdown\n");
 
-  avc_destroy ();
+  if (bus_sid != SECSID_WILD)
+    {
+      sidput (bus_sid);
+      bus_sid = SECSID_WILD;
+
+      bus_avc_print_stats ();
+
+      avc_destroy ();
+#ifdef HAVE_LIBAUDIT
+      audit_close (audit_fd);
+#endif /* HAVE_LIBAUDIT */
+    }
 #endif /* HAVE_SELINUX */
 }
 
+/* The !HAVE_LIBAUDIT case lives in dbus-sysdeps-util-unix.c */
+#ifdef HAVE_LIBAUDIT
+/**
+ * Changes the user and group the bus is running as.
+ *
+ * @param user the user to become
+ * @param error return location for errors
+ * @returns #FALSE on failure
+ */
+dbus_bool_t
+_dbus_change_to_daemon_user  (const char    *user,
+                              DBusError     *error)
+{
+  dbus_uid_t uid;
+  dbus_gid_t gid;
+  DBusString u;
+
+  _dbus_string_init_const (&u, user);
+
+  if (!_dbus_get_user_id_and_primary_group (&u, &uid, &gid))
+    {
+      dbus_set_error (error, DBUS_ERROR_FAILED,
+                      "User '%s' does not appear to exist?",
+                      user);
+      return FALSE;
+    }
+
+  /* If we were root */
+  if (_dbus_geteuid () == 0)
+    {
+      int rc;
+
+      capng_clear (CAPNG_SELECT_BOTH);
+      capng_update (CAPNG_ADD, CAPNG_EFFECTIVE | CAPNG_PERMITTED,
+                    CAP_AUDIT_WRITE);
+      rc = capng_change_id (uid, gid, CAPNG_DROP_SUPP_GRP);
+      if (rc)
+        {
+          switch (rc) {
+            default:
+              dbus_set_error (error, DBUS_ERROR_FAILED,
+                              "Failed to drop capabilities: %s\n",
+                              _dbus_strerror (errno));
+              break;
+            case -4:
+              dbus_set_error (error, _dbus_error_from_errno (errno),
+                              "Failed to set GID to %lu: %s", gid,
+                              _dbus_strerror (errno));
+              break;
+            case -5:
+              _dbus_warn ("Failed to drop supplementary groups: %s\n",
+                          _dbus_strerror (errno));
+              break;
+            case -6:
+              dbus_set_error (error, _dbus_error_from_errno (errno),
+                              "Failed to set UID to %lu: %s", uid,
+                              _dbus_strerror (errno));
+              break;
+            case -7:
+              dbus_set_error (error, _dbus_error_from_errno (errno),
+                              "Failed to unset keep-capabilities: %s\n",
+                              _dbus_strerror (errno));
+              break;
+          }
+          return FALSE;
+        }
+    }
+
+ return TRUE;
+}
+#endif