-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 The goal of this patch is to add the ability for systemd to verify that SELinux policy allows the calling process to do the specified action. Start/Stop/Service
This is expanded upon in this Feature Page Article. https://fedoraproject.org/wiki/Features/SELinuxSystemdAccessControl I am having a hard time testing this, and any suggestions welcomed. -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.12 (GNU/Linux) Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/ iEYEARECAAYFAlAO+00ACgkQrlYvE4MpobPrvQCgsb0XurnGVF31LzO3cvYDYMpl 7+MAoKgFCOOFJAvuMHq19tAg7eE2/Q9n =c5/D -----END PGP SIGNATURE-----
diff --git a/Makefile.am b/Makefile.am index 27666ea..3d44ab0 100644 --- a/Makefile.am +++ b/Makefile.am @@ -943,6 +943,8 @@ libsystemd_core_la_SOURCES = \ src/core/dbus-path.h \ src/core/cgroup.c \ src/core/cgroup.h \ + src/core/selinux-access.c \ + src/core/selinux-access.h \ src/core/selinux-setup.c \ src/core/selinux-setup.h \ src/core/ima-setup.c \ diff --git a/src/core/bus-errors.h b/src/core/bus-errors.h index 04c1b28..dca7824 100644 --- a/src/core/bus-errors.h +++ b/src/core/bus-errors.h @@ -43,6 +43,7 @@ #define BUS_ERROR_TRANSACTION_ORDER_IS_CYCLIC "org.freedesktop.systemd1.TransactionOrderIsCyclic" #define BUS_ERROR_SHUTTING_DOWN "org.freedesktop.systemd1.ShuttingDown" #define BUS_ERROR_NO_SUCH_PROCESS "org.freedesktop.systemd1.NoSuchProcess" +#define BUS_ERROR_ACCESS_DENIED "org.freedesktop.systemd1.AccessDenied" static inline const char *bus_error(const DBusError *e, int r) { if (e && e->message) diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index c341d36..3f6f4af 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -30,6 +30,7 @@ #include "build.h" #include "dbus-common.h" #include "install.h" +#include "selinux-access.h" #include "watchdog.h" #include "hwclock.h" #include "path-util.h" @@ -556,6 +557,8 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection, JobType job_type = _JOB_TYPE_INVALID; bool reload_if_possible = false; const char *member; + const char *perm; + int require_unit; assert(connection); assert(message); @@ -565,6 +568,35 @@ static DBusHandlerResult bus_manager_message_handler(DBusConnection *connection, member = dbus_message_get_member(message); + selinux_perm_lookup(message, &perm, &require_unit); + if (require_unit) { + const char *name; + Unit *u; + + if (!dbus_message_get_args( + message, + &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + return bus_send_error_reply(connection, message, &error, -EINVAL); + + if (!(u = manager_get_unit(m, name))) { + dbus_set_error(&error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s is not loaded.", name); + return bus_send_error_reply(connection, message, &error, -ENOENT); + } + + if (u->fragment_path) { + r = selinux_access_check(connection, message, m, &error, perm, u->fragment_path); + } else { + r = selinux_access_check(connection, message, m, &error, perm, u->source_path); + } + if (r != 0) + return bus_send_error_reply(connection, message, &error, r); + } else { + r = selinux_access_check(connection, message, m, &error, perm, NULL); + if (r != 0) + return bus_send_error_reply(connection, message, &error, r); + } if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Manager", "GetUnit")) { const char *name; Unit *u; diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c index 2d2f378..d592211 100644 --- a/src/core/dbus-unit.c +++ b/src/core/dbus-unit.c @@ -26,6 +26,7 @@ #include "dbus-unit.h" #include "bus-errors.h" #include "dbus-common.h" +#include "selinux-access.h" const char bus_unit_interface[] _introspect_("Unit") = BUS_UNIT_INTERFACE; @@ -411,9 +412,21 @@ static DBusHandlerResult bus_unit_message_dispatch(Unit *u, DBusConnection *conn JobType job_type = _JOB_TYPE_INVALID; char *path = NULL; bool reload_if_possible = false; + int r; + const char *perm; + int require_unit; dbus_error_init(&error); + selinux_perm_lookup(message, &perm, &require_unit); + if (u->fragment_path) { + r = selinux_access_check(connection, message, m, &error, perm, u->fragment_path); + } else { + r = selinux_access_check(connection, message, m, &error, perm, u->source_path); + } + if (r != 0) + return bus_send_error_reply(connection, message, &error, r); + if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Start")) job_type = JOB_START; else if (dbus_message_is_method_call(message, "org.freedesktop.systemd1.Unit", "Stop")) @@ -434,7 +447,6 @@ static DBusHandlerResult bus_unit_message_dispatch(Unit *u, DBusConnection *conn const char *swho; int32_t signo; KillWho who; - int r; if (!dbus_message_get_args( message, @@ -479,7 +491,6 @@ static DBusHandlerResult bus_unit_message_dispatch(Unit *u, DBusConnection *conn const char *smode; JobMode mode; Job *j; - int r; if ((job_type == JOB_START && u->refuse_manual_start) || (job_type == JOB_STOP && u->refuse_manual_stop) || diff --git a/src/core/selinux-access.c b/src/core/selinux-access.c new file mode 100644 index 0000000..8155e55 --- /dev/null +++ b/src/core/selinux-access.c @@ -0,0 +1,390 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2012 Dan Walsh + + systemd 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. + + systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "util.h" +#include "job.h" +#include "manager.h" +#include "selinux-access.h" + +#ifdef HAVE_SELINUX +#include "dbus.h" +#include "log.h" +#include "dbus-unit.h" +#include "bus-errors.h" +#include "dbus-common.h" + +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <selinux/selinux.h> +#include <selinux/avc.h> +#ifdef HAVE_AUDIT +#include <libaudit.h> +#endif +#include <limits.h> + +/* FD to send audit messages to */ +static int audit_fd = -1; +static int selinux_enabled = -1; +static int first_time = 1; +static int selinux_enforcing = 0; + +/* + Define a mapping between the systemd method calls and the SELinux access to check. + We define two tables, one for access checks on unit files, and one for + access checks for the system in general. + + If we do not find a match in either table, then the "undefined" system + check will be called. +*/ + +static const char *unit_methods[][2] = {{ "DisableUnitFiles", "disable" }, + { "EnableUnitFiles", "enable" }, + { "GetUnit", "status" }, + { "GetUnitByPID", "status" }, + { "GetUnitFileState", "status" }, + { "Kill", "stop" }, + { "KillUnit", "stop" }, + { "LinkUnitFiles", "enable" }, + { "ListUnits", "status" }, + { "LoadUnit", "status" }, + { "MaskUnitFiles", "disable" }, + { "PresetUnitFiles", "enable" }, + { "ReenableUnitFiles", "enable" }, + { "Reexecute", "start" }, + { "Reload", "reload" }, + { "ReloadOrRestart", "start" }, + { "ReloadOrRestartUnit", "start" }, + { "ReloadOrTryRestart", "start" }, + { "ReloadOrTryRestartUnit", "start" }, + { "ReloadUnit", "reload" }, + { "ResetFailed", "stop" }, + { "ResetFailedUnit", "stop" }, + { "Restart", "start" }, + { "RestartUnit", "start" }, + { "Start", "start" }, + { "StartUnit", "start" }, + { "StartUnitReplace", "start" }, + { "Stop", "stop" }, + { "StopUnit", "stop" }, + { "TryRestart", "start" }, + { "TryRestartUnit", "start" }, + { "UnmaskUnitFiles", "enable" }, + { NULL, NULL } +}; + +static const char *system_methods[][2] = { { "ClearJobs", "reboot" }, + { "FlushDevices", "halt" }, + { "Get", "status" }, + { "GetAll", "status" }, + { "GetJob", "status" }, + { "GetSeat", "status" }, + { "GetSession", "status" }, + { "GetSessionByPID", "status" }, + { "GetUser", "status" }, + { "Halt", "halt" }, + { "Introspect", "status" }, + { "KExec", "reboot" }, + { "KillSession", "halt" }, + { "KillUser", "halt" }, + { "ListJobs", "status" }, + { "ListSeats", "status" }, + { "ListSessions", "status" }, + { "ListUsers", "status" }, + { "LockSession", "halt" }, + { "PowerOff", "halt" }, + { "Reboot", "reboot" }, + { "SetUserLinger", "halt" }, + { "TerminateSeat", "halt" }, + { "TerminateSession", "halt" }, + { "TerminateUser", "halt" }, + { NULL, NULL } +}; + +/* + If the admin toggles the selinux enforcment mode this callback + will get called before the next access check +*/ +static int setenforce_callback(int enforcing) +{ + selinux_enforcing = enforcing; + return 0; +} + +/* This mimics dbus_bus_get_unix_user() */ +static int bus_get_selinux_security_context( + DBusConnection *connection, + const char *name, + char **scon, + DBusError *error) { + + DBusMessage *m = NULL, *reply = NULL; + int r = -1; + + m = dbus_message_new_method_call( + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "GetConnectionSELinuxSecurityContext"); + if (!m) { + dbus_set_error_const(error, DBUS_ERROR_NO_MEMORY, NULL); + r = -errno; + goto finish; + } + + if (!dbus_message_append_args( + m, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) { + dbus_set_error_const(error, DBUS_ERROR_NO_MEMORY, NULL); + r = -errno; + goto finish; + } + + reply = dbus_connection_send_with_reply_and_block(connection, m, -1, error); + if (!reply) + goto finish; + + if (dbus_set_error_from_message(error, reply)) + goto finish; + + if (!dbus_message_get_args( + reply, error, + DBUS_TYPE_STRING, scon, + DBUS_TYPE_INVALID)) + goto finish; + + r = 0; +finish: + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + return r; +} + +/* + Any time an access gets denied this callback will be called + code copied from dbus. If audit is turned on the messages will go as + user_avc's into the /var/log/audit/audit.log, otherwise they will be + sent to syslog. +*/ +static int log_callback(int type, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); +#ifdef HAVE_AUDIT + if (audit_fd >= 0) + { + char buf[PATH_MAX*2]; + + vsnprintf(buf, sizeof(buf), fmt, ap); + audit_log_user_avc_message(audit_fd, AUDIT_USER_AVC, + buf, NULL, NULL, NULL, 0); + return 0; + } +#endif + log_metav(LOG_USER | LOG_INFO, __FILE__, __LINE__, __FUNCTION__, fmt, ap); + va_end(ap); + return 0; +} + +/* + Function must be called once to initialize the SELinux AVC environment. + Sets up callbacks. + If you want to cleanup memory you should need to call selinux_access_finish. +*/ +static int access_init(void) { + + int r; + + if (avc_open(NULL, 0)) { + log_full(LOG_ERR, "avc_open failed: %m\n"); + return -errno; + } + + selinux_set_callback(SELINUX_CB_LOG, (union selinux_callback) &log_callback); + selinux_set_callback(SELINUX_CB_SETENFORCE, (union selinux_callback) &setenforce_callback); + + if ((r = security_getenforce()) >= 0) { + setenforce_callback(r); + return 0; + } + r = -errno; + avc_destroy(); + return r; +} + +/* + This function returns the security context of the remote end of the dbus + connections. Whether it is on the bus or a local connection. +*/ +static int get_calling_context(DBusConnection *connection, DBusMessage *message, DBusError *error, security_context_t *scon) { + + const char *sender; + int r; + + /* + If sender exists then + if sender is NULL this indicates a local connection. Grab the fd + from dbus and do an getpeercon to peers process context + */ + sender = dbus_message_get_sender(message); + if (sender) { + r = bus_get_selinux_security_context(connection, sender, scon, error); + if (r < 0) + return -EINVAL; + } else { + int fd; + if (! dbus_connection_get_unix_fd(connection, &fd)) + return -EINVAL; + if (getpeercon(fd, scon) < 0) + return -EINVAL; + } + + return 0; +} + +/* + This function returns the SELinux permission to check and whether or not the + check requires a unit file. +*/ +void selinux_perm_lookup(DBusMessage *message, const char **perm, int *require_unit) +{ + int i; + const char *method; + *require_unit = -1; + + method = dbus_message_get_member(message); + + for (i = 0; unit_methods[i][0]; i++) { + if (strcmp(method, unit_methods[i][0]) == 0) { + *perm = unit_methods[i][1]; + *require_unit = 1; + break; + } + } + + if (*require_unit < 0) { + for (i = 0; system_methods[i][0]; i++) { + if (strcmp(method, system_methods[i][0]) == 0) { + *perm = system_methods[i][1]; + *require_unit = 0; + break; + } + } + } + if (*require_unit < 0) { + *require_unit = 0; + *perm = "undefined"; + } +} + +/* + This function communicates with the kernel to check whether or not it should + allow the access. + If the machine is in permissive mode it will return ok. Audit messages will + still be generated if the access would be denied in enforcing mode. +*/ +int selinux_access_check(DBusConnection *connection, DBusMessage *message, Manager *m, DBusError *error, const char *perm, const char *path) { + security_context_t scon = NULL; + security_context_t fcon = NULL; + int r = 0; + const char *tclass = NULL; + +#ifdef HAVE_AUDIT + audit_fd = m->audit_fd; +#endif + if (selinux_enabled < 0) + selinux_enabled = is_selinux_enabled() == 1; + + if (! selinux_enabled) + return 0; + + if (first_time) { + /* if not first time is not set, then initialize access */ + if ((r = access_init()) < 0) { + goto out; + } + first_time = 0; + } + + r = get_calling_context(connection, message, error, &scon); + if (r != 0) { + goto out; + } + + if (path) { + tclass = "service"; + /* get the file context of the unit file */ + if (getfilecon(path, &fcon) < 0) { + log_full(LOG_ERR, "Failed to get security context on: %s %m\n",path); + goto out; + } + + } else { + tclass = "system"; + if (getcon(&fcon) < 0) { + dbus_set_error(error, BUS_ERROR_ACCESS_DENIED, "Unable to get current context, SELinux policy denies access."); + r = -errno; + } + } + if (selinux_check_access(scon, fcon, tclass, perm, NULL) < 0) { + dbus_set_error(error, BUS_ERROR_ACCESS_DENIED, "SELinux policy denies access."); + r = -errno; + } +out: + freecon(scon); + freecon(fcon); + + /* if SELinux is in permissive mode return 0 */ + if ((r < 0) && (!selinux_enforcing)) + r = 0; + + return r; +} + +/* + Clean up memory allocated in selinux_avc_init +*/ +void selinux_access_finish(void) { + if (!first_time) + avc_destroy(); + first_time = 1; +} + +#else +/* + Stub programs for when SELinux is not compiled in +*/ +void selinux_perm_lookup(DBusMessage *message, char **perm, int *require_unit) { + *perm = "unknown"; + *require_unit = 0; + return; +} +int selinux_access_check(Unit *u, DBusConnection *connection, DBusMessage *message, Manager *manager, DBusError *error, const char *perm; const char *path) { + return 0; +} +void selinux_access_finish(void) {} +#endif diff --git a/src/core/selinux-access.h b/src/core/selinux-access.h new file mode 100644 index 0000000..57ab654 --- /dev/null +++ b/src/core/selinux-access.h @@ -0,0 +1,28 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef selinuxaccesshfoo +#define selinuxaccesshfoo + +/*** + This file is part of systemd. + + Copyright 2012 Dan Walsh + + systemd 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. + + systemd 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 systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +void selinux_perm_lookup(DBusMessage *message, const char **perm, int *require_unit); +int selinux_access_check(DBusConnection *connection, DBusMessage *message, Manager *manager, DBusError *error, const char *perm, const char *path); +void selinux_access_finish(void); +#endif diff --git a/src/shared/label.c b/src/shared/label.c index 7aa3621..8cbbb97 100644 --- a/src/shared/label.c +++ b/src/shared/label.c @@ -52,7 +52,7 @@ void label_retest_selinux(void) { #endif -int label_init(const char *prefix) { +int label_init(const char *prefixes[]) { int r = 0; #ifdef HAVE_SELINUX @@ -68,9 +68,9 @@ int label_init(const char *prefix) { before_mallinfo = mallinfo(); before_timestamp = now(CLOCK_MONOTONIC); - if (prefix) { + if (prefixes) { struct selinux_opt options[] = { - { .type = SELABEL_OPT_SUBSET, .value = prefix }, + { .type = SELABEL_OPT_SUBSET, .values = prefixes }, }; label_hnd = selabel_open(SELABEL_CTX_FILE, options, ELEMENTSOF(options)); diff --git a/src/shared/label.h b/src/shared/label.h index 1220b18..05268b3 100644 --- a/src/shared/label.h +++ b/src/shared/label.h @@ -25,7 +25,7 @@ #include <stdbool.h> #include <sys/socket.h> -int label_init(const char *prefix); +int label_init(const char *prefixes[]); void label_finish(void); int label_fix(const char *path, bool ignore_enoent, bool ignore_erofs); diff --git a/src/udev/udevadm.c b/src/udev/udevadm.c index 818edee..95aaee0 100644 --- a/src/udev/udevadm.c +++ b/src/udev/udevadm.c @@ -91,6 +91,7 @@ int main(int argc, char *argv[]) { "version", no_argument, NULL, 'V' }, {} }; + const char *prefixes[] = { "/dev", "/var/run", NULL }; const char *command; unsigned int i; int rc = 1; @@ -102,7 +103,8 @@ int main(int argc, char *argv[]) log_parse_environment(); log_open(); udev_set_log_fn(udev, udev_main_log); - label_init("/dev"); + + label_init(prefixes); for (;;) { int option; diff --git a/src/udev/udevd.c b/src/udev/udevd.c index a028c9c..5fd5762 100644 --- a/src/udev/udevd.c +++ b/src/udev/udevd.c @@ -1069,6 +1069,7 @@ int main(int argc, char *argv[]) int fd_ctrl = -1; int fd_netlink = -1; int fd_worker = -1; + const char *prefixes[] = { "/dev", "/var/run", NULL }; struct epoll_event ep_ctrl, ep_inotify, ep_signal, ep_netlink, ep_worker; struct udev_ctrl_connection *ctrl_conn = NULL; int rc = 1; @@ -1082,7 +1083,7 @@ int main(int argc, char *argv[]) log_open(); udev_set_log_fn(udev, udev_main_log); log_debug("version %s\n", VERSION); - label_init("/dev"); + label_init(prefixes); for (;;) { int option;
_______________________________________________ systemd-devel mailing list systemd-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/systemd-devel