namespace-util: fail if we cannot become root when entering a userns
namespace_enter() tried to become root after joining a user namespace but ignored failures, so if root wasn't mapped we'd just carry on with our original UID/GID. Let's make that an error instead. Dropping supplementary groups stays best effort, as setgroups() might be denied (e.g. by the nsresourced BPF LSM) even when root is mapped. Some callers do need to enter namespaces without root, so add NAMESPACE_ENTER_KEEP_UID_GID_IF_ROOT_UNMAPPED for those. It's used by systemd-analyze unit-shell and by the credential refresh in the user manager. Everything else enters containers or namespaces we allocated ourselves, where root is always mapped.
This commit is contained in:
committed by
Daan De Meyer
parent
63a7cf844d
commit
cfd2f7c73e
@@ -104,6 +104,7 @@ typedef enum ForkFlags ForkFlags;
|
||||
typedef enum Glyph Glyph;
|
||||
typedef enum ImageClass ImageClass;
|
||||
typedef enum JobMode JobMode;
|
||||
typedef enum NamespaceEnterFlags NamespaceEnterFlags;
|
||||
typedef enum RuntimeScope RuntimeScope;
|
||||
typedef enum StringSafeFlags StringSafeFlags;
|
||||
typedef enum TimestampStyle TimestampStyle;
|
||||
|
||||
@@ -230,7 +230,14 @@ int namespace_open(
|
||||
return pidref_namespace_open(&pidref, ret_pidns_fd, ret_mntns_fd, ret_netns_fd, ret_userns_fd, ret_root_fd);
|
||||
}
|
||||
|
||||
int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd) {
|
||||
int namespace_enter_full(
|
||||
int pidns_fd,
|
||||
int mntns_fd,
|
||||
int netns_fd,
|
||||
int userns_fd,
|
||||
int root_fd,
|
||||
NamespaceEnterFlags flags) {
|
||||
|
||||
int r;
|
||||
|
||||
/* Block dlopen() now, to avoid us inadvertently loading shared library from another namespace */
|
||||
@@ -274,6 +281,15 @@ int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int
|
||||
userns_fd = -EBADF;
|
||||
}
|
||||
|
||||
/* Determine this before joining anything, as /proc might not be available afterwards. */
|
||||
bool keep_uid_gid = false;
|
||||
if (userns_fd >= 0 && FLAGS_SET(flags, NAMESPACE_ENTER_KEEP_UID_GID_IF_ROOT_UNMAPPED)) {
|
||||
r = userns_has_root_mapping(userns_fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
keep_uid_gid = r == 0;
|
||||
}
|
||||
|
||||
r = have_effective_cap(CAP_SYS_ADMIN);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@@ -319,13 +335,26 @@ int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int
|
||||
return -errno;
|
||||
}
|
||||
|
||||
if (userns_fd >= 0) {
|
||||
/* Try to become root in the user namespace but don't error out if we can't, since it's not
|
||||
* uncommon to have user namespaces without a root user in them. */
|
||||
r = reset_uid_gid();
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Unable to drop auxiliary groups or reset UID/GID, ignoring: %m");
|
||||
}
|
||||
if (userns_fd < 0)
|
||||
return 0;
|
||||
|
||||
/* Drop supplementary groups where possible. setgroups() may be denied, either via
|
||||
* /proc/self/setgroups or by policy (e.g. a BPF LSM), in which case there's nothing we can do
|
||||
* about them. */
|
||||
r = maybe_setgroups(/* size= */ 0, /* list= */ NULL);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Unable to drop supplementary groups, ignoring: %m");
|
||||
|
||||
if (keep_uid_gid)
|
||||
return 0;
|
||||
|
||||
/* Joining a user namespace preserves the underlying UID/GID. Require becoming root to succeed, as
|
||||
* otherwise we'd continue running with our original identity inside the namespace. */
|
||||
if (setresgid(0, 0, 0) < 0)
|
||||
return log_debug_errno(errno, "Failed to become root group in user namespace: %m");
|
||||
|
||||
if (setresuid(0, 0, 0) < 0)
|
||||
return log_debug_errno(errno, "Failed to become root in user namespace: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -831,6 +860,36 @@ int userns_get_base_uid(int userns_fd, uid_t *ret_uid, gid_t *ret_gid) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int userns_has_root_mapping(int userns_fd) {
|
||||
_cleanup_(uid_range_freep) UIDRange *uid_range = NULL, *gid_range = NULL;
|
||||
_cleanup_(pidref_done_sigkill_wait) PidRef pidref = PIDREF_NULL;
|
||||
int r;
|
||||
|
||||
assert(userns_fd >= 0);
|
||||
|
||||
r = userns_enter_and_pin(userns_fd, &pidref);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
const char *p = procfs_file_alloca(pidref.pid, "uid_map");
|
||||
r = uid_range_load_userns(p, UID_RANGE_USERNS_INSIDE, &uid_range);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
p = procfs_file_alloca(pidref.pid, "gid_map");
|
||||
r = uid_range_load_userns(p, GID_RANGE_USERNS_INSIDE, &gid_range);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* The maps can be written only once. While they are empty the namespace is still being set up and
|
||||
* root might yet get mapped, so refuse to answer rather than guess. */
|
||||
if (uid_range_is_empty(uid_range) || uid_range_is_empty(gid_range))
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(ENODATA),
|
||||
"User namespace has no UID/GID mapping yet.");
|
||||
|
||||
return uid_range_contains(uid_range, 0) && uid_range_contains(gid_range, 0);
|
||||
}
|
||||
|
||||
int process_is_owned_by_uid(const PidRef *pidref, uid_t uid) {
|
||||
int r;
|
||||
|
||||
|
||||
@@ -46,7 +46,25 @@ int namespace_open(
|
||||
int *ret_userns_fd,
|
||||
int *ret_root_fd);
|
||||
|
||||
int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd);
|
||||
typedef enum NamespaceEnterFlags {
|
||||
/* If root is not mapped in the user namespace, keep our UID/GID instead of failing. The caller must
|
||||
* ensure that retaining its original identity is appropriate in that case. Entering still fails
|
||||
* with -ENODATA if the namespace has no mappings at all yet, since root might still get mapped. */
|
||||
NAMESPACE_ENTER_KEEP_UID_GID_IF_ROOT_UNMAPPED = 1 << 0,
|
||||
} NamespaceEnterFlags;
|
||||
|
||||
/* If a user namespace is entered we become root in it, and fail if that's not possible. */
|
||||
int namespace_enter_full(
|
||||
int pidns_fd,
|
||||
int mntns_fd,
|
||||
int netns_fd,
|
||||
int userns_fd,
|
||||
int root_fd,
|
||||
NamespaceEnterFlags flags);
|
||||
|
||||
static inline int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd) {
|
||||
return namespace_enter_full(pidns_fd, mntns_fd, netns_fd, userns_fd, root_fd, /* flags= */ 0);
|
||||
}
|
||||
|
||||
int fd_is_namespace(int fd, NamespaceType type);
|
||||
int is_our_namespace(int fd, NamespaceType type);
|
||||
@@ -88,6 +106,7 @@ int userns_enter_and_pin(int userns_fd, PidRef *ret);
|
||||
bool userns_supported(void);
|
||||
|
||||
int userns_get_base_uid(int userns_fd, uid_t *ret_uid, gid_t *ret_gid);
|
||||
int userns_has_root_mapping(int userns_fd);
|
||||
|
||||
int namespace_open_by_id(uint64_t ns_id);
|
||||
|
||||
|
||||
@@ -1802,6 +1802,7 @@ int namespace_fork_full(
|
||||
int netns_fd,
|
||||
int userns_fd,
|
||||
int root_fd,
|
||||
NamespaceEnterFlags namespace_flags,
|
||||
PidRef *ret) {
|
||||
|
||||
_cleanup_(pidref_done_sigkill_wait) PidRef pidref_outer = PIDREF_NULL;
|
||||
@@ -1847,7 +1848,7 @@ int namespace_fork_full(
|
||||
|
||||
errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]);
|
||||
|
||||
r = namespace_enter(pidns_fd, mntns_fd, netns_fd, userns_fd, root_fd);
|
||||
r = namespace_enter_full(pidns_fd, mntns_fd, netns_fd, userns_fd, root_fd, namespace_flags);
|
||||
if (r < 0) {
|
||||
log_full_errno(prio, r, "Failed to join namespace: %m");
|
||||
report_errno_and_exit(errno_pipe_fd[1], r);
|
||||
@@ -1907,6 +1908,22 @@ int namespace_fork_full(
|
||||
return 1;
|
||||
}
|
||||
|
||||
int namespace_fork(
|
||||
const char *outer_name,
|
||||
const char *inner_name,
|
||||
ForkFlags flags,
|
||||
int pidns_fd,
|
||||
int mntns_fd,
|
||||
int netns_fd,
|
||||
int userns_fd,
|
||||
int root_fd,
|
||||
PidRef *ret) {
|
||||
|
||||
return namespace_fork_full(outer_name, inner_name, /* except_fds= */ NULL, /* n_except_fds= */ 0, flags,
|
||||
pidns_fd, mntns_fd, netns_fd, userns_fd, root_fd,
|
||||
/* namespace_flags= */ 0, ret);
|
||||
}
|
||||
|
||||
bool oom_score_adjust_is_valid(int oa) {
|
||||
return oa >= OOM_SCORE_ADJ_MIN && oa <= OOM_SCORE_ADJ_MAX;
|
||||
}
|
||||
|
||||
@@ -201,9 +201,10 @@ int namespace_fork_full(
|
||||
int netns_fd,
|
||||
int userns_fd,
|
||||
int root_fd,
|
||||
NamespaceEnterFlags namespace_flags,
|
||||
PidRef *ret);
|
||||
|
||||
static inline int namespace_fork(
|
||||
int namespace_fork(
|
||||
const char *outer_name,
|
||||
const char *inner_name,
|
||||
ForkFlags flags,
|
||||
@@ -212,12 +213,7 @@ static inline int namespace_fork(
|
||||
int netns_fd,
|
||||
int userns_fd,
|
||||
int root_fd,
|
||||
PidRef *ret) {
|
||||
|
||||
return namespace_fork_full(outer_name, inner_name, NULL, 0, flags,
|
||||
pidns_fd, mntns_fd, netns_fd, userns_fd, root_fd,
|
||||
ret);
|
||||
}
|
||||
PidRef *ret);
|
||||
|
||||
bool oom_score_adjust_is_valid(int oa);
|
||||
int set_oom_score_adjust(int value);
|
||||
|
||||
Reference in New Issue
Block a user