]>
git.ipfire.org Git - thirdparty/systemd.git/blob - src/home/homework-mount.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
7 #include "alloc-util.h"
9 #include "format-util.h"
10 #include "home-util.h"
11 #include "homework-mount.h"
13 #include "missing_mount.h"
14 #include "missing_syscall.h"
16 #include "mount-util.h"
17 #include "namespace-util.h"
18 #include "path-util.h"
19 #include "string-util.h"
20 #include "user-util.h"
22 static const char *mount_options_for_fstype(const char *fstype
) {
23 if (streq(fstype
, "ext4"))
24 return "noquota,user_xattr";
25 if (streq(fstype
, "xfs"))
27 if (streq(fstype
, "btrfs"))
32 int home_mount_node(const char *node
, const char *fstype
, bool discard
, unsigned long flags
) {
33 _cleanup_free_
char *joined
= NULL
;
34 const char *options
, *discard_option
;
40 options
= mount_options_for_fstype(fstype
);
42 discard_option
= discard
? "discard" : "nodiscard";
45 joined
= strjoin(options
, ",", discard_option
);
51 options
= discard_option
;
53 r
= mount_nofollow_verbose(LOG_ERR
, node
, HOME_RUNTIME_WORK_DIR
, fstype
, flags
|MS_RELATIME
, strempty(options
));
57 log_info("Mounting file system completed.");
61 int home_unshare_and_mkdir(void) {
64 if (unshare(CLONE_NEWNS
) < 0)
65 return log_error_errno(errno
, "Couldn't unshare file system namespace: %m");
67 assert(path_startswith(HOME_RUNTIME_WORK_DIR
, "/run"));
69 r
= mount_nofollow_verbose(LOG_ERR
, "/run", "/run", NULL
, MS_SLAVE
|MS_REC
, NULL
); /* Mark /run as MS_SLAVE in our new namespace */
73 (void) mkdir_p(HOME_RUNTIME_WORK_DIR
, 0700);
77 int home_unshare_and_mount(const char *node
, const char *fstype
, bool discard
, unsigned long flags
) {
83 r
= home_unshare_and_mkdir();
87 return home_mount_node(node
, fstype
, discard
, flags
);
90 int home_move_mount(const char *mount_suffix
, const char *target
) {
91 _cleanup_free_
char *subdir
= NULL
;
97 /* If 'mount_suffix' is set, then we'll mount a subdir of the source mount into the host. If it's
98 * NULL we'll move the mount itself */
100 subdir
= path_join(HOME_RUNTIME_WORK_DIR
, mount_suffix
);
106 d
= HOME_RUNTIME_WORK_DIR
;
108 (void) mkdir_p(target
, 0700);
110 r
= mount_nofollow_verbose(LOG_ERR
, d
, target
, NULL
, MS_BIND
, NULL
);
114 r
= umount_verbose(LOG_ERR
, HOME_RUNTIME_WORK_DIR
, UMOUNT_NOFOLLOW
);
118 log_info("Moving to final mount point %s completed.", target
);
122 static int append_identity_range(char **text
, uid_t start
, uid_t next_start
, uid_t exclude
) {
123 /* Creates an identity range ranging from 'start' to 'next_start-1'. Excludes the UID specified by 'exclude' if
124 * it is in that range. */
128 if (next_start
<= start
) /* Empty range? */
131 if (exclude
< start
|| exclude
>= next_start
) /* UID to exclude it outside of the range? */
132 return strextendf(text
, UID_FMT
" " UID_FMT
" " UID_FMT
"\n", start
, start
, next_start
- start
);
134 if (start
== exclude
&& next_start
== exclude
+ 1) /* The only UID in the range is the one to exclude? */
137 if (exclude
== start
) /* UID to exclude at beginning of range? */
138 return strextendf(text
, UID_FMT
" " UID_FMT
" " UID_FMT
"\n", start
+1, start
+1, next_start
- start
- 1);
140 if (exclude
== next_start
- 1) /* UID to exclude at end of range? */
141 return strextendf(text
, UID_FMT
" " UID_FMT
" " UID_FMT
"\n", start
, start
, next_start
- start
- 1);
143 return strextendf(text
,
144 UID_FMT
" " UID_FMT
" " UID_FMT
"\n"
145 UID_FMT
" " UID_FMT
" " UID_FMT
"\n",
146 start
, start
, exclude
- start
,
147 exclude
+ 1, exclude
+ 1, next_start
- exclude
- 1);
150 static int make_userns(uid_t stored_uid
, uid_t exposed_uid
) {
151 _cleanup_free_
char *text
= NULL
;
152 _cleanup_close_
int userns_fd
= -1;
155 assert(uid_is_valid(stored_uid
));
156 assert(uid_is_valid(exposed_uid
));
158 assert_cc(HOME_UID_MIN
<= HOME_UID_MAX
);
159 assert_cc(HOME_UID_MAX
< UID_NOBODY
);
161 /* Map everything below the homed UID range to itself (except for the UID we actually care about if
162 * it is inside this range) */
163 r
= append_identity_range(&text
, 0, HOME_UID_MIN
, stored_uid
);
167 /* Now map the UID we are doing this for to the target UID. */
168 r
= strextendf(&text
, UID_FMT
" " UID_FMT
" " UID_FMT
"\n", stored_uid
, exposed_uid
, 1);
172 /* Map everything above the homed UID range to itself (again, excluding the UID we actually care
173 * about if it is in that range). Also we leave "nobody" itself excluded) */
174 r
= append_identity_range(&text
, HOME_UID_MAX
, UID_NOBODY
, stored_uid
);
178 /* Leave everything else unmapped, starting from UID_NOBODY itself. Specifically, this means the
179 * whole space outside of 16bit remains unmapped */
181 log_debug("Creating userns with mapping:\n%s", text
);
183 userns_fd
= userns_acquire(text
, text
); /* same uid + gid mapping */
185 return log_error_errno(userns_fd
, "Failed to allocate user namespace: %m");
187 return TAKE_FD(userns_fd
);
190 int home_shift_uid(int dir_fd
, const char *target
, uid_t stored_uid
, uid_t exposed_uid
, int *ret_mount_fd
) {
191 _cleanup_close_
int mount_fd
= -1, userns_fd
= -1;
195 assert(uid_is_valid(stored_uid
));
196 assert(uid_is_valid(exposed_uid
));
198 /* Let's try to set up a UID mapping for this directory. This is called when first creating a home
199 * directory or when activating it again. We do this as optimization only, to avoid having to
200 * recursively chown() things on each activation. If the kernel or file system doesn't support this
201 * scheme we'll handle this gracefully, and not do anything, so that the later recursive chown()ing
202 * then fixes up things for us. Note that the chown()ing is smart enough to skip things if they look
205 * Note that this always creates a new mount (i.e. we use OPEN_TREE_CLONE), since applying idmaps is
206 * not allowed once the mount is put in place. */
208 mount_fd
= open_tree(dir_fd
, "", AT_EMPTY_PATH
| OPEN_TREE_CLONE
| OPEN_TREE_CLOEXEC
);
210 if (ERRNO_IS_NOT_SUPPORTED(errno
)) {
211 log_debug_errno(errno
, "The open_tree() syscall is not supported, not setting up UID shift mount: %m");
219 return log_error_errno(errno
, "Failed to open tree of home directory: %m");
222 userns_fd
= make_userns(stored_uid
, exposed_uid
);
226 /* Set the user namespace mapping attribute on the cloned mount point */
227 if (mount_setattr(mount_fd
, "", AT_EMPTY_PATH
,
228 &(struct mount_attr
) {
229 .attr_set
= MOUNT_ATTR_IDMAP
,
230 .userns_fd
= userns_fd
,
231 }, MOUNT_ATTR_SIZE_VER0
) < 0) {
233 if (ERRNO_IS_NOT_SUPPORTED(errno
) || errno
== EINVAL
) { /* EINVAL is documented in mount_attr() as fs doesn't support idmapping */
234 log_debug_errno(errno
, "UID/GID mapping for shifted mount not available, not setting it up: %m");
242 return log_error_errno(errno
, "Failed to apply UID/GID mapping: %m");
246 r
= move_mount(mount_fd
, "", AT_FDCWD
, target
, MOVE_MOUNT_F_EMPTY_PATH
);
248 r
= move_mount(mount_fd
, "", dir_fd
, "", MOVE_MOUNT_F_EMPTY_PATH
|MOVE_MOUNT_T_EMPTY_PATH
);
250 return log_error_errno(errno
, "Failed to apply UID/GID map: %m");
253 *ret_mount_fd
= TAKE_FD(mount_fd
);