]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/home/homework-mount.c
tree-wide: "<n>bit" → "<n>-bit"
[thirdparty/systemd.git] / src / home / homework-mount.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
70a5db58
LP
2
3#include <sched.h>
4#include <sys/mount.h>
0a58cd00 5#if WANT_LINUX_FS_H
c7bf079b 6#include <linux/fs.h>
0a58cd00 7#endif
70a5db58
LP
8
9#include "alloc-util.h"
c7bf079b
LP
10#include "fd-util.h"
11#include "format-util.h"
e2341b6b 12#include "glyph-util.h"
c7bf079b 13#include "home-util.h"
70a5db58 14#include "homework-mount.h"
498abadb 15#include "homework.h"
c7bf079b
LP
16#include "missing_mount.h"
17#include "missing_syscall.h"
70a5db58
LP
18#include "mkdir.h"
19#include "mount-util.h"
c7bf079b 20#include "namespace-util.h"
70a5db58
LP
21#include "path-util.h"
22#include "string-util.h"
c7bf079b 23#include "user-util.h"
70a5db58
LP
24
25static const char *mount_options_for_fstype(const char *fstype) {
6309512c
LP
26 const char *e;
27 char *n;
28
29 assert(fstype);
30
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));
34 if (e)
35 return e;
36
70a5db58
LP
37 if (streq(fstype, "ext4"))
38 return "noquota,user_xattr";
39 if (streq(fstype, "xfs"))
40 return "noquota";
41 if (streq(fstype, "btrfs"))
a428a451 42 return "noacl,compress=zstd:1";
70a5db58
LP
43 return NULL;
44}
45
2e0001c2
LP
46int home_mount_node(
47 const char *node,
48 const char *fstype,
49 bool discard,
50 unsigned long flags,
51 const char *extra_mount_options) {
52
70a5db58 53 _cleanup_free_ char *joined = NULL;
2e0001c2 54 const char *default_options;
70a5db58
LP
55 int r;
56
7cb791bc
LP
57 assert(node);
58 assert(fstype);
59
2e0001c2
LP
60 default_options = mount_options_for_fstype(fstype);
61 if (default_options) {
62 if (!strextend_with_separator(&joined, ",", default_options))
63 return log_oom();
64 }
70a5db58 65
2e0001c2
LP
66 if (!strextend_with_separator(&joined, ",", discard ? "discard" : "nodiscard"))
67 return log_oom();
70a5db58 68
2e0001c2
LP
69 if (extra_mount_options) {
70 if (!strextend_with_separator(&joined, ",", extra_mount_options))
70a5db58 71 return log_oom();
2e0001c2 72 }
70a5db58 73
2e0001c2 74 r = mount_nofollow_verbose(LOG_ERR, node, HOME_RUNTIME_WORK_DIR, fstype, flags|MS_RELATIME, joined);
70a5db58
LP
75 if (r < 0)
76 return r;
77
78 log_info("Mounting file system completed.");
79 return 0;
80}
81
7cb791bc 82int home_unshare_and_mkdir(void) {
70a5db58
LP
83 int r;
84
85 if (unshare(CLONE_NEWNS) < 0)
86 return log_error_errno(errno, "Couldn't unshare file system namespace: %m");
87
498abadb
LP
88 assert(path_startswith(HOME_RUNTIME_WORK_DIR, "/run"));
89
511a8cfe 90 r = mount_nofollow_verbose(LOG_ERR, "/run", "/run", NULL, MS_SLAVE|MS_REC, NULL); /* Mark /run as MS_SLAVE in our new namespace */
70a5db58
LP
91 if (r < 0)
92 return r;
93
498abadb 94 (void) mkdir_p(HOME_RUNTIME_WORK_DIR, 0700);
7cb791bc
LP
95 return 0;
96}
70a5db58 97
2e0001c2
LP
98int home_unshare_and_mount(
99 const char *node,
100 const char *fstype,
101 bool discard,
102 unsigned long flags,
103 const char *extra_mount_options) {
104
7cb791bc 105 int r;
70a5db58 106
7cb791bc
LP
107 assert(node);
108 assert(fstype);
109
110 r = home_unshare_and_mkdir();
111 if (r < 0)
112 return r;
113
2e0001c2 114 r = home_mount_node(node, fstype, discard, flags, extra_mount_options);
1147c538
LP
115 if (r < 0)
116 return r;
117
118 r = mount_nofollow_verbose(LOG_ERR, NULL, HOME_RUNTIME_WORK_DIR, NULL, MS_PRIVATE, NULL);
119 if (r < 0) {
120 (void) umount_verbose(LOG_ERR, HOME_RUNTIME_WORK_DIR, UMOUNT_NOFOLLOW);
121 return r;
122 }
123
124 return 0;
70a5db58
LP
125}
126
2b9855f9 127int home_move_mount(const char *mount_suffix, const char *target) {
70a5db58
LP
128 _cleanup_free_ char *subdir = NULL;
129 const char *d;
130 int r;
131
70a5db58
LP
132 assert(target);
133
2b9855f9
LP
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 */
136 if (mount_suffix) {
137 subdir = path_join(HOME_RUNTIME_WORK_DIR, mount_suffix);
70a5db58
LP
138 if (!subdir)
139 return log_oom();
140
141 d = subdir;
142 } else
498abadb 143 d = HOME_RUNTIME_WORK_DIR;
70a5db58
LP
144
145 (void) mkdir_p(target, 0700);
146
511a8cfe 147 r = mount_nofollow_verbose(LOG_ERR, d, target, NULL, MS_BIND, NULL);
70a5db58
LP
148 if (r < 0)
149 return r;
150
1147c538 151 r = umount_recursive(HOME_RUNTIME_WORK_DIR, 0);
70a5db58 152 if (r < 0)
1147c538 153 return log_error_errno(r, "Failed to unmount %s: %m", HOME_RUNTIME_WORK_DIR);
70a5db58
LP
154
155 log_info("Moving to final mount point %s completed.", target);
156 return 0;
157}
c7bf079b
LP
158
159static 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. */
162
163 assert(text);
164
165 if (next_start <= start) /* Empty range? */
166 return 0;
167
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);
170
171 if (start == exclude && next_start == exclude + 1) /* The only UID in the range is the one to exclude? */
172 return 0;
173
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);
176
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);
179
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);
185}
186
0593b34a 187static int make_home_userns(uid_t stored_uid, uid_t exposed_uid) {
c7bf079b 188 _cleanup_free_ char *text = NULL;
254d1313 189 _cleanup_close_ int userns_fd = -EBADF;
c7bf079b
LP
190 int r;
191
192 assert(uid_is_valid(stored_uid));
193 assert(uid_is_valid(exposed_uid));
194
195 assert_cc(HOME_UID_MIN <= HOME_UID_MAX);
196 assert_cc(HOME_UID_MAX < UID_NOBODY);
197
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);
201 if (r < 0)
202 return log_oom();
203
204 /* Now map the UID we are doing this for to the target UID. */
c0f86d66 205 r = strextendf(&text, UID_FMT " " UID_FMT " " UID_FMT "\n", stored_uid, exposed_uid, 1u);
c7bf079b
LP
206 if (r < 0)
207 return log_oom();
208
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);
212 if (r < 0)
213 return log_oom();
214
1af53c0f
LP
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);
219 if (r < 0)
220 return log_oom();
221
04561188
LP
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. */
c0f86d66 224 r = strextendf(&text, UID_FMT " " UID_FMT " " UID_FMT "\n", UID_MAPPED_ROOT, UID_MAPPED_ROOT, 1u);
04561188
LP
225 if (r < 0)
226 return log_oom();
227
c7bf079b 228 /* Leave everything else unmapped, starting from UID_NOBODY itself. Specifically, this means the
da890466 229 * whole space outside of 16-bit remains unmapped */
c7bf079b
LP
230
231 log_debug("Creating userns with mapping:\n%s", text);
232
233 userns_fd = userns_acquire(text, text); /* same uid + gid mapping */
234 if (userns_fd < 0)
235 return log_error_errno(userns_fd, "Failed to allocate user namespace: %m");
236
237 return TAKE_FD(userns_fd);
238}
239
240int home_shift_uid(int dir_fd, const char *target, uid_t stored_uid, uid_t exposed_uid, int *ret_mount_fd) {
254d1313 241 _cleanup_close_ int mount_fd = -EBADF, userns_fd = -EBADF;
c7bf079b
LP
242 int r;
243
244 assert(dir_fd >= 0);
245 assert(uid_is_valid(stored_uid));
246 assert(uid_is_valid(exposed_uid));
247
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
253 * alright already.
254 *
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. */
257
258 mount_fd = open_tree(dir_fd, "", AT_EMPTY_PATH | OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
259 if (mount_fd < 0) {
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");
262
263 if (ret_mount_fd)
254d1313 264 *ret_mount_fd = -EBADF;
c7bf079b
LP
265
266 return 0;
267 }
268
269 return log_error_errno(errno, "Failed to open tree of home directory: %m");
270 }
271
0593b34a 272 userns_fd = make_home_userns(stored_uid, exposed_uid);
c7bf079b
LP
273 if (userns_fd < 0)
274 return userns_fd;
275
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) {
282
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");
285
286 if (ret_mount_fd)
254d1313 287 *ret_mount_fd = -EBADF;
c7bf079b
LP
288
289 return 0;
290 }
291
292 return log_error_errno(errno, "Failed to apply UID/GID mapping: %m");
293 }
294
295 if (target)
296 r = move_mount(mount_fd, "", AT_FDCWD, target, MOVE_MOUNT_F_EMPTY_PATH);
297 else
298 r = move_mount(mount_fd, "", dir_fd, "", MOVE_MOUNT_F_EMPTY_PATH|MOVE_MOUNT_T_EMPTY_PATH);
299 if (r < 0)
300 return log_error_errno(errno, "Failed to apply UID/GID map: %m");
301
e2341b6b
DT
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);
6c68d5ad 304
c7bf079b
LP
305 if (ret_mount_fd)
306 *ret_mount_fd = TAKE_FD(mount_fd);
307
308 return 1;
309}