summaryrefslogtreecommitdiff
path: root/bus/apparmor.c
diff options
context:
space:
mode:
Diffstat (limited to 'bus/apparmor.c')
-rw-r--r--bus/apparmor.c1128
1 files changed, 1128 insertions, 0 deletions
diff --git a/bus/apparmor.c b/bus/apparmor.c
new file mode 100644
index 00000000..a1b3621a
--- /dev/null
+++ b/bus/apparmor.c
@@ -0,0 +1,1128 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ * apparmor.c AppArmor security checks for D-Bus
+ *
+ * Based on selinux.c
+ *
+ * Copyright © 2014-2015 Canonical, Ltd.
+ *
+ * Licensed under the Academic Free License version 2.1
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include <config.h>
+#include "apparmor.h"
+
+#ifdef HAVE_APPARMOR
+
+#include <dbus/dbus-internals.h>
+#include <dbus/dbus-string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/apparmor.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#ifdef HAVE_LIBAUDIT
+#include <cap-ng.h>
+#include <libaudit.h>
+#endif /* HAVE_LIBAUDIT */
+
+#include "connection.h"
+#include "utils.h"
+
+/* Store the value telling us if AppArmor D-Bus mediation is enabled. */
+static dbus_bool_t apparmor_enabled = FALSE;
+
+typedef enum {
+ APPARMOR_DISABLED,
+ APPARMOR_ENABLED,
+ APPARMOR_REQUIRED
+} AppArmorConfigMode;
+
+/* Store the value of the AppArmor mediation mode in the bus configuration */
+static AppArmorConfigMode apparmor_config_mode = APPARMOR_ENABLED;
+
+#ifdef HAVE_LIBAUDIT
+static int audit_fd = -1;
+#endif
+
+/* The AppArmor context, consisting of a label and a mode. */
+struct BusAppArmorConfinement
+{
+ int refcount; /* Reference count */
+
+ char *label; /* AppArmor confinement label */
+ const char *mode; /* AppArmor confinement mode (freed by freeing *label) */
+};
+
+static BusAppArmorConfinement *bus_con = NULL;
+
+/**
+ * Callers of this function give up ownership of the *label and *mode
+ * pointers.
+ *
+ * Additionally, the responsibility of freeing *label and *mode becomes the
+ * responsibility of the bus_apparmor_confinement_unref() function. However, it
+ * does not free *mode because libapparmor's aa_getcon(), and libapparmor's
+ * other related functions, allocate a single buffer for *label and *mode and
+ * then separate the two char arrays with a NUL char. See the aa_getcon(2) man
+ * page for more details.
+ */
+static BusAppArmorConfinement*
+bus_apparmor_confinement_new (char *label,
+ const char *mode)
+{
+ BusAppArmorConfinement *confinement;
+
+ confinement = dbus_new0 (BusAppArmorConfinement, 1);
+ if (confinement != NULL)
+ {
+ confinement->refcount = 1;
+ confinement->label = label;
+ confinement->mode = mode;
+ }
+
+ return confinement;
+}
+
+void
+bus_apparmor_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 */
+}
+
+/*
+ * Return TRUE on successful check, FALSE on OOM.
+ * Set *is_supported to whether AA has D-Bus features.
+ */
+static dbus_bool_t
+_bus_apparmor_detect_aa_dbus_support (dbus_bool_t *is_supported)
+{
+ int mask_file;
+ DBusString aa_dbus;
+ char *aa_securityfs = NULL;
+ dbus_bool_t retval = FALSE;
+
+ *is_supported = FALSE;
+
+ if (!_dbus_string_init (&aa_dbus))
+ return FALSE;
+
+ if (aa_find_mountpoint (&aa_securityfs) != 0)
+ goto out;
+
+ /*
+ * John Johansen has confirmed that the mainline kernel will not have
+ * the apparmorfs/features/dbus/mask file until the mainline kernel
+ * has AppArmor getpeersec support.
+ */
+ if (!_dbus_string_append (&aa_dbus, aa_securityfs) ||
+ !_dbus_string_append (&aa_dbus, "/features/dbus/mask"))
+ goto out;
+
+ /* We need to open() the flag file, not just stat() it, because AppArmor
+ * does not mediate stat() in the apparmorfs. If you have a
+ * dbus-daemon inside an LXC container, with insufficiently broad
+ * AppArmor privileges to do its own AppArmor mediation, the desired
+ * result is that it behaves as if AppArmor was not present; but a stat()
+ * here would succeed, and result in it trying and failing to do full
+ * mediation. https://bugs.launchpad.net/ubuntu/+source/dbus/+bug/1238267 */
+ mask_file = open (_dbus_string_get_const_data (&aa_dbus),
+ O_RDONLY | O_CLOEXEC);
+ if (mask_file != -1)
+ {
+ *is_supported = TRUE;
+ close (mask_file);
+ }
+
+ retval = TRUE;
+
+out:
+ free (aa_securityfs);
+ _dbus_string_free (&aa_dbus);
+
+ return retval;
+}
+
+static dbus_bool_t
+modestr_is_complain (const char *mode)
+{
+ if (mode && strcmp (mode, "complain") == 0)
+ return TRUE;
+ return FALSE;
+}
+
+static void
+log_message (dbus_bool_t allow, const char *op, DBusString *data)
+{
+ const char *mstr;
+
+ if (allow)
+ mstr = "ALLOWED";
+ else
+ mstr = "DENIED";
+
+#ifdef HAVE_LIBAUDIT
+ if (audit_fd >= 0)
+ {
+ DBusString avc;
+
+ capng_get_caps_process ();
+ if (!capng_have_capability (CAPNG_EFFECTIVE, CAP_AUDIT_WRITE))
+ goto syslog;
+
+ if (!_dbus_string_init (&avc))
+ goto syslog;
+
+ if (!_dbus_string_append_printf (&avc,
+ "apparmor=\"%s\" operation=\"dbus_%s\" %s\n",
+ mstr, op, _dbus_string_get_const_data (data)))
+ {
+ _dbus_string_free (&avc);
+ goto syslog;
+ }
+
+ /* FIXME: need to change this to show real user */
+ audit_log_user_avc_message (audit_fd, AUDIT_USER_AVC,
+ _dbus_string_get_const_data (&avc),
+ NULL, NULL, NULL, getuid ());
+ _dbus_string_free (&avc);
+ return;
+ }
+
+syslog:
+#endif /* HAVE_LIBAUDIT */
+
+ syslog (LOG_USER | LOG_NOTICE, "apparmor=\"%s\" operation=\"dbus_%s\" %s\n",
+ mstr, op, _dbus_string_get_const_data (data));
+}
+
+static dbus_bool_t
+_dbus_append_pair_uint (DBusString *auxdata, const char *name,
+ unsigned long value)
+{
+ return _dbus_string_append (auxdata, " ") &&
+ _dbus_string_append (auxdata, name) &&
+ _dbus_string_append (auxdata, "=") &&
+ _dbus_string_append_uint (auxdata, value);
+}
+
+static dbus_bool_t
+_dbus_append_pair_str (DBusString *auxdata, const char *name, const char *value)
+{
+ return _dbus_string_append (auxdata, " ") &&
+ _dbus_string_append (auxdata, name) &&
+ _dbus_string_append (auxdata, "=\"") &&
+ _dbus_string_append (auxdata, value) &&
+ _dbus_string_append (auxdata, "\"");
+}
+
+static dbus_bool_t
+_dbus_append_mask (DBusString *auxdata, uint32_t mask)
+{
+ const char *mask_str;
+
+ /* Only one permission bit can be set */
+ if (mask == AA_DBUS_SEND)
+ mask_str = "send";
+ else if (mask == AA_DBUS_RECEIVE)
+ mask_str = "receive";
+ else if (mask == AA_DBUS_BIND)
+ mask_str = "bind";
+ else
+ return FALSE;
+
+ return _dbus_append_pair_str (auxdata, "mask", mask_str);
+}
+
+static dbus_bool_t
+is_unconfined (const char *con, const char *mode)
+{
+ /* treat con == NULL as confined as it is going to result in a denial */
+ if ((!mode && con && strcmp (con, "unconfined") == 0) ||
+ strcmp (mode, "unconfined") == 0)
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static dbus_bool_t
+query_append (DBusString *query, const char *buffer)
+{
+ if (!_dbus_string_append_byte (query, '\0'))
+ return FALSE;
+
+ if (buffer && !_dbus_string_append (query, buffer))
+ return FALSE;
+
+ return TRUE;
+}
+
+static dbus_bool_t
+build_common_query (DBusString *query, const char *con, const char *bustype)
+{
+ /**
+ * libapparmor's aa_query_label() function scribbles over the first
+ * AA_QUERY_CMD_LABEL_SIZE bytes of the query string with a private value.
+ */
+ return _dbus_string_insert_bytes (query, 0, AA_QUERY_CMD_LABEL_SIZE, 0) &&
+ _dbus_string_append (query, con) &&
+ _dbus_string_append_byte (query, '\0') &&
+ _dbus_string_append_byte (query, AA_CLASS_DBUS) &&
+ _dbus_string_append (query, bustype ? bustype : "");
+}
+
+static dbus_bool_t
+build_service_query (DBusString *query,
+ const char *con,
+ const char *bustype,
+ const char *name)
+{
+ return build_common_query (query, con, bustype) &&
+ query_append (query, name);
+}
+
+static dbus_bool_t
+build_message_query (DBusString *query,
+ const char *src_con,
+ const char *bustype,
+ const char *name,
+ const char *dst_con,
+ const char *path,
+ const char *interface,
+ const char *member)
+{
+ return build_common_query (query, src_con, bustype) &&
+ query_append (query, name) &&
+ query_append (query, dst_con) &&
+ query_append (query, path) &&
+ query_append (query, interface) &&
+ query_append (query, member);
+}
+
+static dbus_bool_t
+build_eavesdrop_query (DBusString *query, const char *con, const char *bustype)
+{
+ return build_common_query (query, con, bustype);
+}
+
+static void
+set_error_from_query_errno (DBusError *error, int error_number)
+{
+ dbus_set_error (error, _dbus_error_from_errno (error_number),
+ "Failed to query AppArmor policy: %s",
+ _dbus_strerror (error_number));
+}
+
+static void
+set_error_from_denied_message (DBusError *error,
+ DBusConnection *sender,
+ DBusConnection *proposed_recipient,
+ dbus_bool_t requested_reply,
+ const char *msgtype,
+ const char *path,
+ const char *interface,
+ const char *member,
+ const char *error_name,
+ const char *destination)
+{
+ const char *proposed_recipient_loginfo;
+ const char *unset = "(unset)";
+
+ proposed_recipient_loginfo = proposed_recipient ?
+ bus_connection_get_loginfo (proposed_recipient) :
+ "bus";
+
+ dbus_set_error (error, DBUS_ERROR_ACCESS_DENIED,
+ "An AppArmor policy prevents this sender from sending this "
+ "message to this recipient; type=\"%s\", "
+ "sender=\"%s\" (%s) interface=\"%s\" member=\"%s\" "
+ "error name=\"%s\" requested_reply=\"%d\" "
+ "destination=\"%s\" (%s)",
+ msgtype,
+ bus_connection_get_name (sender),
+ bus_connection_get_loginfo (sender),
+ interface ? interface : unset,
+ member ? member : unset,
+ error_name ? error_name : unset,
+ requested_reply,
+ destination,
+ proposed_recipient_loginfo);
+}
+#endif /* HAVE_APPARMOR */
+
+/**
+ * Do early initialization; determine whether AppArmor is enabled.
+ * Return TRUE on successful check (whether AppArmor is actually
+ * enabled or not) or FALSE on OOM.
+ */
+dbus_bool_t
+bus_apparmor_pre_init (void)
+{
+#ifdef HAVE_APPARMOR
+ apparmor_enabled = FALSE;
+
+ if (!aa_is_enabled ())
+ return TRUE;
+
+ if (!_bus_apparmor_detect_aa_dbus_support (&apparmor_enabled))
+ return FALSE;
+#endif
+
+ return TRUE;
+}
+
+dbus_bool_t
+bus_apparmor_set_mode_from_config (const char *mode, DBusError *error)
+{
+#ifdef HAVE_APPARMOR
+ if (mode != NULL)
+ {
+ if (strcmp (mode, "disabled") == 0)
+ apparmor_config_mode = APPARMOR_DISABLED;
+ else if (strcmp (mode, "enabled") == 0)
+ apparmor_config_mode = APPARMOR_ENABLED;
+ else if (strcmp (mode, "required") == 0)
+ apparmor_config_mode = APPARMOR_REQUIRED;
+ else
+ {
+ dbus_set_error (error, DBUS_ERROR_FAILED,
+ "Mode attribute on <apparmor> must have value "
+ "\"required\", \"enabled\" or \"disabled\", "
+ "not \"%s\"", mode);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+#else
+ if (mode == NULL || strcmp (mode, "disabled") == 0 ||
+ strcmp (mode, "enabled") == 0)
+ return TRUE;
+
+ dbus_set_error (error, DBUS_ERROR_FAILED,
+ "Mode attribute on <apparmor> must have value \"enabled\" or "
+ "\"disabled\" but cannot be \"%s\" when D-Bus is built "
+ "without AppArmor support", mode);
+ return FALSE;
+#endif
+}
+
+/**
+ * Verify that the config mode is compatible with the kernel's AppArmor
+ * support. If AppArmor mediation will be enabled, determine the bus
+ * confinement label.
+ */
+dbus_bool_t
+bus_apparmor_full_init (DBusError *error)
+{
+#ifdef HAVE_APPARMOR
+ char *label, *mode;
+
+ if (apparmor_enabled)
+ {
+ if (apparmor_config_mode == APPARMOR_DISABLED)
+ {
+ apparmor_enabled = FALSE;
+ return TRUE;
+ }
+
+ if (bus_con == NULL)
+ {
+ if (aa_getcon (&label, &mode) == -1)
+ {
+ dbus_set_error (error, DBUS_ERROR_FAILED,
+ "Error getting AppArmor context of bus: %s",
+ _dbus_strerror (errno));
+ return FALSE;
+ }
+
+ bus_con = bus_apparmor_confinement_new (label, mode);
+ if (bus_con == NULL)
+ {
+ BUS_SET_OOM (error);
+ free (label);
+ return FALSE;
+ }
+ }
+ }
+ else
+ {
+ if (apparmor_config_mode == APPARMOR_REQUIRED)
+ {
+ dbus_set_error (error, DBUS_ERROR_FAILED,
+ "AppArmor mediation required but not present");
+ return FALSE;
+ }
+ else if (apparmor_config_mode == APPARMOR_ENABLED)
+ {
+ return TRUE;
+ }
+ }
+#endif
+
+ return TRUE;
+}
+
+void
+bus_apparmor_shutdown (void)
+{
+#ifdef HAVE_APPARMOR
+ if (!apparmor_enabled)
+ return;
+
+ _dbus_verbose ("AppArmor shutdown\n");
+
+ bus_apparmor_confinement_unref (bus_con);
+ bus_con = NULL;
+
+#ifdef HAVE_LIBAUDIT
+ audit_close (audit_fd);
+#endif /* HAVE_LIBAUDIT */
+
+#endif /* HAVE_APPARMOR */
+}
+
+dbus_bool_t
+bus_apparmor_enabled (void)
+{
+#ifdef HAVE_APPARMOR
+ return apparmor_enabled;
+#else
+ return FALSE;
+#endif
+}
+
+void
+bus_apparmor_confinement_unref (BusAppArmorConfinement *confinement)
+{
+#ifdef HAVE_APPARMOR
+ if (!apparmor_enabled)
+ return;
+
+ _dbus_assert (confinement != NULL);
+ _dbus_assert (confinement->refcount > 0);
+
+ confinement->refcount -= 1;
+
+ if (confinement->refcount == 0)
+ {
+ /**
+ * Do not free confinement->mode, as libapparmor does a single malloc for
+ * both confinement->label and confinement->mode.
+ */
+ free (confinement->label);
+ dbus_free (confinement);
+ }
+#endif
+}
+
+void
+bus_apparmor_confinement_ref (BusAppArmorConfinement *confinement)
+{
+#ifdef HAVE_APPARMOR
+ if (!apparmor_enabled)
+ return;
+
+ _dbus_assert (confinement != NULL);
+ _dbus_assert (confinement->refcount > 0);
+
+ confinement->refcount += 1;
+#endif /* HAVE_APPARMOR */
+}
+
+BusAppArmorConfinement*
+bus_apparmor_init_connection_confinement (DBusConnection *connection,
+ DBusError *error)
+{
+#ifdef HAVE_APPARMOR
+ BusAppArmorConfinement *confinement;
+ char *label, *mode;
+ int fd;
+
+ if (!apparmor_enabled)
+ return NULL;
+
+ _dbus_assert (connection != NULL);
+
+ if (!dbus_connection_get_socket (connection, &fd))
+ {
+ dbus_set_error (error, DBUS_ERROR_FAILED,
+ "Failed to get socket file descriptor of connection");
+ return NULL;
+ }
+
+ if (aa_getpeercon (fd, &label, &mode) == -1)
+ {
+ if (errno == ENOMEM)
+ BUS_SET_OOM (error);
+ else
+ dbus_set_error (error, _dbus_error_from_errno (errno),
+ "Failed to get AppArmor confinement information of socket peer: %s",
+ _dbus_strerror (errno));
+ return NULL;
+ }
+
+ confinement = bus_apparmor_confinement_new (label, mode);
+ if (confinement == NULL)
+ {
+ BUS_SET_OOM (error);
+ free (label);
+ return NULL;
+ }
+
+ return confinement;
+#else
+ return NULL;
+#endif /* HAVE_APPARMOR */
+}
+
+/**
+ * Returns true if the given connection can acquire a service,
+ * using the tasks security context
+ *
+ * @param connection connection that wants to own the service
+ * @param bustype name of the bus
+ * @param service_name the name of the service to acquire
+ * @param error the reason for failure when FALSE is returned
+ * @returns TRUE if acquire is permitted
+ */
+dbus_bool_t
+bus_apparmor_allows_acquire_service (DBusConnection *connection,
+ const char *bustype,
+ const char *service_name,
+ DBusError *error)
+{
+
+#ifdef HAVE_APPARMOR
+ BusAppArmorConfinement *con = NULL;
+ DBusString qstr, auxdata;
+ dbus_bool_t free_auxdata = FALSE;
+ dbus_bool_t allow = FALSE, audit = TRUE;
+ unsigned long pid;
+ int res, serrno = 0;
+
+ if (!apparmor_enabled)
+ return TRUE;
+
+ _dbus_assert (connection != NULL);
+
+ con = bus_connection_dup_apparmor_confinement (connection);
+
+ if (is_unconfined (con->label, con->mode))
+ {
+ allow = TRUE;
+ audit = FALSE;
+ goto out;
+ }
+
+ if (!_dbus_string_init (&qstr))
+ goto oom;
+
+ if (!build_service_query (&qstr, con->label, bustype, service_name))
+ {
+ _dbus_string_free (&qstr);
+ goto oom;
+ }
+
+ res = aa_query_label (AA_DBUS_BIND,
+ _dbus_string_get_data (&qstr),
+ _dbus_string_get_length (&qstr),
+ &allow, &audit);
+ _dbus_string_free (&qstr);
+ if (res == -1)
+ {
+ serrno = errno;
+ set_error_from_query_errno (error, serrno);
+ goto audit;
+ }
+
+ /* Don't fail operations on profiles in complain mode */
+ if (modestr_is_complain (con->mode))
+ allow = TRUE;
+
+ if (!allow)
+ dbus_set_error (error, DBUS_ERROR_ACCESS_DENIED,
+ "Connection \"%s\" is not allowed to own the service "
+ "\"%s\" due to AppArmor policy",
+ bus_connection_is_active (connection) ?
+ bus_connection_get_name (connection) : "(inactive)",
+ service_name);
+
+ if (!audit)
+ goto out;
+
+ audit:
+ if (!_dbus_string_init (&auxdata))
+ goto oom;
+ free_auxdata = TRUE;
+
+ if (!_dbus_append_pair_str (&auxdata, "bus", bustype ? bustype : "unknown"))
+ goto oom;
+
+ if (!_dbus_append_pair_str (&auxdata, "name", service_name))
+ goto oom;
+
+ if (serrno && !_dbus_append_pair_str (&auxdata, "info", strerror (serrno)))
+ goto oom;
+
+ if (!_dbus_append_mask (&auxdata, AA_DBUS_BIND))
+ goto oom;
+
+ if (connection && dbus_connection_get_unix_process_id (connection, &pid) &&
+ !_dbus_append_pair_uint (&auxdata, "pid", pid))
+ goto oom;
+
+ if (con->label && !_dbus_append_pair_str (&auxdata, "label", con->label))
+ goto oom;
+
+ log_message (allow, "bind", &auxdata);
+
+ out:
+ if (con != NULL)
+ bus_apparmor_confinement_unref (con);
+ if (free_auxdata)
+ _dbus_string_free (&auxdata);
+ return allow;
+
+ oom:
+ if (error != NULL && !dbus_error_is_set (error))
+ BUS_SET_OOM (error);
+ allow = FALSE;
+ goto out;
+
+#else
+ return TRUE;
+#endif /* HAVE_APPARMOR */
+}
+
+/**
+ * Check if Apparmor security controls allow the message to be sent to a
+ * particular connection based on the security context of the sender and
+ * that of the receiver. The destination connection need not be the
+ * addressed recipient, it could be an "eavesdropper"
+ *
+ * @param sender the sender of the message.
+ * @param proposed_recipient the connection the message is to be sent to.
+ * @param requested_reply TRUE if the message is a reply requested by
+ * proposed_recipient
+ * @param bustype name of the bus
+ * @param msgtype message type (DBUS_MESSAGE_TYPE_METHOD_CALL, etc.)
+ * @param path object path the message should be sent to
+ * @param interface the type of the object instance
+ * @param member the member of the object
+ * @param error_name the name of the error if the message type is error
+ * @param destination name that the message should be sent to
+ * @param source name that the message should be sent from
+ * @param error the reason for failure when FALSE is returned
+ * @returns TRUE if the message is permitted
+ */
+dbus_bool_t
+bus_apparmor_allows_send (DBusConnection *sender,
+ DBusConnection *proposed_recipient,
+ dbus_bool_t requested_reply,
+ const char *bustype,
+ int msgtype,
+ const char *path,
+ const char *interface,
+ const char *member,
+ const char *error_name,
+ const char *destination,
+ const char *source,
+ DBusError *error)
+{
+#ifdef HAVE_APPARMOR
+ BusAppArmorConfinement *src_con = NULL, *dst_con = NULL;
+ DBusString qstr, auxdata;
+ dbus_bool_t src_allow = FALSE, dst_allow = FALSE;
+ dbus_bool_t src_audit = TRUE, dst_audit = TRUE;
+ dbus_bool_t free_auxdata = FALSE;
+ unsigned long pid;
+ int len, res, src_errno = 0, dst_errno = 0;
+ uint32_t src_perm = AA_DBUS_SEND, dst_perm = AA_DBUS_RECEIVE;
+ const char *msgtypestr = dbus_message_type_to_string(msgtype);
+
+ if (!apparmor_enabled)
+ return TRUE;
+
+ _dbus_assert (sender != NULL);
+
+ src_con = bus_connection_dup_apparmor_confinement (sender);
+
+ if (proposed_recipient)
+ {
+ dst_con = bus_connection_dup_apparmor_confinement (proposed_recipient);
+ }
+ else
+ {
+ dst_con = bus_con;
+ bus_apparmor_confinement_ref (dst_con);
+ }
+
+ /* map reply messages to initial send and receive permission. That is
+ * permission to receive a message from X grants permission to reply to X.
+ * And permission to send a message to Y grants permission to receive a reply
+ * from Y. Note that this only applies to requested replies. Unrequested
+ * replies still require a policy query.
+ */
+ if (requested_reply)
+ {
+ /* ignore requested reply messages and let dbus reply mapping handle them
+ * as the send was already allowed
+ */
+ src_allow = TRUE;
+ dst_allow = TRUE;
+ goto out;
+ }
+
+ if (is_unconfined (src_con->label, src_con->mode))
+ {
+ src_allow = TRUE;
+ src_audit = FALSE;
+ }
+ else
+ {
+ if (!_dbus_string_init (&qstr))
+ goto oom;
+
+ if (!build_message_query (&qstr, src_con->label, bustype, destination,
+ dst_con->label, path, interface, member))
+ {
+ _dbus_string_free (&qstr);
+ goto oom;
+ }
+
+ res = aa_query_label (src_perm,
+ _dbus_string_get_data (&qstr),
+ _dbus_string_get_length (&qstr),
+ &src_allow, &src_audit);
+ _dbus_string_free (&qstr);
+ if (res == -1)
+ {
+ src_errno = errno;
+ set_error_from_query_errno (error, src_errno);
+ goto audit;
+ }
+ }
+
+ if (is_unconfined (dst_con->label, dst_con->mode))
+ {
+ dst_allow = TRUE;
+ dst_audit = FALSE;
+ }
+ else
+ {
+ if (!_dbus_string_init (&qstr))
+ goto oom;
+
+ if (!build_message_query (&qstr, dst_con->label, bustype, source,
+ src_con->label, path, interface, member))
+ {
+ _dbus_string_free (&qstr);
+ goto oom;
+ }
+
+ res = aa_query_label (dst_perm,
+ _dbus_string_get_data (&qstr),
+ _dbus_string_get_length (&qstr),
+ &dst_allow, &dst_audit);
+ _dbus_string_free (&qstr);
+ if (res == -1)
+ {
+ dst_errno = errno;
+ set_error_from_query_errno (error, dst_errno);
+ goto audit;
+ }
+ }
+
+ /* Don't fail operations on profiles in complain mode */
+ if (modestr_is_complain (src_con->mode))
+ src_allow = TRUE;
+ if (modestr_is_complain (dst_con->mode))
+ dst_allow = TRUE;
+
+ if (!src_allow || !dst_allow)
+ set_error_from_denied_message (error,
+ sender,
+ proposed_recipient,
+ requested_reply,
+ msgtypestr,
+ path,
+ interface,
+ member,
+ error_name,
+ destination);
+
+ /* Don't audit the message if one of the following conditions is true:
+ * 1) The AppArmor query indicates that auditing should not happen.
+ * 2) The message is a reply type. Reply message are not audited because
+ * the AppArmor policy language does not have the notion of a reply
+ * message. Unrequested replies will be silently discarded if the sender
+ * does not have permission to send to the receiver or if the receiver
+ * does not have permission to receive from the sender.
+ */
+ if ((!src_audit && !dst_audit) ||
+ (msgtype == DBUS_MESSAGE_TYPE_METHOD_RETURN ||
+ msgtype == DBUS_MESSAGE_TYPE_ERROR))
+ goto out;
+
+ audit:
+ if (!_dbus_string_init (&auxdata))
+ goto oom;
+ free_auxdata = TRUE;
+
+ if (!_dbus_append_pair_str (&auxdata, "bus", bustype ? bustype : "unknown"))
+ goto oom;
+
+ if (path && !_dbus_append_pair_str (&auxdata, "path", path))
+ goto oom;
+
+ if (interface && !_dbus_append_pair_str (&auxdata, "interface", interface))
+ goto oom;
+
+ if (member && !_dbus_append_pair_str (&auxdata, "member", member))
+ goto oom;
+
+ if (error_name && !_dbus_append_pair_str (&auxdata, "error_name", error_name))
+ goto oom;
+
+ len = _dbus_string_get_length (&auxdata);
+
+ if (src_audit)
+ {
+ if (!_dbus_append_mask (&auxdata, src_perm))
+ goto oom;
+
+ if (destination && !_dbus_append_pair_str (&auxdata, "name", destination))
+ goto oom;
+
+ if (sender && dbus_connection_get_unix_process_id (sender, &pid) &&
+ !_dbus_append_pair_uint (&auxdata, "pid", pid))
+ goto oom;
+
+ if (src_con->label &&
+ !_dbus_append_pair_str (&auxdata, "label", src_con->label))
+ goto oom;
+
+ if (proposed_recipient &&
+ dbus_connection_get_unix_process_id (proposed_recipient, &pid) &&
+ !_dbus_append_pair_uint (&auxdata, "peer_pid", pid))
+ goto oom;
+
+ if (dst_con->label &&
+ !_dbus_append_pair_str (&auxdata, "peer_label", dst_con->label))
+ goto oom;
+
+ if (src_errno && !_dbus_append_pair_str (&auxdata, "info", strerror (src_errno)))
+ goto oom;
+
+ if (dst_errno &&
+ !_dbus_append_pair_str (&auxdata, "peer_info", strerror (dst_errno)))
+ goto oom;
+
+ log_message (src_allow, msgtypestr, &auxdata);
+ }
+ if (dst_audit)
+ {
+ _dbus_string_set_length (&auxdata, len);
+
+ if (source && !_dbus_append_pair_str (&auxdata, "name", source))
+ goto oom;
+
+ if (!_dbus_append_mask (&auxdata, dst_perm))
+ goto oom;
+
+ if (proposed_recipient &&
+ dbus_connection_get_unix_process_id (proposed_recipient, &pid) &&
+ !_dbus_append_pair_uint (&auxdata, "pid", pid))
+ goto oom;
+
+ if (dst_con->label &&
+ !_dbus_append_pair_str (&auxdata, "label", dst_con->label))
+ goto oom;
+
+ if (sender && dbus_connection_get_unix_process_id (sender, &pid) &&
+ !_dbus_append_pair_uint (&auxdata, "peer_pid", pid))
+ goto oom;
+
+ if (src_con->label &&
+ !_dbus_append_pair_str (&auxdata, "peer_label", src_con->label))
+ goto oom;
+
+ if (dst_errno && !_dbus_append_pair_str (&auxdata, "info", strerror (dst_errno)))
+ goto oom;
+
+ if (src_errno &&
+ !_dbus_append_pair_str (&auxdata, "peer_info", strerror (src_errno)))
+ goto oom;
+
+ log_message (dst_allow, msgtypestr, &auxdata);
+ }
+
+ out:
+ if (src_con != NULL)
+ bus_apparmor_confinement_unref (src_con);
+ if (dst_con != NULL)
+ bus_apparmor_confinement_unref (dst_con);
+ if (free_auxdata)
+ _dbus_string_free (&auxdata);
+
+ return src_allow && dst_allow;
+
+ oom:
+ if (error != NULL && !dbus_error_is_set (error))
+ BUS_SET_OOM (error);
+ src_allow = FALSE;
+ dst_allow = FALSE;
+ goto out;
+
+#else
+ return TRUE;
+#endif /* HAVE_APPARMOR */
+}
+
+/**
+ * Check if Apparmor security controls allow the connection to eavesdrop on
+ * other connections.
+ *
+ * @param connection the connection attempting to eavesdrop.
+ * @param bustype name of the bus
+ * @param error the reason for failure when FALSE is returned
+ * @returns TRUE if eavesdropping is permitted
+ */
+dbus_bool_t
+bus_apparmor_allows_eavesdropping (DBusConnection *connection,
+ const char *bustype,
+ DBusError *error)
+{
+#ifdef HAVE_APPARMOR
+ BusAppArmorConfinement *con = NULL;
+ DBusString qstr, auxdata;
+ dbus_bool_t allow = FALSE, audit = TRUE;
+ dbus_bool_t free_auxdata = FALSE;
+ unsigned long pid;
+ int res, serrno = 0;
+
+ if (!apparmor_enabled)
+ return TRUE;
+
+ con = bus_connection_dup_apparmor_confinement (connection);
+
+ if (is_unconfined (con->label, con->mode))
+ {
+ allow = TRUE;
+ audit = FALSE;
+ goto out;
+ }
+
+ if (!_dbus_string_init (&qstr))
+ goto oom;
+
+ if (!build_eavesdrop_query (&qstr, con->label, bustype))
+ {
+ _dbus_string_free (&qstr);
+ goto oom;
+ }
+
+ res = aa_query_label (AA_DBUS_EAVESDROP,
+ _dbus_string_get_data (&qstr),
+ _dbus_string_get_length (&qstr),
+ &allow, &audit);
+ _dbus_string_free (&qstr);
+ if (res == -1)
+ {
+ serrno = errno;
+ set_error_from_query_errno (error, serrno);
+ goto audit;
+ }
+
+ /* Don't fail operations on profiles in complain mode */
+ if (modestr_is_complain (con->mode))
+ allow = TRUE;
+
+ if (!allow)
+ dbus_set_error (error, DBUS_ERROR_ACCESS_DENIED,
+ "Connection \"%s\" is not allowed to eavesdrop due to "
+ "AppArmor policy",
+ bus_connection_is_active (connection) ?
+ bus_connection_get_name (connection) : "(inactive)");
+
+ if (!audit)
+ goto out;
+
+ audit:
+ if (!_dbus_string_init (&auxdata))
+ goto oom;
+ free_auxdata = TRUE;
+
+ if (!_dbus_append_pair_str (&auxdata, "bus", bustype ? bustype : "unknown"))
+ goto oom;
+
+ if (serrno && !_dbus_append_pair_str (&auxdata, "info", strerror (serrno)))
+ goto oom;
+
+ if (!_dbus_append_pair_str (&auxdata, "mask", "eavesdrop"))
+ goto oom;
+
+ if (connection && dbus_connection_get_unix_process_id (connection, &pid) &&
+ !_dbus_append_pair_uint (&auxdata, "pid", pid))
+ goto oom;
+
+ if (con->label && !_dbus_append_pair_str (&auxdata, "label", con->label))
+ goto oom;
+
+ log_message (allow, "eavesdrop", &auxdata);
+
+ out:
+ if (con != NULL)
+ bus_apparmor_confinement_unref (con);
+ if (free_auxdata)
+ _dbus_string_free (&auxdata);
+
+ return allow;
+
+ oom:
+ if (error != NULL && !dbus_error_is_set (error))
+ BUS_SET_OOM (error);
+ allow = FALSE;
+ goto out;
+
+#else
+ return TRUE;
+#endif /* HAVE_APPARMOR */
+}