selinux: add LabelContext for alternate-root labeling
When operating on an alternate root (--root/--image), SELinux labels must come from that root's policy, not the host's. This requires opening a separate selabel_handle against the target's policy database and remembering the root path for prefix stripping. Introduce LabelContext to carry both, and mac_label_context_new() to set it up: it reads the target's SELinux config, validates SELINUXTYPE to prevent path traversal, sets the policy root, and opens a label database scoped to the target image. Signed-off-by: Simon de Vlieger <cmdr@supakeen.com>
This commit is contained in:
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
+209
-6
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user