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