]>
Commit | Line | Data |
---|---|---|
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 | |
25 | static 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 |
46 | int 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 | 82 | int 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 |
98 | int 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 | 127 | int 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 | |
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. */ | |
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 | 187 | static 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 | ||
240 | int 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 | } |