From f509ed4b61d414e031459f190e33d32a1e40d80d Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 20 Oct 2021 16:14:27 +0200 Subject: [PATCH 1/6] homed: move HOME_UID_{MIN,MAX} into a header we can reuse in homework.c --- src/home/home-util.h | 4 ++++ src/home/homed-manager.h | 3 --- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/home/home-util.h b/src/home/home-util.h index fba1c7d8f1..f7bf637dd2 100644 --- a/src/home/home-util.h +++ b/src/home/home-util.h @@ -8,6 +8,10 @@ #include "time-util.h" #include "user-record.h" +/* See https://systemd.io/UIDS-GIDS for details how this range fits into the rest of the world */ +#define HOME_UID_MIN 60001 +#define HOME_UID_MAX 60513 + bool suitable_user_name(const char *name); int suitable_realm(const char *realm); int suitable_image_path(const char *path); diff --git a/src/home/homed-manager.h b/src/home/homed-manager.h index 851b302f59..f5659636c3 100644 --- a/src/home/homed-manager.h +++ b/src/home/homed-manager.h @@ -13,9 +13,6 @@ typedef struct Manager Manager; #include "homed-home.h" #include "varlink.h" -#define HOME_UID_MIN 60001 -#define HOME_UID_MAX 60513 - struct Manager { sd_event *event; sd_bus *bus; From e1aeaf27f5d537283e6cacd1bb5d2d9fa0860dab Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 20 Oct 2021 21:45:51 +0200 Subject: [PATCH 2/6] homework: port home_create_directory_or_subvolume() to use HomeSetup Let's migrate home_create_directory_or_subvolume() to also use HomeSetup for storing its runtime objects we'd like to destroy in case of failure. In the beginning this is just the root_fd, but later on we can add more. No change in behaviour, just shifting things around. --- src/home/homework-directory.c | 12 ++++++------ src/home/homework-directory.h | 2 +- src/home/homework.c | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/home/homework-directory.c b/src/home/homework-directory.c index 8f2f512b1b..876dbd1baa 100644 --- a/src/home/homework-directory.c +++ b/src/home/homework-directory.c @@ -80,16 +80,16 @@ int home_activate_directory( return 0; } -int home_create_directory_or_subvolume(UserRecord *h, UserRecord **ret_home) { +int home_create_directory_or_subvolume(UserRecord *h, HomeSetup *setup, UserRecord **ret_home) { _cleanup_(rm_rf_subvolume_and_freep) char *temporary = NULL; _cleanup_(user_record_unrefp) UserRecord *new_home = NULL; - _cleanup_close_ int root_fd = -1; _cleanup_free_ char *d = NULL; const char *ip; int r; assert(h); assert(IN_SET(user_record_storage(h), USER_DIRECTORY, USER_SUBVOLUME)); + assert(setup); assert(ret_home); assert_se(ip = user_record_image_path(h)); @@ -149,15 +149,15 @@ int home_create_directory_or_subvolume(UserRecord *h, UserRecord **ret_home) { temporary = TAKE_PTR(d); /* Needs to be destroyed now */ - root_fd = open(temporary, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW); - if (root_fd < 0) + setup->root_fd = open(temporary, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW); + if (setup->root_fd < 0) return log_error_errno(errno, "Failed to open temporary home directory: %m"); - r = home_populate(h, root_fd); + r = home_populate(h, setup->root_fd); if (r < 0) return r; - r = home_sync_and_statfs(root_fd, NULL); + r = home_sync_and_statfs(setup->root_fd, NULL); if (r < 0) return r; diff --git a/src/home/homework-directory.h b/src/home/homework-directory.h index 98b1804774..92cc755546 100644 --- a/src/home/homework-directory.h +++ b/src/home/homework-directory.h @@ -6,5 +6,5 @@ int home_setup_directory(UserRecord *h, HomeSetup *setup); int home_activate_directory(UserRecord *h, HomeSetup *setup, PasswordCache *cache, UserRecord **ret_home); -int home_create_directory_or_subvolume(UserRecord *h, UserRecord **ret_home); +int home_create_directory_or_subvolume(UserRecord *h, HomeSetup *setup, UserRecord **ret_home); int home_resize_directory(UserRecord *h, HomeSetupFlags flags, PasswordCache *cache, HomeSetup *setup, UserRecord **ret_home); diff --git a/src/home/homework.c b/src/home/homework.c index f0ef2def7c..e739ba9334 100644 --- a/src/home/homework.c +++ b/src/home/homework.c @@ -1232,7 +1232,7 @@ static int home_create(UserRecord *h, UserRecord **ret_home) { case USER_DIRECTORY: case USER_SUBVOLUME: - r = home_create_directory_or_subvolume(h, &new_home); + r = home_create_directory_or_subvolume(h, &setup, &new_home); break; case USER_FSCRYPT: From cfef46f5fe22a63bff3c2e070925341a5b8583b9 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 20 Oct 2021 22:03:30 +0200 Subject: [PATCH 3/6] homework: when activating a directory, include info about it in resulting record For the other backends we synthesize a "binding" section in the json record of the user that stores meta info how a user record is "bound" to the local host. It declares storage info and such. Let's do the same for the directory/subvolume backends. --- src/home/homework-directory.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/home/homework-directory.c b/src/home/homework-directory.c index 876dbd1baa..bd68b405c1 100644 --- a/src/home/homework-directory.c +++ b/src/home/homework-directory.c @@ -53,6 +53,10 @@ int home_activate_directory( if (r < 0) return r; + r = home_extend_embedded_identity(new_home, h, setup); + if (r < 0) + return r; + setup->root_fd = safe_close(setup->root_fd); /* Create mount point to mount over if necessary */ From 2ba38f78c3048f9c117b45111791fef4c603455e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 20 Oct 2021 22:07:57 +0200 Subject: [PATCH 4/6] homework: rework directory backend to set up mounts in /run/systemd/user-home-mount before moving them to /home This does what we already do for the LUKS backend: instead of mounting the source directory directly to the final home dir, we instead bind mount it to /run/systemd/user-home-mount (where /run/ is unshared and specific to our own mount namespace), then adjust its mount flags and then bind mount it in a single atomic operation into the final destination, fully set up. This doesn't improve much on its own, but it makes things a tiny bit more correct: this way MS_NODEV/MS_NOEXEC/MS_NOSUID will already be applied when the bind mount appears in the host mount namespace, instead of being adjusted after the fact. Doing things this way also makes things work more like the LUKS backend, reducing surprises. Most importantly it's preparation for doing uidmapping for directory homes, added in a later commit. --- src/home/homework-directory.c | 53 +++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/src/home/homework-directory.c b/src/home/homework-directory.c index bd68b405c1..0e7197405d 100644 --- a/src/home/homework-directory.c +++ b/src/home/homework-directory.c @@ -5,6 +5,7 @@ #include "btrfs-util.h" #include "fd-util.h" #include "homework-directory.h" +#include "homework-mount.h" #include "homework-quota.h" #include "mkdir.h" #include "mount-util.h" @@ -14,10 +15,38 @@ #include "umask-util.h" int home_setup_directory(UserRecord *h, HomeSetup *setup) { + const char *ip; + int r; + assert(h); assert(setup); + assert(setup->root_fd < 0); - setup->root_fd = open(user_record_image_path(h), O_RDONLY|O_CLOEXEC|O_DIRECTORY); + /* We'll bind mount the image directory to a new mount point where we'll start adjusting it. Only + * once that's complete we'll move the thing to its final place eventually. */ + r = home_unshare_and_mkdir(); + if (r < 0) + return r; + + assert_se(ip = user_record_image_path(h)); + + r = mount_follow_verbose(LOG_ERR, ip, HOME_RUNTIME_WORK_DIR, NULL, MS_BIND, NULL); + if (r < 0) + return r; + + setup->undo_mount = true; + + /* Turn off any form of propagation for this */ + r = mount_nofollow_verbose(LOG_ERR, NULL, HOME_RUNTIME_WORK_DIR, NULL, MS_PRIVATE, NULL); + if (r < 0) + return r; + + /* Adjust MS_SUID and similar flags */ + r = mount_nofollow_verbose(LOG_ERR, NULL, HOME_RUNTIME_WORK_DIR, NULL, MS_BIND|MS_REMOUNT|user_record_mount_flags(h), NULL); + if (r < 0) + return r; + + setup->root_fd = open(HOME_RUNTIME_WORK_DIR, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW); if (setup->root_fd < 0) return log_error_errno(errno, "Failed to open home directory: %m"); @@ -31,7 +60,7 @@ int home_activate_directory( UserRecord **ret_home) { _cleanup_(user_record_unrefp) UserRecord *new_home = NULL, *header_home = NULL; - const char *hdo, *hd, *ipo, *ip; + const char *hd, *hdo; int r; assert(h); @@ -39,9 +68,6 @@ int home_activate_directory( assert(setup); assert(ret_home); - assert_se(ipo = user_record_image_path(h)); - ip = strdupa_safe(ipo); /* copy out, since reconciliation might cause changing of the field */ - assert_se(hdo = user_record_home_directory(h)); hd = strdupa_safe(hdo); @@ -57,24 +83,15 @@ int home_activate_directory( if (r < 0) return r; + /* Close fd to private mount before moving mount */ setup->root_fd = safe_close(setup->root_fd); - /* Create mount point to mount over if necessary */ - if (!path_equal(ip, hd)) - (void) mkdir_p(hd, 0700); - - /* Create a mount point (even if the directory is already placed correctly), as a way to indicate - * this mount point is now "activated". Moreover, we want to set per-user - * MS_NOSUID/MS_NOEXEC/MS_NODEV. */ - r = mount_nofollow_verbose(LOG_ERR, ip, hd, NULL, MS_BIND, NULL); + /* We are now done with everything, move the mount into place */ + r = home_move_mount(NULL, hd); if (r < 0) return r; - r = mount_nofollow_verbose(LOG_ERR, NULL, hd, NULL, MS_BIND|MS_REMOUNT|user_record_mount_flags(h), NULL); - if (r < 0) { - (void) umount_verbose(LOG_ERR, hd, UMOUNT_NOFOLLOW); - return r; - } + setup->undo_mount = false; setup->do_drop_caches = false; From c7bf079bbc19e3b409acc0c7acc3e14749211fe2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 20 Oct 2021 22:12:16 +0200 Subject: [PATCH 5/6] homework: add new helper call that can shift home dir UID/GID ranges This new helper is not used yet, but it's useful for apply UID/GID shifts so that the underlying home dir can use an arbitrary UID (for example "nobody") and we'll still make it appear as owned by the target UID. This operates roughly like this: 1. The relevant underlying UID is mapped to the target UID 2. Everything in the homed UID range except for the target UID is left unmapped (and thus will appear as "nobody") 3. Everything in the 16bit UID range outside of the homed UID range/target UID/nobody user is mapped to itself 4. Everything else is left unmapped (in particular everything outside of the 16 bit range). Why do it like this? The 2nd rule done to ensure that any files from homed's managed UID range that do not match the user's own UID will be shown as "unmapped" basically. Of course, IRL this should never happen, except if people managed to manipulate the underlying fs directly. The 3rd rule is to allow that if devs untar an OS image it more or less just works as before: 16bit UIDs outside of the homed range will be mapped onto themselves: you can untar things and tar it back up and things will just work. --- src/home/homework-mount.c | 144 ++++++++++++++++++++++++++++++++++++++ src/home/homework-mount.h | 1 + 2 files changed, 145 insertions(+) diff --git a/src/home/homework-mount.c b/src/home/homework-mount.c index 47345eb839..7ab503fb7a 100644 --- a/src/home/homework-mount.c +++ b/src/home/homework-mount.c @@ -2,14 +2,22 @@ #include #include +#include #include "alloc-util.h" +#include "fd-util.h" +#include "format-util.h" +#include "home-util.h" #include "homework-mount.h" #include "homework.h" +#include "missing_mount.h" +#include "missing_syscall.h" #include "mkdir.h" #include "mount-util.h" +#include "namespace-util.h" #include "path-util.h" #include "string-util.h" +#include "user-util.h" static const char *mount_options_for_fstype(const char *fstype) { if (streq(fstype, "ext4")) @@ -110,3 +118,139 @@ int home_move_mount(const char *user_name_and_realm, const char *target) { log_info("Moving to final mount point %s completed.", target); return 0; } + +static int append_identity_range(char **text, uid_t start, uid_t next_start, uid_t exclude) { + /* Creates an identity range ranging from 'start' to 'next_start-1'. Excludes the UID specified by 'exclude' if + * it is in that range. */ + + assert(text); + + if (next_start <= start) /* Empty range? */ + return 0; + + if (exclude < start || exclude >= next_start) /* UID to exclude it outside of the range? */ + return strextendf(text, UID_FMT " " UID_FMT " " UID_FMT "\n", start, start, next_start - start); + + if (start == exclude && next_start == exclude + 1) /* The only UID in the range is the one to exclude? */ + return 0; + + if (exclude == start) /* UID to exclude at beginning of range? */ + return strextendf(text, UID_FMT " " UID_FMT " " UID_FMT "\n", start+1, start+1, next_start - start - 1); + + if (exclude == next_start - 1) /* UID to exclude at end of range? */ + return strextendf(text, UID_FMT " " UID_FMT " " UID_FMT "\n", start, start, next_start - start - 1); + + return strextendf(text, + UID_FMT " " UID_FMT " " UID_FMT "\n" + UID_FMT " " UID_FMT " " UID_FMT "\n", + start, start, exclude - start, + exclude + 1, exclude + 1, next_start - exclude - 1); +} + +static int make_userns(uid_t stored_uid, uid_t exposed_uid) { + _cleanup_free_ char *text = NULL; + _cleanup_close_ int userns_fd = -1; + int r; + + assert(uid_is_valid(stored_uid)); + assert(uid_is_valid(exposed_uid)); + + assert_cc(HOME_UID_MIN <= HOME_UID_MAX); + assert_cc(HOME_UID_MAX < UID_NOBODY); + + /* Map everything below the homed UID range to itself (except for the UID we actually care about if + * it is inside this range) */ + r = append_identity_range(&text, 0, HOME_UID_MIN, stored_uid); + if (r < 0) + return log_oom(); + + /* Now map the UID we are doing this for to the target UID. */ + r = strextendf(&text, UID_FMT " " UID_FMT " " UID_FMT "\n", stored_uid, exposed_uid, 1); + if (r < 0) + return log_oom(); + + /* Map everything above the homed UID range to itself (again, excluding the UID we actually care + * about if it is in that range). Also we leave "nobody" itself excluded) */ + r = append_identity_range(&text, HOME_UID_MAX, UID_NOBODY, stored_uid); + if (r < 0) + return log_oom(); + + /* Leave everything else unmapped, starting from UID_NOBODY itself. Specifically, this means the + * whole space outside of 16bit remains unmapped */ + + log_debug("Creating userns with mapping:\n%s", text); + + userns_fd = userns_acquire(text, text); /* same uid + gid mapping */ + if (userns_fd < 0) + return log_error_errno(userns_fd, "Failed to allocate user namespace: %m"); + + return TAKE_FD(userns_fd); +} + +int home_shift_uid(int dir_fd, const char *target, uid_t stored_uid, uid_t exposed_uid, int *ret_mount_fd) { + _cleanup_close_ int mount_fd = -1, userns_fd = -1; + int r; + + assert(dir_fd >= 0); + assert(uid_is_valid(stored_uid)); + assert(uid_is_valid(exposed_uid)); + + /* Let's try to set up a UID mapping for this directory. This is called when first creating a home + * directory or when activating it again. We do this as optimization only, to avoid having to + * recursively chown() things on each activation. If the kernel or file system doesn't support this + * scheme we'll handle this gracefully, and not do anything, so that the later recursive chown()ing + * then fixes up things for us. Note that the chown()ing is smart enough to skip things if they look + * alright already. + * + * Note that this always creates a new mount (i.e. we use OPEN_TREE_CLONE), since applying idmaps is + * not allowed once the mount is put in place. */ + + mount_fd = open_tree(dir_fd, "", AT_EMPTY_PATH | OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + if (mount_fd < 0) { + if (ERRNO_IS_NOT_SUPPORTED(errno)) { + log_debug_errno(errno, "The open_tree() syscall is not supported, not setting up UID shift mount: %m"); + + if (ret_mount_fd) + *ret_mount_fd = -1; + + return 0; + } + + return log_error_errno(errno, "Failed to open tree of home directory: %m"); + } + + userns_fd = make_userns(stored_uid, exposed_uid); + if (userns_fd < 0) + return userns_fd; + + /* Set the user namespace mapping attribute on the cloned mount point */ + if (mount_setattr(mount_fd, "", AT_EMPTY_PATH, + &(struct mount_attr) { + .attr_set = MOUNT_ATTR_IDMAP, + .userns_fd = userns_fd, + }, MOUNT_ATTR_SIZE_VER0) < 0) { + + if (ERRNO_IS_NOT_SUPPORTED(errno) || errno == EINVAL) { /* EINVAL is documented in mount_attr() as fs doesn't support idmapping */ + log_debug_errno(errno, "UID/GID mapping for shifted mount not available, not setting it up: %m"); + + if (ret_mount_fd) + *ret_mount_fd = -1; + + return 0; + } + + return log_error_errno(errno, "Failed to apply UID/GID mapping: %m"); + } + + if (target) + r = move_mount(mount_fd, "", AT_FDCWD, target, MOVE_MOUNT_F_EMPTY_PATH); + else + r = move_mount(mount_fd, "", dir_fd, "", MOVE_MOUNT_F_EMPTY_PATH|MOVE_MOUNT_T_EMPTY_PATH); + if (r < 0) + return log_error_errno(errno, "Failed to apply UID/GID map: %m"); + + if (ret_mount_fd) + *ret_mount_fd = TAKE_FD(mount_fd); + + return 1; +} diff --git a/src/home/homework-mount.h b/src/home/homework-mount.h index 893ecdc586..c9190ae722 100644 --- a/src/home/homework-mount.h +++ b/src/home/homework-mount.h @@ -7,3 +7,4 @@ int home_mount_node(const char *node, const char *fstype, bool discard, unsigned int home_unshare_and_mkdir(void); int home_unshare_and_mount(const char *node, const char *fstype, bool discard, unsigned long flags); int home_move_mount(const char *user_name_and_realm, const char *target); +int home_shift_uid(int dir_fd, const char *target, uid_t stored_uid, uid_t exposed_uid, int *ret_mount_fd); From cf5115f6e531c65bbb5b78c86fe94be577617d33 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 20 Oct 2021 22:18:12 +0200 Subject: [PATCH 6/6] homework: support uidmapping in the "directory" backend --- src/home/homework-directory.c | 50 +++++++++++++++++++++++++++++++++++ src/home/homework.c | 36 +++++++++++++++++++++++++ src/home/homework.h | 1 + 3 files changed, 87 insertions(+) diff --git a/src/home/homework-directory.c b/src/home/homework-directory.c index 0e7197405d..6557571361 100644 --- a/src/home/homework-directory.c +++ b/src/home/homework-directory.c @@ -13,13 +13,16 @@ #include "rm-rf.h" #include "tmpfile-util.h" #include "umask-util.h" +#include "user-util.h" int home_setup_directory(UserRecord *h, HomeSetup *setup) { const char *ip; int r; assert(h); + assert(IN_SET(user_record_storage(h), USER_DIRECTORY, USER_SUBVOLUME)); assert(setup); + assert(!setup->undo_mount); assert(setup->root_fd < 0); /* We'll bind mount the image directory to a new mount point where we'll start adjusting it. Only @@ -104,7 +107,9 @@ int home_activate_directory( int home_create_directory_or_subvolume(UserRecord *h, HomeSetup *setup, UserRecord **ret_home) { _cleanup_(rm_rf_subvolume_and_freep) char *temporary = NULL; _cleanup_(user_record_unrefp) UserRecord *new_home = NULL; + _cleanup_close_ int mount_fd = -1; _cleanup_free_ char *d = NULL; + bool is_subvolume = false; const char *ip; int r; @@ -129,6 +134,7 @@ int home_create_directory_or_subvolume(UserRecord *h, HomeSetup *setup, UserReco if (r >= 0) { log_info("Subvolume created."); + is_subvolume = true; if (h->disk_size != UINT64_MAX) { @@ -170,10 +176,39 @@ int home_create_directory_or_subvolume(UserRecord *h, HomeSetup *setup, UserReco temporary = TAKE_PTR(d); /* Needs to be destroyed now */ + /* Let's decouple namespaces now, so that we can possibly mount a UID map mount into + * /run/systemd/user-home-mount/ that noone will see but us. */ + r = home_unshare_and_mkdir(); + if (r < 0) + return r; + setup->root_fd = open(temporary, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW); if (setup->root_fd < 0) return log_error_errno(errno, "Failed to open temporary home directory: %m"); + /* Try to apply a UID shift, so that the directory is actually owned by "nobody", and is only mapped + * to the proper UID while active. — Well, that's at least the theory. Unfortunately, only btrfs does + * per-subvolume quota. The others do per-uid quota. Which means mapping all home directories to the + * same UID of "nobody" makes quota impossible. Hence unless we actually managed to create a btrfs + * subvolume for this user we'll map the user's UID to itself. Now you might ask: why bother mapping + * at all? It's because we want to restrict the UIDs used on the home directory: we leave all other + * UIDs of the homed UID range unmapped, thus making them unavailable to programs accessing the + * mount. */ + r = home_shift_uid(setup->root_fd, HOME_RUNTIME_WORK_DIR, is_subvolume ? UID_NOBODY : h->uid, h->uid, &mount_fd); + if (r > 0) + setup->undo_mount = true; /* If uidmaps worked we have a mount to undo again */ + + if (mount_fd >= 0) { + /* If we have established a new mount, then we can use that as new root fd to our home directory. */ + safe_close(setup->root_fd); + + setup->root_fd = fd_reopen(mount_fd, O_RDONLY|O_CLOEXEC|O_DIRECTORY); + if (setup->root_fd < 0) + return log_error_errno(setup->root_fd, "Unable to convert mount fd into proper directory fd: %m"); + + mount_fd = safe_close(mount_fd); + } + r = home_populate(h, setup->root_fd); if (r < 0) return r; @@ -203,6 +238,17 @@ int home_create_directory_or_subvolume(UserRecord *h, HomeSetup *setup, UserReco if (r < 0) return log_error_errno(r, "Failed to add binding to record: %m"); + setup->root_fd = safe_close(setup->root_fd); + + /* Unmount mapped mount before we move the dir into place */ + if (setup->undo_mount) { + r = umount_verbose(LOG_ERR, HOME_RUNTIME_WORK_DIR, UMOUNT_NOFOLLOW); + if (r < 0) + return r; + + setup->undo_mount = false; + } + if (rename(temporary, ip) < 0) return log_error_errno(errno, "Failed to rename %s to %s: %m", temporary, ip); @@ -237,6 +283,10 @@ int home_resize_directory( if (r < 0) return r; + r = home_maybe_shift_uid(h, setup); + if (r < 0) + return r; + r = home_update_quota_auto(h, NULL); if (ERRNO_IS_NOT_SUPPORTED(r)) return -ESOCKTNOSUPPORT; /* make recognizable */ diff --git a/src/home/homework.c b/src/home/homework.c index e739ba9334..cfc0c945de 100644 --- a/src/home/homework.c +++ b/src/home/homework.c @@ -716,6 +716,38 @@ static int chown_recursive_directory(int root_fd, uid_t uid) { return 0; } +int home_maybe_shift_uid( + UserRecord *h, + HomeSetup *setup) { + + _cleanup_close_ int mount_fd = -1; + struct stat st; + + assert(h); + assert(setup); + assert(setup->root_fd >= 0); + + if (fstat(setup->root_fd, &st) < 0) + return log_error_errno(errno, "Failed to stat() home directory: %m"); + + /* Let's shift UIDs of this mount. Hopefully this makes the later chowning unnecessary. (Note that we + * also prefer to do UID mapping even if the UID already matches our goal UID. That's because we want + * to leave UIDs in the homed managed range unmapped.) */ + (void) home_shift_uid(setup->root_fd, NULL, st.st_uid, h->uid, &mount_fd); + + /* If this worked, then we'll have a reference to the mount now, which we can also use like an O_PATH + * fd to the new dir. Let's convert it into a proper O_DIRECTORY fd. */ + if (mount_fd >= 0) { + safe_close(setup->root_fd); + + setup->root_fd = fd_reopen(mount_fd, O_RDONLY|O_CLOEXEC|O_DIRECTORY); + if (setup->root_fd < 0) + return log_error_errno(setup->root_fd, "Failed to convert mount fd into regular directory fd: %m"); + } + + return 0; +} + int home_refresh( UserRecord *h, HomeSetup *setup, @@ -738,6 +770,10 @@ int home_refresh( if (r < 0) return r; + r = home_maybe_shift_uid(h, setup); + if (r < 0) + return r; + r = home_store_header_identity_luks(new_home, setup, header_home); if (r < 0) return r; diff --git a/src/home/homework.h b/src/home/homework.h index 7bd31b5cea..1fa5a1e37a 100644 --- a/src/home/homework.h +++ b/src/home/homework.h @@ -74,6 +74,7 @@ int home_setup(UserRecord *h, HomeSetupFlags flags, PasswordCache *cache, HomeSe int home_refresh(UserRecord *h, HomeSetup *setup, UserRecord *header_home, PasswordCache *cache, struct statfs *ret_statfs, UserRecord **ret_new_home); +int home_maybe_shift_uid(UserRecord *h, HomeSetup *setup); int home_populate(UserRecord *h, int dir_fd); int home_load_embedded_identity(UserRecord *h, int root_fd, UserRecord *header_home, UserReconcileMode mode, PasswordCache *cache, UserRecord **ret_embedded_home, UserRecord **ret_new_home);