1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
9 #include "alloc-util.h"
11 #include "format-util.h"
12 #include "glyph-util.h"
13 #include "home-util.h"
14 #include "homework-mount.h"
16 #include "missing_mount.h"
17 #include "missing_syscall.h"
19 #include "mount-util.h"
20 #include "namespace-util.h"
21 #include "path-util.h"
22 #include "string-util.h"
23 #include "user-util.h"
25 static const char *mount_options_for_fstype(const char *fstype
) {
31 /* Allow overriding our built-in defaults with an environment variable */
32 n
= strjoina("SYSTEMD_HOME_MOUNT_OPTIONS_", fstype
);
33 e
= getenv(ascii_strupper(n
));
37 if (streq(fstype
, "ext4"))
38 return "noquota,user_xattr";
39 if (streq(fstype
, "xfs"))
41 if (streq(fstype
, "btrfs"))
42 return "noacl,compress=zstd:1";
51 const char *extra_mount_options
) {
53 _cleanup_free_
char *joined
= NULL
;
54 const char *default_options
;
60 default_options
= mount_options_for_fstype(fstype
);
61 if (default_options
) {
62 if (!strextend_with_separator(&joined
, ",", default_options
))
66 if (!strextend_with_separator(&joined
, ",", discard
? "discard" : "nodiscard"))
69 if (extra_mount_options
) {
70 if (!strextend_with_separator(&joined
, ",", extra_mount_options
))
74 r
= mount_nofollow_verbose(LOG_ERR
, node
, HOME_RUNTIME_WORK_DIR
, fstype
, flags
|MS_RELATIME
, joined
);
78 log_info("Mounting file system completed.");
82 int home_unshare_and_mkdir(void) {
85 if (unshare(CLONE_NEWNS
) < 0)
86 return log_error_errno(errno
, "Couldn't unshare file system namespace: %m");
88 assert(path_startswith(HOME_RUNTIME_WORK_DIR
, "/run"));
90 r
= mount_nofollow_verbose(LOG_ERR
, "/run", "/run", NULL
, MS_SLAVE
|MS_REC
, NULL
); /* Mark /run as MS_SLAVE in our new namespace */
94 (void) mkdir_p(HOME_RUNTIME_WORK_DIR
, 0700);
98 int home_unshare_and_mount(
103 const char *extra_mount_options
) {
110 r
= home_unshare_and_mkdir();
114 r
= home_mount_node(node
, fstype
, discard
, flags
, extra_mount_options
);
118 r
= mount_nofollow_verbose(LOG_ERR
, NULL
, HOME_RUNTIME_WORK_DIR
, NULL
, MS_PRIVATE
, NULL
);
120 (void) umount_verbose(LOG_ERR
, HOME_RUNTIME_WORK_DIR
, UMOUNT_NOFOLLOW
);
127 int home_move_mount(const char *mount_suffix
, const char *target
) {
128 _cleanup_free_
char *subdir
= NULL
;
134 /* If 'mount_suffix' is set, then we'll mount a subdir of the source mount into the host. If it's
135 * NULL we'll move the mount itself */
137 subdir
= path_join(HOME_RUNTIME_WORK_DIR
, mount_suffix
);
143 d
= HOME_RUNTIME_WORK_DIR
;
145 (void) mkdir_p(target
, 0700);
147 r
= mount_nofollow_verbose(LOG_ERR
, d
, target
, NULL
, MS_BIND
, NULL
);
151 r
= umount_recursive(HOME_RUNTIME_WORK_DIR
, 0);
153 return log_error_errno(r
, "Failed to unmount %s: %m", HOME_RUNTIME_WORK_DIR
);
155 log_info("Moving to final mount point %s completed.", target
);
159 static int append_identity_range(char **text
, uid_t start
, uid_t next_start
, uid_t exclude
) {
160 /* Creates an identity range ranging from 'start' to 'next_start-1'. Excludes the UID specified by 'exclude' if
161 * it is in that range. */
165 if (next_start
<= start
) /* Empty range? */
168 if (exclude
< start
|| exclude
>= next_start
) /* UID to exclude it outside of the range? */
169 return strextendf(text
, UID_FMT
" " UID_FMT
" " UID_FMT
"\n", start
, start
, next_start
- start
);
171 if (start
== exclude
&& next_start
== exclude
+ 1) /* The only UID in the range is the one to exclude? */
174 if (exclude
== start
) /* UID to exclude at beginning of range? */
175 return strextendf(text
, UID_FMT
" " UID_FMT
" " UID_FMT
"\n", start
+1, start
+1, next_start
- start
- 1);
177 if (exclude
== next_start
- 1) /* UID to exclude at end of range? */
178 return strextendf(text
, UID_FMT
" " UID_FMT
" " UID_FMT
"\n", start
, start
, next_start
- start
- 1);
180 return strextendf(text
,
181 UID_FMT
" " UID_FMT
" " UID_FMT
"\n"
182 UID_FMT
" " UID_FMT
" " UID_FMT
"\n",
183 start
, start
, exclude
- start
,
184 exclude
+ 1, exclude
+ 1, next_start
- exclude
- 1);
187 static int make_home_userns(uid_t stored_uid
, uid_t exposed_uid
) {
188 _cleanup_free_
char *text
= NULL
;
189 _cleanup_close_
int userns_fd
= -EBADF
;
192 assert(uid_is_valid(stored_uid
));
193 assert(uid_is_valid(exposed_uid
));
195 assert_cc(HOME_UID_MIN
<= HOME_UID_MAX
);
196 assert_cc(HOME_UID_MAX
< UID_NOBODY
);
198 /* Map everything below the homed UID range to itself (except for the UID we actually care about if
199 * it is inside this range) */
200 r
= append_identity_range(&text
, 0, HOME_UID_MIN
, stored_uid
);
204 /* Now map the UID we are doing this for to the target UID. */
205 r
= strextendf(&text
, UID_FMT
" " UID_FMT
" " UID_FMT
"\n", stored_uid
, exposed_uid
, 1u);
209 /* Map everything above the homed UID range to itself (again, excluding the UID we actually care
210 * about if it is in that range). Also we leave "nobody" itself excluded) */
211 r
= append_identity_range(&text
, HOME_UID_MAX
, UID_NOBODY
, stored_uid
);
215 /* Also map the container range. People can use that to place containers owned by high UIDs in their
216 * home directories if they really want. We won't manage this UID range for them but pass it through
217 * 1:1, and it will lose its meaning once migrated between hosts. */
218 r
= append_identity_range(&text
, CONTAINER_UID_BASE_MIN
, CONTAINER_UID_BASE_MAX
+1, stored_uid
);
222 /* Map nspawn's mapped root UID as identity mapping so that people can run nspawn uidmap mounted
223 * containers off $HOME, if they want. */
224 r
= strextendf(&text
, UID_FMT
" " UID_FMT
" " UID_FMT
"\n", UID_MAPPED_ROOT
, UID_MAPPED_ROOT
, 1u);
228 /* Leave everything else unmapped, starting from UID_NOBODY itself. Specifically, this means the
229 * whole space outside of 16-bit remains unmapped */
231 log_debug("Creating userns with mapping:\n%s", text
);
233 userns_fd
= userns_acquire(text
, text
); /* same uid + gid mapping */
235 return log_error_errno(userns_fd
, "Failed to allocate user namespace: %m");
237 return TAKE_FD(userns_fd
);
240 int home_shift_uid(int dir_fd
, const char *target
, uid_t stored_uid
, uid_t exposed_uid
, int *ret_mount_fd
) {
241 _cleanup_close_
int mount_fd
= -EBADF
, userns_fd
= -EBADF
;
245 assert(uid_is_valid(stored_uid
));
246 assert(uid_is_valid(exposed_uid
));
248 /* Let's try to set up a UID mapping for this directory. This is called when first creating a home
249 * directory or when activating it again. We do this as optimization only, to avoid having to
250 * recursively chown() things on each activation. If the kernel or file system doesn't support this
251 * scheme we'll handle this gracefully, and not do anything, so that the later recursive chown()ing
252 * then fixes up things for us. Note that the chown()ing is smart enough to skip things if they look
255 * Note that this always creates a new mount (i.e. we use OPEN_TREE_CLONE), since applying idmaps is
256 * not allowed once the mount is put in place. */
258 mount_fd
= open_tree(dir_fd
, "", AT_EMPTY_PATH
| OPEN_TREE_CLONE
| OPEN_TREE_CLOEXEC
);
260 if (ERRNO_IS_NOT_SUPPORTED(errno
)) {
261 log_debug_errno(errno
, "The open_tree() syscall is not supported, not setting up UID shift mount: %m");
264 *ret_mount_fd
= -EBADF
;
269 return log_error_errno(errno
, "Failed to open tree of home directory: %m");
272 userns_fd
= make_home_userns(stored_uid
, exposed_uid
);
276 /* Set the user namespace mapping attribute on the cloned mount point */
277 if (mount_setattr(mount_fd
, "", AT_EMPTY_PATH
,
278 &(struct mount_attr
) {
279 .attr_set
= MOUNT_ATTR_IDMAP
,
280 .userns_fd
= userns_fd
,
281 }, MOUNT_ATTR_SIZE_VER0
) < 0) {
283 if (ERRNO_IS_NOT_SUPPORTED(errno
) || errno
== EINVAL
) { /* EINVAL is documented in mount_attr() as fs doesn't support idmapping */
284 log_debug_errno(errno
, "UID/GID mapping for shifted mount not available, not setting it up: %m");
287 *ret_mount_fd
= -EBADF
;
292 return log_error_errno(errno
, "Failed to apply UID/GID mapping: %m");
296 r
= move_mount(mount_fd
, "", AT_FDCWD
, target
, MOVE_MOUNT_F_EMPTY_PATH
);
298 r
= move_mount(mount_fd
, "", dir_fd
, "", MOVE_MOUNT_F_EMPTY_PATH
|MOVE_MOUNT_T_EMPTY_PATH
);
300 return log_error_errno(errno
, "Failed to apply UID/GID map: %m");
302 log_debug("Applied uidmap mount to %s. Mapping is " UID_FMT
" %s " UID_FMT
".",
303 strna(target
), stored_uid
, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT
), exposed_uid
);
306 *ret_mount_fd
= TAKE_FD(mount_fd
);