]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/home/homework-mount.c
homework: fix a bad error propagation
[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>
c7bf079b 5#include <linux/fs.h>
70a5db58
LP
6
7#include "alloc-util.h"
c7bf079b
LP
8#include "fd-util.h"
9#include "format-util.h"
10#include "home-util.h"
70a5db58 11#include "homework-mount.h"
498abadb 12#include "homework.h"
c7bf079b
LP
13#include "missing_mount.h"
14#include "missing_syscall.h"
70a5db58
LP
15#include "mkdir.h"
16#include "mount-util.h"
c7bf079b 17#include "namespace-util.h"
70a5db58
LP
18#include "path-util.h"
19#include "string-util.h"
c7bf079b 20#include "user-util.h"
70a5db58
LP
21
22static const char *mount_options_for_fstype(const char *fstype) {
6309512c
LP
23 const char *e;
24 char *n;
25
26 assert(fstype);
27
28 /* Allow overriding our built-in defaults with an environment variable */
29 n = strjoina("SYSTEMD_HOME_MOUNT_OPTIONS_", fstype);
30 e = getenv(ascii_strupper(n));
31 if (e)
32 return e;
33
70a5db58
LP
34 if (streq(fstype, "ext4"))
35 return "noquota,user_xattr";
36 if (streq(fstype, "xfs"))
37 return "noquota";
38 if (streq(fstype, "btrfs"))
a428a451 39 return "noacl,compress=zstd:1";
70a5db58
LP
40 return NULL;
41}
42
2e0001c2
LP
43int home_mount_node(
44 const char *node,
45 const char *fstype,
46 bool discard,
47 unsigned long flags,
48 const char *extra_mount_options) {
49
70a5db58 50 _cleanup_free_ char *joined = NULL;
2e0001c2 51 const char *default_options;
70a5db58
LP
52 int r;
53
7cb791bc
LP
54 assert(node);
55 assert(fstype);
56
2e0001c2
LP
57 default_options = mount_options_for_fstype(fstype);
58 if (default_options) {
59 if (!strextend_with_separator(&joined, ",", default_options))
60 return log_oom();
61 }
70a5db58 62
2e0001c2
LP
63 if (!strextend_with_separator(&joined, ",", discard ? "discard" : "nodiscard"))
64 return log_oom();
70a5db58 65
2e0001c2
LP
66 if (extra_mount_options) {
67 if (!strextend_with_separator(&joined, ",", extra_mount_options))
70a5db58 68 return log_oom();
2e0001c2 69 }
70a5db58 70
2e0001c2 71 r = mount_nofollow_verbose(LOG_ERR, node, HOME_RUNTIME_WORK_DIR, fstype, flags|MS_RELATIME, joined);
70a5db58
LP
72 if (r < 0)
73 return r;
74
75 log_info("Mounting file system completed.");
76 return 0;
77}
78
7cb791bc 79int home_unshare_and_mkdir(void) {
70a5db58
LP
80 int r;
81
82 if (unshare(CLONE_NEWNS) < 0)
83 return log_error_errno(errno, "Couldn't unshare file system namespace: %m");
84
498abadb
LP
85 assert(path_startswith(HOME_RUNTIME_WORK_DIR, "/run"));
86
511a8cfe 87 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
88 if (r < 0)
89 return r;
90
498abadb 91 (void) mkdir_p(HOME_RUNTIME_WORK_DIR, 0700);
7cb791bc
LP
92 return 0;
93}
70a5db58 94
2e0001c2
LP
95int home_unshare_and_mount(
96 const char *node,
97 const char *fstype,
98 bool discard,
99 unsigned long flags,
100 const char *extra_mount_options) {
101
7cb791bc 102 int r;
70a5db58 103
7cb791bc
LP
104 assert(node);
105 assert(fstype);
106
107 r = home_unshare_and_mkdir();
108 if (r < 0)
109 return r;
110
2e0001c2 111 r = home_mount_node(node, fstype, discard, flags, extra_mount_options);
1147c538
LP
112 if (r < 0)
113 return r;
114
115 r = mount_nofollow_verbose(LOG_ERR, NULL, HOME_RUNTIME_WORK_DIR, NULL, MS_PRIVATE, NULL);
116 if (r < 0) {
117 (void) umount_verbose(LOG_ERR, HOME_RUNTIME_WORK_DIR, UMOUNT_NOFOLLOW);
118 return r;
119 }
120
121 return 0;
70a5db58
LP
122}
123
2b9855f9 124int home_move_mount(const char *mount_suffix, const char *target) {
70a5db58
LP
125 _cleanup_free_ char *subdir = NULL;
126 const char *d;
127 int r;
128
70a5db58
LP
129 assert(target);
130
2b9855f9
LP
131 /* If 'mount_suffix' is set, then we'll mount a subdir of the source mount into the host. If it's
132 * NULL we'll move the mount itself */
133 if (mount_suffix) {
134 subdir = path_join(HOME_RUNTIME_WORK_DIR, mount_suffix);
70a5db58
LP
135 if (!subdir)
136 return log_oom();
137
138 d = subdir;
139 } else
498abadb 140 d = HOME_RUNTIME_WORK_DIR;
70a5db58
LP
141
142 (void) mkdir_p(target, 0700);
143
511a8cfe 144 r = mount_nofollow_verbose(LOG_ERR, d, target, NULL, MS_BIND, NULL);
70a5db58
LP
145 if (r < 0)
146 return r;
147
1147c538 148 r = umount_recursive(HOME_RUNTIME_WORK_DIR, 0);
70a5db58 149 if (r < 0)
1147c538 150 return log_error_errno(r, "Failed to unmount %s: %m", HOME_RUNTIME_WORK_DIR);
70a5db58
LP
151
152 log_info("Moving to final mount point %s completed.", target);
153 return 0;
154}
c7bf079b
LP
155
156static int append_identity_range(char **text, uid_t start, uid_t next_start, uid_t exclude) {
157 /* Creates an identity range ranging from 'start' to 'next_start-1'. Excludes the UID specified by 'exclude' if
158 * it is in that range. */
159
160 assert(text);
161
162 if (next_start <= start) /* Empty range? */
163 return 0;
164
165 if (exclude < start || exclude >= next_start) /* UID to exclude it outside of the range? */
166 return strextendf(text, UID_FMT " " UID_FMT " " UID_FMT "\n", start, start, next_start - start);
167
168 if (start == exclude && next_start == exclude + 1) /* The only UID in the range is the one to exclude? */
169 return 0;
170
171 if (exclude == start) /* UID to exclude at beginning of range? */
172 return strextendf(text, UID_FMT " " UID_FMT " " UID_FMT "\n", start+1, start+1, next_start - start - 1);
173
174 if (exclude == next_start - 1) /* UID to exclude at end of range? */
175 return strextendf(text, UID_FMT " " UID_FMT " " UID_FMT "\n", start, start, next_start - start - 1);
176
177 return strextendf(text,
178 UID_FMT " " UID_FMT " " UID_FMT "\n"
179 UID_FMT " " UID_FMT " " UID_FMT "\n",
180 start, start, exclude - start,
181 exclude + 1, exclude + 1, next_start - exclude - 1);
182}
183
184static int make_userns(uid_t stored_uid, uid_t exposed_uid) {
185 _cleanup_free_ char *text = NULL;
186 _cleanup_close_ int userns_fd = -1;
187 int r;
188
189 assert(uid_is_valid(stored_uid));
190 assert(uid_is_valid(exposed_uid));
191
192 assert_cc(HOME_UID_MIN <= HOME_UID_MAX);
193 assert_cc(HOME_UID_MAX < UID_NOBODY);
194
195 /* Map everything below the homed UID range to itself (except for the UID we actually care about if
196 * it is inside this range) */
197 r = append_identity_range(&text, 0, HOME_UID_MIN, stored_uid);
198 if (r < 0)
199 return log_oom();
200
201 /* Now map the UID we are doing this for to the target UID. */
202 r = strextendf(&text, UID_FMT " " UID_FMT " " UID_FMT "\n", stored_uid, exposed_uid, 1);
203 if (r < 0)
204 return log_oom();
205
206 /* Map everything above the homed UID range to itself (again, excluding the UID we actually care
207 * about if it is in that range). Also we leave "nobody" itself excluded) */
208 r = append_identity_range(&text, HOME_UID_MAX, UID_NOBODY, stored_uid);
209 if (r < 0)
210 return log_oom();
211
212 /* Leave everything else unmapped, starting from UID_NOBODY itself. Specifically, this means the
213 * whole space outside of 16bit remains unmapped */
214
215 log_debug("Creating userns with mapping:\n%s", text);
216
217 userns_fd = userns_acquire(text, text); /* same uid + gid mapping */
218 if (userns_fd < 0)
219 return log_error_errno(userns_fd, "Failed to allocate user namespace: %m");
220
221 return TAKE_FD(userns_fd);
222}
223
224int home_shift_uid(int dir_fd, const char *target, uid_t stored_uid, uid_t exposed_uid, int *ret_mount_fd) {
225 _cleanup_close_ int mount_fd = -1, userns_fd = -1;
226 int r;
227
228 assert(dir_fd >= 0);
229 assert(uid_is_valid(stored_uid));
230 assert(uid_is_valid(exposed_uid));
231
232 /* Let's try to set up a UID mapping for this directory. This is called when first creating a home
233 * directory or when activating it again. We do this as optimization only, to avoid having to
234 * recursively chown() things on each activation. If the kernel or file system doesn't support this
235 * scheme we'll handle this gracefully, and not do anything, so that the later recursive chown()ing
236 * then fixes up things for us. Note that the chown()ing is smart enough to skip things if they look
237 * alright already.
238 *
239 * Note that this always creates a new mount (i.e. we use OPEN_TREE_CLONE), since applying idmaps is
240 * not allowed once the mount is put in place. */
241
242 mount_fd = open_tree(dir_fd, "", AT_EMPTY_PATH | OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC);
243 if (mount_fd < 0) {
244 if (ERRNO_IS_NOT_SUPPORTED(errno)) {
245 log_debug_errno(errno, "The open_tree() syscall is not supported, not setting up UID shift mount: %m");
246
247 if (ret_mount_fd)
248 *ret_mount_fd = -1;
249
250 return 0;
251 }
252
253 return log_error_errno(errno, "Failed to open tree of home directory: %m");
254 }
255
256 userns_fd = make_userns(stored_uid, exposed_uid);
257 if (userns_fd < 0)
258 return userns_fd;
259
260 /* Set the user namespace mapping attribute on the cloned mount point */
261 if (mount_setattr(mount_fd, "", AT_EMPTY_PATH,
262 &(struct mount_attr) {
263 .attr_set = MOUNT_ATTR_IDMAP,
264 .userns_fd = userns_fd,
265 }, MOUNT_ATTR_SIZE_VER0) < 0) {
266
267 if (ERRNO_IS_NOT_SUPPORTED(errno) || errno == EINVAL) { /* EINVAL is documented in mount_attr() as fs doesn't support idmapping */
268 log_debug_errno(errno, "UID/GID mapping for shifted mount not available, not setting it up: %m");
269
270 if (ret_mount_fd)
271 *ret_mount_fd = -1;
272
273 return 0;
274 }
275
276 return log_error_errno(errno, "Failed to apply UID/GID mapping: %m");
277 }
278
279 if (target)
280 r = move_mount(mount_fd, "", AT_FDCWD, target, MOVE_MOUNT_F_EMPTY_PATH);
281 else
282 r = move_mount(mount_fd, "", dir_fd, "", MOVE_MOUNT_F_EMPTY_PATH|MOVE_MOUNT_T_EMPTY_PATH);
283 if (r < 0)
284 return log_error_errno(errno, "Failed to apply UID/GID map: %m");
285
286 if (ret_mount_fd)
287 *ret_mount_fd = TAKE_FD(mount_fd);
288
289 return 1;
290}