nsresourced: Add support for delegated ranges
We want to support the scenario where we bind mount the nsresourced varlink socket into a container to allow nested containers where the outer container runs in its own transient range from nsresourced but can still allocate transient ranges for its own nested containers. To support this use case let's add support for delegation. Delegated ranges are allocated when allocating the primary range and are propagated 1:1 to the user namespace. We track delegated ranges in ".delegate" files in the userns registry so that they can't be used for other range allocations. We make one exception for delegated ranges though, if we get a request from a user namespace that is a child of the user namespace that owns the delegated ranges, we allow allocating from the delegated range. The parent userns already has full ownership over the child userns, so it doesn't matter that the parent userns and the child userns share the same range. This allows making use of delegated ranges without having to run another copy of nsresourced inside the parent userns to hand out from the delegated range. To support recursive delegations, we keep track of the previous owners of the delegated range and restore ownership to the last previous owner when the current owner is freed.
This commit is contained in:
committed by
Daan De Meyer
parent
6c2d437585
commit
025439faaa
+207
-7
@@ -1,5 +1,6 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <sched.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
@@ -115,7 +116,7 @@ int uid_range_add_internal(UIDRange **range, uid_t start, uid_t nr, bool coalesc
|
||||
return 0;
|
||||
}
|
||||
|
||||
int uid_range_add_str(UIDRange **range, const char *s) {
|
||||
int uid_range_add_str_full(UIDRange **range, const char *s, bool coalesce) {
|
||||
uid_t start, end;
|
||||
int r;
|
||||
|
||||
@@ -126,7 +127,7 @@ int uid_range_add_str(UIDRange **range, const char *s) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return uid_range_add_internal(range, start, end - start + 1, /* coalesce= */ true);
|
||||
return uid_range_add_internal(range, start, end - start + 1, coalesce);
|
||||
}
|
||||
|
||||
int uid_range_next_lower(const UIDRange *range, uid_t *uid) {
|
||||
@@ -230,7 +231,7 @@ bool uid_range_is_empty(const UIDRange *range) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int uid_range_load_userns(const char *path, UIDRangeUsernsMode mode, UIDRange **ret) {
|
||||
int uid_range_load_userns_full(const char *path, UIDRangeUsernsMode mode, bool coalesce, UIDRange **ret) {
|
||||
_cleanup_(uid_range_freep) UIDRange *range = NULL;
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
int r;
|
||||
@@ -280,13 +281,14 @@ int uid_range_load_userns(const char *path, UIDRangeUsernsMode mode, UIDRange **
|
||||
return r;
|
||||
}
|
||||
|
||||
uid_range_coalesce(range);
|
||||
if (coalesce)
|
||||
uid_range_coalesce(range);
|
||||
|
||||
*ret = TAKE_PTR(range);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int uid_range_load_userns_by_fd(int userns_fd, UIDRangeUsernsMode mode, UIDRange **ret) {
|
||||
int uid_range_load_userns_by_fd_full(int userns_fd, UIDRangeUsernsMode mode, bool coalesce, UIDRange **ret) {
|
||||
_cleanup_(pidref_done_sigkill_wait) PidRef pidref = PIDREF_NULL;
|
||||
int r;
|
||||
|
||||
@@ -299,7 +301,7 @@ int uid_range_load_userns_by_fd(int userns_fd, UIDRangeUsernsMode mode, UIDRange
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0)
|
||||
return uid_range_load_userns(/* path= */ NULL, mode, ret);
|
||||
return uid_range_load_userns_full(/* path= */ NULL, mode, coalesce, ret);
|
||||
|
||||
r = userns_enter_and_pin(userns_fd, &pidref);
|
||||
if (r < 0)
|
||||
@@ -309,7 +311,7 @@ int uid_range_load_userns_by_fd(int userns_fd, UIDRangeUsernsMode mode, UIDRange
|
||||
pidref.pid,
|
||||
IN_SET(mode, UID_RANGE_USERNS_INSIDE, UID_RANGE_USERNS_OUTSIDE) ? "uid_map" : "gid_map");
|
||||
|
||||
return uid_range_load_userns(p, mode, ret);
|
||||
return uid_range_load_userns_full(p, mode, coalesce, ret);
|
||||
}
|
||||
|
||||
bool uid_range_overlaps(const UIDRange *range, uid_t start, uid_t nr) {
|
||||
@@ -332,6 +334,204 @@ bool uid_range_overlaps(const UIDRange *range, uid_t start, uid_t nr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int uid_range_clip(UIDRange *range, uid_t min, uid_t max) {
|
||||
assert(range);
|
||||
|
||||
if (min > max)
|
||||
return -EINVAL;
|
||||
|
||||
size_t t = 0;
|
||||
FOREACH_ARRAY(e, range->entries, range->n_entries) {
|
||||
uid_t entry_end = e->start + e->nr; /* one past the last UID in entry */
|
||||
|
||||
/* Skip entries completely outside [min, max] */
|
||||
if (entry_end <= min || e->start > max)
|
||||
continue;
|
||||
|
||||
/* Trim the entry to fit within [min, max] */
|
||||
uid_t new_start = MAX(e->start, min);
|
||||
/* entry_end is exclusive, avoid overflow when max == UINT32_MAX */
|
||||
uid_t new_end = entry_end <= max ? entry_end : max + 1;
|
||||
assert(new_end > new_start);
|
||||
|
||||
range->entries[t++] = (UIDRangeEntry) {
|
||||
.start = new_start,
|
||||
.nr = new_end - new_start,
|
||||
};
|
||||
}
|
||||
|
||||
range->n_entries = t;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int uid_range_partition(UIDRange *range, uid_t size) {
|
||||
assert(range);
|
||||
assert(size > 0);
|
||||
|
||||
/* Partitions the UID range entries into buckets of the given size. Any entry larger than the given
|
||||
* size will be partitioned into multiple entries, each of the given size. Any leftover UIDs in the
|
||||
* entry are dropped. Any entries smaller than the given size are also dropped. */
|
||||
|
||||
/* Count how many entries we'll need after partitioning */
|
||||
size_t n_new_entries = 0;
|
||||
FOREACH_ARRAY(e, range->entries, range->n_entries)
|
||||
n_new_entries += e->nr / size;
|
||||
|
||||
if (n_new_entries == 0) {
|
||||
range->n_entries = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (n_new_entries > range->n_entries && !GREEDY_REALLOC(range->entries, n_new_entries))
|
||||
return -ENOMEM;
|
||||
|
||||
/* Work backwards to avoid overwriting entries we still need to read */
|
||||
size_t t = n_new_entries;
|
||||
for (size_t i = range->n_entries; i > 0; i--) {
|
||||
UIDRangeEntry *e = range->entries + i - 1;
|
||||
unsigned n_parts = e->nr / size;
|
||||
|
||||
for (unsigned j = n_parts; j > 0; j--)
|
||||
range->entries[--t] = (UIDRangeEntry) {
|
||||
.start = e->start + (j - 1) * size,
|
||||
.nr = size,
|
||||
};
|
||||
}
|
||||
|
||||
range->n_entries = n_new_entries;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int uid_range_copy(const UIDRange *range, UIDRange **ret) {
|
||||
assert(ret);
|
||||
|
||||
if (!range) {
|
||||
*ret = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
_cleanup_(uid_range_freep) UIDRange *copy = new0(UIDRange, 1);
|
||||
if (!copy)
|
||||
return -ENOMEM;
|
||||
|
||||
if (range->n_entries > 0) {
|
||||
copy->entries = newdup(UIDRangeEntry, range->entries, range->n_entries);
|
||||
if (!copy->entries)
|
||||
return -ENOMEM;
|
||||
|
||||
copy->n_entries = range->n_entries;
|
||||
}
|
||||
|
||||
*ret = TAKE_PTR(copy);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int uid_range_remove(UIDRange *range, uid_t start, uid_t size) {
|
||||
assert(range);
|
||||
|
||||
if (size == 0)
|
||||
return 0;
|
||||
|
||||
uid_t end = start + size; /* one past the last UID to remove */
|
||||
|
||||
for (size_t i = 0; i < range->n_entries; i++) {
|
||||
UIDRangeEntry *e = range->entries + i;
|
||||
uid_t entry_end = e->start + e->nr;
|
||||
|
||||
/* No overlap */
|
||||
if (entry_end <= start || e->start >= end)
|
||||
continue;
|
||||
|
||||
/* Check if this removal splits the entry into two parts */
|
||||
if (e->start < start && entry_end > end) {
|
||||
/* Need to split: grow the array first */
|
||||
if (!GREEDY_REALLOC(range->entries, range->n_entries + 1))
|
||||
return -ENOMEM;
|
||||
|
||||
/* Re-fetch pointer after potential realloc */
|
||||
e = range->entries + i;
|
||||
entry_end = e->start + e->nr;
|
||||
|
||||
/* Shift everything after this entry to make room */
|
||||
memmove(range->entries + i + 2, range->entries + i + 1,
|
||||
(range->n_entries - i - 1) * sizeof(UIDRangeEntry));
|
||||
range->n_entries++;
|
||||
|
||||
/* First part: before the removed range */
|
||||
range->entries[i] = (UIDRangeEntry) {
|
||||
.start = e->start,
|
||||
.nr = start - e->start,
|
||||
};
|
||||
|
||||
/* Second part: after the removed range */
|
||||
range->entries[i + 1] = (UIDRangeEntry) {
|
||||
.start = end,
|
||||
.nr = entry_end - end,
|
||||
};
|
||||
|
||||
/* Skip the newly inserted entry */
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Removal covers the entire entry */
|
||||
if (start <= e->start && end >= entry_end) {
|
||||
memmove(e, e + 1, (range->n_entries - i - 1) * sizeof(UIDRangeEntry));
|
||||
range->n_entries--;
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Removal trims the start of the entry */
|
||||
if (start <= e->start && end > e->start) {
|
||||
e->nr = entry_end - end;
|
||||
e->start = end;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Removal trims the end of the entry */
|
||||
if (start < entry_end && end >= entry_end) {
|
||||
e->nr = start - e->start;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int uid_range_translate(const UIDRange *outside, const UIDRange *inside, uid_t uid, uid_t *ret) {
|
||||
assert(uid_range_entries(outside) == uid_range_entries(inside));
|
||||
assert(ret);
|
||||
|
||||
/* Given two UID ranges that represent the outside UID range of a user namespace (the 2nd and 3rd
|
||||
* columns in /proc/xxx/uid_map) and the inside UID range of a user namespace (the 1st and 3rd
|
||||
* columns in /proc/xxx/uid_map), translates the given UID from the outside range to the inside
|
||||
* range. For example, given the following UID range:
|
||||
*
|
||||
* 0 1000 1
|
||||
*
|
||||
* calling uid_range_translate(outside, inside, 1000) will return 0 as the output UID. Alternatively,
|
||||
* calling uid_range_translate(inside, outside, 0) will return 1000 as the output UID.
|
||||
*/
|
||||
|
||||
for (size_t i = 0; i < uid_range_entries(outside); i++)
|
||||
assert(outside->entries[i].nr == inside->entries[i].nr);
|
||||
|
||||
for (size_t i = 0; i < uid_range_entries(outside); i++) {
|
||||
const UIDRangeEntry *e = outside->entries + i;
|
||||
|
||||
if (uid < e->start || uid >= e->start + e->nr)
|
||||
continue;
|
||||
|
||||
*ret = inside->entries[i].start + uid - e->start;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -ESRCH;
|
||||
}
|
||||
|
||||
bool uid_range_equal(const UIDRange *a, const UIDRange *b) {
|
||||
if (a == b)
|
||||
return true;
|
||||
|
||||
+18
-3
@@ -19,7 +19,10 @@ int uid_range_add_internal(UIDRange **range, uid_t start, uid_t nr, bool coalesc
|
||||
static inline int uid_range_add(UIDRange **range, uid_t start, uid_t nr) {
|
||||
return uid_range_add_internal(range, start, nr, true);
|
||||
}
|
||||
int uid_range_add_str(UIDRange **range, const char *s);
|
||||
int uid_range_add_str_full(UIDRange **range, const char *s, bool coalesce);
|
||||
static inline int uid_range_add_str(UIDRange **range, const char *s) {
|
||||
return uid_range_add_str_full(range, s, true);
|
||||
}
|
||||
|
||||
int uid_range_next_lower(const UIDRange *range, uid_t *uid);
|
||||
|
||||
@@ -48,11 +51,23 @@ typedef enum UIDRangeUsernsMode {
|
||||
_UID_RANGE_USERNS_MODE_INVALID = -EINVAL,
|
||||
} UIDRangeUsernsMode;
|
||||
|
||||
int uid_range_load_userns(const char *path, UIDRangeUsernsMode mode, UIDRange **ret);
|
||||
int uid_range_load_userns_by_fd(int userns_fd, UIDRangeUsernsMode mode, UIDRange **ret);
|
||||
int uid_range_load_userns_full(const char *path, UIDRangeUsernsMode mode, bool coalesce, UIDRange **ret);
|
||||
static inline int uid_range_load_userns(const char *path, UIDRangeUsernsMode mode, UIDRange **ret) {
|
||||
return uid_range_load_userns_full(path, mode, true, ret);
|
||||
}
|
||||
int uid_range_load_userns_by_fd_full(int userns_fd, UIDRangeUsernsMode mode, bool coalesce, UIDRange **ret);
|
||||
static inline int uid_range_load_userns_by_fd(int userns_fd, UIDRangeUsernsMode mode, UIDRange **ret) {
|
||||
return uid_range_load_userns_by_fd_full(userns_fd, mode, true, ret);
|
||||
}
|
||||
|
||||
bool uid_range_overlaps(const UIDRange *range, uid_t start, uid_t nr);
|
||||
|
||||
int uid_range_clip(UIDRange *range, uid_t min, uid_t max);
|
||||
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_map_search_root(pid_t pid, UIDRangeUsernsMode mode, uid_t *ret);
|
||||
|
||||
uid_t uid_range_base(const UIDRange *range);
|
||||
|
||||
Reference in New Issue
Block a user