1
0
mirror of https://github.com/systemd/systemd.git synced 2025-03-23 10:50:16 +03:00

Merge pull request #23986 from poettering/cache-selinux-unit-labels

selinux: make selinux access checks based on unit file contexts read at unit load time
This commit is contained in:
Lennart Poettering 2022-07-21 10:24:51 +02:00 committed by GitHub
commit 3e9b77dea4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 62 additions and 69 deletions

View File

@ -1650,6 +1650,11 @@ node /org/freedesktop/systemd1 {
that the root path is encoded as the empty string here (not as <literal>/</literal>!), so that it can be
appended to <filename>/sys/fs/cgroup/systemd</filename> easily. This value will be set to the empty
string for the host instance and some other string for container instances.</para>
<para><varname>AccessSELinuxContext</varname> 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).</para>
</refsect2>
<refsect2>
@ -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 {
<variablelist class="dbus-property" generated="True" extra-ref="Description"/>
<variablelist class="dbus-property" generated="True" extra-ref="AccessSELinuxContext"/>
<variablelist class="dbus-property" generated="True" extra-ref="LoadState"/>
<variablelist class="dbus-property" generated="True" extra-ref="ActiveState"/>

View File

@ -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),

View File

@ -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);

View File

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

View File

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

View File

@ -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);

View File

@ -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;

View File

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