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) {