blob: 985f5e9fd3e9af167fd94fec7d5a17dcea004736 [file] [log] [blame]
/* -*- 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 <libaudit.h>
#endif /* HAVE_LIBAUDIT */
#include "activation.h"
#include "audit.h"
#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;
/* 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;
}
/*
* 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;
#ifdef HAVE_LIBAUDIT
int audit_fd;
#endif
if (allow)
mstr = "ALLOWED";
else
mstr = "DENIED";
#ifdef HAVE_LIBAUDIT
audit_fd = bus_audit_get_fd ();
if (audit_fd >= 0)
{
DBusString avc;
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;
#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;
/* the AppArmor API uses pointers to int for pointers to boolean, and
* int is not strictly guaranteed to be the same as dbus_bool_t */
int 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,
BusActivationEntry *activation_entry,
DBusError *error)
{
#ifdef HAVE_APPARMOR
BusAppArmorConfinement *src_con = NULL, *dst_con = NULL;
DBusString qstr, auxdata;
int src_allow = FALSE, dst_allow = FALSE;
int 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);
const char *dst_label = NULL;
const char *dst_mode = NULL;
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 if (activation_entry != NULL)
{
dst_label = bus_activation_entry_get_assumed_apparmor_label (activation_entry);
}
else
{
dst_con = bus_con;
bus_apparmor_confinement_ref (dst_con);
}
if (dst_con != NULL)
{
dst_label = dst_con->label;
dst_mode = dst_con->mode;
}
/* 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_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;
}
}
/* When deciding whether we can activate a service, we only check that
* we are allowed to send a message to it, not that it is allowed to
* receive that message from us.
*/
if (activation_entry != NULL || is_unconfined (dst_label, dst_mode))
{
dst_allow = TRUE;
dst_audit = FALSE;
}
else
{
if (!_dbus_string_init (&qstr))
goto oom;
if (!build_message_query (&qstr, dst_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_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_label &&
!_dbus_append_pair_str (&auxdata, "peer_label", dst_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_label &&
!_dbus_append_pair_str (&auxdata, "label", dst_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;
int 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 */
}