Plumbing to perform SELinux checks in varlink API (#38146)
This PR does minimal changes to introduce varlink support. Ideally, the code should switch to using `mac_selinux_get_our_label()` and new `mac_selinux_get_peer_label()`. But I leave it for now to minimize breakage. `mac_selinux_get_peer_label()` remains unused. This is a prep step to merge https://github.com/systemd/systemd/pull/38032
This commit is contained in:
+164
-38
@@ -9,6 +9,7 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#include "sd-bus.h"
|
||||
#include "sd-varlink.h"
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "audit-fd.h"
|
||||
@@ -26,6 +27,7 @@ static bool initialized = false;
|
||||
|
||||
struct audit_info {
|
||||
sd_bus_creds *creds;
|
||||
sd_varlink *link;
|
||||
const char *path;
|
||||
const char *cmdline;
|
||||
const char *function;
|
||||
@@ -48,12 +50,23 @@ static int audit_callback(
|
||||
char uid_buf[DECIMAL_STR_MAX(uid_t) + 1] = "n/a";
|
||||
char gid_buf[DECIMAL_STR_MAX(gid_t) + 1] = "n/a";
|
||||
|
||||
if (sd_bus_creds_get_audit_login_uid(audit->creds, &login_uid) >= 0)
|
||||
xsprintf(login_uid_buf, UID_FMT, login_uid);
|
||||
if (sd_bus_creds_get_euid(audit->creds, &uid) >= 0)
|
||||
xsprintf(uid_buf, UID_FMT, uid);
|
||||
if (sd_bus_creds_get_egid(audit->creds, &gid) >= 0)
|
||||
xsprintf(gid_buf, GID_FMT, gid);
|
||||
if (audit->creds) {
|
||||
/* DBus case */
|
||||
if (sd_bus_creds_get_audit_login_uid(audit->creds, &login_uid) >= 0)
|
||||
xsprintf(login_uid_buf, UID_FMT, login_uid);
|
||||
if (sd_bus_creds_get_euid(audit->creds, &uid) >= 0)
|
||||
xsprintf(uid_buf, UID_FMT, uid);
|
||||
if (sd_bus_creds_get_egid(audit->creds, &gid) >= 0)
|
||||
xsprintf(gid_buf, GID_FMT, gid);
|
||||
}
|
||||
|
||||
if (audit->link) {
|
||||
/* varlink */
|
||||
if (sd_varlink_get_peer_uid(audit->link, &uid) >= 0)
|
||||
xsprintf(uid_buf, UID_FMT, uid);
|
||||
if (sd_varlink_get_peer_gid(audit->link, &gid) >= 0)
|
||||
xsprintf(gid_buf, GID_FMT, gid);
|
||||
}
|
||||
|
||||
(void) snprintf(msgbuf, msgbufsize,
|
||||
"auid=%s uid=%s gid=%s%s%s%s%s%s%s%s%s%s",
|
||||
@@ -164,13 +177,84 @@ static int access_init(sd_bus_error *error) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int get_our_contexts(const Unit *unit, const char **ret_acon, const char **ret_tclass, char **ret_fcon) {
|
||||
_cleanup_freecon_ char *fcon = NULL;
|
||||
|
||||
assert(ret_acon);
|
||||
assert(ret_tclass);
|
||||
assert(ret_fcon);
|
||||
|
||||
if (unit && unit->access_selinux_context) {
|
||||
/* Nice! The unit comes with a SELinux context read from the unit file */
|
||||
*ret_acon = unit->access_selinux_context;
|
||||
*ret_tclass = "service";
|
||||
*ret_fcon = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* If no unit context is known, use our own */
|
||||
|
||||
/* Ideally, we should call mac_selinux_get_our_label() here because it
|
||||
* does exactly the same - call getcon_raw(). However, it involves
|
||||
* selinux_init() which opens label DB. It was not part of the
|
||||
* original code. I don't want to change it for now. */
|
||||
if (getcon_raw(&fcon) < 0)
|
||||
return log_debug_errno(errno, "SELinux getcon_raw() failed: %m");
|
||||
|
||||
if (!fcon)
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "SELinux returned no context of the current process");
|
||||
|
||||
*ret_acon = fcon;
|
||||
*ret_tclass = "system";
|
||||
*ret_fcon = TAKE_PTR(fcon);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int check_access(
|
||||
const char *scon,
|
||||
const char *tcon,
|
||||
const char *tclass,
|
||||
const char *permission,
|
||||
struct audit_info *audit_info,
|
||||
sd_bus_error *error) {
|
||||
bool enforce = mac_selinux_enforcing();
|
||||
int r;
|
||||
|
||||
assert(scon);
|
||||
assert(tcon);
|
||||
assert(tclass);
|
||||
assert(permission);
|
||||
assert(audit_info);
|
||||
assert(audit_info->function);
|
||||
|
||||
r = selinux_check_access(scon, tcon, tclass, permission, &audit_info);
|
||||
if (r < 0) {
|
||||
errno = -(r = errno_or_else(EPERM));
|
||||
|
||||
if (enforce)
|
||||
sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "SELinux policy denies access: %m");
|
||||
}
|
||||
|
||||
return log_selinux_enforcing_errno(
|
||||
r,
|
||||
"SELinux access check scon=%s tcon=%s tclass=%s perm=%s state=%s function=%s path=%s cmdline=%s: %m",
|
||||
scon,
|
||||
tcon,
|
||||
tclass,
|
||||
permission,
|
||||
enforce ? "enforcing" : "permissive",
|
||||
audit_info->function,
|
||||
empty_to_na(audit_info->path),
|
||||
empty_to_na(audit_info->cmdline));
|
||||
}
|
||||
|
||||
/*
|
||||
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 mac_selinux_access_check_internal(
|
||||
int mac_selinux_access_check_bus_internal(
|
||||
sd_bus_message *message,
|
||||
const Unit *unit,
|
||||
const char *permission,
|
||||
@@ -216,30 +300,21 @@ int mac_selinux_access_check_internal(
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (unit && unit->access_selinux_context) {
|
||||
/* Nice! The unit comes with a SELinux context read from the unit file */
|
||||
acon = unit->access_selinux_context;
|
||||
tclass = "service";
|
||||
} else {
|
||||
/* If no unit context is known, use our own */
|
||||
if (getcon_raw(&fcon) < 0) {
|
||||
log_warning_errno(errno, "SELinux getcon_raw() failed%s (perm=%s): %m",
|
||||
enforce ? "" : ", ignoring",
|
||||
permission);
|
||||
if (!enforce)
|
||||
return 0;
|
||||
r = get_our_contexts(unit, &acon, &tclass, &fcon);
|
||||
if (r < 0) {
|
||||
log_selinux_enforcing_errno(
|
||||
r,
|
||||
"Failed to retrieves SELinux context of current process (perm=%s)%s: %m",
|
||||
permission,
|
||||
enforce ? "" : ", ignoring");
|
||||
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Failed to get current context: %m");
|
||||
}
|
||||
if (!fcon) {
|
||||
if (!enforce)
|
||||
return 0;
|
||||
if (!enforce)
|
||||
return 0;
|
||||
|
||||
if (r == -EOPNOTSUPP)
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "We appear not to have any SELinux context: %m");
|
||||
}
|
||||
|
||||
acon = fcon;
|
||||
tclass = "system";
|
||||
return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Failed to get current context: %m");
|
||||
}
|
||||
|
||||
(void) sd_bus_creds_get_cmdline(creds, &cmdline);
|
||||
@@ -252,23 +327,66 @@ int mac_selinux_access_check_internal(
|
||||
.function = function,
|
||||
};
|
||||
|
||||
r = selinux_check_access(scon, acon, tclass, permission, &audit_info);
|
||||
if (r < 0) {
|
||||
errno = -(r = errno_or_else(EPERM));
|
||||
return check_access(scon, acon, tclass, permission, &audit_info, error);
|
||||
}
|
||||
|
||||
if (enforce)
|
||||
sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "SELinux policy denies access: %m");
|
||||
}
|
||||
int mac_selinux_access_check_varlink_internal(
|
||||
sd_varlink *link,
|
||||
const Unit *unit,
|
||||
const char *permission,
|
||||
const char *function) {
|
||||
_cleanup_freecon_ char *fcon = NULL, *scon = NULL;
|
||||
const char *tclass, *acon;
|
||||
int r;
|
||||
|
||||
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, acon, tclass, permission, enforce ? "enforcing" : "permissive", function, strna(unit ? unit->fragment_path : NULL), empty_to_na(cl));
|
||||
return enforce ? r : 0;
|
||||
assert(link);
|
||||
assert(permission);
|
||||
assert(function);
|
||||
|
||||
r = access_init(/* error= */ NULL);
|
||||
if (r <= 0)
|
||||
return log_debug_errno(r, "Failed to init SELinux: %m");
|
||||
|
||||
/* delay call until we checked in `access_init()` if SELinux is actually enabled */
|
||||
bool enforce = mac_selinux_enforcing();
|
||||
|
||||
int fd = sd_varlink_get_fd(link);
|
||||
if (fd < 0)
|
||||
return log_debug_errno(fd, "Failed to get varlink peer fd: %m");
|
||||
|
||||
/* We should call mac_selinux_get_peer_label() here similarly to get_our_contexts().
|
||||
* See the explanation there why not. */
|
||||
if (getpeercon_raw(fd, &scon) < 0)
|
||||
return log_selinux_enforcing_errno(
|
||||
errno,
|
||||
"Failed to get peer SELinux context%s: %m",
|
||||
enforce ? "" : ", ignoring");
|
||||
|
||||
if (!scon)
|
||||
return log_selinux_enforcing_errno(
|
||||
SYNTHETIC_ERRNO(EOPNOTSUPP),
|
||||
"Peer does not have SELinux context");
|
||||
|
||||
r = get_our_contexts(unit, &acon, &tclass, &fcon);
|
||||
if (r < 0)
|
||||
return log_selinux_enforcing_errno(
|
||||
r,
|
||||
"Failed to retrieves SELinux context of current process (perm=%s)%s: %m",
|
||||
permission,
|
||||
enforce ? "" : ", ignoring");
|
||||
|
||||
struct audit_info audit_info = {
|
||||
.link = link,
|
||||
.path = unit ? unit->fragment_path : NULL,
|
||||
.function = function,
|
||||
};
|
||||
|
||||
return check_access(scon, acon, tclass, permission, &audit_info, /* error= */ NULL);
|
||||
}
|
||||
|
||||
#else /* HAVE_SELINUX */
|
||||
|
||||
int mac_selinux_access_check_internal(
|
||||
int mac_selinux_access_check_bus_internal(
|
||||
sd_bus_message *message,
|
||||
const Unit *unit,
|
||||
const char *permission,
|
||||
@@ -278,4 +396,12 @@ int mac_selinux_access_check_internal(
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mac_selinux_access_check_varlink_internal(
|
||||
sd_varlink *link,
|
||||
const Unit *unit,
|
||||
const char *permission,
|
||||
const char *function) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* HAVE_SELINUX */
|
||||
|
||||
@@ -3,10 +3,17 @@
|
||||
|
||||
#include "core-forward.h"
|
||||
|
||||
int mac_selinux_access_check_internal(sd_bus_message *message, const Unit *unit, const char *permission, const char *function, sd_bus_error *error);
|
||||
int mac_selinux_access_check_bus_internal(sd_bus_message *message, const Unit *unit, const char *permission, const char *function, sd_bus_error *error);
|
||||
int mac_selinux_access_check_varlink_internal(sd_varlink *link, const Unit *unit, const char *permission, const char *function);
|
||||
|
||||
#define mac_selinux_access_check(message, permission, error) \
|
||||
mac_selinux_access_check_internal((message), NULL, (permission), __func__, (error))
|
||||
mac_selinux_access_check_bus_internal((message), NULL, (permission), __func__, (error))
|
||||
|
||||
#define mac_selinux_unit_access_check(unit, message, permission, error) \
|
||||
mac_selinux_access_check_internal((message), (unit), (permission), __func__, (error))
|
||||
mac_selinux_access_check_bus_internal((message), (unit), (permission), __func__, (error))
|
||||
|
||||
#define mac_selinux_access_check_varlink(link, permission) \
|
||||
mac_selinux_access_check_varlink_internal((link), NULL, (permission), __func__)
|
||||
|
||||
#define mac_selinux_unit_access_check_varlink(unit, link, permission) \
|
||||
mac_selinux_access_check_bus_internal((link), (unit), (permission), __func__)
|
||||
|
||||
Reference in New Issue
Block a user