#include <sys/eventfd.h>
#include <sys/stat.h>
#include <sys/wait.h>
+#include <utmpx.h>
#include "sd-daemon.h"
#include "sd-netlink.h"
return 0;
}
-static int validate_name(sd_varlink *link, const char *name, char **ret) {
- _cleanup_free_ char *un = NULL;
+static char* random_name(void) {
+ char *s = NULL;
+
+ /* Make up a random name for this userns. Make sure the random 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));
+
+ if (asprintf(&s, "%016" PRIx64, random_u64()) < 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);
if (r < 0)
return r;
+ _cleanup_free_ char *un = NULL;
if (peer_uid == 0) {
- if (!userns_name_is_valid(name))
- return sd_varlink_error_invalid_parameter_name(link, "name");
-
- un = strdup(name);
- if (!un)
- return -ENOMEM;
+ /* 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 random name */
+ un = random_name();
+ if (!un)
+ return -ENOMEM;
+ }
+ }
} else {
- /* The the client is not root then prefix the name with the UID of the peer, so that they
- * live in separate namespaces and cannot steal each other's names. */
+ /* 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))
- return sd_varlink_error_invalid_parameter_name(link, "name");
+ 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 = random_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);
unsigned size;
unsigned target;
unsigned userns_fd_idx;
+ bool mangle_name;
} AllocateParameters;
static int vl_method_allocate_user_range(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
{ "size", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, offsetof(AllocateParameters, size), SD_JSON_MANDATORY },
{ "target", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, 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 },
{}
};
if (r != 0)
return r;
- r = validate_name(link, p.name, &userns_name);
+ r = validate_name(link, p.name, p.mangle_name, &userns_name);
if (r != 0)
return r;
/* 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_replyb(link, SD_JSON_BUILD_EMPTY_OBJECT);
+ return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_STRING("name", userns_name));
fail:
/* Note: we don't have to clean-up the BPF maps in the error path: the bpf map type used will
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(AllocateParameters, mangle_name), 0 },
{}
};
if (r != 0)
return r;
- r = validate_name(link, p.name, &userns_name);
+ r = validate_name(link, p.name, p.mangle_name, &userns_name);
if (r != 0)
return r;
if (r < 0)
goto fail;
- return sd_varlink_replyb(link, SD_JSON_BUILD_EMPTY_OBJECT);
+ return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_STRING("name", userns_name));
fail:
userns_registry_remove(registry_dir_fd, userns_info);
/* So the namespace name should be 16 chars at max (because we want that it is usable in usernames,
* which have a limit of 31 chars effectively, and the nsresourced service wants to prefix/suffix
* some bits). But it also should be unique if we are called multiple times in a row. Hence we take
- * the "comm" name (which is 15 chars), and suffix it with the UID, possibly overriding the end. */
+ * the "comm" name (which is 15 chars), and suffix it with the PID, possibly overriding the end. */
assert_cc(TASK_COMM_LEN == 15 + 1);
char spid[DECIMAL_STR_MAX(pid_t)];
&reply,
&error_id,
SD_JSON_BUILD_PAIR("name", SD_JSON_BUILD_STRING(name)),
+ SD_JSON_BUILD_PAIR("mangleName", SD_JSON_BUILD_BOOLEAN(true)),
SD_JSON_BUILD_PAIR("size", SD_JSON_BUILD_UNSIGNED(size)),
SD_JSON_BUILD_PAIR("userNamespaceFileDescriptor", SD_JSON_BUILD_UNSIGNED(userns_fd_idx)));
if (r < 0)
&reply,
&error_id,
SD_JSON_BUILD_PAIR("name", SD_JSON_BUILD_STRING(name)),
+ SD_JSON_BUILD_PAIR("mangleName", SD_JSON_BUILD_BOOLEAN(true)),
SD_JSON_BUILD_PAIR("userNamespaceFileDescriptor", SD_JSON_BUILD_UNSIGNED(userns_fd_idx)));
if (r < 0)
return log_debug_errno(r, "Failed to call RegisterUserNamespace() varlink call: %m");
static SD_VARLINK_DEFINE_METHOD(
AllocateUserRange,
+ SD_VARLINK_FIELD_COMMENT("The name for the user namespace, a short string that must be fit to be included in a file name and in a user name"),
SD_VARLINK_DEFINE_INPUT(name, SD_VARLINK_STRING, 0),
+ SD_VARLINK_FIELD_COMMENT("Controls whether to mangle the provided name if needed so that it is suitable for naming a user namespace. If true this will shorten the name as necessary or randomize it if that's not sufficient. If null defaults to false."),
+ SD_VARLINK_DEFINE_INPUT(mangleName, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
+ SD_VARLINK_FIELD_COMMENT("The number of UIDs to assign. Must be 1 or 65536"),
SD_VARLINK_DEFINE_INPUT(size, SD_VARLINK_INT, 0),
+ SD_VARLINK_FIELD_COMMENT("The target UID inside the user namespace"),
SD_VARLINK_DEFINE_INPUT(target, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
- SD_VARLINK_DEFINE_INPUT(userNamespaceFileDescriptor, SD_VARLINK_INT, 0));
+ SD_VARLINK_FIELD_COMMENT("A file descriptor to an allocated userns with no current UID range assignments"),
+ SD_VARLINK_DEFINE_INPUT(userNamespaceFileDescriptor, SD_VARLINK_INT, 0),
+ SD_VARLINK_FIELD_COMMENT("The name assigned to the user namespace"),
+ SD_VARLINK_DEFINE_OUTPUT(name, SD_VARLINK_STRING, SD_VARLINK_NULLABLE));
static SD_VARLINK_DEFINE_METHOD(
RegisterUserNamespace,
+ SD_VARLINK_FIELD_COMMENT("The name for the user namespce, a short string that must be fit to be included in a file name and in a user name"),
SD_VARLINK_DEFINE_INPUT(name, SD_VARLINK_STRING, 0),
- SD_VARLINK_DEFINE_INPUT(userNamespaceFileDescriptor, SD_VARLINK_INT, 0));
+ SD_VARLINK_FIELD_COMMENT("Controls whether to mangle the provided name if needed so that it is suitable for naming a user namespace. If true this will shorten the name as necessary or randomize it if that's not sufficient. If null defaults to false."),
+ SD_VARLINK_DEFINE_INPUT(mangleName, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
+ SD_VARLINK_FIELD_COMMENT("A user namespace file descriptor that is fully initialized"),
+ SD_VARLINK_DEFINE_INPUT(userNamespaceFileDescriptor, SD_VARLINK_INT, 0),
+ SD_VARLINK_FIELD_COMMENT("The name assigned to the user namespace"),
+ SD_VARLINK_DEFINE_OUTPUT(name, SD_VARLINK_STRING, SD_VARLINK_NULLABLE));
static SD_VARLINK_DEFINE_METHOD(
AddMountToUserNamespace,