diff --git a/src/shared/label-util.c b/src/shared/label-util.c index 2ffb43f88e..e5db8cda3f 100644 --- a/src/shared/label-util.c +++ b/src/shared/label-util.c @@ -6,6 +6,7 @@ #include "btrfs-util.h" #include "errno-util.h" #include "label-util.h" +#include "path-util.h" #include "selinux-util.h" #include "smack-util.h" @@ -97,6 +98,21 @@ int btrfs_subvol_make_label(const char *path, LabelContext *label_context) { return mac_smack_fix(path, 0); } +int mac_label_context_new(const char *root, LabelContext **ret) { + assert(ret); + + if (empty_or_root(root)) { + *ret = NULL; + return 0; + } + + return mac_selinux_label_context_new(root, ret); +} + +LabelContext* mac_label_context_free(LabelContext *c) { + return mac_selinux_label_context_free(c); +} + static int init_internal(bool lazy) { int r; diff --git a/src/shared/label-util.h b/src/shared/label-util.h index ea8c2b7165..5a810df8e5 100644 --- a/src/shared/label-util.h +++ b/src/shared/label-util.h @@ -27,3 +27,7 @@ int btrfs_subvol_make_label(const char *path, LabelContext *label_context); int mac_init(void); int mac_init_lazy(void); + +int mac_label_context_new(const char *root, LabelContext **ret); +LabelContext* mac_label_context_free(LabelContext *c); +DEFINE_TRIVIAL_CLEANUP_FUNC(LabelContext*, mac_label_context_free); diff --git a/src/shared/selinux-util.c b/src/shared/selinux-util.c index f275db1c81..7f959c2535 100644 --- a/src/shared/selinux-util.c +++ b/src/shared/selinux-util.c @@ -19,17 +19,22 @@ #include "alloc-util.h" #include "fd-util.h" -#include "path-util.h" #include "string-util.h" +#include "strv.h" #include "time-util.h" #endif +#include "chase.h" +#include "env-file.h" #include "errno-util.h" #include "label-util.h" +#include "path-util.h" #include "selinux-util.h" +#include "smack-util.h" #if HAVE_SELINUX DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(context_t, sym_context_free, context_freep, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_RENAME(struct selabel_handle *, sym_selabel_close, selabel_closep, NULL); typedef enum Initialized { UNINITIALIZED, @@ -43,6 +48,20 @@ static int last_policyload = 0; static struct selabel_handle *label_hnd = NULL; static bool have_status_page = false; +struct LabelContext { + struct selabel_handle *label_hnd; + int rfd; + char *root; +}; + +static void selinux_policy_root_reset_and_freep(char **s) { + assert(s); + if (!*s) + return; + (void) sym_selinux_set_policy_root(*s); + *s = mfree(*s); +} + static int mac_selinux_label_pre(int dir_fd, const char *path, mode_t mode, LabelContext *label_context) { return mac_selinux_create_file_prepare_at(dir_fd, path, mode, label_context); } @@ -52,6 +71,11 @@ static int mac_selinux_label_post(int dir_fd, const char *path, bool created, La return 0; } +static const LabelOps selinux_label_ops = { + .pre = mac_selinux_label_pre, + .post = mac_selinux_label_post, +}; + DLSYM_PROTOTYPE(avc_open) = NULL; DLSYM_PROTOTYPE(context_free) = NULL; DLSYM_PROTOTYPE(context_new) = NULL; @@ -75,7 +99,9 @@ DLSYM_PROTOTYPE(selinux_check_access) = NULL; DLSYM_PROTOTYPE(selinux_getenforcemode) = NULL; DLSYM_PROTOTYPE(selinux_init_load_policy) = NULL; DLSYM_PROTOTYPE(selinux_path) = NULL; +DLSYM_PROTOTYPE(selinux_policy_root) = NULL; DLSYM_PROTOTYPE(selinux_set_callback) = NULL; +DLSYM_PROTOTYPE(selinux_set_policy_root) = NULL; DLSYM_PROTOTYPE(selinux_status_close) = NULL; DLSYM_PROTOTYPE(selinux_status_getenforce) = NULL; DLSYM_PROTOTYPE(selinux_status_open) = NULL; @@ -121,7 +147,9 @@ int dlopen_libselinux(int log_level) { DLSYM_ARG(selinux_getenforcemode), DLSYM_ARG(selinux_init_load_policy), DLSYM_ARG(selinux_path), + DLSYM_ARG(selinux_policy_root), DLSYM_ARG(selinux_set_callback), + DLSYM_ARG(selinux_set_policy_root), DLSYM_ARG(selinux_status_close), DLSYM_ARG(selinux_status_getenforce), DLSYM_ARG(selinux_status_open), @@ -220,10 +248,6 @@ static int open_label_db(void) { static int selinux_init(bool force) { #if HAVE_SELINUX - static const LabelOps label_ops = { - .pre = mac_selinux_label_pre, - .post = mac_selinux_label_post, - }; int r; if (!mac_selinux_use()) @@ -256,7 +280,7 @@ static int selinux_init(bool force) { return r; } - r = label_ops_set(&label_ops); + r = label_ops_set(&selinux_label_ops); if (r < 0) return r; @@ -872,3 +896,182 @@ skipped: #endif return RET_NERRNO(bind(fd, addr, addrlen)); } + +#if HAVE_SELINUX +static int get_selinux_policy_root(int rfd, const char *root, char **ret) { + _cleanup_free_ char *policytype = NULL; + int r; + + assert(rfd >= 0); + assert(root); + assert(ret); + + /* Read the SELinux config from the alternate root, trying /etc and /usr/lib */ + FOREACH_STRING(config_dir, "/etc/selinux/config", "/usr/lib/selinux/config") { + _cleanup_close_ int fd = -EBADF; + + r = chaseat(rfd, rfd, config_dir, CHASE_MUST_BE_REGULAR, /* ret_path= */ NULL, &fd); + if (IN_SET(r, -ENOENT, -ENOTDIR, -EISDIR)) + continue; + if (r < 0) + return log_debug_errno(r, "Failed to resolve SELinux config path in root '%s': %m", root); + + r = parse_env_file_fd(fd, config_dir, "SELINUXTYPE", &policytype); + if (r == -ENOENT) + continue; + if (r < 0) + return log_debug_errno(r, "Failed to parse SELinux config '%s' in root '%s': %m", config_dir, root); + + if (!isempty(policytype)) + break; + } + + if (isempty(policytype)) { + log_debug("No SELINUXTYPE configured in alternate root '%s', skipping label context setup.", root); + *ret = NULL; + return 0; + } + + if (!filename_is_valid(policytype)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "SELINUXTYPE '%s' in alternate root '%s' is not a valid filename.", policytype, root); + + /* Look for the policy database under the alternate root */ + FOREACH_STRING(policy_dir, "/etc/selinux", "/usr/share/selinux") { + _cleanup_free_ char *p = NULL, *policy_subdir = NULL; + + policy_subdir = path_join(policy_dir, policytype); + if (!policy_subdir) + return log_oom(); + + r = chaseat(rfd, rfd, policy_subdir, CHASE_MUST_BE_DIRECTORY, &p, /* ret_fd= */ NULL); + if (IN_SET(r, -ENOENT, -ENOTDIR)) + continue; + if (r < 0) { + log_debug_errno(r, "Failed to resolve policy directory '%s' in root '%s': %m", policy_subdir, root); + continue; + } + + /* Validate that the file_contexts file resolves within the root, since libselinux + * will open it with plain fopen() which follows symlinks without confinement */ + _cleanup_free_ char *fc_subpath = NULL; + fc_subpath = path_join(policy_subdir, "contexts/files/file_contexts"); + if (!fc_subpath) + return log_oom(); + + r = chaseat(rfd, rfd, fc_subpath, CHASE_MUST_BE_REGULAR, /* ret_path= */ NULL, /* ret_fd= */ NULL); + if (IN_SET(r, -ENOENT, -ENOTDIR)) { + log_debug_errno(r, "file_contexts not found at '%s' in root '%s', skipping.", fc_subpath, root); + continue; + } + if (r < 0) { + log_debug_errno(r, "Failed to resolve file_contexts '%s' in root '%s': %m", fc_subpath, root); + continue; + } + + r = chaseat_prefix_root(p, root, ret); + if (r < 0) + return r; + + return 1; + } + + log_debug("SELinux policy directory for type '%s' not found in alternate root '%s', skipping.", policytype, root); + *ret = NULL; + return 0; +} +#endif + +int mac_selinux_label_context_new(const char *root, LabelContext **ret) { + assert(root); + assert(!empty_or_root(root)); + assert(ret); + +#if HAVE_SELINUX + _cleanup_free_ char *policyroot = NULL, *canonical_root = NULL; + _cleanup_close_ int rfd = -EBADF; + int r; + + if (mac_smack_use()) { + *ret = NULL; + return 0; + } + + r = dlopen_libselinux(LOG_ERR); + if (r < 0) + return r; + + mac_selinux_disable_logging(); + + r = chaseat(AT_FDCWD, AT_FDCWD, root, CHASE_MUST_BE_DIRECTORY, &canonical_root, &rfd); + if (r < 0) + return log_debug_errno(r, "Failed to open root directory '%s': %m", root); + + r = get_selinux_policy_root(rfd, root, &policyroot); + if (r < 0) + return r; + if (r == 0) { + *ret = NULL; + return 0; + } + + /* Temporarily switch the policy root so selabel_open() reads the image's file_contexts. + * The handle bakes in the database at open time, so we can reset the root immediately after. + * We must strdup the saved root because selinux_set_policy_root() frees the internal buffer. */ + _cleanup_(selinux_policy_root_reset_and_freep) char *saved_policy_root = NULL; + const char *p = sym_selinux_policy_root(); + if (p) { + saved_policy_root = strdup(p); + if (!saved_policy_root) + return log_oom(); + } + + r = RET_NERRNO(sym_selinux_set_policy_root(policyroot)); + if (r < 0) + return log_debug_errno(r, "Failed to set SELinux policy root to '%s': %m", policyroot); + + _cleanup_(selabel_closep) struct selabel_handle *hnd = sym_selabel_open(SELABEL_CTX_FILE, /* opts= */ NULL, /* nopts= */ 0); + int saved_errno = errno; + + /* Reset immediately - the handle baked in the database at open time */ + selinux_policy_root_reset_and_freep(&saved_policy_root); + + if (!hnd) + return log_debug_errno(saved_errno, "Failed to open SELinux label database for alternate root '%s': %m", root); + + r = label_ops_set(&selinux_label_ops); + if (r < 0 && r != -EBUSY) + return r; + + _cleanup_free_ LabelContext *c = new(LabelContext, 1); + if (!c) + return log_oom(); + + *c = (LabelContext) { + .label_hnd = TAKE_PTR(hnd), + .rfd = TAKE_FD(rfd), + .root = TAKE_PTR(canonical_root), + }; + + *ret = TAKE_PTR(c); + return 0; +#else + *ret = NULL; + return 0; +#endif +} + +LabelContext* mac_selinux_label_context_free(LabelContext *c) { + if (!c) + return NULL; + +#if HAVE_SELINUX + if (c->label_hnd) + sym_selabel_close(c->label_hnd); + + safe_close(c->rfd); + free(c->root); +#endif + + return mfree(c); +} diff --git a/src/shared/selinux-util.h b/src/shared/selinux-util.h index 06bae27446..3f2e4dc77b 100644 --- a/src/shared/selinux-util.h +++ b/src/shared/selinux-util.h @@ -41,7 +41,9 @@ extern DLSYM_PROTOTYPE(selinux_check_access); extern DLSYM_PROTOTYPE(selinux_getenforcemode); extern DLSYM_PROTOTYPE(selinux_init_load_policy); extern DLSYM_PROTOTYPE(selinux_path); +extern DLSYM_PROTOTYPE(selinux_policy_root); extern DLSYM_PROTOTYPE(selinux_set_callback); +extern DLSYM_PROTOTYPE(selinux_set_policy_root); extern DLSYM_PROTOTYPE(selinux_status_close); extern DLSYM_PROTOTYPE(selinux_status_getenforce); extern DLSYM_PROTOTYPE(selinux_status_open); @@ -114,3 +116,6 @@ int mac_selinux_create_socket_prepare(const char *label); void mac_selinux_create_socket_clear(void); int mac_selinux_bind(int fd, const struct sockaddr *addr, socklen_t addrlen); + +int mac_selinux_label_context_new(const char *root, LabelContext **ret); +LabelContext* mac_selinux_label_context_free(LabelContext *c);