return sd_json_buildo(
ret,
SD_JSON_BUILD_PAIR("userName", SD_JSON_BUILD_STRING(name)),
- SD_JSON_BUILD_PAIR("uid", SD_JSON_BUILD_UNSIGNED(userns_info->start + offset)),
+ SD_JSON_BUILD_PAIR("uid", SD_JSON_BUILD_UNSIGNED(userns_info->start_uid + offset)),
SD_JSON_BUILD_PAIR("gid", SD_JSON_BUILD_UNSIGNED(GID_NOBODY)),
SD_JSON_BUILD_PAIR("realName", SD_JSON_BUILD_STRING(realname)),
SD_JSON_BUILD_PAIR("homeDirectory", JSON_BUILD_CONST_STRING("/")),
if (offset >= userns_info->size) /* Outside of range? */
goto not_found;
- if (uid_is_valid(p.uid) && p.uid != userns_info->start + offset)
+ 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)) {
return sd_json_buildo(
ret,
SD_JSON_BUILD_PAIR("groupName", SD_JSON_BUILD_STRING(name)),
- SD_JSON_BUILD_PAIR("gid", SD_JSON_BUILD_UNSIGNED(userns_info->start + offset)),
+ SD_JSON_BUILD_PAIR("gid", SD_JSON_BUILD_UNSIGNED(userns_info->start_gid + offset)),
SD_JSON_BUILD_PAIR("description", SD_JSON_BUILD_STRING(description)),
SD_JSON_BUILD_PAIR("service", JSON_BUILD_CONST_STRING("io.systemd.NamespaceResource")),
SD_JSON_BUILD_PAIR("disposition", SD_JSON_BUILD_STRING(user_disposition_to_string(disposition))));
if (offset >= userns_info->size) /* Outside of range? */
goto not_found;
- if (gid_is_valid(p.gid) && p.gid != userns_info->start + offset)
+ 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)) {
start = p.gid & gidmask;
offset = p.gid - start;
- r = userns_registry_load_by_start_uid(
+ r = userns_registry_load_by_start_gid(
/* registry_fd= */ -EBADF,
- (uid_t) start,
+ start,
&userns_info);
if (r == -ENOENT)
goto not_found;
if (r > 0)
return false;
+ r = userns_registry_gid_exists(registry_dir_fd, (gid_t) candidate);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ return false;
+
r = userdb_by_uid(candidate, /* match= */ NULL, USERDB_AVOID_MULTIPLEXER, /* ret_record= */ NULL);
if (r >= 0)
return false;
if (r < 0)
return log_debug_errno(r, "Can't determine if UID range " UID_FMT " is available: %m", candidate);
if (r > 0) {
- info->start = candidate;
+ info->start_uid = candidate;
+ info->start_gid = (gid_t) candidate;
log_debug("Allocating UID range " UID_FMT "…" UID_FMT, candidate, candidate + info->size - 1);
assert(usernsfd >= 0);
assert(userns_info);
- assert(uid_is_valid(userns_info->target));
- assert(uid_is_valid(userns_info->start));
+ 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);
+ 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)
if (asprintf(&pmap, "/proc/" PID_FMT "/uid_map", pid) < 0)
return log_oom();
- r = write_string_filef(pmap, 0, UID_FMT " " UID_FMT " " UID_FMT "\n", userns_info->target, userns_info->start, userns_info->size);
+ r = write_string_filef(pmap, 0, UID_FMT " " UID_FMT " %" PRIu32 "\n", userns_info->target_uid, userns_info->start_uid, userns_info->size);
if (r < 0)
return log_error_errno(r, "Failed to write 'uid_map' file of user namespace: %m");
if (asprintf(&pmap, "/proc/" PID_FMT "/gid_map", pid) < 0)
return log_oom();
- r = write_string_filef(pmap, 0, GID_FMT " " GID_FMT " " GID_FMT "\n", (gid_t) userns_info->target, (gid_t) userns_info->start, (gid_t) userns_info->size);
+ r = write_string_filef(pmap, 0, GID_FMT " " GID_FMT " %" PRIu32 "\n", userns_info->target_gid, userns_info->start_gid, userns_info->size);
if (r < 0)
return log_error_errno(r, "Failed to write 'gid_map' file of user namespace: %m");
userns_info->owner = peer_uid;
userns_info->userns_inode = userns_st.st_ino;
userns_info->size = p.size;
- userns_info->target = p.target;
+ userns_info->target_uid = p.target;
+ userns_info->target_gid = (gid_t) p.target;
r = allocate_now(registry_dir_fd, userns_info, &lock_fd);
if (r == -EHOSTDOWN) /* The needed UID range is not delegated to us */
if (r < 0)
return r;
- if (userns_info->size > 0)
- log_debug("Granting access to mount %i to user namespace " INO_FMT " ('%s' @ UID " UID_FMT ")",
- mnt_id, userns_st.st_ino, userns_info->name, userns_info->start);
- else
- log_debug("Granting access to mount %i to user namespace " INO_FMT " ('%s')",
- mnt_id, userns_st.st_ino, userns_info->name);
+ if (DEBUG_LOGGING) {
+ if (userns_info->size > 0)
+ log_debug("Granting access to 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("Granting access to 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);
}
if (r < 0)
return r;
- if (fchown(cgroup_fd, userns_info->start, userns_info->start) < 0)
+ 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)
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, userns_info->start, 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);
+ cgroup_id, userns_st.st_ino, userns_info->name, userns_info->start_uid);
return sd_varlink_replyb(link, SD_JSON_BUILD_EMPTY_OBJECT);
}
return r;
log_debug("Adding 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, ifname_namespace);
+ ifname_host, userns_st.st_ino, userns_info->name, userns_info->start_uid, ifname_namespace);
return sd_varlink_replybo(
link,
*info = (UserNamespaceInfo) {
.owner = UID_INVALID,
- .start = UID_INVALID,
- .target = UID_INVALID,
+ .start_uid = UID_INVALID,
+ .target_uid = UID_INVALID,
+ .start_gid = GID_INVALID,
+ .target_gid = GID_INVALID,
};
return info;
static int userns_registry_load(int dir_fd, const char *fn, UserNamespaceInfo **ret) {
static const sd_json_dispatch_field dispatch_table[] = {
- { "owner", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uid_gid, offsetof(UserNamespaceInfo, owner), SD_JSON_MANDATORY },
- { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(UserNamespaceInfo, name), SD_JSON_MANDATORY },
- { "userns", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint64, offsetof(UserNamespaceInfo, userns_inode), SD_JSON_MANDATORY },
- { "start", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uid_gid, offsetof(UserNamespaceInfo, start), 0 },
- { "size", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint32, offsetof(UserNamespaceInfo, size), 0 },
- { "target", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uid_gid, offsetof(UserNamespaceInfo, target), 0 },
- { "cgroups", SD_JSON_VARIANT_ARRAY, dispatch_cgroups_array, 0, 0 },
+ { "owner", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uid_gid, offsetof(UserNamespaceInfo, owner), SD_JSON_MANDATORY },
+ { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(UserNamespaceInfo, name), SD_JSON_MANDATORY },
+ { "userns", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint64, offsetof(UserNamespaceInfo, userns_inode), SD_JSON_MANDATORY },
+ { "size", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uint32, offsetof(UserNamespaceInfo, size), 0 },
+ { "start", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uid_gid, offsetof(UserNamespaceInfo, start_uid), 0 },
+ { "target", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uid_gid, offsetof(UserNamespaceInfo, target_uid), 0 },
+ { "startGid", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uid_gid, offsetof(UserNamespaceInfo, start_gid), 0 },
+ { "targetGid", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uid_gid, offsetof(UserNamespaceInfo, target_gid), 0 },
+ { "cgroups", SD_JSON_VARIANT_ARRAY, dispatch_cgroups_array, 0, 0 },
{}
};
if (userns_info->userns_inode == 0)
return -EBADMSG;
- if (userns_info->start == 0)
+ if (userns_info->start_uid == 0 || userns_info->start_gid == 0)
return -EBADMSG;
+
if (userns_info->size == 0) {
- if (uid_is_valid(userns_info->start) || uid_is_valid(userns_info->target))
+ if (uid_is_valid(userns_info->start_uid) || uid_is_valid(userns_info->target_uid))
+ return -EBADMSG;
+
+ if (gid_is_valid(userns_info->start_gid) || gid_is_valid(userns_info->target_gid))
return -EBADMSG;
} else {
- if (!uid_is_valid(userns_info->start) || !uid_is_valid(userns_info->target))
+ if (!uid_is_valid(userns_info->start_uid) || !uid_is_valid(userns_info->target_uid))
return -EBADMSG;
- if (userns_info->size > UINT32_MAX - userns_info->start ||
- userns_info->size > UINT32_MAX - userns_info->target)
+ if (userns_info->size > UINT32_MAX - userns_info->start_uid ||
+ userns_info->size > UINT32_MAX - userns_info->target_uid)
+ return -EBADMSG;
+
+ /* Older versions of the registry didn't maintain UID/GID separately, hence copy over if not
+ * set */
+ if (!gid_is_valid(userns_info->start_gid))
+ userns_info->start_gid = userns_info->start_uid;
+ if (!gid_is_valid(userns_info->target_gid))
+ userns_info->target_gid = userns_info->target_gid;
+
+ if (userns_info->size > UINT32_MAX - userns_info->start_gid ||
+ userns_info->size > UINT32_MAX - userns_info->target_gid)
return -EBADMSG;
}
return true;
}
+int userns_registry_gid_exists(int dir_fd, gid_t start) {
+ _cleanup_free_ char *fn = NULL;
+
+ assert(dir_fd >= 0);
+
+ if (!gid_is_valid(start))
+ return -ENOENT;
+
+ if (start == 0)
+ return true;
+
+ if (asprintf(&fn, "g" GID_FMT ".userns", start) < 0)
+ return -ENOMEM;
+
+ if (faccessat(dir_fd, fn, F_OK, AT_SYMLINK_NOFOLLOW) < 0)
+ return errno == ENOENT ? false : -errno;
+
+ return true;
+}
+
int userns_registry_name_exists(int dir_fd, const char *name) {
_cleanup_free_ char *fn = NULL;
if (r < 0)
return r;
- if (userns_info->start != start)
+ if (userns_info->start_uid != start)
+ return -EBADMSG;
+
+ if (ret)
+ *ret = TAKE_PTR(userns_info);
+
+ return 0;
+}
+
+int userns_registry_load_by_start_gid(int dir_fd, gid_t start, UserNamespaceInfo **ret) {
+ _cleanup_(userns_info_freep) UserNamespaceInfo *userns_info = NULL;
+ _cleanup_close_ int registry_fd = -EBADF;
+ _cleanup_free_ char *fn = NULL;
+ int r;
+
+ if (!gid_is_valid(start))
+ return -ENOENT;
+
+ if (dir_fd < 0) {
+ registry_fd = userns_registry_open_fd();
+ if (registry_fd < 0)
+ return registry_fd;
+
+ dir_fd = registry_fd;
+ }
+
+ if (asprintf(&fn, "g" GID_FMT ".userns", start) < 0)
+ return -ENOMEM;
+
+ r = userns_registry_load(dir_fd, fn, &userns_info);
+ if (r < 0)
+ return r;
+
+ if (userns_info->start_gid != start)
return -EBADMSG;
if (ret)
SD_JSON_BUILD_PAIR("owner", SD_JSON_BUILD_UNSIGNED(info->owner)),
SD_JSON_BUILD_PAIR("name", SD_JSON_BUILD_STRING(info->name)),
SD_JSON_BUILD_PAIR("userns", SD_JSON_BUILD_UNSIGNED(info->userns_inode)),
- SD_JSON_BUILD_PAIR_CONDITION(uid_is_valid(info->start), "start", SD_JSON_BUILD_UNSIGNED(info->start)),
- SD_JSON_BUILD_PAIR_CONDITION(uid_is_valid(info->start), "size", SD_JSON_BUILD_UNSIGNED(info->size)),
- SD_JSON_BUILD_PAIR_CONDITION(uid_is_valid(info->start), "target", SD_JSON_BUILD_UNSIGNED(info->target)),
+ SD_JSON_BUILD_PAIR_CONDITION(info->size > 0, "size", SD_JSON_BUILD_UNSIGNED(info->size)),
+ SD_JSON_BUILD_PAIR_CONDITION(uid_is_valid(info->start_uid), "start", SD_JSON_BUILD_UNSIGNED(info->start_uid)),
+ SD_JSON_BUILD_PAIR_CONDITION(uid_is_valid(info->target_uid), "target", SD_JSON_BUILD_UNSIGNED(info->target_uid)),
+ SD_JSON_BUILD_PAIR_CONDITION(gid_is_valid(info->start_gid), "startGid", SD_JSON_BUILD_UNSIGNED(info->start_gid)),
+ SD_JSON_BUILD_PAIR_CONDITION(gid_is_valid(info->target_gid), "targetGid", SD_JSON_BUILD_UNSIGNED(info->target_gid)),
SD_JSON_BUILD_PAIR_CONDITION(!!cgroup_array, "cgroups", SD_JSON_BUILD_VARIANT(cgroup_array)));
if (r < 0)
return r;
if (r < 0)
return log_debug_errno(r, "Failed to format userns JSON object: %m");
- _cleanup_free_ char *reg_fn = NULL, *link1_fn = NULL, *link2_fn = NULL, *owner_fn = NULL, *uid_fn = NULL;
+ _cleanup_free_ char *reg_fn = NULL, *link1_fn = NULL, *link2_fn = NULL, *link3_fn = NULL, *owner_fn = NULL, *uid_fn = NULL;
if (asprintf(®_fn, "i%" PRIu64 ".userns", info->userns_inode) < 0)
return log_oom_debug();
goto fail;
}
- if (uid_is_valid(info->start)) {
- if (asprintf(&link2_fn, "u" UID_FMT ".userns", info->start) < 0) {
+ if (uid_is_valid(info->start_uid)) {
+ if (asprintf(&link2_fn, "u" UID_FMT ".userns", info->start_uid) < 0) {
r = log_oom_debug();
goto fail;
}
}
}
+ if (gid_is_valid(info->start_gid)) {
+ if (asprintf(&link3_fn, "g" GID_FMT ".userns", info->start_gid) < 0) {
+ r = log_oom_debug();
+ goto fail;
+ }
+
+ r = linkat_replace(dir_fd, reg_fn, dir_fd, link3_fn);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to link userns data to '%s' in registry: %m", link3_fn);
+ goto fail;
+ }
+ }
+
if (asprintf(&uid_fn, "o" UID_FMT ".owns", info->owner) < 0) {
r = log_oom_debug();
goto fail;
(void) unlinkat(dir_fd, link1_fn, /* flags= */ 0);
if (link2_fn)
(void) unlinkat(dir_fd, link2_fn, /* flags= */ 0);
+ if (link3_fn)
+ (void) unlinkat(dir_fd, link3_fn, /* flags= */ 0);
if (owner_fn)
(void) unlinkat(dir_fd, owner_fn, /* flags= */ 0);
if (uid_fn)
RET_GATHER(ret, RET_NERRNO(unlinkat(dir_fd, link1_fn, 0)));
- if (uid_is_valid(info->start)) {
+ if (uid_is_valid(info->start_uid)) {
_cleanup_free_ char *link2_fn = NULL;
- if (asprintf(&link2_fn, "u" UID_FMT ".userns", info->start) < 0)
+ if (asprintf(&link2_fn, "u" UID_FMT ".userns", info->start_uid) < 0)
return log_oom_debug();
RET_GATHER(ret, RET_NERRNO(unlinkat(dir_fd, link2_fn, 0)));
}
+ if (uid_is_valid(info->start_gid)) {
+ _cleanup_free_ char *link3_fn = NULL;
+
+ if (asprintf(&link3_fn, "g" GID_FMT ".userns", info->start_gid) < 0)
+ return log_oom_debug();
+
+ RET_GATHER(ret, RET_NERRNO(unlinkat(dir_fd, link3_fn, 0)));
+ }
+
_cleanup_free_ char *uid_fn = NULL;
if (asprintf(&uid_fn, "o" UID_FMT ".owns", info->owner) < 0)
return log_oom_debug();