diff options
Diffstat (limited to 'bus')
-rw-r--r-- | bus/Makefile.am | 4 | ||||
-rw-r--r-- | bus/Makefile.in | 49 | ||||
-rw-r--r-- | bus/apparmor.c | 1128 | ||||
-rw-r--r-- | bus/apparmor.h | 66 | ||||
-rw-r--r-- | bus/bus.c | 90 | ||||
-rw-r--r-- | bus/bus.h | 1 | ||||
-rw-r--r-- | bus/config-parser-common.c | 6 | ||||
-rw-r--r-- | bus/config-parser-common.h | 3 | ||||
-rw-r--r-- | bus/config-parser.c | 24 | ||||
-rw-r--r-- | bus/connection.c | 35 | ||||
-rw-r--r-- | bus/connection.h | 1 | ||||
-rw-r--r-- | bus/driver.c | 136 | ||||
-rw-r--r-- | bus/main.c | 8 | ||||
-rw-r--r-- | bus/services.c | 7 | ||||
-rw-r--r-- | bus/session.conf.in | 3 | ||||
-rw-r--r-- | bus/signals.c | 9 | ||||
-rw-r--r-- | bus/signals.h | 2 | ||||
-rw-r--r-- | bus/system.conf.in | 3 |
18 files changed, 1528 insertions, 47 deletions
diff --git a/bus/Makefile.am b/bus/Makefile.am index c0bc1549..9d3cb006 100644 --- a/bus/Makefile.am +++ b/bus/Makefile.am @@ -4,6 +4,7 @@ dbus_daemon_execdir = $(DBUS_DAEMONDIR) DBUS_BUS_LIBS = \ $(XML_LIBS) \ $(SELINUX_LIBS) \ + $(APPARMOR_LIBS) \ $(THREAD_LIBS) \ $(ADT_LIBS) \ $(NETWORK_libs) \ @@ -18,6 +19,7 @@ DBUS_LAUNCHER_LIBS = \ AM_CPPFLAGS = \ -I$(top_srcdir) \ $(XML_CFLAGS) \ + $(APPARMOR_CFLAGS) \ -DDBUS_SYSTEM_CONFIG_FILE=\""$(configdir)/system.conf"\" \ -DDBUS_COMPILATION \ -DDBUS_STATIC_BUILD \ @@ -69,6 +71,8 @@ BUS_SOURCES= \ activation.c \ activation.h \ activation-exit-codes.h \ + apparmor.c \ + apparmor.h \ bus.c \ bus.h \ config-parser.c \ diff --git a/bus/Makefile.in b/bus/Makefile.in index 6bbcecd6..f162f171 100644 --- a/bus/Makefile.in +++ b/bus/Makefile.in @@ -136,21 +136,21 @@ am__installdirs = "$(DESTDIR)$(dbus_daemon_execdir)" \ PROGRAMS = $(dbus_daemon_exec_PROGRAMS) $(libexec_PROGRAMS) \ $(noinst_PROGRAMS) am__dbus_daemon_SOURCES_DIST = activation.c activation.h \ - activation-exit-codes.h bus.c bus.h config-parser.c \ - config-parser.h config-parser-common.c config-parser-common.h \ - connection.c connection.h desktop-file.c desktop-file.h \ - dir-watch-default.c dir-watch-inotify.c dir-watch-kqueue.c \ - dir-watch.h dispatch.c dispatch.h driver.c driver.h \ - expirelist.c expirelist.h policy.c policy.h selinux.h \ - selinux.c services.c services.h signals.c signals.h stats.c \ - stats.h test.c test.h utils.c utils.h config-loader-expat.c \ - main.c + activation-exit-codes.h apparmor.c apparmor.h bus.c bus.h \ + config-parser.c config-parser.h config-parser-common.c \ + config-parser-common.h connection.c connection.h \ + desktop-file.c desktop-file.h dir-watch-default.c \ + dir-watch-inotify.c dir-watch-kqueue.c dir-watch.h dispatch.c \ + dispatch.h driver.c driver.h expirelist.c expirelist.h \ + policy.c policy.h selinux.h selinux.c services.c services.h \ + signals.c signals.h stats.c stats.h test.c test.h utils.c \ + utils.h config-loader-expat.c main.c @DBUS_BUS_ENABLE_INOTIFY_FALSE@@DBUS_BUS_ENABLE_KQUEUE_FALSE@am__objects_1 = dir-watch-default.$(OBJEXT) @DBUS_BUS_ENABLE_INOTIFY_TRUE@@DBUS_BUS_ENABLE_KQUEUE_FALSE@am__objects_1 = dir-watch-inotify.$(OBJEXT) @DBUS_BUS_ENABLE_KQUEUE_TRUE@am__objects_1 = \ @DBUS_BUS_ENABLE_KQUEUE_TRUE@ dir-watch-kqueue.$(OBJEXT) am__objects_2 = config-loader-expat.$(OBJEXT) -am__objects_3 = activation.$(OBJEXT) bus.$(OBJEXT) \ +am__objects_3 = activation.$(OBJEXT) apparmor.$(OBJEXT) bus.$(OBJEXT) \ config-parser.$(OBJEXT) config-parser-common.$(OBJEXT) \ connection.$(OBJEXT) desktop-file.$(OBJEXT) $(am__objects_1) \ dispatch.$(OBJEXT) driver.$(OBJEXT) expirelist.$(OBJEXT) \ @@ -162,7 +162,7 @@ dbus_daemon_OBJECTS = $(am_dbus_daemon_OBJECTS) am__DEPENDENCIES_1 = am__DEPENDENCIES_2 = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ - $(am__DEPENDENCIES_1) + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) dbus_daemon_DEPENDENCIES = $(top_builddir)/dbus/libdbus-internal.la \ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_2) AM_V_lt = $(am__v_lt_@AM_V@) @@ -195,15 +195,15 @@ dbus_daemon_launch_helper_test_OBJECTS = \ dbus_daemon_launch_helper_test_DEPENDENCIES = \ $(top_builddir)/dbus/libdbus-internal.la $(am__DEPENDENCIES_3) am__test_bus_SOURCES_DIST = activation.c activation.h \ - activation-exit-codes.h bus.c bus.h config-parser.c \ - config-parser.h config-parser-common.c config-parser-common.h \ - connection.c connection.h desktop-file.c desktop-file.h \ - dir-watch-default.c dir-watch-inotify.c dir-watch-kqueue.c \ - dir-watch.h dispatch.c dispatch.h driver.c driver.h \ - expirelist.c expirelist.h policy.c policy.h selinux.h \ - selinux.c services.c services.h signals.c signals.h stats.c \ - stats.h test.c test.h utils.c utils.h config-loader-expat.c \ - test-main.c + activation-exit-codes.h apparmor.c apparmor.h bus.c bus.h \ + config-parser.c config-parser.h config-parser-common.c \ + config-parser-common.h connection.c connection.h \ + desktop-file.c desktop-file.h dir-watch-default.c \ + dir-watch-inotify.c dir-watch-kqueue.c dir-watch.h dispatch.c \ + dispatch.h driver.c driver.h expirelist.c expirelist.h \ + policy.c policy.h selinux.h selinux.c services.c services.h \ + signals.c signals.h stats.c stats.h test.c test.h utils.c \ + utils.h config-loader-expat.c test-main.c am_test_bus_OBJECTS = $(am__objects_3) test-main.$(OBJEXT) test_bus_OBJECTS = $(am_test_bus_OBJECTS) test_bus_DEPENDENCIES = $(top_builddir)/dbus/libdbus-internal.la \ @@ -328,6 +328,8 @@ ACLOCAL = @ACLOCAL@ ADT_LIBS = @ADT_LIBS@ AMTAR = @AMTAR@ AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +APPARMOR_CFLAGS = @APPARMOR_CFLAGS@ +APPARMOR_LIBS = @APPARMOR_LIBS@ AR = @AR@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ @@ -382,6 +384,7 @@ DEPDIR = @DEPDIR@ DLLTOOL = @DLLTOOL@ DOXYGEN = @DOXYGEN@ DSYMUTIL = @DSYMUTIL@ +DUCKTYPE = @DUCKTYPE@ DUMPBIN = @DUMPBIN@ ECHO_C = @ECHO_C@ ECHO_N = @ECHO_N@ @@ -471,6 +474,7 @@ X_CFLAGS = @X_CFLAGS@ X_EXTRA_LIBS = @X_EXTRA_LIBS@ X_LIBS = @X_LIBS@ X_PRE_LIBS = @X_PRE_LIBS@ +YELP_BUILD = @YELP_BUILD@ abs_builddir = @abs_builddir@ abs_srcdir = @abs_srcdir@ abs_top_builddir = @abs_top_builddir@ @@ -535,6 +539,7 @@ dbus_daemon_execdir = $(DBUS_DAEMONDIR) DBUS_BUS_LIBS = \ $(XML_LIBS) \ $(SELINUX_LIBS) \ + $(APPARMOR_LIBS) \ $(THREAD_LIBS) \ $(ADT_LIBS) \ $(NETWORK_libs) \ @@ -549,6 +554,7 @@ DBUS_LAUNCHER_LIBS = \ AM_CPPFLAGS = \ -I$(top_srcdir) \ $(XML_CFLAGS) \ + $(APPARMOR_CFLAGS) \ -DDBUS_SYSTEM_CONFIG_FILE=\""$(configdir)/system.conf"\" \ -DDBUS_COMPILATION \ -DDBUS_STATIC_BUILD \ @@ -583,6 +589,8 @@ BUS_SOURCES = \ activation.c \ activation.h \ activation-exit-codes.h \ + apparmor.c \ + apparmor.h \ bus.c \ bus.h \ config-parser.c \ @@ -976,6 +984,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/activation-helper-bin.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/activation-helper.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/activation.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/apparmor.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/bus.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/config-loader-expat.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/config-parser-common.Po@am__quote@ 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 */ +} diff --git a/bus/apparmor.h b/bus/apparmor.h new file mode 100644 index 00000000..4a47aecc --- /dev/null +++ b/bus/apparmor.h @@ -0,0 +1,66 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * apparmor.c AppArmor security checks for D-Bus + * + * Authors: John Johansen <john.johansen@canonical.com> + * Tyler Hicks <tyhicks@canonical.com> + * Based on: selinux.h by Matthew Rickard + * + * 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 + * + */ + +#ifndef BUS_APPARMOR_H +#define BUS_APPARMOR_H + +#include <dbus/dbus.h> +#include "bus.h" + +void bus_apparmor_audit_init (void); +dbus_bool_t bus_apparmor_pre_init (void); +dbus_bool_t bus_apparmor_set_mode_from_config (const char *mode, + DBusError *error); +dbus_bool_t bus_apparmor_full_init (DBusError *error); +void bus_apparmor_shutdown (void); +dbus_bool_t bus_apparmor_enabled (void); + +void bus_apparmor_confinement_unref (BusAppArmorConfinement *confinement); +void bus_apparmor_confinement_ref (BusAppArmorConfinement *confinement); +BusAppArmorConfinement* bus_apparmor_init_connection_confinement (DBusConnection *connection, + DBusError *error); + +dbus_bool_t bus_apparmor_allows_acquire_service (DBusConnection *connection, + const char *bustype, + const char *service_name, + DBusError *error); +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); + +dbus_bool_t bus_apparmor_allows_eavesdropping (DBusConnection *connection, + const char *bustype, + DBusError *error); + +#endif /* BUS_APPARMOR_H */ @@ -34,6 +34,7 @@ #include "config-parser.h" #include "signals.h" #include "selinux.h" +#include "apparmor.h" #include "dir-watch.h" #include <dbus/dbus-list.h> #include <dbus/dbus-hash.h> @@ -933,6 +934,20 @@ bus_context_new (const DBusString *config_file, bus_context_log (context, DBUS_SYSTEM_LOG_FATAL, "SELinux enabled but D-Bus initialization failed; check system log\n"); } + if (!bus_apparmor_full_init (error)) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + goto failed; + } + + if (bus_apparmor_enabled ()) + { + /* Only print AppArmor mediation message when syslog support is enabled */ + if (context->syslog) + bus_context_log (context, DBUS_SYSTEM_LOG_INFO, + "AppArmor D-Bus mediation is enabled\n"); + } + if (!process_config_postinit (context, parser, error)) { _DBUS_ASSERT_ERROR_IS_SET (error); @@ -960,6 +975,9 @@ bus_context_new (const DBusString *config_file, /* FIXME - why not just put this in full_init() below? */ bus_selinux_audit_init (); #endif +#ifdef HAVE_APPARMOR + bus_apparmor_audit_init (); +#endif } dbus_server_free_data_slot (&server_data_slot); @@ -1503,7 +1521,7 @@ bus_context_check_security_policy (BusContext *context, DBusMessage *message, DBusError *error) { - const char *dest; + const char *src, *dest; BusClientPolicy *sender_policy; BusClientPolicy *recipient_policy; dbus_int32_t toggles; @@ -1512,6 +1530,7 @@ bus_context_check_security_policy (BusContext *context, dbus_bool_t requested_reply; type = dbus_message_get_type (message); + src = dbus_message_get_sender (message); dest = dbus_message_get_destination (message); /* dispatch.c was supposed to ensure these invariants */ @@ -1544,30 +1563,6 @@ bus_context_check_security_policy (BusContext *context, if (sender != NULL) { - /* First verify the SELinux access controls. If allowed then - * go on with the standard checks. - */ - if (!bus_selinux_allows_send (sender, proposed_recipient, - dbus_message_type_to_string (dbus_message_get_type (message)), - dbus_message_get_interface (message), - dbus_message_get_member (message), - dbus_message_get_error_name (message), - dest ? dest : DBUS_SERVICE_DBUS, error)) - { - if (error != NULL && !dbus_error_is_set (error)) - { - /* don't syslog this, just set the error: avc_has_perm should - * have already written to either the audit log or syslog */ - complain_about_message (context, DBUS_ERROR_ACCESS_DENIED, - "An SELinux policy prevents this sender from sending this " - "message to this recipient", - 0, message, sender, proposed_recipient, FALSE, FALSE, error); - _dbus_verbose ("SELinux security check denying send to service\n"); - } - - return FALSE; - } - if (bus_connection_is_active (sender)) { sender_policy = bus_connection_get_policy (sender); @@ -1598,6 +1593,51 @@ bus_context_check_security_policy (BusContext *context, } else { + sender_policy = NULL; + } + + /* First verify the SELinux access controls. If allowed then + * go on with the standard checks. + */ + if (!bus_selinux_allows_send (sender, proposed_recipient, + dbus_message_type_to_string (dbus_message_get_type (message)), + dbus_message_get_interface (message), + dbus_message_get_member (message), + dbus_message_get_error_name (message), + dest ? dest : DBUS_SERVICE_DBUS, error)) + { + if (error != NULL && !dbus_error_is_set (error)) + { + /* don't syslog this, just set the error: avc_has_perm should + * have already written to either the audit log or syslog */ + complain_about_message (context, DBUS_ERROR_ACCESS_DENIED, + "An SELinux policy prevents this sender from sending this " + "message to this recipient", + 0, message, sender, proposed_recipient, FALSE, FALSE, error); + _dbus_verbose ("SELinux security check denying send to service\n"); + } + + return FALSE; + } + + /* next verify AppArmor access controls. If allowed then + * go on with the standard checks. + */ + if (!bus_apparmor_allows_send (sender, proposed_recipient, + requested_reply, + bus_context_get_type (context), + dbus_message_get_type (message), + dbus_message_get_path (message), + dbus_message_get_interface (message), + dbus_message_get_member (message), + dbus_message_get_error_name (message), + dest ? dest : DBUS_SERVICE_DBUS, + src ? src : DBUS_SERVICE_DBUS, + error)) + return FALSE; + + if (!bus_connection_is_active (sender)) + { /* Policy for inactive connections is that they can only send * the hello message to the bus driver */ @@ -38,6 +38,7 @@ typedef struct BusClientPolicy BusClientPolicy; typedef struct BusPolicyRule BusPolicyRule; typedef struct BusRegistry BusRegistry; typedef struct BusSELinuxID BusSELinuxID; +typedef struct BusAppArmorConfinement BusAppArmorConfinement; typedef struct BusService BusService; typedef struct BusOwner BusOwner; typedef struct BusTransaction BusTransaction; diff --git a/bus/config-parser-common.c b/bus/config-parser-common.c index c522ff49..5db6b289 100644 --- a/bus/config-parser-common.c +++ b/bus/config-parser-common.c @@ -127,6 +127,10 @@ bus_config_parser_element_name_to_type (const char *name) { return ELEMENT_ALLOW_ANONYMOUS; } + else if (strcmp (name, "apparmor") == 0) + { + return ELEMENT_APPARMOR; + } return ELEMENT_NONE; } @@ -181,6 +185,8 @@ bus_config_parser_element_type_to_name (ElementType type) return "keep_umask"; case ELEMENT_ALLOW_ANONYMOUS: return "allow_anonymous"; + case ELEMENT_APPARMOR: + return "apparmor"; } _dbus_assert_not_reached ("bad element type"); diff --git a/bus/config-parser-common.h b/bus/config-parser-common.h index 186bf4cf..382a0141 100644 --- a/bus/config-parser-common.h +++ b/bus/config-parser-common.h @@ -49,7 +49,8 @@ typedef enum ELEMENT_STANDARD_SYSTEM_SERVICEDIRS, ELEMENT_KEEP_UMASK, ELEMENT_SYSLOG, - ELEMENT_ALLOW_ANONYMOUS + ELEMENT_ALLOW_ANONYMOUS, + ELEMENT_APPARMOR } ElementType; ElementType bus_config_parser_element_name_to_type (const char *element_name); diff --git a/bus/config-parser.c b/bus/config-parser.c index ee2d4e7d..58048a50 100644 --- a/bus/config-parser.c +++ b/bus/config-parser.c @@ -28,6 +28,7 @@ #include "utils.h" #include "policy.h" #include "selinux.h" +#include "apparmor.h" #include <dbus/dbus-list.h> #include <dbus/dbus-internals.h> #include <dbus/dbus-misc.h> @@ -1136,6 +1137,27 @@ start_busconfig_child (BusConfigParser *parser, return TRUE; } + else if (element_type == ELEMENT_APPARMOR) + { + Element *e; + const char *mode; + + if ((e = push_element (parser, ELEMENT_APPARMOR)) == NULL) + { + BUS_SET_OOM (error); + return FALSE; + } + + if (!locate_attributes (parser, "apparmor", + attribute_names, + attribute_values, + error, + "mode", &mode, + NULL)) + return FALSE; + + return bus_apparmor_set_mode_from_config (mode, error); + } else { dbus_set_error (error, DBUS_ERROR_FAILED, @@ -2074,6 +2096,7 @@ bus_config_parser_end_element (BusConfigParser *parser, case ELEMENT_STANDARD_SESSION_SERVICEDIRS: case ELEMENT_STANDARD_SYSTEM_SERVICEDIRS: case ELEMENT_ALLOW_ANONYMOUS: + case ELEMENT_APPARMOR: break; } @@ -2373,6 +2396,7 @@ bus_config_parser_content (BusConfigParser *parser, case ELEMENT_ALLOW_ANONYMOUS: case ELEMENT_SELINUX: case ELEMENT_ASSOCIATE: + case ELEMENT_APPARMOR: if (all_whitespace (content)) return TRUE; else diff --git a/bus/connection.c b/bus/connection.c index 64da1292..690f3a5d 100644 --- a/bus/connection.c +++ b/bus/connection.c @@ -30,6 +30,7 @@ #include "signals.h" #include "expirelist.h" #include "selinux.h" +#include "apparmor.h" #include <dbus/dbus-list.h> #include <dbus/dbus-hash.h> #include <dbus/dbus-timeout.h> @@ -99,6 +100,7 @@ typedef struct char *cached_loginfo_string; BusSELinuxID *selinux_id; + BusAppArmorConfinement *apparmor_confinement; long connection_tv_sec; /**< Time when we connected (seconds component) */ long connection_tv_usec; /**< Time when we connected (microsec component) */ @@ -439,6 +441,9 @@ free_connection_data (void *data) if (d->selinux_id) bus_selinux_id_unref (d->selinux_id); + + if (d->apparmor_confinement) + bus_apparmor_confinement_unref (d->apparmor_confinement); dbus_free (d->cached_loginfo_string); @@ -714,6 +719,19 @@ bus_connections_setup_connection (BusConnections *connections, goto out; } + d->apparmor_confinement = bus_apparmor_init_connection_confinement (connection, + &error); + if (dbus_error_is_set (&error)) + { + /* This is a bit bogus because we pretend all errors + * are OOM; this is done because we know that in bus.c + * an OOM error disconnects the connection, which is + * the same thing we want on any other error. + */ + dbus_error_free (&error); + goto out; + } + if (!dbus_connection_set_watch_functions (connection, add_connection_watch, remove_connection_watch, @@ -801,6 +819,10 @@ bus_connections_setup_connection (BusConnections *connections, if (d->selinux_id) bus_selinux_id_unref (d->selinux_id); d->selinux_id = NULL; + + if (d->apparmor_confinement) + bus_apparmor_confinement_unref (d->apparmor_confinement); + d->apparmor_confinement = NULL; if (!dbus_connection_set_watch_functions (connection, NULL, NULL, NULL, @@ -1201,6 +1223,19 @@ bus_connection_get_selinux_id (DBusConnection *connection) return d->selinux_id; } +BusAppArmorConfinement* +bus_connection_dup_apparmor_confinement (DBusConnection *connection) +{ + BusConnectionData *d; + + d = BUS_CONNECTION_DATA (connection); + + _dbus_assert (d != NULL); + + bus_apparmor_confinement_ref (d->apparmor_confinement); + return d->apparmor_confinement; +} + /** * Checks whether the connection is registered with the message bus. * diff --git a/bus/connection.h b/bus/connection.h index dca22633..8c68d0a0 100644 --- a/bus/connection.h +++ b/bus/connection.h @@ -54,6 +54,7 @@ BusActivation* bus_connection_get_activation (DBusConnection BusMatchmaker* bus_connection_get_matchmaker (DBusConnection *connection); const char * bus_connection_get_loginfo (DBusConnection *connection); BusSELinuxID* bus_connection_get_selinux_id (DBusConnection *connection); +BusAppArmorConfinement* bus_connection_dup_apparmor_confinement (DBusConnection *connection); dbus_bool_t bus_connections_check_limits (BusConnections *connections, DBusConnection *requesting_completion, DBusError *error); diff --git a/bus/driver.c b/bus/driver.c index ceebb6f2..aab922ae 100644 --- a/bus/driver.c +++ b/bus/driver.c @@ -24,6 +24,7 @@ #include <config.h> #include "activation.h" +#include "apparmor.h" #include "connection.h" #include "driver.h" #include "dispatch.h" @@ -34,10 +35,12 @@ #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> #include <dbus/dbus-marshal-recursive.h> +#include <dbus/dbus-marshal-validate.h> #include <string.h> static DBusConnection * @@ -1108,9 +1111,10 @@ bus_driver_handle_add_match (DBusConnection *connection, DBusError *error) { BusMatchRule *rule; - const char *text; + const char *text, *bustype; DBusString str; BusMatchmaker *matchmaker; + BusContext *context; _DBUS_ASSERT_ERROR_IS_CLEAR (error); @@ -1143,6 +1147,12 @@ bus_driver_handle_add_match (DBusConnection *connection, if (rule == NULL) goto failed; + context = bus_transaction_get_context (transaction); + bustype = context ? bus_context_get_type (context) : NULL; + if (bus_match_rule_get_client_is_eavesdropping (rule) && + !bus_apparmor_allows_eavesdropping (connection, bustype, error)) + goto failed; + matchmaker = bus_connection_get_matchmaker (connection); if (!bus_matchmaker_add_rule (matchmaker, rule)) @@ -1646,6 +1656,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); @@ -1680,6 +1691,45 @@ bus_driver_handle_get_connection_credentials (DBusConnection *connection, goto oom; } + if (dbus_connection_get_windows_user (conn, &s)) + { + DBusString str; + dbus_bool_t result; + + if (s == NULL) + goto oom; + + _dbus_string_init_const (&str, s); + result = _dbus_validate_utf8 (&str, 0, _dbus_string_get_length (&str)); + _dbus_string_free (&str); + if (result) + { + if (!_dbus_asv_add_string (&array_iter, "WindowsSID", s)) + { + dbus_free (s); + goto oom; + } + } + dbus_free (s); + } + + 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; @@ -1746,6 +1796,72 @@ bus_driver_handle_reload_config (DBusConnection *connection, return FALSE; } +#ifdef DBUS_ENABLE_VERBOSE_MODE +static dbus_bool_t +bus_driver_handle_enable_verbose (DBusConnection *connection, + BusTransaction *transaction, + DBusMessage *message, + DBusError *error) +{ + DBusMessage *reply = NULL; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + reply = dbus_message_new_method_return (message); + if (reply == NULL) + goto oom; + + if (! bus_transaction_send_from_driver (transaction, connection, reply)) + goto oom; + + _dbus_set_verbose(TRUE); + + dbus_message_unref (reply); + return TRUE; + + oom: + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + BUS_SET_OOM (error); + + if (reply) + dbus_message_unref (reply); + return FALSE; +} + +static dbus_bool_t +bus_driver_handle_disable_verbose (DBusConnection *connection, + BusTransaction *transaction, + DBusMessage *message, + DBusError *error) +{ + DBusMessage *reply = NULL; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + reply = dbus_message_new_method_return (message); + if (reply == NULL) + goto oom; + + if (! bus_transaction_send_from_driver (transaction, connection, reply)) + goto oom; + + _dbus_set_verbose(FALSE); + + dbus_message_unref (reply); + return TRUE; + + oom: + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + BUS_SET_OOM (error); + + if (reply) + dbus_message_unref (reply); + return FALSE; +} +#endif + static dbus_bool_t bus_driver_handle_get_id (DBusConnection *connection, BusTransaction *transaction, @@ -1808,6 +1924,8 @@ bus_driver_handle_become_monitor (DBusConnection *connection, DBusError *error) { char **match_rules = NULL; + const char *bustype; + BusContext *context; BusMatchRule *rule; DBusList *rules = NULL; DBusList *iter; @@ -1822,6 +1940,11 @@ bus_driver_handle_become_monitor (DBusConnection *connection, if (!bus_driver_check_message_is_for_us (message, error)) goto out; + context = bus_transaction_get_context (transaction); + bustype = context ? bus_context_get_type (context) : NULL; + if (!bus_apparmor_allows_eavesdropping (connection, bustype, error)) + goto out; + if (!bus_driver_check_caller_is_privileged (connection, transaction, message, error)) goto out; @@ -2018,6 +2141,14 @@ static const MessageHandler monitoring_message_handlers[] = { { NULL, NULL, NULL, NULL } }; +#ifdef DBUS_ENABLE_VERBOSE_MODE +static const MessageHandler verbose_message_handlers[] = { + { "EnableVerbose", "", "", bus_driver_handle_enable_verbose}, + { "DisableVerbose", "", "", bus_driver_handle_disable_verbose}, + { NULL, NULL, NULL, NULL } +}; +#endif + #ifdef DBUS_ENABLE_STATS static const MessageHandler stats_message_handlers[] = { { "GetStats", "", "a{sv}", bus_stats_handle_get_stats }, @@ -2050,6 +2181,9 @@ static InterfaceHandler interface_handlers[] = { " </signal>\n" }, { DBUS_INTERFACE_INTROSPECTABLE, introspectable_message_handlers, NULL }, { DBUS_INTERFACE_MONITORING, monitoring_message_handlers, NULL }, +#ifdef DBUS_ENABLE_VERBOSE_MODE + { DBUS_INTERFACE_VERBOSE, verbose_message_handlers, NULL }, +#endif #ifdef DBUS_ENABLE_STATS { BUS_INTERFACE_STATS, stats_message_handlers, NULL }, #endif @@ -39,6 +39,7 @@ #include <unistd.h> /* for write() and STDERR_FILENO */ #endif #include "selinux.h" +#include "apparmor.h" static BusContext *context; @@ -614,6 +615,12 @@ main (int argc, char **argv) exit (1); } + if (!bus_apparmor_pre_init ()) + { + _dbus_warn ("AppArmor pre-initialization failed: out of memory\n"); + exit (1); + } + dbus_error_init (&error); context = bus_context_new (&config_file, flags, &print_addr_pipe, &print_pid_pipe, @@ -649,6 +656,7 @@ main (int argc, char **argv) bus_context_shutdown (context); bus_context_unref (context); bus_selinux_shutdown (); + bus_apparmor_shutdown (); return 0; } diff --git a/bus/services.c b/bus/services.c index 584485b1..8e625867 100644 --- a/bus/services.c +++ b/bus/services.c @@ -36,6 +36,7 @@ #include "policy.h" #include "bus.h" #include "selinux.h" +#include "apparmor.h" struct BusService { @@ -458,6 +459,12 @@ bus_registry_acquire_service (BusRegistry *registry, _dbus_string_get_const_data (service_name)); goto out; } + + if (!bus_apparmor_allows_acquire_service (connection, + (registry->context ? + bus_context_get_type (registry->context) : NULL), + _dbus_string_get_const_data (service_name), error)) + goto out; if (!bus_client_policy_check_can_own (policy, service_name)) { diff --git a/bus/session.conf.in b/bus/session.conf.in index cfe9544f..d2e3f2d0 100644 --- a/bus/session.conf.in +++ b/bus/session.conf.in @@ -25,6 +25,9 @@ <allow own="*"/> </policy> + <!-- Enable AppArmor mediation when it is available --> + <apparmor mode="enabled"/> + <!-- Config files are placed here that among other things, further restrict the above policy for specific services. --> <includedir>session.d</includedir> diff --git a/bus/signals.c b/bus/signals.c index 4390028f..260dd243 100644 --- a/bus/signals.c +++ b/bus/signals.c @@ -411,6 +411,15 @@ bus_match_rule_set_client_is_eavesdropping (BusMatchRule *rule, } dbus_bool_t +bus_match_rule_get_client_is_eavesdropping (BusMatchRule *rule) +{ + if (rule->flags & BUS_MATCH_CLIENT_IS_EAVESDROPPING) + return TRUE; + else + return FALSE; +} + +dbus_bool_t bus_match_rule_set_path (BusMatchRule *rule, const char *path, dbus_bool_t is_namespace) diff --git a/bus/signals.h b/bus/signals.h index d19fc7c5..0edfb07e 100644 --- a/bus/signals.h +++ b/bus/signals.h @@ -73,6 +73,8 @@ dbus_bool_t bus_match_rule_set_arg (BusMatchRule *rule, void bus_match_rule_set_client_is_eavesdropping (BusMatchRule *rule, dbus_bool_t is_eavesdropping); +dbus_bool_t bus_match_rule_get_client_is_eavesdropping (BusMatchRule *rule); + BusMatchRule* bus_match_rule_parse (DBusConnection *matches_go_to, const DBusString *rule_text, DBusError *error); diff --git a/bus/system.conf.in b/bus/system.conf.in index ac78c734..fc472bd7 100644 --- a/bus/system.conf.in +++ b/bus/system.conf.in @@ -97,6 +97,9 @@ send_interface="org.freedesktop.DBus.Debug.Stats"/> </policy> + <!-- Enable AppArmor mediation when it is available --> + <apparmor mode="enabled"/> + <!-- Config files are placed here that among other things, punch holes in the above policy for specific services. --> <includedir>system.d</includedir> |