diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml index 7803cc9cf9..f821d6562a 100644 --- a/man/org.freedesktop.systemd1.xml +++ b/man/org.freedesktop.systemd1.xml @@ -1650,6 +1650,11 @@ node /org/freedesktop/systemd1 { that the root path is encoded as the empty string here (not as /!), so that it can be appended to /sys/fs/cgroup/systemd easily. This value will be set to the empty string for the host instance and some other string for container instances. + + AccessSELinuxContext contains the SELinux context that is used to control + access to the unit. It's read from the unit file when it is loaded and cached until the service manager + is reloaded. This property contains an empty string if SELinux is not used or if no label could be read + (for example because the unit is not backed by a file on disk). @@ -1783,6 +1788,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly s Description = '...'; @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly s AccessSELinuxContext = '...'; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") readonly s LoadState = '...'; readonly s ActiveState = '...'; readonly s FreezerState = '...'; @@ -2090,6 +2097,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c index aaa72cec80..3c5e62ae4b 100644 --- a/src/core/dbus-unit.c +++ b/src/core/dbus-unit.c @@ -894,6 +894,7 @@ const sd_bus_vtable bus_unit_vtable[] = { SD_BUS_PROPERTY("RequiresMountsFor", "as", property_get_requires_mounts_for, offsetof(Unit, requires_mounts_for), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Documentation", "as", NULL, offsetof(Unit, documentation), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Description", "s", property_get_description, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("AccessSELinuxContext", "s", NULL, offsetof(Unit, access_selinux_context), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("LoadState", "s", property_get_load_state, offsetof(Unit, load_state), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("ActiveState", "s", property_get_active_state, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("FreezerState", "s", property_get_freezer_state, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index 92e99e7d53..d16c7b1df2 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -58,6 +58,7 @@ #include "seccomp-util.h" #endif #include "securebits-util.h" +#include "selinux-util.h" #include "signal-util.h" #include "socket-netlink.h" #include "specifier.h" @@ -6096,6 +6097,7 @@ int unit_load_fragment(Unit *u) { assert(u->id); if (u->transient) { + u->access_selinux_context = mfree(u->access_selinux_context); u->load_state = UNIT_LOADED; return 0; } @@ -6141,7 +6143,24 @@ int unit_load_fragment(Unit *u) { u->load_state = u->perpetual ? UNIT_LOADED : UNIT_MASKED; /* don't allow perpetual units to ever be masked */ u->fragment_mtime = 0; + u->access_selinux_context = mfree(u->access_selinux_context); } else { +#if HAVE_SELINUX + if (mac_selinux_use()) { + _cleanup_freecon_ char *selcon = NULL; + + /* Cache the SELinux context of the unit file here. We'll make use of when checking access permissions to loaded units */ + r = fgetfilecon_raw(fileno(f), &selcon); + if (r < 0) + log_unit_warning_errno(u, r, "Failed to read SELinux context of '%s', ignoring: %m", fragment); + + r = free_and_strdup(&u->access_selinux_context, selcon); + if (r < 0) + return r; + } else +#endif + u->access_selinux_context = mfree(u->access_selinux_context); + u->load_state = UNIT_LOADED; u->fragment_mtime = timespec_load(&st.st_mtim); diff --git a/src/core/selinux-access.c b/src/core/selinux-access.c index d77901caa5..848ae246a7 100644 --- a/src/core/selinux-access.c +++ b/src/core/selinux-access.c @@ -179,13 +179,14 @@ static int access_init(sd_bus_error *error) { */ int mac_selinux_access_check_internal( sd_bus_message *message, - const char *path, + const char *unit_path, + const char *unit_context, const char *permission, const char *function, sd_bus_error *error) { _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - const char *tclass, *scon; + const char *tclass, *scon, *acon; _cleanup_free_ char *cl = NULL; _cleanup_freecon_ char *fcon = NULL; char **cmdline = NULL; @@ -214,37 +215,22 @@ int mac_selinux_access_check_internal( if (r < 0) return r; - /* The SELinux context is something we really should have - * gotten directly from the message or sender, and not be an - * augmented field. If it was augmented we cannot use it for - * authorization, since this is racy and vulnerable. Let's add - * an extra check, just in case, even though this really - * shouldn't be possible. */ + /* The SELinux context is something we really should have gotten directly from the message or sender, + * and not be an augmented field. If it was augmented we cannot use it for authorization, since this + * is racy and vulnerable. Let's add an extra check, just in case, even though this really shouldn't + * be possible. */ assert_return((sd_bus_creds_get_augmented_mask(creds) & SD_BUS_CREDS_SELINUX_CONTEXT) == 0, -EPERM); r = sd_bus_creds_get_selinux_context(creds, &scon); if (r < 0) return r; - if (path) { - /* Get the file context of the unit file */ - - if (getfilecon_raw(path, &fcon) < 0) { - r = -errno; - - log_warning_errno(r, "SELinux getfilecon_raw() on '%s' failed%s (perm=%s): %m", - path, - enforce ? "" : ", ignoring", - permission); - if (!enforce) - return 0; - - return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Failed to get file context on %s.", path); - } - + if (unit_context) { + /* Nice! The unit comes with a SELinux context read from the unit file */ + acon = unit_context; tclass = "service"; - } else { + /* If no unit context is known, use our own */ if (getcon_raw(&fcon) < 0) { r = -errno; @@ -254,9 +240,10 @@ int mac_selinux_access_check_internal( if (!enforce) return 0; - return sd_bus_error_set(error, SD_BUS_ERROR_ACCESS_DENIED, "Failed to get current context."); + return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Failed to get current context: %m"); } + acon = fcon; tclass = "system"; } @@ -265,22 +252,22 @@ int mac_selinux_access_check_internal( struct audit_info audit_info = { .creds = creds, - .path = path, + .path = unit_path, .cmdline = cl, .function = function, }; - r = selinux_check_access(scon, fcon, tclass, permission, &audit_info); + r = selinux_check_access(scon, acon, tclass, permission, &audit_info); if (r < 0) { - r = errno_or_else(EPERM); + errno = -(r = errno_or_else(EPERM)); if (enforce) - sd_bus_error_set(error, SD_BUS_ERROR_ACCESS_DENIED, "SELinux policy denies access."); + sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "SELinux policy denies access: %m"); } log_full_errno_zerook(LOG_DEBUG, r, "SELinux access check scon=%s tcon=%s tclass=%s perm=%s state=%s function=%s path=%s cmdline=%s: %m", - scon, fcon, tclass, permission, enforce ? "enforcing" : "permissive", function, strna(path), isempty(cl) ? "n/a" : cl); + scon, acon, tclass, permission, enforce ? "enforcing" : "permissive", function, strna(unit_path), strna(empty_to_null(cl))); return enforce ? r : 0; } @@ -288,7 +275,8 @@ int mac_selinux_access_check_internal( int mac_selinux_access_check_internal( sd_bus_message *message, - const char *path, + const char *unit_path, + const char *unit_label, const char *permission, const char *function, sd_bus_error *error) { diff --git a/src/core/selinux-access.h b/src/core/selinux-access.h index e5ebcb13a2..dc8da9e97b 100644 --- a/src/core/selinux-access.h +++ b/src/core/selinux-access.h @@ -5,14 +5,10 @@ #include "manager.h" -int mac_selinux_access_check_internal(sd_bus_message *message, - const char *path, - const char *permission, - const char *function, - sd_bus_error *error); +int mac_selinux_access_check_internal(sd_bus_message *message, const char *unit_path, const char *unit_label, const char *permission, const char *function, sd_bus_error *error); #define mac_selinux_access_check(message, permission, error) \ - mac_selinux_access_check_internal((message), NULL, (permission), __func__, (error)) + mac_selinux_access_check_internal((message), NULL, NULL, (permission), __func__, (error)) #define mac_selinux_unit_access_check(unit, message, permission, error) \ - mac_selinux_access_check_internal((message), unit_label_path(unit), (permission), __func__, (error)) + mac_selinux_access_check_internal((message), (unit)->fragment_path, (unit)->access_selinux_context, (permission), __func__, (error)) diff --git a/src/core/unit-serialize.c b/src/core/unit-serialize.c index 65ed110ce6..3897bdee7c 100644 --- a/src/core/unit-serialize.c +++ b/src/core/unit-serialize.c @@ -732,6 +732,9 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { STRV_FOREACH(j, u->documentation) fprintf(f, "%s\tDocumentation: %s\n", prefix, *j); + if (u->access_selinux_context) + fprintf(f, "%s\tAccess SELinux Context: %s\n", prefix, u->access_selinux_context); + following = unit_following(u); if (following) fprintf(f, "%s\tFollowing: %s\n", prefix, following->id); diff --git a/src/core/unit.c b/src/core/unit.c index 40fc79995a..a082741bf8 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -804,6 +804,8 @@ Unit* unit_free(Unit *u) { free(u->job_timeout_reboot_arg); free(u->reboot_arg); + free(u->access_selinux_context); + set_free_free(u->aliases); free(u->id); @@ -5550,34 +5552,6 @@ bool unit_needs_console(Unit *u) { return exec_context_may_touch_console(ec); } -const char *unit_label_path(const Unit *u) { - const char *p; - - assert(u); - - /* Returns the file system path to use for MAC access decisions, i.e. the file to read the SELinux label off - * when validating access checks. */ - - if (IN_SET(u->load_state, UNIT_MASKED, UNIT_NOT_FOUND, UNIT_MERGED)) - return NULL; /* Shortcut things if we know there is no real, relevant unit file around */ - - p = u->source_path ?: u->fragment_path; - if (!p) - return NULL; - - if (IN_SET(u->load_state, UNIT_LOADED, UNIT_BAD_SETTING, UNIT_ERROR)) - return p; /* Shortcut things, if we successfully loaded at least some stuff from the unit file */ - - /* Not loaded yet, we need to go to disk */ - assert(u->load_state == UNIT_STUB); - - /* If a unit is masked, then don't read the SELinux label of /dev/null, as that really makes no sense */ - if (null_or_empty_path(p) > 0) - return NULL; - - return p; -} - int unit_pid_attachable(Unit *u, pid_t pid, sd_bus_error *error) { int r; diff --git a/src/core/unit.h b/src/core/unit.h index c6a2fadf52..33884fc991 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -151,6 +151,11 @@ typedef struct Unit { char *description; char **documentation; + /* The SELinux context used for checking access to this unit read off the unit file at load time (do + * not confuse with the selinux_context field in ExecContext which is the SELinux context we'll set + * for processes) */ + char *access_selinux_context; + char *fragment_path; /* if loaded from a config file this is the primary path to it */ char *source_path; /* if converted, the source file */ char **dropin_paths; @@ -944,8 +949,6 @@ int unit_warn_leftover_processes(Unit *u, cg_kill_log_func_t log_func); bool unit_needs_console(Unit *u); -const char *unit_label_path(const Unit *u); - int unit_pid_attachable(Unit *unit, pid_t pid, sd_bus_error *error); static inline bool unit_has_job_type(Unit *u, JobType type) {