Files
systemd/src/nsresourced/nsresourcework.c
T
Siteshwar Vashisht a6a831d0d9 nsresourced: Fix reading from NULL pointers (#43566)
`TAKE_PTR(userns_name)` sets `userns_name` to `NULL` earlier.

Follow-up for ee6fd7ef82.
2026-08-31 17:10:20 +09:00

2683 lines
111 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <fcntl.h>
#include <linux/if_tun.h>
#include <linux/magic.h>
#include <linux/nsfs.h>
#include <linux/veth.h>
#include <net/if.h>
#include <poll.h>
#include <sched.h>
#include <sys/eventfd.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <utmpx.h>
#include "sd-daemon.h"
#include "sd-event.h"
#include "sd-netlink.h"
#include "sd-varlink.h"
#include "argv-util.h"
#include "bus-polkit.h"
#include "env-util.h"
#include "errno-util.h"
#include "ether-addr-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "format-util.h"
#include "hashmap.h"
#include "io-util.h"
#include "json-util.h"
#include "main-func.h"
#include "mountpoint-util.h"
#include "namespace-util.h"
#include "netlink-util.h"
#include "nsresource.h"
#include "pidref.h"
#include "process-util.h"
#include "random-util.h"
#include "siphash24.h"
#include "socket-util.h"
#include "stat-util.h"
#include "string-table.h"
#include "string-util.h"
#include "strv.h"
#include "time-util.h"
#include "uid-classification.h"
#include "uid-range.h"
#include "user-record.h"
#include "user-util.h"
#include "userdb.h"
#include "userns-registry.h"
#include "userns-restrict.h"
#include "varlink-io.systemd.NamespaceResource.h"
#include "varlink-io.systemd.UserDatabase.h"
#include "varlink-util.h"
#define ITERATIONS_MAX 64U
#define RUNTIME_MAX_USEC (5 * USEC_PER_MINUTE)
#define PRESSURE_SLEEP_TIME_USEC (50 * USEC_PER_MSEC)
#define LISTEN_IDLE_USEC (90 * USEC_PER_SEC)
#define USERNS_PER_UID 256
typedef struct Context {
Hashmap *polkit_registry;
struct userns_restrict_bpf *bpf;
} Context;
typedef struct LookupParameters {
const char *user_name;
const char *group_name;
union {
uid_t uid;
gid_t gid;
};
const char *service;
} LookupParameters;
typedef enum AllocateUserRangeType {
ALLOCATE_USER_RANGE_MANAGED,
ALLOCATE_USER_RANGE_SELF,
_ALLOCATE_USER_RANGE_TYPE_MAX,
_ALLOCATE_USER_RANGE_TYPE_INVALID = -EINVAL,
} AllocateUserRangeType;
static const char *const allocate_user_range_type_table[_ALLOCATE_USER_RANGE_TYPE_MAX] = {
[ALLOCATE_USER_RANGE_MANAGED] = "managed",
[ALLOCATE_USER_RANGE_SELF] = "self",
};
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(allocate_user_range_type, AllocateUserRangeType);
static JSON_DISPATCH_ENUM_DEFINE(dispatch_allocate_user_range_type, AllocateUserRangeType, allocate_user_range_type_from_string);
static int build_user_json(UserNamespaceInfo *userns_info, uid_t offset, sd_json_variant **ret) {
_cleanup_free_ char *name = NULL, *realname = NULL;
UserDisposition disposition;
int r;
assert(userns_info);
assert(offset < userns_info->size);
if (asprintf(&name, "ns-%s-" UID_FMT, userns_info->name, offset) < 0)
return -ENOMEM;
if (userns_info->size > 1) {
disposition = USER_CONTAINER;
r = asprintf(&realname, "User " UID_FMT " of Allocated Namespace %s", offset, userns_info->name);
} else {
disposition = USER_DYNAMIC;
r = asprintf(&realname, "Allocated Namespace %s", userns_info->name);
}
if (r < 0)
return -ENOMEM;
return sd_json_buildo(
ret,
SD_JSON_BUILD_PAIR_STRING("userName", name),
SD_JSON_BUILD_PAIR_UNSIGNED("uid", userns_info->start_uid + offset),
SD_JSON_BUILD_PAIR_UNSIGNED("gid", GID_NOBODY),
SD_JSON_BUILD_PAIR_STRING("realName", realname),
SD_JSON_BUILD_PAIR("homeDirectory", JSON_BUILD_CONST_STRING("/")),
SD_JSON_BUILD_PAIR_STRING("shell", NOLOGIN),
SD_JSON_BUILD_PAIR_BOOLEAN("locked", true),
SD_JSON_BUILD_PAIR("service", JSON_BUILD_CONST_STRING("io.systemd.NamespaceResource")),
SD_JSON_BUILD_PAIR_STRING("disposition", user_disposition_to_string(disposition)));
}
static int vl_method_get_user_record(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
static const sd_json_dispatch_field dispatch_table[] = {
{ "uid", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, offsetof(LookupParameters, uid), 0 },
{ "userName", SD_JSON_VARIANT_STRING, json_dispatch_const_user_group_name, offsetof(LookupParameters, user_name), SD_JSON_RELAX },
{ "service", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(LookupParameters, service), 0 },
{}
};
_cleanup_(userns_info_freep) UserNamespaceInfo *userns_info = NULL;
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
LookupParameters p = {
.uid = UID_INVALID,
};
uid_t offset;
int r;
assert(parameters);
r = sd_varlink_dispatch(link, parameters, dispatch_table, &p);
if (r != 0)
return r;
if (!streq_ptr(p.service, "io.systemd.NamespaceResource"))
return sd_varlink_error(link, "io.systemd.UserDatabase.BadService", NULL);
if (p.user_name) {
_cleanup_free_ char *n = NULL;
const char *e, *f;
e = startswith(p.user_name, "ns-");
if (!e)
goto not_found;
f = strrchr(e, '-');
if (!f)
goto not_found;
if (parse_uid(f+1, &offset) < 0)
goto not_found;
n = strndup(e, f - e);
if (!n)
return log_oom();
r = userns_registry_load_by_name(
/* dir_fd= */ -EBADF,
n,
&userns_info);
if (r == -ENOENT)
goto not_found;
if (r < 0)
return r;
if (offset >= userns_info->size) /* Outside of range? */
goto not_found;
if (uid_is_valid(p.uid) && p.uid != userns_info->start_uid + offset)
return sd_varlink_error(link, "io.systemd.UserDatabase.ConflictingRecordFound", NULL);
} else if (uid_is_valid(p.uid)) {
uid_t start, uidmask;
if (uid_is_container(p.uid))
uidmask = (uid_t) UINT32_C(0xFFFF0000);
else if (uid_is_dynamic(p.uid))
uidmask = (uid_t) UINT32_C(0xFFFFFFFF);
else
goto not_found;
start = p.uid & uidmask;
offset = p.uid - start;
r = userns_registry_load_by_start_uid(
/* dir_fd= */ -EBADF,
start,
&userns_info);
if (r == -ENOENT)
goto not_found;
if (r < 0)
return r;
if (offset >= userns_info->size) /* Outside of range? */
goto not_found;
} else
return sd_varlink_error(link, "io.systemd.UserDatabase.EnumerationNotSupported", NULL);
r = build_user_json(userns_info, offset, &v);
if (r < 0)
return r;
return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_VARIANT("record", v));
not_found:
return sd_varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
}
static int build_group_json(UserNamespaceInfo *userns_info, gid_t offset, sd_json_variant **ret) {
_cleanup_free_ char *name = NULL, *description = NULL;
UserDisposition disposition;
int r;
assert(userns_info);
assert(offset < userns_info->size);
if (asprintf(&name, "ns-%s-" GID_FMT, userns_info->name, offset) < 0)
return -ENOMEM;
if (userns_info->size > 1) {
disposition = USER_CONTAINER;
r = asprintf(&description, "Group " GID_FMT " of Allocated Namespace %s", offset, userns_info->name);
} else {
disposition = USER_DYNAMIC;
r = asprintf(&description, "Allocated Namespace %s", userns_info->name);
}
if (r < 0)
return -ENOMEM;
return sd_json_buildo(
ret,
SD_JSON_BUILD_PAIR_STRING("groupName", name),
SD_JSON_BUILD_PAIR_UNSIGNED("gid", userns_info->start_gid + offset),
SD_JSON_BUILD_PAIR_STRING("description", description),
SD_JSON_BUILD_PAIR("service", JSON_BUILD_CONST_STRING("io.systemd.NamespaceResource")),
SD_JSON_BUILD_PAIR_STRING("disposition", user_disposition_to_string(disposition)));
}
static int vl_method_get_group_record(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
static const sd_json_dispatch_field dispatch_table[] = {
{ "gid", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, offsetof(LookupParameters, gid), 0 },
{ "groupName", SD_JSON_VARIANT_STRING, json_dispatch_const_user_group_name, offsetof(LookupParameters, group_name), SD_JSON_RELAX },
{ "service", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(LookupParameters, service), 0 },
{}
};
_cleanup_(userns_info_freep) UserNamespaceInfo *userns_info = NULL;
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
LookupParameters p = {
.gid = GID_INVALID,
};
gid_t offset;
int r;
assert(parameters);
r = sd_varlink_dispatch(link, parameters, dispatch_table, &p);
if (r != 0)
return r;
if (!streq_ptr(p.service, "io.systemd.NamespaceResource"))
return sd_varlink_error(link, "io.systemd.UserDatabase.BadService", NULL);
if (p.group_name) {
_cleanup_free_ char *n = NULL;
const char *e, *f;
e = startswith(p.group_name, "ns-");
if (!e)
goto not_found;
f = strrchr(e, '-');
if (!f)
goto not_found;
if (parse_gid(f+1, &offset) < 0)
goto not_found;
n = strndup(e, f - e);
if (!n)
return log_oom();
r = userns_registry_load_by_name(
/* dir_fd= */ -EBADF,
n,
&userns_info);
if (r == -ENOENT)
goto not_found;
if (r < 0)
return r;
if (offset >= userns_info->size) /* Outside of range? */
goto not_found;
if (gid_is_valid(p.gid) && p.gid != userns_info->start_gid + offset)
return sd_varlink_error(link, "io.systemd.UserDatabase.ConflictingRecordFound", NULL);
} else if (gid_is_valid(p.gid)) {
gid_t start, gidmask;
if (gid_is_container(p.gid))
gidmask = (gid_t) UINT32_C(0xFFFF0000);
else if (gid_is_dynamic(p.gid))
gidmask = (gid_t) UINT32_C(0xFFFFFFFF);
else
goto not_found;
start = p.gid & gidmask;
offset = p.gid - start;
r = userns_registry_load_by_start_gid(
/* dir_fd= */ -EBADF,
start,
&userns_info);
if (r == -ENOENT)
goto not_found;
if (r < 0)
return r;
if (offset >= userns_info->size) /* Outside of range? */
goto not_found;
} else
return sd_varlink_error(link, "io.systemd.UserDatabase.EnumerationNotSupported", NULL);
r = build_group_json(userns_info, offset, &v);
if (r < 0)
return r;
return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_VARIANT("record", v));
not_found:
return sd_varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
}
static int vl_method_get_memberships(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
static const sd_json_dispatch_field dispatch_table[] = {
{ "userName", SD_JSON_VARIANT_STRING, json_dispatch_const_user_group_name, offsetof(LookupParameters, user_name), SD_JSON_RELAX },
{ "groupName", SD_JSON_VARIANT_STRING, json_dispatch_const_user_group_name, offsetof(LookupParameters, group_name), SD_JSON_RELAX },
{ "service", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(LookupParameters, service), 0 },
{}
};
LookupParameters p = {};
int r;
assert(parameters);
r = sd_varlink_dispatch(link, parameters, dispatch_table, &p);
if (r != 0)
return r;
if (!streq_ptr(p.service, "io.systemd.NamespaceResource"))
return sd_varlink_error(link, "io.systemd.UserDatabase.BadService", NULL);
/* We don't support auxiliary groups for namespace allocations */
return sd_varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
}
static int registry_range_is_available(
struct userns_restrict_bpf *bpf,
int registry_dir_fd,
uid_t candidate,
bool gid) {
_cleanup_(userns_info_freep) UserNamespaceInfo *owner = NULL;
int r;
assert(registry_dir_fd >= 0);
/* Checks whether the (UID or GID) range starting at candidate is free to allocate as a fresh
* transient range. If an existing registry entry claims it but its owning namespace has since
* died — without a BPF death event reaping it yet — we reclaim it on the spot and treat the range
* as available. Unlike delegations, transient registrations have no ancestor chain, so a single
* reap suffices. */
r = gid ? userns_registry_gid_exists(registry_dir_fd, (gid_t) candidate)
: userns_registry_uid_exists(registry_dir_fd, candidate);
if (r < 0)
return r;
if (r == 0)
return true; /* No registry entry for this range → available. */
r = gid ? userns_registry_load_by_start_gid(registry_dir_fd, (gid_t) candidate, &owner)
: userns_registry_load_by_start_uid(registry_dir_fd, candidate, &owner);
if (r == -ENOENT)
return true; /* Raced away between the existence check and the load → available. */
if (r < 0)
return r;
r = userns_registry_reap_if_dead(bpf, registry_dir_fd, owner->userns_inode, /* ret_info= */ NULL);
if (r < 0)
return r;
if (r == USERNS_REAP_RELEASED)
return true; /* Owner was dead and reaped → range is now free. */
/* Owner is alive or its liveness couldn't be determined → we can't reclaim it, so the range
* is taken. */
log_debug("%s" UID_FMT " unavailable: already registered in userns registry.",
gid ? "GID " : "UID ", candidate);
return false;
}
static int delegation_range_is_available(
struct userns_restrict_bpf *bpf,
int registry_dir_fd,
uid_t candidate,
uint64_t parent_userns_inode,
bool gid) {
int r;
assert(registry_dir_fd >= 0);
/* Checks whether the (UID or GID) range starting at candidate is free to allocate as far as
* the delegation registry is concerned. A delegated range is available only if it's owned by
* the requesting parent user namespace (which may then sub-allocate within it). If it's owned
* by another namespace that has since died — without a BPF death event reaping it yet — we reclaim
* it on the spot, which restores the range to its ancestor (possibly the parent), and
* re-evaluate. This walks a chain of dead ancestors until it reaches a live owner, the
* requesting parent, or a fully freed range. */
r = gid ? userns_registry_delegation_gid_exists(registry_dir_fd, (gid_t) candidate)
: userns_registry_delegation_uid_exists(registry_dir_fd, candidate);
if (r < 0)
return r;
if (r == 0)
return true; /* No delegation for this range → available. */
for (;;) {
_cleanup_(delegated_userns_info_done) DelegatedUserNamespaceInfo delegation = DELEGATED_USER_NAMESPACE_INFO_NULL;
r = gid ? userns_registry_load_delegation_by_gid(registry_dir_fd, (gid_t) candidate, &delegation)
: userns_registry_load_delegation_by_uid(registry_dir_fd, candidate, &delegation);
if (r == -ENOENT)
return true; /* Delegation removed while reaping (no ancestor left) → fully free. */
if (r < 0)
return r;
if (delegation.userns_inode == parent_userns_inode) {
/* The parent userns owns this delegation, so the range is available for nested
* allocation. */
log_debug("%s" UID_FMT " is delegated by parent userns inode %" PRIu64 ", available for nested allocation.",
gid ? "GID " : "UID ", candidate, parent_userns_inode);
return true;
}
/* Owned by some other namespace. If that namespace is dead, reclaim it (restoring the
* range to its ancestor) and loop to re-evaluate; otherwise the range is genuinely
* taken. */
r = userns_registry_reap_if_dead(bpf, registry_dir_fd, delegation.userns_inode, /* ret_info= */ NULL);
if (r < 0)
return r;
if (r == USERNS_REAP_RELEASED)
continue; /* Reaped a dead owner; re-evaluate the now-restored delegation. */
/* Owner is alive, or its liveness couldn't be determined (no recorded id, or a dead
* ancestor that an earlier reap popped into ownership but left no registry entry to
* drive a further restore). We can't reclaim the range here — and mustn't loop, since
* re-reaping would not make progress — so treat it as taken. */
log_debug("%s" UID_FMT " unavailable: delegated to userns inode %" PRIu64 " that can't be reclaimed (parent is %" PRIu64 ").",
gid ? "GID " : "UID ", candidate, delegation.userns_inode, parent_userns_inode);
return false;
}
}
static int uid_is_available(
struct userns_restrict_bpf *bpf,
int registry_dir_fd,
uid_t candidate,
int parent_userns_fd) {
int r;
assert(registry_dir_fd >= 0);
log_debug("Checking if UID " UID_FMT " is available.", candidate);
uint64_t parent_userns_inode = 0;
struct stat parent_st;
if (fstat(parent_userns_fd, &parent_st) < 0)
return log_debug_errno(errno, "Failed to fstat parent user namespace: %m");
parent_userns_inode = parent_st.st_ino;
/* Check whether an existing transient registration claims this range; a dead owner is reclaimed
* inline so its range doesn't stay blocked. The UID and GID are checked separately as either
* can exist independently. */
r = registry_range_is_available(bpf, registry_dir_fd, candidate, /* gid= */ false);
if (r < 0)
return r;
if (r == 0)
return false;
r = registry_range_is_available(bpf, registry_dir_fd, candidate, /* gid= */ true);
if (r < 0)
return r;
if (r == 0)
return false;
/* Also check delegation files. A delegated range is only available for nested allocation when
* the requesting parent owns it; dead owners are reclaimed inline so their ranges don't stay
* blocked. We check the UID and GID delegations separately as either can exist independently. */
r = delegation_range_is_available(bpf, registry_dir_fd, candidate, parent_userns_inode, /* gid= */ false);
if (r < 0)
return r;
if (r == 0)
return false;
r = delegation_range_is_available(bpf, registry_dir_fd, candidate, parent_userns_inode, /* gid= */ true);
if (r < 0)
return r;
if (r == 0)
return false;
r = is_our_namespace(parent_userns_fd, NAMESPACE_USER);
if (r < 0)
return log_debug_errno(r, "Failed to check if parent user namespace is our user namespace: %m");
if (r > 0) {
/* Only check userdb if we're allocating from our current user namespace. userdb won't be
* to tell us anything on whether UIDs/GIDs in another user namespace are in use or not. On
* top of that, for nspawn containers registered with machined's userdb implementation, it
* would tell us that any ranges delegated to the container are in use (which is true in the
* nsresourced user namespace, but not in the nspawn user namespace). */
r = userdb_by_uid(candidate, /* match= */ NULL, USERDB_AVOID_MULTIPLEXER, /* ret= */ NULL);
if (r >= 0) {
log_debug("UID " UID_FMT " unavailable: known to userdb.", candidate);
return false;
}
if (r != -ESRCH)
return r;
r = groupdb_by_gid(candidate, /* match= */ NULL, USERDB_AVOID_MULTIPLEXER, /* ret= */ NULL);
if (r >= 0) {
log_debug("UID " UID_FMT " unavailable: GID known to groupdb.", candidate);
return false;
}
if (r != -ESRCH)
return r;
}
log_debug("UID " UID_FMT " is available.", candidate);
return true;
}
static int name_is_available(
int registry_dir_fd,
const char *name) {
_cleanup_free_ char *user_name = NULL;
int r;
assert(registry_dir_fd >= 0);
assert(name);
r = userns_registry_name_exists(registry_dir_fd, name);
if (r < 0)
return r;
if (r > 0)
return false;
user_name = strjoin("ns-", name, "-0");
if (!user_name)
return -ENOMEM;
r = userdb_by_name(user_name, /* match= */ NULL, USERDB_AVOID_MULTIPLEXER, /* ret= */ NULL);
if (r >= 0)
return false;
if (r != -ESRCH)
return r;
r = groupdb_by_name(user_name, /* match= */ NULL, USERDB_AVOID_MULTIPLEXER, /* ret= */ NULL);
if (r >= 0)
return false;
if (r != -ESRCH)
return r;
log_debug("Namespace name '%s' is available.", name);
return true;
}
static int inode_slot_is_available(
int registry_dir_fd,
int userns_fd,
struct userns_restrict_bpf *bpf,
uint64_t inode) {
_cleanup_(userns_info_freep) UserNamespaceInfo *existing = NULL;
int r;
assert(registry_dir_fd >= 0);
assert(userns_fd >= 0);
assert(inode != 0);
/* Returns true if the registry has no entry for this inode (after cleaning up any stale
* leftover from a previously-registered namespace whose inode was recycled by the kernel),
* false if a live registration already occupies the slot, negative on error. */
r = userns_registry_load_by_userns_inode(registry_dir_fd, inode, &existing);
if (r == -ENOENT)
return true;
if (r < 0)
return log_debug_errno(r, "Failed to load existing registry entry: %m");
r = userns_info_verify_fd(userns_fd, existing);
if (r >= 0)
return false;
if (r != -ESTALE)
return log_debug_errno(r, "Failed to verify user namespace identity: %m");
log_debug("Inode %" PRIu64 " was reused by the kernel; cleaning up stale registry entry for namespace id %" PRIu64 ".",
inode, existing->userns_id);
userns_registry_release_by_info(bpf, registry_dir_fd, existing);
return true;
}
static int allocate_one(
struct userns_restrict_bpf *bpf,
int registry_dir_fd,
const char *name,
uint32_t size,
int parent_userns_fd,
UIDRange *candidates,
uid_t *ret_candidate) {
static const uint8_t hash_key[16] = {
0xd4, 0xd7, 0x33, 0xa7, 0x4d, 0xd3, 0x42, 0xcd,
0xaa, 0xe9, 0x45, 0xd0, 0xfb, 0xec, 0x79, 0xee,
};
_cleanup_(uid_range_freep) UIDRange *copy = NULL;
uid_t candidate, uidmin, uidmax;
unsigned n_tries = 100;
size_t idx;
int r;
assert(registry_dir_fd >= 0);
assert(candidates);
assert(ret_candidate);
switch (size) {
case NSRESOURCE_UIDS_64K:
uidmin = CONTAINER_UID_BASE_MIN;
uidmax = CONTAINER_UID_BASE_MAX;
break;
case NSRESOURCE_UIDS_1:
uidmin = DYNAMIC_UID_MIN;
uidmax = DYNAMIC_UID_MAX;
break;
default:
assert_not_reached();
}
/* Make a copy of candidates that we can modify for the selection algorithm */
r = uid_range_copy(candidates, &copy);
if (r < 0)
return log_debug_errno(r, "Failed to copy UID range: %m");
/* Clip the copy with the valid UID range for this allocation size */
r = uid_range_clip(copy, uidmin, uidmax);
if (r < 0)
return log_debug_errno(r, "Failed to intersect UID range: %m");
/* Partition entries into entries of exactly the right size */
r = uid_range_partition(copy, size);
if (r < 0)
return log_debug_errno(r, "Failed to partition UID ranges: %m");
if (uid_range_is_empty(copy))
return log_debug_errno(SYNTHETIC_ERRNO(EHOSTDOWN), "Relevant UID range not delegated, can't allocate.");
log_debug("Partitioned UID range into %zu entries of size %" PRIu32, copy->n_entries, size);
/* Start from a hash of the input name if we have one, use random values afterwards. */
idx = name ? siphash24_string(name, hash_key) : random_u32();
for (;; idx = random_u32()) {
if (uid_range_is_empty(copy))
return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), "All candidate UIDs already taken.");
if (--n_tries <= 0)
return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), "Try limit hit, no UIDs available.");
idx %= copy->n_entries;
candidate = copy->entries[idx].start;
/* We only check the base UID for each range. Pass the parent userns inode so that
* allocating from a delegated range owned by the parent is allowed. */
r = uid_is_available(bpf, registry_dir_fd, candidate, parent_userns_fd);
if (r < 0)
return log_debug_errno(r, "Can't determine if UID range " UID_FMT " is available: %m", candidate);
if (r > 0)
break;
log_debug("UID range " UID_FMT " already taken.", candidate);
/* Remove this unavailable range from candidates so we don't try it again */
r = uid_range_remove(copy, candidate, size);
if (r < 0)
return log_debug_errno(r, "Failed to remove unavailable range from candidates: %m");
}
/* Remove the allocated range from the original candidates */
r = uid_range_remove(candidates, candidate, size);
if (r < 0)
return log_debug_errno(r, "Failed to remove allocated range from candidates: %m");
*ret_candidate = candidate;
log_debug("Allocating UID range " UID_FMT "…" UID_FMT, candidate, candidate + size - 1);
return 0;
}
static int allocate_now(
int registry_dir_fd,
int userns_fd,
int parent_userns_fd,
struct userns_restrict_bpf *bpf,
UserNamespaceInfo *info,
int *ret_lock_fd) {
_cleanup_(uid_range_freep) UIDRange *candidates = NULL;
uid_t candidate;
int r;
/* Returns the following error codes:
*
* EBUSY → all UID candidates we checked are already taken
* EEXIST → the name for the userns already exists
* EDEADLK → the userns is already registered in the registry
*/
assert(registry_dir_fd >= 0);
assert(userns_fd >= 0);
assert(info);
r = uid_range_load_userns_by_fd(parent_userns_fd, UID_RANGE_USERNS_INSIDE, &candidates);
if (r < 0)
return log_debug_errno(r, "Failed to read userns UID range: %m");
log_debug("Parent user namespace exposes %zu UID range(s) inside:", uid_range_entries(candidates));
if (DEBUG_LOGGING)
FOREACH_ARRAY(e, candidates->entries, candidates->n_entries)
log_debug(" " UID_FMT "…" UID_FMT " (size " UID_FMT ")",
e->start, e->start + e->nr - 1, e->nr);
_cleanup_close_ int lock_fd = -EBADF;
lock_fd = userns_registry_lock(registry_dir_fd);
if (lock_fd < 0)
return log_debug_errno(lock_fd, "Failed to open nsresource registry lock file: %m");
/* Enforce limit on user namespaces per UID */
r = userns_registry_per_uid(registry_dir_fd, info->owner);
if (r < 0)
return log_debug_errno(r, "Failed to determine number of currently registered user namespaces per UID " UID_FMT ": %m", info->owner);
if (r >= USERNS_PER_UID)
return log_debug_errno(SYNTHETIC_ERRNO(EUSERS), "User already registered %i user namespaces, refusing.", r);
r = inode_slot_is_available(registry_dir_fd, userns_fd, bpf, info->userns_inode);
if (r < 0)
return r;
if (r == 0)
return -EDEADLK;
r = name_is_available(registry_dir_fd, info->name);
if (r < 0)
return r;
if (r == 0)
return -EEXIST;
/* If the source UID/GID are already set we're doing a "self" user namespace and don't need to
* allocate a transient range. */
if (!uid_is_valid(info->start_uid) && !gid_is_valid(info->start_gid)) {
r = allocate_one(
bpf,
registry_dir_fd,
info->name, info->size,
parent_userns_fd,
candidates,
&candidate);
if (r < 0)
return r;
info->start_uid = candidate;
info->start_gid = (gid_t) candidate;
}
/* Now allocate delegated ranges if requested */
if (info->n_delegates > 0) {
assert(info->delegates);
FOREACH_ARRAY(delegate, info->delegates, info->n_delegates) {
r = allocate_one(
bpf,
registry_dir_fd,
/* name= */ NULL,
delegate->size,
parent_userns_fd,
candidates,
&candidate);
if (r < 0)
return r;
delegate->userns_inode = info->userns_inode;
delegate->start_uid = candidate;
delegate->start_gid = (gid_t) candidate;
}
}
if (ret_lock_fd)
*ret_lock_fd = TAKE_FD(lock_fd);
return 0;
}
static int write_userns_mappings(PidRef *pidref, const char *uidmap, const char *gidmap) {
const char *pmap;
int r;
assert(pidref);
assert(uidmap);
assert(gidmap);
pmap = procfs_file_alloca(pidref->pid, "uid_map");
r = write_string_file(pmap, uidmap, /* flags= */ 0);
if (r < 0)
return log_error_errno(r, "Failed to write 'uid_map' file of user namespace: %m");
pmap = procfs_file_alloca(pidref->pid, "gid_map");
r = write_string_file(pmap, gidmap, /* flags= */ 0);
if (r < 0)
return log_error_errno(r, "Failed to write 'gid_map' file of user namespace: %m");
return 0;
}
static int write_userns(
int userns_fd,
int parent_userns_fd,
const UserNamespaceInfo *userns_info,
bool map_foreign) {
_cleanup_(pidref_done_sigkill_wait) PidRef pidref = PIDREF_NULL;
_cleanup_close_ int efd = -EBADF;
uint64_t u;
int r;
assert(userns_fd >= 0);
assert(parent_userns_fd >= 0);
assert(userns_info);
assert(uid_is_valid(userns_info->target_uid));
assert(uid_is_valid(userns_info->start_uid));
assert(gid_is_valid(userns_info->target_gid));
assert(gid_is_valid(userns_info->start_gid));
assert(userns_info->size > 0);
assert(userns_info->size <= UINT32_MAX - userns_info->start_uid);
assert(userns_info->size <= UINT32_MAX - userns_info->start_gid);
efd = eventfd(0, EFD_CLOEXEC);
if (efd < 0)
return log_error_errno(errno, "Failed to allocate eventfd(): %m");
r = pidref_safe_fork("(sd-userns)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL|FORK_LOG, &pidref);
if (r < 0)
return r;
if (r == 0) {
/* child */
if (setns(userns_fd, CLONE_NEWUSER) < 0) {
log_error_errno(errno, "Failed to join user namespace: %m");
goto child_fail;
}
if (eventfd_write(efd, 1) < 0) {
log_error_errno(errno, "Failed to ping event fd: %m");
goto child_fail;
}
freeze();
child_fail:
_exit(EXIT_FAILURE);
}
/* Wait until child joined the user namespace */
if (eventfd_read(efd, &u) < 0)
return log_error_errno(errno, "Failed to wait for event fd: %m");
/* Now write mapping */
_cleanup_(uid_range_freep) UIDRange *outside_range = NULL;
r = uid_range_load_userns_by_fd_full(parent_userns_fd, UID_RANGE_USERNS_OUTSIDE, /* coalesce= */ false, &outside_range);
if (r < 0)
return log_debug_errno(r, "Failed to read userns UID range: %m");
_cleanup_(uid_range_freep) UIDRange *inside_range = NULL;
r = uid_range_load_userns_by_fd_full(parent_userns_fd, UID_RANGE_USERNS_INSIDE, /* coalesce= */ false, &inside_range);
if (r < 0)
return log_debug_errno(r, "Failed to read userns UID range: %m");
uid_t start_uid;
r = uid_range_translate(outside_range, inside_range, userns_info->start_uid, &start_uid);
if (r < 0)
return log_debug_errno(r, "Failed to translate UID "UID_FMT" to parent userns: %m", userns_info->start_uid);
/* Let's enforce that the transient UID/GID ranges are mapped 1:1 in the parent user namespace, to
* avoid any weird mapping shenanigans that might happen otherwise. */
if (uid_is_transient(userns_info->start_uid) && start_uid != userns_info->start_uid)
return log_debug_errno(
SYNTHETIC_ERRNO(ERANGE),
"Transient UID range not mapped 1:1 in parent userns ("UID_FMT" -> "UID_FMT")",
userns_info->start_uid, start_uid);
/* Build uid_map content: primary mapping + delegated mappings (1:1) */
_cleanup_free_ char *uidmap = NULL;
if (asprintf(&uidmap, UID_FMT " " UID_FMT " %" PRIu32 "\n",
userns_info->target_uid, start_uid, userns_info->size) < 0)
return log_oom();
log_debug("UID mapping: " UID_FMT " " UID_FMT " %" PRIu32,
userns_info->target_uid, userns_info->start_uid, userns_info->size);
FOREACH_ARRAY(delegate, userns_info->delegates, userns_info->n_delegates) {
r = uid_range_translate(outside_range, inside_range, delegate->start_uid, &start_uid);
if (r < 0)
return log_debug_errno(r, "Failed to translate UID "UID_FMT" to parent userns: %m", userns_info->start_uid);
if (start_uid != delegate->start_uid)
return log_debug_errno(
SYNTHETIC_ERRNO(ERANGE),
"Delegated transient UID range not mapped 1:1 in parent userns ("UID_FMT" -> "UID_FMT")",
delegate->start_uid, start_uid);
if (strextendf(&uidmap,
UID_FMT " " UID_FMT " %" PRIu32 "\n",
delegate->start_uid,
start_uid,
delegate->size) < 0)
return log_oom();
log_debug("UID mapping: " UID_FMT " " UID_FMT " %" PRIu32,
delegate->start_uid, start_uid, delegate->size);
}
if (map_foreign) {
r = uid_range_translate(outside_range, inside_range, FOREIGN_UID_MIN, &start_uid);
if (r < 0)
return log_debug_errno(r, "Failed to translate UID "UID_FMT" to parent userns: %m", FOREIGN_UID_MIN);
if (start_uid != FOREIGN_UID_MIN)
return log_debug_errno(
SYNTHETIC_ERRNO(ERANGE),
"Foreign UID range not mapped 1:1 in parent userns ("UID_FMT" -> "UID_FMT")",
FOREIGN_UID_MIN, start_uid);
if (strextendf(&uidmap, UID_FMT " " UID_FMT " %" PRIu32 "\n",
FOREIGN_UID_MIN, start_uid, NSRESOURCE_UIDS_64K) < 0)
return log_oom();
}
outside_range = uid_range_free(outside_range);
inside_range = uid_range_free(inside_range);
r = uid_range_load_userns_by_fd_full(parent_userns_fd, GID_RANGE_USERNS_OUTSIDE, /* coalesce= */ false, &outside_range);
if (r < 0)
return log_debug_errno(r, "Failed to read userns GID range: %m");
r = uid_range_load_userns_by_fd_full(parent_userns_fd, GID_RANGE_USERNS_INSIDE, /* coalesce= */ false, &inside_range);
if (r < 0)
return log_debug_errno(r, "Failed to read userns GID range: %m");
gid_t start_gid;
r = uid_range_translate(outside_range, inside_range, userns_info->start_gid, &start_gid);
if (r < 0)
return log_debug_errno(r, "Failed to translate GID "GID_FMT" to parent userns: %m", userns_info->start_gid);
if (gid_is_transient(userns_info->start_gid) && start_gid != userns_info->start_gid)
return log_debug_errno(
SYNTHETIC_ERRNO(ERANGE),
"Transient GID range not mapped 1:1 in parent userns ("GID_FMT" -> "GID_FMT")",
userns_info->start_gid, start_gid);
_cleanup_free_ char *gidmap = NULL;
if (asprintf(&gidmap, GID_FMT " " GID_FMT " %" PRIu32 "\n",
userns_info->target_gid, start_gid, userns_info->size) < 0)
return log_oom();
log_debug("GID mapping: " GID_FMT " " GID_FMT " %" PRIu32,
userns_info->target_gid, userns_info->start_gid, userns_info->size);
FOREACH_ARRAY(delegate, userns_info->delegates, userns_info->n_delegates) {
r = uid_range_translate(outside_range, inside_range, delegate->start_gid, &start_gid);
if (r < 0)
return log_debug_errno(r, "Failed to translate GID "GID_FMT" to parent userns: %m", userns_info->start_gid);
if (start_gid != delegate->start_gid)
return log_debug_errno(
SYNTHETIC_ERRNO(ERANGE),
"Delegated transient GID range not mapped 1:1 in parent userns ("GID_FMT" -> "GID_FMT")",
delegate->start_gid, start_gid);
/* Delegated ranges are mapped 1:1 (inside GID == outside GID) */
if (strextendf(&gidmap, GID_FMT " " GID_FMT " %" PRIu32 "\n",
delegate->start_gid,
start_gid,
delegate->size) < 0)
return log_oom();
log_debug("GID mapping: " GID_FMT " " GID_FMT " %" PRIu32,
delegate->start_gid, start_gid, delegate->size);
}
if (map_foreign) {
r = uid_range_translate(outside_range, inside_range, FOREIGN_UID_MIN, &start_gid);
if (r < 0)
return log_debug_errno(r, "Failed to translate GID "GID_FMT" to parent userns: %m", FOREIGN_UID_MIN);
if (start_gid != FOREIGN_UID_MIN)
return log_debug_errno(
SYNTHETIC_ERRNO(ERANGE),
"Foreign GID range not mapped 1:1 in parent userns ("GID_FMT" -> "GID_FMT")",
FOREIGN_UID_MIN, start_gid);
if (strextendf(&gidmap, GID_FMT " " GID_FMT " %" PRIu32 "\n",
FOREIGN_UID_MIN, start_gid, NSRESOURCE_UIDS_64K) < 0)
return log_oom();
}
r = is_our_namespace(parent_userns_fd, NAMESPACE_USER);
if (r < 0)
return log_debug_errno(r, "Failed to check if parent user namespace refers to our own user namespace: %m");
if (r > 0)
return write_userns_mappings(&pidref, uidmap, gidmap);
/* The kernel is paranoid that the uid_map and gid_map files are written either from the user
* namespace itself or its parent user namespace, so we have to join the parent user namespace to
* write the files. */
r = pidref_safe_fork("(sd-userns)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL|FORK_LOG|FORK_WAIT, /* ret= */ NULL);
if (r < 0)
return r;
if (r == 0) {
if (setns(parent_userns_fd, CLONE_NEWUSER) < 0) {
log_error_errno(errno, "Failed to join parent user namespace: %m");
_exit(EXIT_FAILURE);
}
r = write_userns_mappings(&pidref, uidmap, gidmap);
_exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
}
/* We are done! */
log_debug("Successfully configured user namespace.");
return 0;
}
static int test_userns_api_support(sd_varlink *link) {
int r;
assert(link);
/* We only expose the userns API if our manager daemon told us this OK to do. It will set this
* boolean only if it managed to set up BPF correctly for itself (i.e. watches for userns going away
* via BPF APIs). This should make very sure we don't accidentally allow any of the userns stuff to
* go through without the BPF LSM in effect. */
r = getenv_bool("NSRESOURCE_API");
if (r < 0)
return log_error_errno(r, "Failed to parse $NSRESOURCE_API: %m");
if (r == 0)
return sd_varlink_error(link, "io.systemd.NamespaceResource.UserNamespaceInterfaceNotSupported", NULL);
return 0;
}
static char* hash_name(sd_varlink *link, const char *name) {
int r;
assert(link);
assert(name);
/* Make up a hashed name for this userns. We take the passed name, and hash it together with the
* connection cookie. This should make collisions unlikely but generation still deterministic (this
* matters because on polkit requests we might be called twice, and should generate the same string
* each time, to ensure the Polkit query looks the same) */
uint64_t cookie = 0;
r = socket_get_cookie(sd_varlink_get_fd(link), &cookie);
if (r < 0)
log_debug_errno(r, "Failed to determine connection cookie, ignoring: %m");
struct siphash h;
static sd_id128_t key = SD_ID128_MAKE(ed,3a,bb,01,3a,14,4b,b3,8a,63,a4,ad,ba,2d,c9,0a);
siphash24_init(&h, key.bytes);
siphash24_compress_typesafe(cookie, &h);
siphash24_compress_string(name, &h);
/* Make sure the hashed name fits into utmpx even if prefixed with "ns-", the peer's UID, "-", and
* suffixed by "-65535". */
assert_cc(STRLEN("ns-65535-") + 16 + STRLEN("-65535") < sizeof_field(struct utmpx, ut_user));
char *s = NULL;
if (asprintf(&s, "%016" PRIx64, siphash24_finalize(&h)) < 0)
return NULL;
return s;
}
static char *shorten_name(const char *name) {
/* Shorten the specified name, so that it works as a userns name */
char *n = strdup(name);
if (!n)
return NULL;
/* make sure the truncated name fits into utmpx even if prefixed with "ns-" and suffixed by "-65535" */
strshorten(n, sizeof_field(struct utmpx, ut_user) - STRLEN("ns-") - STRLEN("-65536") - 1);
return n;
}
static int validate_name(sd_varlink *link, const char *name, bool mangle, char **ret) {
int r;
assert(link);
assert(name);
assert(ret);
uid_t peer_uid;
r = sd_varlink_get_peer_uid(link, &peer_uid);
if (r < 0)
return r;
_cleanup_free_ char *un = NULL;
if (peer_uid == 0) {
/* If the client is root, we'll not prefix it, but we will make sure it's suitable for
* inclusion in a user name */
if (userns_name_is_valid(name)) {
un = strdup(name);
if (!un)
return -ENOMEM;
} else {
if (!mangle)
return sd_varlink_error_invalid_parameter_name(link, "name");
un = shorten_name(name);
if (!un)
return -ENOMEM;
/* Let's see if shortening was enough? (It might not be, for example because an empty
* string was provided – which no truncation would make valid.) */
if (!userns_name_is_valid(un)) {
free(un);
/* if not, make up a hashed name */
un = hash_name(link, name);
if (!un)
return -ENOMEM;
}
}
} else {
/* If the client is not root then prefix the name with the UID of the peer, so that they live
* in separate namespaces and cannot interfere with each other's names. */
if (asprintf(&un, UID_FMT "-%s", peer_uid, name) < 0)
return -ENOMEM;
if (!userns_name_is_valid(un)) {
if (!mangle)
return sd_varlink_error_invalid_parameter_name(link, "name");
_cleanup_free_ char *c = shorten_name(un);
if (!c)
return -ENOMEM;
/* Let's see if shortening was enough? */
if (userns_name_is_valid(c))
free_and_replace(un, c);
else {
free(c);
c = hash_name(link, name);
if (!c)
return -ENOMEM;
un = mfree(un);
if (asprintf(&un, UID_FMT "-%s", peer_uid, c) < 0)
return -ENOMEM;
if (!userns_name_is_valid(un))
return sd_varlink_error_invalid_parameter_name(link, "name");
}
}
}
*ret = TAKE_PTR(un);
return 0;
}
static int validate_target_and_size(sd_varlink *link, uid_t target, uint32_t size, AllocateUserRangeType type) {
assert(link);
if (type == ALLOCATE_USER_RANGE_SELF) {
/* Self userns must have size 1 and target must be 0 or unset */
if (size != 1)
return sd_varlink_error_invalid_parameter_name(link, "size");
if (!IN_SET(target, UID_INVALID, 0))
return sd_varlink_error_invalid_parameter_name(link, "target");
} else {
if (!IN_SET(size, 1U, 0x10000))
return sd_varlink_error_invalid_parameter_name(link, "size");
if (!uid_is_valid(target) || target > UINT32_MAX - size)
return sd_varlink_error_invalid_parameter_name(link, "target");
}
return 0;
}
static int validate_userns(sd_varlink *link, int userns_fd) {
int r;
assert(link);
assert(userns_fd >= 0);
r = fd_verify_safe_flags(userns_fd);
if (r < 0)
return log_debug_errno(r, "User namespace file descriptor has unsafe flags set: %m");
/* Validate this is actually a valid user namespace fd */
r = fd_is_namespace(userns_fd, NAMESPACE_USER);
if (r < 0)
return log_debug_errno(r, "Failed to check if user namespace fd is actually a user namespace: %m");
if (r == 0)
return sd_varlink_error_invalid_parameter_name(link, "userNamespaceFileDescriptor");
/* And refuse the thing if it is our own */
r = is_our_namespace(userns_fd, NAMESPACE_USER);
if (r < 0)
return log_debug_errno(r, "Failed to check if user namespace fd refers to our own user namespace: %m");
if (r > 0)
return sd_varlink_error_invalid_parameter_name(link, "userNamespaceFileDescriptor");
uid_t peer_uid;
r = sd_varlink_get_peer_uid(link, &peer_uid);
if (r < 0)
return log_debug_errno(r, "Failed to acquire peer UID: %m");
if (peer_uid != 0) {
/* Refuse if the userns is not actually owned by our client. */
uid_t owner_uid;
if (ioctl(userns_fd, NS_GET_OWNER_UID, &owner_uid) < 0)
return log_debug_errno(errno, "Failed to get owner UID of user namespace: %m");
if (owner_uid != peer_uid)
return sd_varlink_error_invalid_parameter_name(link, "userNamespaceFileDescriptor");
}
return 0;
}
static int validate_userns_is_empty(sd_varlink *link, int userns_fd) {
int r;
assert(link);
assert(userns_fd >= 0);
_cleanup_(uid_range_freep) UIDRange *range = NULL;
r = uid_range_load_userns_by_fd(userns_fd, UID_RANGE_USERNS_OUTSIDE, &range);
if (r < 0)
return log_debug_errno(r, "Failed to read userns UID range: %m");
if (!uid_range_is_empty(range))
return sd_varlink_error_invalid_parameter_name(link, "userNamespaceFileDescriptor");
range = uid_range_free(range);
r = uid_range_load_userns_by_fd(userns_fd, GID_RANGE_USERNS_OUTSIDE, &range);
if (r < 0)
return log_debug_errno(r, "Failed to read userns GID range: %m");
if (!uid_range_is_empty(range))
return sd_varlink_error_invalid_parameter_name(link, "userNamespaceFileDescriptor");
return 0;
}
typedef struct AllocateParameters {
const char *name;
AllocateUserRangeType type;
uint32_t size;
uid_t target;
unsigned userns_fd_idx;
bool mangle_name;
uint32_t delegate_container_ranges;
bool map_foreign;
} AllocateParameters;
static int vl_method_allocate_user_range(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
static const sd_json_dispatch_field dispatch_table[] = {
{ "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(AllocateParameters, name), SD_JSON_MANDATORY },
{ "size", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint32, offsetof(AllocateParameters, size), SD_JSON_MANDATORY },
{ "target", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, offsetof(AllocateParameters, target), 0 },
{ "userNamespaceFileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, offsetof(AllocateParameters, userns_fd_idx), SD_JSON_MANDATORY },
{ "mangleName", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(AllocateParameters, mangle_name), 0 },
{ "type", SD_JSON_VARIANT_STRING, dispatch_allocate_user_range_type, offsetof(AllocateParameters, type), 0 },
{ "delegateContainerRanges", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint32, offsetof(AllocateParameters, delegate_container_ranges), 0 },
{ "mapForeign", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_stdbool, offsetof(AllocateParameters, map_foreign), 0 },
{}
};
_cleanup_close_ int userns_fd = -EBADF, registry_dir_fd = -EBADF, lock_fd = -EBADF;
_cleanup_free_ char *userns_name = NULL;
Context *c = ASSERT_PTR(userdata);
uid_t peer_uid;
gid_t peer_gid;
struct stat userns_st;
AllocateParameters p = {
.type = ALLOCATE_USER_RANGE_MANAGED,
.size = UINT32_MAX,
.target = UID_INVALID,
.userns_fd_idx = UINT_MAX,
};
int r;
assert(link);
assert(parameters);
r = test_userns_api_support(link);
if (r != 0)
return r;
r = sd_varlink_dispatch(link, parameters, dispatch_table, &p);
if (r != 0)
return r;
if (p.type != ALLOCATE_USER_RANGE_SELF && p.target == UID_INVALID)
p.target = 0;
r = validate_name(link, p.name, p.mangle_name, &userns_name);
if (r != 0)
return r;
r = validate_target_and_size(link, p.target, p.size, p.type);
if (r != 0)
return r;
if (p.delegate_container_ranges > USER_NAMESPACE_DELEGATIONS_MAX)
return sd_varlink_error(link, "io.systemd.NamespaceResource.TooManyDelegations", NULL);
userns_fd = sd_varlink_peek_dup_fd(link, p.userns_fd_idx);
if (userns_fd < 0)
return log_debug_errno(userns_fd, "Failed to take user namespace fd from Varlink connection: %m");
r = validate_userns(link, userns_fd);
if (r != 0)
return r;
r = validate_userns_is_empty(link, userns_fd);
if (r != 0)
return r;
if (fstat(userns_fd, &userns_st) < 0)
return log_debug_errno(errno, "Failed to fstat() user namespace fd: %m");
_cleanup_close_ int parent_userns_fd = ioctl(userns_fd, NS_GET_PARENT);
if (parent_userns_fd < 0)
return log_debug_errno(errno, "Failed to get parent user namespace: %m");
struct stat parent_st = {};
if (DEBUG_LOGGING && fstat(parent_userns_fd, &parent_st) < 0)
return log_debug_errno(errno, "Failed to fstat() parent user namespace fd: %m");
r = sd_varlink_get_peer_uid(link, &peer_uid);
if (r < 0)
return r;
r = sd_varlink_get_peer_gid(link, &peer_gid);
if (r < 0)
return r;
log_debug("Processing AllocateUserRange request: name='%s' type=%s peer=" UID_FMT ":" GID_FMT
" userns=" INO_FMT " parent_userns=" INO_FMT
" size=%" PRIu32 " delegates=%" PRIu32 " mapForeign=%s",
userns_name,
p.type == ALLOCATE_USER_RANGE_SELF ? "self" : "managed",
peer_uid, peer_gid,
userns_st.st_ino, parent_st.st_ino,
p.size, p.delegate_container_ranges, yes_no(p.map_foreign));
const char *polkit_details[] = {
"name", userns_name,
NULL,
};
r = varlink_verify_polkit_async_full(
link,
/* bus= */ NULL,
"io.systemd.namespace-resource.allocate-user-namespace",
polkit_details,
/* good_user= */ UID_INVALID,
POLKIT_DEFAULT_ALLOW, /* If no polkit is installed, allow unpriv userns namespace allocation */
&c->polkit_registry,
/* ret_admin= */ NULL);
if (r <= 0)
return r;
if (!c->bpf) {
r = userns_restrict_install(/* pin= */ true, &c->bpf);
if (r < 0)
return r;
}
registry_dir_fd = userns_registry_open_fd();
if (registry_dir_fd < 0)
return registry_dir_fd;
_cleanup_(userns_info_freep) UserNamespaceInfo *userns_info = userns_info_new();
if (!userns_info)
return -ENOMEM;
userns_info->name = TAKE_PTR(userns_name);
if (!userns_info->name)
return -ENOMEM;
userns_info->owner = peer_uid;
userns_info->userns_inode = userns_st.st_ino;
if (ioctl(userns_fd, NS_GET_ID, &userns_info->userns_id) < 0)
log_debug_errno(errno, "Failed to query userns ID, ignoring: %m");
userns_info->size = p.size;
userns_info->target_uid = p.target;
userns_info->target_gid = (gid_t) p.target;
/* For "self" allocations we deny setgroups() via the BPF-LSM (see below). Record it so the manager
* can re-establish the denial after a restart that reset the BPF maps. */
userns_info->setgroups_deny = p.type == ALLOCATE_USER_RANGE_SELF;
if (p.type == ALLOCATE_USER_RANGE_SELF) {
/* The start UID/GID will be mapped to the parent userns in write_userns(). If a self
* mapping to the peer UID/GID is requested, we have to map the target UID/GID ourselves here
* as write_userns() doesn't take care of that. */
userns_info->start_uid = peer_uid;
userns_info->start_gid = peer_gid;
if (p.target == UID_INVALID) {
r = uid_range_translate_userns_fd(
parent_userns_fd,
UID_RANGE_USERNS_OUTSIDE,
peer_uid,
&userns_info->target_uid);
if (r < 0)
return log_debug_errno(r, "Failed to translate UID "UID_FMT" to parent user namespace: %m", peer_uid);
r = uid_range_translate_userns_fd(
parent_userns_fd,
GID_RANGE_USERNS_OUTSIDE,
peer_gid,
&userns_info->target_gid);
if (r < 0)
return log_debug_errno(r, "Failed to translate GID "GID_FMT" to parent user namespace: %m", peer_gid);
}
}
/* Set up delegation arrays if requested */
if (p.delegate_container_ranges > 0) {
userns_info->delegates = new0(DelegatedUserNamespaceInfo, p.delegate_container_ranges);
if (!userns_info->delegates)
return -ENOMEM;
FOREACH_ARRAY(delegate, userns_info->delegates, p.delegate_container_ranges) {
*delegate = DELEGATED_USER_NAMESPACE_INFO_NULL;
delegate->size = NSRESOURCE_UIDS_64K;
}
userns_info->n_delegates = p.delegate_container_ranges;
}
r = allocate_now(registry_dir_fd, userns_fd, parent_userns_fd, c->bpf, userns_info, &lock_fd);
if (r == -EHOSTDOWN) /* The needed UID range is not delegated to us */
return sd_varlink_error(link, "io.systemd.NamespaceResource.DynamicRangeUnavailable", NULL);
if (r == -EBUSY) /* All used up */
return sd_varlink_error(link, "io.systemd.NamespaceResource.NoDynamicRange", NULL);
if (r == -EDEADLK)
return sd_varlink_error(link, "io.systemd.NamespaceResource.UserNamespaceExists", NULL);
if (r == -EEXIST)
return sd_varlink_error(link, "io.systemd.NamespaceResource.NameExists", NULL);
if (r < 0)
return r;
r = userns_registry_store(registry_dir_fd, userns_info);
if (r < 0)
return r;
/* Subject the userns to the BPF-LSM policy that keeps its transient range off persistent file systems */
r = userns_restrict_register_by_fd(c->bpf, userns_fd);
if (r < 0)
goto fail;
if (userns_info->setgroups_deny > 0) {
/* For "self" allocations we deny setgroups() via the BPF LSM. We can't use
* /proc/self/setgroups for this as that is transitive and also applies to child user
* namespaces. The BPF LSM hook only applies to the specific user namespace. */
r = userns_restrict_setgroups_deny_by_fd(c->bpf, userns_fd);
if (r < 0)
goto fail;
}
r = write_userns(userns_fd, parent_userns_fd, userns_info, p.map_foreign);
if (r < 0)
goto fail;
lock_fd = safe_close(lock_fd);
/* Send user namespace and process fd to our manager process, which will watch the process and user namespace */
r = sd_pid_notifyf_with_fds(
/* pid= */ 0,
/* unset_environment= */ false,
&userns_fd, 1,
"FDSTORE=1\n"
"FDNAME=userns-" INO_FMT "\n", userns_info->userns_inode);
if (r < 0)
goto fail;
/* Note, we'll not return UID values from the host, since the child might not run in the same
* user namespace as us. If they want to know the ranges they should read them off the userns fd, so
* that they are translated into their PoV */
return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_STRING("name", userns_info->name));
fail:
/* Note: we don't have to clean-up the BPF maps in the error path: the bpf map type used will
* automatically do that once the userns inode goes away */
userns_registry_remove(registry_dir_fd, userns_info);
return r;
}
static int validate_userns_is_safe(sd_varlink *link, int userns_fd) {
int r;
assert(link);
assert(userns_fd >= 0);
/* Read the outside UID range and verify it isn't empty */
_cleanup_(uid_range_freep) UIDRange *outside_range = NULL;
r = uid_range_load_userns_by_fd(userns_fd, UID_RANGE_USERNS_OUTSIDE, &outside_range);
if (r < 0)
return log_debug_errno(r, "Failed to read userns UID range: %m");
if (uid_range_is_empty(outside_range))
return sd_varlink_error_invalid_parameter_name(link, "userNamespaceFileDescriptor");
/* Read the outside GID range and check it is the same as the UID range */
_cleanup_(uid_range_freep) UIDRange *outside_range_gid = NULL;
r = uid_range_load_userns_by_fd(userns_fd, GID_RANGE_USERNS_OUTSIDE, &outside_range_gid);
if (r < 0)
return log_debug_errno(r, "Failed to read userns GID range: %m");
if (!uid_range_equal(outside_range, outside_range_gid))
return sd_varlink_error_invalid_parameter_name(link, "userNamespaceFileDescriptor");
/* Read the inside UID range, and verify it matches the size of the outside UID range */
_cleanup_(uid_range_freep) UIDRange *inside_range = NULL;
r = uid_range_load_userns_by_fd(userns_fd, UID_RANGE_USERNS_INSIDE, &inside_range);
if (r < 0)
return log_debug_errno(r, "Failed to read userns contents: %m");
if (uid_range_size(outside_range) != uid_range_size(inside_range))
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Uh, inside and outside UID range sizes don't match.");
/* Read the inside GID range, and verify it matches the inside UID range */
_cleanup_(uid_range_freep) UIDRange *inside_range_gid = NULL;
r = uid_range_load_userns_by_fd(userns_fd, GID_RANGE_USERNS_INSIDE, &inside_range_gid);
if (r < 0)
return log_debug_errno(r, "Failed to read userns contents: %m");
if (!uid_range_equal(inside_range, inside_range_gid))
return sd_varlink_error_invalid_parameter_name(link, "userNamespaceFileDescriptor");
uid_t peer_uid;
r = sd_varlink_get_peer_uid(link, &peer_uid);
if (r < 0)
return r;
uid_t peer_gid;
r = sd_varlink_get_peer_gid(link, &peer_gid);
if (r < 0)
return r;
/* Insist that the first UID/GID in the range matches the client's UID/GID */
if (outside_range->entries[0].start != peer_uid ||
outside_range_gid->entries[0].start != peer_gid)
return sd_varlink_error_invalid_parameter_name(link, "userNamespaceFileDescriptor");
/* If there are more than one UID in the range, then also insist that the first UID maps to root inside the userns */
if (uid_range_size(outside_range) > 1 && inside_range->entries[0].start != 0)
return sd_varlink_error_invalid_parameter_name(link, "userNamespaceFileDescriptor");
return 0;
}
typedef struct RegisterParameters {
const char *name;
unsigned userns_fd_idx;
bool mangle_name;
} RegisterParameters;
static int vl_method_register_user_namespace(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
static const sd_json_dispatch_field dispatch_table[] = {
{ "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(RegisterParameters, name), SD_JSON_MANDATORY },
{ "userNamespaceFileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, offsetof(RegisterParameters, userns_fd_idx), SD_JSON_MANDATORY },
{ "mangleName", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(RegisterParameters, mangle_name), 0 },
{}
};
_cleanup_close_ int userns_fd = -EBADF, registry_dir_fd = -EBADF;
_cleanup_free_ char *userns_name = NULL;
Context *c = ASSERT_PTR(userdata);
uid_t peer_uid;
struct stat userns_st;
RegisterParameters p = {
.userns_fd_idx = UINT_MAX,
};
int r;
assert(link);
assert(parameters);
r = test_userns_api_support(link);
if (r != 0)
return r;
r = sd_varlink_dispatch(link, parameters, dispatch_table, &p);
if (r != 0)
return r;
r = validate_name(link, p.name, p.mangle_name, &userns_name);
if (r != 0)
return r;
userns_fd = sd_varlink_peek_dup_fd(link, p.userns_fd_idx);
if (userns_fd < 0)
return userns_fd;
r = validate_userns(link, userns_fd);
if (r != 0)
return r;
r = validate_userns_is_safe(link, userns_fd);
if (r != 0)
return r;
if (fstat(userns_fd, &userns_st) < 0)
return log_debug_errno(errno, "Failed to fstat() user namespace fd: %m");
r = sd_varlink_get_peer_uid(link, &peer_uid);
if (r < 0)
return r;
const char *polkit_details[] = {
"name", userns_name,
NULL,
};
r = varlink_verify_polkit_async_full(
link,
/* bus= */ NULL,
"io.systemd.namespace-resource.register-user-namespace",
polkit_details,
/* good_user= */ UID_INVALID,
POLKIT_DEFAULT_ALLOW, /* If no polkit is installed, allow unpriv userns namespace registration */
&c->polkit_registry,
/* ret_admin= */ NULL);
if (r <= 0)
return r;
if (!c->bpf) {
r = userns_restrict_install(/* pin= */ true, &c->bpf);
if (r < 0)
return r;
}
registry_dir_fd = userns_registry_open_fd();
if (registry_dir_fd < 0)
return registry_dir_fd;
_cleanup_close_ int lock_fd = -EBADF;
lock_fd = userns_registry_lock(registry_dir_fd);
if (lock_fd < 0)
return log_debug_errno(lock_fd, "Failed to open nsresource registry lock file: %m");
r = inode_slot_is_available(registry_dir_fd, userns_fd, c->bpf, userns_st.st_ino);
if (r < 0)
return r;
if (r == 0)
return sd_varlink_error(link, "io.systemd.NamespaceResource.UserNamespaceExists", NULL);
r = name_is_available(registry_dir_fd, userns_name);
if (r < 0)
return r;
if (r == 0)
return sd_varlink_error(link, "io.systemd.NamespaceResource.NameExists", NULL);
_cleanup_(userns_info_freep) UserNamespaceInfo *userns_info = userns_info_new();
if (!userns_info)
return -ENOMEM;
userns_info->name = TAKE_PTR(userns_name);
if (!userns_info->name)
return -ENOMEM;
userns_info->owner = peer_uid;
userns_info->userns_inode = userns_st.st_ino;
if (ioctl(userns_fd, NS_GET_ID, &userns_info->userns_id) < 0)
log_debug_errno(errno, "Failed to query userns ID, ignoring: %m");
r = userns_registry_store(registry_dir_fd, userns_info);
if (r < 0)
return log_debug_errno(r, "Failed to update userns registry: %m");
/* Subject the userns to the BPF-LSM policy that keeps its transient range off persistent file systems */
r = userns_restrict_register_by_fd(c->bpf, userns_fd);
if (r < 0)
goto fail;
/* Send user namespace and process fd to our manager process, which will watch the process and user namespace */
r = sd_pid_notifyf_with_fds(
/* pid= */ 0,
/* unset_environment= */ false,
&userns_fd, 1,
"FDSTORE=1\n"
"FDNAME=userns-" INO_FMT "\n", userns_info->userns_inode);
if (r < 0)
goto fail;
return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_STRING("name", userns_info->name));
fail:
userns_registry_remove(registry_dir_fd, userns_info);
return r;
}
typedef struct AddMountParameters {
unsigned userns_fd_idx;
unsigned mount_fd_idx;
} AddMountParameters;
static int vl_method_add_mount_to_user_namespace(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
static const sd_json_dispatch_field parameter_dispatch_table[] = {
{ "userNamespaceFileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, offsetof(AddMountParameters, userns_fd_idx), SD_JSON_MANDATORY },
{ "mountFileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, offsetof(AddMountParameters, mount_fd_idx), SD_JSON_MANDATORY },
{}
};
_cleanup_close_ int userns_fd = -EBADF, mount_fd = -EBADF, registry_dir_fd = -EBADF;
Context *c = ASSERT_PTR(userdata);
AddMountParameters p = {
.userns_fd_idx = UINT_MAX,
.mount_fd_idx = UINT_MAX,
};
int r, mnt_id = 0;
struct stat userns_st;
assert(link);
assert(parameters);
r = test_userns_api_support(link);
if (r != 0)
return r;
/* Allowlisting arbitrary mounts is a privileged operation */
r = varlink_check_privileged_peer(link);
if (r < 0)
return r;
r = sd_varlink_dispatch(link, parameters, parameter_dispatch_table, &p);
if (r != 0)
return r;
userns_fd = sd_varlink_peek_dup_fd(link, p.userns_fd_idx);
if (userns_fd < 0)
return userns_fd;
r = validate_userns(link, userns_fd);
if (r != 0)
return r;
if (fstat(userns_fd, &userns_st) < 0)
return -errno;
mount_fd = sd_varlink_peek_dup_fd(link, p.mount_fd_idx);
if (mount_fd < 0)
return mount_fd;
r = fd_verify_safe_flags_full(mount_fd, O_PATH|O_DIRECTORY);
if (r < 0)
return log_debug_errno(r, "Mount file descriptor has unsafe flags set: %m");
r = fd_verify_directory(mount_fd);
if (r < 0)
return r;
r = path_get_mnt_id_at(mount_fd, NULL, &mnt_id);
if (r < 0)
return r;
r = varlink_verify_polkit_async_full(
link,
/* bus= */ NULL,
"io.systemd.namespace-resource.delegate-mount",
/* details= */ NULL,
/* good_user= */ UID_INVALID,
POLKIT_DEFAULT_ALLOW, /* If no polkit is installed, allow delegation of mounts to registered userns */
&c->polkit_registry,
/* ret_admin= */ NULL);
if (r <= 0)
return r;
registry_dir_fd = userns_registry_open_fd();
if (registry_dir_fd < 0)
return registry_dir_fd;
_cleanup_close_ int lock_fd = -EBADF;
lock_fd = userns_registry_lock(registry_dir_fd);
if (lock_fd < 0)
return log_debug_errno(lock_fd, "Failed to open nsresource registry lock file: %m");
_cleanup_(userns_info_freep) UserNamespaceInfo *userns_info = NULL;
r = userns_registry_load_by_userns_inode(
registry_dir_fd,
userns_st.st_ino,
&userns_info);
if (r == -ENOENT)
return sd_varlink_error(link, "io.systemd.NamespaceResource.UserNamespaceNotRegistered", NULL);
if (r < 0)
return r;
r = userns_info_verify_fd(userns_fd, userns_info);
if (r == -ESTALE)
return sd_varlink_error(link, "io.systemd.NamespaceResource.UserNamespaceNotRegistered", NULL);
if (r < 0)
return log_debug_errno(r, "Failed to verify user namespace identity: %m");
if (!c->bpf) {
r = userns_restrict_install(/* pin= */ true, &c->bpf);
if (r < 0)
return r;
}
/* Pin the mount fd */
r = sd_pid_notifyf_with_fds(
/* pid= */ 0,
/* unset_environment= */ false,
&mount_fd, 1,
"FDSTORE=1\n"
"FDNAME=userns-" INO_FMT "\n", userns_st.st_ino);
if (r < 0)
return r;
/* There's no mount allowlist anymore: the BPF-LSM policy decides per operation whether the ID that
* would be recorded on disk is a transient one, which needs no per-mount knowledge. We keep
* accepting and validating the call for the sake of older clients, and keep the mount pinned as
* before, but there is nothing left to allowlist. */
if (DEBUG_LOGGING) {
if (userns_info->size > 0)
log_debug("Delegated mount %i to user namespace " INO_FMT " ('%s' @ UID " UID_FMT ")",
mnt_id, userns_st.st_ino, userns_info->name, userns_info->start_uid);
else
log_debug("Delegated mount %i to user namespace " INO_FMT " ('%s')",
mnt_id, userns_st.st_ino, userns_info->name);
}
return sd_varlink_replyb(link, SD_JSON_BUILD_EMPTY_OBJECT);
}
static int validate_cgroup(sd_varlink *link, int fd, uint64_t *ret_cgroup_id) {
int r;
assert(link);
assert(fd >= 0);
assert(ret_cgroup_id);
r = fd_verify_safe_flags_full(fd, O_DIRECTORY);
if (r < 0)
return log_debug_errno(r, "Control group file descriptor has unsafe flags set: %m");
r = fd_verify_directory(fd);
if (r < 0)
return log_debug_errno(r, "Verification that cgroup fd refers to directory failed: %m");
r = fd_is_fs_type(fd, CGROUP2_SUPER_MAGIC);
if (r < 0)
return log_debug_errno(r, "Failed to check if cgroup fd actually refers to cgroupfs: %m");
if (r == 0)
return sd_varlink_error_invalid_parameter_name(link, "controlGroupFileDescriptor");
r = fd_to_handle_u64(fd, ret_cgroup_id);
if (r < 0)
return log_debug_errno(r, "Failed to read cgroup ID from cgroupfs: %m");
return 0;
}
typedef struct AddCGroupParameters {
unsigned userns_fd_idx;
unsigned cgroup_fd_idx;
} AddCGroupParameters;
static int vl_method_add_cgroup_to_user_namespace(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
static const sd_json_dispatch_field parameter_dispatch_table[] = {
{ "userNamespaceFileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, offsetof(AddCGroupParameters, userns_fd_idx), SD_JSON_MANDATORY },
{ "controlGroupFileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, offsetof(AddCGroupParameters, cgroup_fd_idx), SD_JSON_MANDATORY },
{}
};
_cleanup_close_ int userns_fd = -EBADF, cgroup_fd = -EBADF, registry_dir_fd = -EBADF;
AddCGroupParameters p = {
.userns_fd_idx = UINT_MAX,
.cgroup_fd_idx = UINT_MAX,
};
_cleanup_(userns_info_freep) UserNamespaceInfo *userns_info = NULL;
Context *c = ASSERT_PTR(userdata);
struct stat userns_st, cgroup_st;
uid_t peer_uid;
int r;
assert(link);
assert(parameters);
r = test_userns_api_support(link);
if (r != 0)
return r;
r = sd_varlink_dispatch(link, parameters, parameter_dispatch_table, &p);
if (r != 0)
return r;
userns_fd = sd_varlink_peek_dup_fd(link, p.userns_fd_idx);
if (userns_fd < 0)
return log_debug_errno(userns_fd, "Failed to take user namespace fd from Varlink connection: %m");
r = validate_userns(link, userns_fd);
if (r != 0)
return r;
if (fstat(userns_fd, &userns_st) < 0)
return log_debug_errno(errno, "Failed to fstat() user namespace fd: %m");
cgroup_fd = sd_varlink_peek_dup_fd(link, p.cgroup_fd_idx);
if (cgroup_fd < 0)
return log_debug_errno(cgroup_fd, "Failed to take cgroup fd from Varlink connection: %m");
uint64_t cgroup_id;
r = validate_cgroup(link, cgroup_fd, &cgroup_id);
if (r != 0)
return r;
if (fstat(cgroup_fd, &cgroup_st) < 0)
return log_debug_errno(errno, "Failed to fstat() cgroup fd: %m");
r = varlink_verify_polkit_async_full(
link,
/* bus= */ NULL,
"io.systemd.namespace-resource.delegate-cgroup",
/* details= */ NULL,
/* good_user= */ UID_INVALID,
POLKIT_DEFAULT_ALLOW, /* If no polkit is installed, allow delegation of cgroups to registered userns */
&c->polkit_registry,
/* ret_admin= */ NULL);
if (r <= 0)
return r;
registry_dir_fd = userns_registry_open_fd();
if (registry_dir_fd < 0)
return registry_dir_fd;
_cleanup_close_ int lock_fd = -EBADF;
lock_fd = userns_registry_lock(registry_dir_fd);
if (lock_fd < 0)
return lock_fd;
r = userns_registry_load_by_userns_inode(
registry_dir_fd,
userns_st.st_ino,
&userns_info);
if (r == -ENOENT)
return sd_varlink_error(link, "io.systemd.NamespaceResource.UserNamespaceNotRegistered", NULL);
if (r < 0)
return r;
r = userns_info_verify_fd(userns_fd, userns_info);
if (r == -ESTALE)
return sd_varlink_error(link, "io.systemd.NamespaceResource.UserNamespaceNotRegistered", NULL);
if (r < 0)
return log_debug_errno(r, "Failed to verify user namespace identity: %m");
/* The user namespace must have a user assigned */
if (userns_info->size == 0)
return sd_varlink_error(link, "io.systemd.NamespaceResource.UserNamespaceWithoutUserRange", NULL);
if (userns_info_has_cgroup(userns_info, cgroup_id))
return sd_varlink_error(link, "io.systemd.NamespaceResource.ControlGroupAlreadyAdded", NULL);
if (userns_info->n_cgroups > USER_NAMESPACE_CGROUPS_DELEGATE_MAX)
return sd_varlink_error(link, "io.systemd.NamespaceResource.TooManyControlGroups", NULL);
/* Registering a cgroup for this client is only allowed for the root or the owner of a userns */
r = sd_varlink_get_peer_uid(link, &peer_uid);
if (r < 0)
return log_debug_errno(r, "Failed to get connection peer: %m");
if (peer_uid != 0) {
if (peer_uid != userns_info->owner)
return sd_varlink_error(link, SD_VARLINK_ERROR_PERMISSION_DENIED, NULL);
/* The cgroup must be owned by the owner of the userns */
if (cgroup_st.st_uid != userns_info->owner)
return sd_varlink_error(link, SD_VARLINK_ERROR_PERMISSION_DENIED, NULL);
}
r = userns_info_add_cgroup(userns_info, cgroup_id);
if (r < 0)
return r;
r = userns_registry_store(registry_dir_fd, userns_info);
if (r < 0)
return r;
if (fchown(cgroup_fd, userns_info->start_uid, userns_info->start_gid) < 0)
return log_debug_errno(errno, "Failed to change ownership of cgroup: %m");
if (fchmod(cgroup_fd, 0755) < 0)
return log_debug_errno(errno, "Failed to change access mode of cgroup: %m");
FOREACH_STRING(attr, "cgroup.procs", "cgroup.subtree_control", "cgroup.threads") {
(void) fchmodat(cgroup_fd, attr, 0644, AT_SYMLINK_NOFOLLOW);
(void) fchownat(cgroup_fd, attr, userns_info->start_uid, userns_info->start_gid, AT_SYMLINK_NOFOLLOW);
}
log_debug("Granting ownership to cgroup %" PRIu64 " to userns " INO_FMT " ('%s' @ UID " UID_FMT ")",
cgroup_id, userns_st.st_ino, userns_info->name, userns_info->start_uid);
return sd_varlink_replyb(link, SD_JSON_BUILD_EMPTY_OBJECT);
}
static uint64_t hash_ifname_id(UserNamespaceInfo *userns_info, const char *ifname) {
struct siphash state;
assert(userns_info);
siphash24_init(&state, (const uint8_t[]) { 0xc4, 0x6c, 0x96, 0xe8, 0xad, 0x37, 0x4d, 0x5f, 0xa1, 0xae, 0xfe, 0x70, 0x40, 0xed, 0x41, 0x5f });
siphash24_compress_string(userns_info->name, &state);
siphash24_compress_byte(0, &state); /* separator */
siphash24_compress_string(strempty(ifname), &state);
return siphash24_finalize(&state);
}
static void hash_ether_addr(UserNamespaceInfo *userns_info, const char *ifname, uint64_t n, struct ether_addr *ret) {
struct siphash state;
uint64_t h;
assert(userns_info);
assert(ret);
siphash24_init(&state, (const uint8_t[]) { 0x36, 0xaa, 0xd1, 0x69, 0xc7, 0xe5, 0x4c, 0xaa, 0x1e, 0xb2, 0x9e, 0xb3, 0x3a, 0x6b, 0xd4, 0x71 });
siphash24_compress_string(userns_info->name, &state);
siphash24_compress_byte(0, &state); /* separator */
siphash24_compress_string(strempty(ifname), &state);
siphash24_compress_byte(0, &state); /* separator */
n = htole64(n); /* add the 'index' to the mix in an endianness-independent fashion */
siphash24_compress_typesafe(n, &state);
h = htole64(siphash24_finalize(&state));
assert(sizeof(h) >= sizeof_field(struct ether_addr, ether_addr_octet));
memcpy(ret->ether_addr_octet, &h, sizeof_field(struct ether_addr, ether_addr_octet));
ether_addr_mark_random(ret);
}
static int create_veth(
int netns_fd,
const char *ifname_host,
const char *altifname_host,
struct ether_addr *mac_host,
const char *ifname_namespace,
struct ether_addr *mac_namespace) {
int r;
assert(netns_fd >= 0);
assert(ifname_host);
assert(mac_host);
assert(ifname_namespace);
assert(mac_namespace);
log_debug("Creating veth link on host %s (%s) with address %s to container as %s with address %s",
ifname_host, strna(altifname_host), ETHER_ADDR_TO_STR(mac_host),
ifname_namespace, ETHER_ADDR_TO_STR(mac_namespace));
_cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
r = sd_netlink_open(&rtnl);
if (r < 0)
return log_error_errno(r, "Failed to allocation netlink connection: %m");
_cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL;
r = sd_rtnl_message_new_link(rtnl, &m, RTM_NEWLINK, 0);
if (r < 0)
return log_error_errno(r, "Failed to allocate netlink message: %m");
r = sd_netlink_message_append_string(m, IFLA_IFNAME, ifname_host);
if (r < 0)
return log_error_errno(r, "Failed to add netlink interface name: %m");
r = sd_netlink_message_append_ether_addr(m, IFLA_ADDRESS, mac_host);
if (r < 0)
return log_error_errno(r, "Failed to add netlink MAC address: %m");
r = sd_netlink_message_open_container(m, IFLA_LINKINFO);
if (r < 0)
return log_error_errno(r, "Failed to open netlink container: %m");
r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, "veth");
if (r < 0)
return log_error_errno(r, "Failed to open netlink container: %m");
r = sd_netlink_message_open_container(m, VETH_INFO_PEER);
if (r < 0)
return log_error_errno(r, "Failed to open netlink container: %m");
r = sd_netlink_message_append_string(m, IFLA_IFNAME, ifname_namespace);
if (r < 0)
return log_error_errno(r, "Failed to add netlink interface name: %m");
r = sd_netlink_message_append_ether_addr(m, IFLA_ADDRESS, mac_namespace);
if (r < 0)
return log_error_errno(r, "Failed to add netlink MAC address: %m");
r = sd_netlink_message_append_u32(m, IFLA_NET_NS_FD, netns_fd);
if (r < 0)
return log_error_errno(r, "Failed to add netlink namespace field: %m");
r = sd_netlink_message_close_container(m);
if (r < 0)
return log_error_errno(r, "Failed to close netlink container: %m");
r = sd_netlink_message_close_container(m);
if (r < 0)
return log_error_errno(r, "Failed to close netlink container: %m");
r = sd_netlink_message_close_container(m);
if (r < 0)
return log_error_errno(r, "Failed to close netlink container: %m");
r = sd_netlink_call(rtnl, m, 0, NULL);
if (r < 0)
return log_error_errno(r, "Failed to add new veth interfaces (%s:%s): %m", ifname_host, ifname_namespace);
r = rtnl_set_link_alternative_names_by_ifname(&rtnl, ifname_host, STRV_MAKE(altifname_host));
if (r < 0)
log_warning_errno(r, "Failed to set alternative interface name to '%s', ignoring: %m", altifname_host);
return 0;
}
static int create_tap(
int userns_fd,
const char *ifname_host,
char *const *altifname_host,
struct ether_addr *mac_host) {
int r;
assert(ifname_host);
assert(mac_host);
if (!ifname_valid(ifname_host))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid interface name: %s", ifname_host);
log_debug("Creating tap link on host %s (%s) with address %s",
ifname_host, strna(altifname_host ? altifname_host[0] : NULL), ETHER_ADDR_TO_STR(mac_host));
_cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
if (altifname_host) {
r = sd_netlink_open(&rtnl);
if (r < 0)
return log_error_errno(r, "Failed to allocate netlink connection: %m");
}
uid_t uid;
r = userns_get_base_uid(userns_fd, &uid, /* ret_gid= */ NULL);
if (r < 0)
return log_error_errno(r, "Failed to get namespace base UID/GID: %m");
struct ifreq ifr = {
.ifr_flags = IFF_TAP | IFF_NO_PI | IFF_VNET_HDR,
};
assert(strlen(ifname_host) < sizeof(ifr.ifr_name));
strcpy(ifr.ifr_name, ifname_host);
_cleanup_close_ int fd = open("/dev/net/tun", O_RDWR|O_CLOEXEC);
if (fd < 0) {
if (errno == ENOENT) /* Turn ENOENT → EOPNOTSUPP */
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Network tap device node /dev/net/tun not found, cannot create network interface.");
return log_error_errno(errno, "Failed to open %s: %m", "/dev/net/tun");
}
if (ioctl(fd, TUNSETIFF, &ifr) < 0)
return log_error_errno(errno, "TUNSETIFF failed: %m");
if (ioctl(fd, TUNSETOWNER, uid) < 0)
return log_error_errno(errno, "TUNSETOWNER failed: %m");
if (!strv_isempty(altifname_host)) {
r = rtnl_set_link_alternative_names_by_ifname(&rtnl, ifname_host, altifname_host);
if (r < 0)
log_warning_errno(r, "Failed to set alternative interface names, ignoring: %m");
}
return TAKE_FD(fd);
}
static int validate_netns(sd_varlink *link, int userns_fd, int netns_fd) {
int r;
assert(link);
assert(userns_fd >= 0);
assert(netns_fd >= 0);
r = fd_verify_safe_flags(netns_fd);
if (r < 0)
return log_debug_errno(r, "Network namespace file descriptor has unsafe flags set: %m");
/* Validate this is actually a valid network namespace fd */
r = fd_is_namespace(netns_fd, NAMESPACE_NET);
if (r < 0)
return r;
if (r == 0)
return sd_varlink_error_invalid_parameter_name(link, "networkNamespaceFileDescriptor");
/* And refuse the thing if it is our own */
r = is_our_namespace(netns_fd, NAMESPACE_NET);
if (r < 0)
return r;
if (r > 0)
return sd_varlink_error_invalid_parameter_name(link, "networkNamespaceFileDescriptor");
/* Check if the netns actually belongs to the userns */
_cleanup_close_ int owner_userns_fd = -EBADF;
owner_userns_fd = ioctl(netns_fd, NS_GET_USERNS);
if (owner_userns_fd < 0)
return -errno;
r = inode_same_at(owner_userns_fd, /* filea= */ NULL, userns_fd, /* fileb= */ NULL, AT_EMPTY_PATH);
if (r < 0)
return r;
if (r == 0)
return sd_varlink_error_invalid_parameter_name(link, "networkNamespaceFileDescriptor");
uid_t peer_uid;
r = sd_varlink_get_peer_uid(link, &peer_uid);
if (r < 0)
return r;
if (peer_uid != 0) {
/* Refuse if the netns is not actually owned by our client. */
uid_t owner_uid;
if (ioctl(owner_userns_fd, NS_GET_OWNER_UID, &owner_uid) < 0)
return -errno;
if (owner_uid != peer_uid)
return sd_varlink_error_invalid_parameter_name(link, "networkNamespaceFileDescriptor");
}
return 0;
}
typedef struct AddNetworkParameters {
unsigned userns_fd_idx;
unsigned netns_fd_idx;
const char *ifname;
const char *mode;
} AddNetworkParameters;
static int vl_method_add_netif_to_user_namespace(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
static const sd_json_dispatch_field parameter_dispatch_table[] = {
{ "userNamespaceFileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, offsetof(AddNetworkParameters, userns_fd_idx), SD_JSON_MANDATORY },
{ "networkNamespaceFileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, offsetof(AddNetworkParameters, netns_fd_idx), 0 },
{ "namespaceInterfaceName", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(AddNetworkParameters, ifname), 0 },
{ "mode", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(AddNetworkParameters, mode), SD_JSON_MANDATORY },
{}
};
_cleanup_close_ int userns_fd = -EBADF, netns_fd = -EBADF, registry_dir_fd = -EBADF;
Context *c = ASSERT_PTR(userdata);
AddNetworkParameters p = {
.userns_fd_idx = UINT_MAX,
.netns_fd_idx = UINT_MAX,
};
int r;
assert(link);
assert(parameters);
r = test_userns_api_support(link);
if (r != 0)
return r;
r = sd_varlink_dispatch(link, parameters, parameter_dispatch_table, &p);
if (r != 0)
return r;
userns_fd = sd_varlink_peek_dup_fd(link, p.userns_fd_idx);
if (userns_fd < 0)
return userns_fd;
r = validate_userns(link, userns_fd);
if (r != 0)
return r;
struct stat userns_st;
if (fstat(userns_fd, &userns_st) < 0)
return -errno;
if (p.netns_fd_idx != UINT_MAX) {
netns_fd = sd_varlink_peek_dup_fd(link, p.netns_fd_idx);
if (netns_fd < 0)
return netns_fd;
r = validate_netns(link, userns_fd, netns_fd);
if (r != 0)
return r;
}
if (p.ifname && !ifname_valid(p.ifname))
return sd_varlink_error_invalid_parameter_name(link, "interfaceName");
if (streq(p.mode, "veth")) {
/* In veth mode we need a netns */
if (netns_fd < 0)
return sd_varlink_error_invalid_parameter_name(link, "networkNamespaceFileDescriptor");
} else if (streq(p.mode, "tap")) {
/* In tap mode we do want a netns, nor an interface name for it */
if (p.ifname)
return sd_varlink_error_invalid_parameter_name(link, "namespaceInterfaceName");
if (netns_fd >= 0)
return sd_varlink_error_invalid_parameter_name(link, "networkNamespaceFileDescriptor");
} else
return sd_varlink_error_invalid_parameter_name(link, "mode");
const char *polkit_details[] = {
"type", p.mode,
NULL,
};
r = varlink_verify_polkit_async_full(
link,
/* bus= */ NULL,
"io.systemd.namespace-resource.delegate-network-interface",
polkit_details,
/* good_user= */ UID_INVALID,
POLKIT_DEFAULT_ALLOW, /* If no polkit is installed, allow delegation of network interfaces to registered userns */
&c->polkit_registry,
/* ret_admin= */ NULL);
if (r <= 0)
return r;
registry_dir_fd = userns_registry_open_fd();
if (registry_dir_fd < 0)
return registry_dir_fd;
_cleanup_close_ int lock_fd = userns_registry_lock(registry_dir_fd);
if (lock_fd < 0)
return log_debug_errno(lock_fd, "Failed to open nsresource registry lock file: %m");
_cleanup_(userns_info_freep) UserNamespaceInfo *userns_info = NULL;
r = userns_registry_load_by_userns_inode(
registry_dir_fd,
userns_st.st_ino,
&userns_info);
if (r == -ENOENT)
return sd_varlink_error(link, "io.systemd.NamespaceResource.UserNamespaceNotRegistered", NULL);
if (r < 0)
return r;
r = userns_info_verify_fd(userns_fd, userns_info);
if (r == -ESTALE)
return sd_varlink_error(link, "io.systemd.NamespaceResource.UserNamespaceNotRegistered", NULL);
if (r < 0)
return log_debug_errno(r, "Failed to verify user namespace identity: %m");
if (strv_length(userns_info->netifs) > USER_NAMESPACE_NETIFS_DELEGATE_MAX)
return sd_varlink_error(link, "io.systemd.NamespaceResource.TooManyNetworkInterfaces", NULL);
/* Registering a network interface for this client is only allowed for the root or the owner of a userns */
uid_t peer_uid;
r = sd_varlink_get_peer_uid(link, &peer_uid);
if (r < 0)
return r;
if (peer_uid != 0 && peer_uid != userns_info->owner)
return sd_varlink_error(link, SD_VARLINK_ERROR_PERMISSION_DENIED, NULL);
_cleanup_free_ char *ifname_host = NULL, *altifname_host = NULL;
const char *ifname_namespace = p.ifname ?: "host0";
/* The short ifname is just too short to generate readable and unique names where unprivileged users
* can't take each others names. Hence just hash it. The alternative name however contains more useful
* information. */
if (asprintf(&ifname_host, "ns-%08" PRIx64, hash_ifname_id(userns_info, p.ifname)) < 0)
return -ENOMEM;
strshorten(ifname_host, IFNAMSIZ-1);
/* Register the interface in the userns store first, so that we can be sure it's properly 'owned' at
* any time, in case setup fails for some reason. Given we the interface name is hashed accidental
* collisions should be unlikely. */
r = userns_info_add_netif(userns_info, ifname_host);
if (r < 0)
return r;
r = userns_registry_store(registry_dir_fd, userns_info);
if (r < 0)
return r;
if (p.ifname)
r = asprintf(&altifname_host, "ns-" UID_FMT "-%s-%s", userns_info->owner, userns_info->name, p.ifname);
else
r = asprintf(&altifname_host, "ns-" UID_FMT "-%s", userns_info->owner, userns_info->name);
if (r < 0)
return -ENOMEM;
if (!ifname_valid_full(altifname_host, IFNAME_VALID_ALTERNATIVE))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Generated alternative interface name not valid: %s", altifname_host);
struct ether_addr ether_addr_host;
hash_ether_addr(userns_info, p.ifname, 0, &ether_addr_host);
if (streq(p.mode, "veth")) {
struct ether_addr ether_addr_namespace;
hash_ether_addr(userns_info, p.ifname, 1, &ether_addr_namespace);
r = create_veth(netns_fd,
ifname_host, altifname_host, &ether_addr_host,
ifname_namespace, &ether_addr_namespace);
if (r < 0)
return r;
log_debug("Added veth tunnel %s from host to userns " INO_FMT " ('%s' @ UID " UID_FMT ", interface %s).",
ifname_host, userns_st.st_ino, userns_info->name, userns_info->start_uid, ifname_namespace);
return sd_varlink_replybo(
link,
SD_JSON_BUILD_PAIR_STRING("hostInterfaceName", ifname_host),
SD_JSON_BUILD_PAIR_STRING("namespaceInterfaceName", ifname_namespace));
} else if (streq(p.mode, "tap")) {
/* NB: when we do the "tap" stuff we do not actually do any namespace operation here, neither
* netns nor userns. We use the userns only as conduit for user identity information and
* indication that the calling user has some control over the UID they want to assign the tap
* device to. */
_cleanup_close_ int tap_fd = create_tap(userns_fd, ifname_host, STRV_MAKE(altifname_host), &ether_addr_host);
if (tap_fd < 0)
return tap_fd;
log_debug("Added tap device %s from host to userns " INO_FMT " ('%s' @ UID " UID_FMT ").",
ifname_host, userns_st.st_ino, userns_info->name, userns_info->start_uid);
int fd_index = sd_varlink_push_fd(link, tap_fd);
if (fd_index < 0)
return log_error_errno(fd_index, "Failed to push tap fd into varlink socket: %m");
TAKE_FD(tap_fd);
return sd_varlink_replybo(
link,
SD_JSON_BUILD_PAIR_STRING("hostInterfaceName", ifname_host),
SD_JSON_BUILD_PAIR_INTEGER("interfaceFileDescriptor", fd_index));
} else
assert_not_reached();
}
static int process_connection(sd_varlink_server *server, int _fd) {
_cleanup_close_ int fd = TAKE_FD(_fd); /* always take possession */
_cleanup_(sd_varlink_close_unrefp) sd_varlink *vl = NULL;
_cleanup_(sd_event_unrefp) sd_event *event = NULL;
int r;
assert(server);
assert(fd >= 0);
r = sd_event_new(&event);
if (r < 0)
return r;
r = sd_varlink_server_attach_event(server, event, /* priority= */ 0);
if (r < 0)
return log_error_errno(r, "Failed to attach Varlink server to event loop: %m");
r = sd_varlink_server_add_connection(server, fd, &vl);
if (r < 0)
return log_error_errno(r, "Failed to add connection: %m");
TAKE_FD(fd);
vl = sd_varlink_ref(vl);
r = sd_event_loop(event);
if (r < 0)
return log_error_errno(r, "Failed to run event loop: %m");
r = sd_varlink_server_detach_event(server);
if (r < 0)
return log_error_errno(r, "Failed to detach Varlink server from event loop: %m");
return 0;
}
static void context_free(Context *c) {
assert(c);
c->polkit_registry = hashmap_free(c->polkit_registry);
c->bpf = userns_restrict_bpf_free(c->bpf);
}
static int run(int argc, char *argv[]) {
usec_t start_time, listen_idle_usec, last_busy_usec = USEC_INFINITY;
_cleanup_(sd_varlink_server_unrefp) sd_varlink_server *server = NULL;
_cleanup_(context_free) Context c = {};
_cleanup_(pidref_done) PidRef parent = PIDREF_NULL;
unsigned n_iterations = 0;
int m, listen_fd, r;
log_setup();
m = sd_listen_fds(false);
if (m < 0)
return log_error_errno(m, "Failed to determine number of listening fds: %m");
if (m == 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No socket to listen on received.");
if (m > 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Worker can only listen on a single socket at a time.");
listen_fd = SD_LISTEN_FDS_START;
r = fd_nonblock(listen_fd, false);
if (r < 0)
return log_error_errno(r, "Failed to turn off non-blocking mode for listening socket: %m");
r = varlink_server_new(
&server,
SD_VARLINK_SERVER_INHERIT_USERDATA|
SD_VARLINK_SERVER_ALLOW_FD_PASSING_INPUT|SD_VARLINK_SERVER_ALLOW_FD_PASSING_OUTPUT,
&c);
if (r < 0)
return log_error_errno(r, "Failed to allocate varlink server: %m");
r = sd_varlink_server_add_interface_many(
server,
&vl_interface_io_systemd_NamespaceResource,
&vl_interface_io_systemd_UserDatabase);
if (r < 0)
return log_error_errno(r, "Failed to add UserDatabase and NamespaceResource interface to varlink server: %m");
r = sd_varlink_server_bind_method_many(
server,
"io.systemd.NamespaceResource.AllocateUserRange", vl_method_allocate_user_range,
"io.systemd.NamespaceResource.RegisterUserNamespace", vl_method_register_user_namespace,
"io.systemd.NamespaceResource.AddMountToUserNamespace", vl_method_add_mount_to_user_namespace,
"io.systemd.NamespaceResource.AddControlGroupToUserNamespace", vl_method_add_cgroup_to_user_namespace,
"io.systemd.NamespaceResource.AddNetworkToUserNamespace", vl_method_add_netif_to_user_namespace,
"io.systemd.UserDatabase.GetUserRecord", vl_method_get_user_record,
"io.systemd.UserDatabase.GetGroupRecord", vl_method_get_group_record,
"io.systemd.UserDatabase.GetMemberships", vl_method_get_memberships);
if (r < 0)
return log_error_errno(r, "Failed to bind methods: %m");
r = sd_varlink_server_set_exit_on_idle(server, true);
if (r < 0)
return log_error_errno(r, "Failed to enable exit-on-idle mode: %m");
r = getenv_bool("NSRESOURCE_FIXED_WORKER");
if (r < 0)
return log_error_errno(r, "Failed to parse NSRESOURCE_FIXED_WORKER: %m");
listen_idle_usec = r ? USEC_INFINITY : LISTEN_IDLE_USEC;
r = pidref_set_parent(&parent);
if (r < 0)
return log_error_errno(r, "Failed to acquire pidfd of parent process: %m");
start_time = now(CLOCK_MONOTONIC);
for (;;) {
_cleanup_close_ int fd = -EBADF;
usec_t n;
/* Exit the worker in regular intervals, to flush out all memory use */
if (n_iterations++ > ITERATIONS_MAX) {
log_debug("Exiting worker, processed %u iterations, that's enough.", n_iterations);
break;
}
n = now(CLOCK_MONOTONIC);
if (n >= usec_add(start_time, RUNTIME_MAX_USEC)) {
log_debug("Exiting worker, ran for %s, that's enough.",
FORMAT_TIMESPAN(usec_sub_unsigned(n, start_time), 0));
break;
}
if (last_busy_usec == USEC_INFINITY)
last_busy_usec = n;
else if (listen_idle_usec != USEC_INFINITY && n >= usec_add(last_busy_usec, listen_idle_usec)) {
log_debug("Exiting worker, been idle for %s.",
FORMAT_TIMESPAN(usec_sub_unsigned(n, last_busy_usec), 0));
break;
}
(void) rename_process("systemd-nsresourcework: waiting...");
fd = RET_NERRNO(accept4(listen_fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC));
(void) rename_process("systemd-nsresourcework: processing...");
if (fd == -EAGAIN)
continue; /* The listening socket has SO_RECVTIMEO set, hence a timeout is expected
* after a while, let's check if it's time to exit though. */
if (fd == -EINTR)
continue; /* Might be that somebody attached via strace, let's just continue in that
* case */
if (fd < 0)
return log_error_errno(fd, "Failed to accept() from listening socket: %m");
if (now(CLOCK_MONOTONIC) <= usec_add(n, PRESSURE_SLEEP_TIME_USEC)) {
/* We only slept a very short time? If so, let's see if there are more sockets
* pending, and if so, let's ask our parent for more workers */
r = fd_wait_for_event(listen_fd, POLLIN, 0);
if (r < 0)
return log_error_errno(r, "Failed to test for POLLIN on listening socket: %m");
if (FLAGS_SET(r, POLLIN)) {
r = pidref_kill(&parent, SIGUSR2);
if (r == -ESRCH)
return log_error_errno(r, "Parent already died?");
if (r < 0)
return log_error_errno(r, "Failed to send SIGUSR2 signal to parent: %m");
}
}
(void) process_connection(server, TAKE_FD(fd));
last_busy_usec = USEC_INFINITY;
}
return 0;
}
DEFINE_MAIN_FUNCTION(run);