nsresourced: Add support for self mappings with delegations

mkosi does all of its environment setup in an unprivileged user
namespace with an identity mapping. When it invokes nspawn and nspawn
tries to get a transient userns from nsresourced, this fails as no
transient ranges are mapped into mkosi's unprivileged userns (as doing
so would require privileges).

To fix this problem, let's allow allocating unprivileged self user
namespaces in nsresourced, similar to what the kernel allows, except that
we also support delegations for these. This means that mkosi can get its
unprivileged userns as before from nsresourced, but it can also request a
delegated 64K range inside that userns as well, which nsresourced can then
allocate to nspawn later when it asks for one.

Similar to the kernel, we disallow setgroups for self mappings. However,
instead of doing this via /proc/self/setgroups, which applies to the current
user namespace and all its child user namespaces, we use the BPF LSM to deny
setgroups instead, so that it can still be allowed for child user namespaces.
We need this because as soon as a container launches in a child user namespace
using one of the delegated mappings, it has to be able to do setgroups() to be
able to function properly.

To allow mapping the root user, we need to add the CAP_SETFCAP capability to
nsresourced.
This commit is contained in:
Daan De Meyer
2026-02-24 18:29:37 +01:00
committed by Daan De Meyer
parent 025439faaa
commit 95d05079bf
13 changed files with 441 additions and 38 deletions
+8
View File
@@ -46,6 +46,14 @@ static inline bool gid_is_foreign(gid_t gid) {
return uid_is_foreign((uid_t) gid);
}
static inline bool uid_is_transient(uid_t uid) {
return uid_is_container(uid) || uid_is_dynamic(uid);
}
static inline bool gid_is_transient(gid_t gid) {
return uid_is_container((uid_t) gid) || uid_is_dynamic((uid_t) gid);
}
typedef struct UGIDAllocationRange {
uid_t system_alloc_uid_min;
uid_t system_uid_max;
+21
View File
@@ -532,6 +532,27 @@ int uid_range_translate(const UIDRange *outside, const UIDRange *inside, uid_t u
return -ESRCH;
}
int uid_range_translate_userns_fd(int userns_fd, UIDRangeUsernsMode mode, uid_t uid, uid_t *ret) {
int r;
assert(userns_fd >= 0);
assert(IN_SET(mode, UID_RANGE_USERNS_OUTSIDE, GID_RANGE_USERNS_OUTSIDE));
_cleanup_(uid_range_freep) UIDRange *outside_range = NULL;
r = uid_range_load_userns_by_fd_full(userns_fd, mode, /* coalesce= */ false, &outside_range);
if (r < 0)
return r;
mode = mode == UID_RANGE_USERNS_OUTSIDE ? UID_RANGE_USERNS_INSIDE : GID_RANGE_USERNS_INSIDE;
_cleanup_(uid_range_freep) UIDRange *inside_range = NULL;
r = uid_range_load_userns_by_fd_full(userns_fd, mode, /* coalesce= */ false, &inside_range);
if (r < 0)
return r;
return uid_range_translate(outside_range, inside_range, uid, ret);
}
bool uid_range_equal(const UIDRange *a, const UIDRange *b) {
if (a == b)
return true;
+1
View File
@@ -67,6 +67,7 @@ int uid_range_partition(UIDRange *range, uid_t size);
int uid_range_copy(const UIDRange *range, UIDRange **ret);
int uid_range_remove(UIDRange *range, uid_t start, uid_t size);
int uid_range_translate(const UIDRange *outside, const UIDRange *inside, uid_t uid, uid_t *ret);
int uid_range_translate_userns_fd(int userns_fd, UIDRangeUsernsMode mode, uid_t uid, uid_t *ret);
int uid_map_search_root(pid_t pid, UIDRangeUsernsMode mode, uid_t *ret);