diff options
-rw-r--r-- | src/home/homework-directory.c | 50 | ||||
-rw-r--r-- | src/home/homework.c | 36 | ||||
-rw-r--r-- | src/home/homework.h | 1 |
3 files changed, 87 insertions, 0 deletions
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); |