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:
Yu Watanabe
2025-07-11 10:20:36 +09:00
committed by GitHub
5 changed files with 233 additions and 69 deletions
+164 -38
View File
@@ -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 */
+10 -3
View File
@@ -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__)