-/* 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))
/* 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);
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 =
{
* @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);
}
}
/**
+ * 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.
*
bus_selinux_pre_init (void)
{
#ifdef HAVE_SELINUX
+ int r;
_dbus_assert (bus_sid == SECSID_WILD);
/* Determine if we are running an SELinux kernel. */
}
/**
- * Initialize the user space access vector cache (AVC) for D-BUS and set up
+ * 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
- int r;
char *bus_context;
_dbus_assert (bus_sid == SECSID_WILD);
}
else
{
- openlog ("dbus", LOG_PERROR, LOG_USER);
_dbus_verbose ("Access Vector Cache (AVC) started.\n");
}
freecon (bus_context);
- return TRUE;
-#else
- return TRUE;
#endif /* HAVE_SELINUX */
+ return TRUE;
}
/**
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;
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;
*/
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 */
*/
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)
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.
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;
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;
}
}
/**
- * 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 *
if (!selinux_enabled)
return;
- sidput (bus_sid);
- bus_sid = SECSID_WILD;
-
+ _dbus_verbose ("AVC shutdown\n");
+
+ if (bus_sid != SECSID_WILD)
+ {
+ sidput (bus_sid);
+ bus_sid = SECSID_WILD;
+
#ifdef DBUS_ENABLE_VERBOSE_MODE
- bus_avc_print_stats ();
+
+ if (_dbus_is_verbose())
+ bus_avc_print_stats ();
+
#endif /* DBUS_ENABLE_VERBOSE_MODE */
- avc_destroy ();
+ 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