]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/login/user-runtime-dir.c
77498081ea2db779c19dd9180a89dc71be7e6136
[thirdparty/systemd.git] / src / login / user-runtime-dir.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <sys/mount.h>
4
5 #include "sd-bus.h"
6
7 #include "bus-error.h"
8 #include "bus-locator.h"
9 #include "devnum-util.h"
10 #include "errno-util.h"
11 #include "fd-util.h"
12 #include "format-util.h"
13 #include "fs-util.h"
14 #include "label-util.h"
15 #include "limits-util.h"
16 #include "log.h"
17 #include "main-func.h"
18 #include "missing_magic.h"
19 #include "missing_syscall.h"
20 #include "mkdir-label.h"
21 #include "mount-util.h"
22 #include "mountpoint-util.h"
23 #include "path-util.h"
24 #include "quota-util.h"
25 #include "rm-rf.h"
26 #include "set.h"
27 #include "smack-util.h"
28 #include "stat-util.h"
29 #include "stdio-util.h"
30 #include "string-util.h"
31 #include "strv.h"
32 #include "user-util.h"
33 #include "userdb.h"
34
35 static int acquire_runtime_dir_properties(uint64_t *ret_size, uint64_t *ret_inodes) {
36 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
37 _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
38 uint64_t size, inodes;
39 int r;
40
41 assert(ret_size);
42 assert(ret_inodes);
43
44 r = sd_bus_default_system(&bus);
45 if (r < 0)
46 return log_error_errno(r, "Failed to connect to system bus: %m");
47
48 r = bus_get_property_trivial(bus, bus_login_mgr, "RuntimeDirectorySize", &error, 't', &size);
49 if (r < 0) {
50 log_warning_errno(r, "Failed to acquire runtime directory size, ignoring: %s", bus_error_message(&error, r));
51 sd_bus_error_free(&error);
52
53 size = physical_memory_scale(10U, 100U); /* 10% */
54 }
55
56 r = bus_get_property_trivial(bus, bus_login_mgr, "RuntimeDirectoryInodesMax", &error, 't', &inodes);
57 if (r < 0) {
58 log_warning_errno(r, "Failed to acquire number of inodes for runtime directory, ignoring: %s", bus_error_message(&error, r));
59 sd_bus_error_free(&error);
60
61 inodes = DIV_ROUND_UP(size, 4096);
62 }
63
64 *ret_size = size;
65 *ret_inodes = inodes;
66
67 return 0;
68 }
69
70 static int user_mkdir_runtime_path(
71 const char *runtime_path,
72 uid_t uid,
73 gid_t gid,
74 uint64_t runtime_dir_size,
75 uint64_t runtime_dir_inodes) {
76
77 int r;
78
79 assert(runtime_path);
80 assert(path_is_absolute(runtime_path));
81 assert(uid_is_valid(uid));
82 assert(gid_is_valid(gid));
83
84 r = mkdir_safe_label("/run/user", 0755, 0, 0, MKDIR_WARN_MODE);
85 if (r < 0)
86 return log_error_errno(r, "Failed to create /run/user: %m");
87
88 if (path_is_mount_point(runtime_path) > 0)
89 log_debug("%s is already a mount point", runtime_path);
90 else {
91 char options[STRLEN("mode=0700,uid=,gid=,size=,nr_inodes=,smackfsroot=*")
92 + DECIMAL_STR_MAX(uid_t)
93 + DECIMAL_STR_MAX(gid_t)
94 + DECIMAL_STR_MAX(uint64_t)
95 + DECIMAL_STR_MAX(uint64_t)];
96
97 xsprintf(options,
98 "mode=0700,uid=" UID_FMT ",gid=" GID_FMT ",size=%" PRIu64 ",nr_inodes=%" PRIu64 "%s",
99 uid, gid, runtime_dir_size, runtime_dir_inodes,
100 mac_smack_use() ? ",smackfsroot=*" : "");
101
102 _cleanup_free_ char *d = strdup(runtime_path);
103 if (!d)
104 return log_oom();
105
106 r = mkdir_label(runtime_path, 0700);
107 if (r < 0 && r != -EEXIST)
108 return log_error_errno(r, "Failed to create %s: %m", runtime_path);
109
110 _cleanup_(rmdir_and_freep) char *destroy = TAKE_PTR(d); /* auto-destroy */
111
112 r = mount_nofollow_verbose(LOG_DEBUG, "tmpfs", runtime_path, "tmpfs", MS_NODEV|MS_NOSUID, options);
113 if (r < 0) {
114 if (!ERRNO_IS_PRIVILEGE(r))
115 return log_error_errno(r, "Failed to mount per-user tmpfs directory %s: %m", runtime_path);
116
117 log_debug_errno(r,
118 "Failed to mount per-user tmpfs directory %s.\n"
119 "Assuming containerized execution, ignoring: %m", runtime_path);
120
121 r = chmod_and_chown(runtime_path, 0700, uid, gid);
122 if (r < 0)
123 return log_error_errno(r, "Failed to change ownership and mode of \"%s\": %m", runtime_path);
124 }
125
126 destroy = mfree(destroy); /* deactivate auto-destroy */
127
128 r = label_fix(runtime_path, 0);
129 if (r < 0)
130 log_warning_errno(r, "Failed to fix label of \"%s\", ignoring: %m", runtime_path);
131 }
132
133 return 0;
134 }
135
136 static int do_mount(UserRecord *ur) {
137 int r;
138
139 assert(ur);
140
141 if (!uid_is_valid(ur->uid) || !gid_is_valid(user_record_gid(ur)))
142 return log_error_errno(SYNTHETIC_ERRNO(ENOMSG), "User '%s' lacks UID or GID, refusing.", ur->user_name);
143
144 uint64_t runtime_dir_size, runtime_dir_inodes;
145 r = acquire_runtime_dir_properties(&runtime_dir_size, &runtime_dir_inodes);
146 if (r < 0)
147 return r;
148
149 char runtime_path[STRLEN("/run/user/") + DECIMAL_STR_MAX(uid_t)];
150 xsprintf(runtime_path, "/run/user/" UID_FMT, ur->uid);
151
152 log_debug("Will mount %s owned by "UID_FMT":"GID_FMT, runtime_path, ur->uid, user_record_gid(ur));
153 return user_mkdir_runtime_path(runtime_path, ur->uid, user_record_gid(ur), runtime_dir_size, runtime_dir_inodes);
154 }
155
156 static int user_remove_runtime_path(const char *runtime_path) {
157 int r;
158
159 assert(runtime_path);
160 assert(path_is_absolute(runtime_path));
161
162 r = rm_rf(runtime_path, 0);
163 if (r < 0)
164 log_debug_errno(r, "Failed to remove runtime directory %s (before unmounting), ignoring: %m", runtime_path);
165
166 /* Ignore cases where the directory isn't mounted, as that's quite possible, if we lacked the permissions to
167 * mount something */
168 r = RET_NERRNO(umount2(runtime_path, MNT_DETACH));
169 if (r < 0 && !IN_SET(r, -EINVAL, -ENOENT))
170 log_debug_errno(r, "Failed to unmount user runtime directory %s, ignoring: %m", runtime_path);
171
172 r = rm_rf(runtime_path, REMOVE_ROOT);
173 if (r < 0 && r != -ENOENT)
174 return log_error_errno(r, "Failed to remove runtime directory %s (after unmounting): %m", runtime_path);
175
176 return 0;
177 }
178
179 static int do_umount(const char *user) {
180 char runtime_path[STRLEN("/run/user/") + DECIMAL_STR_MAX(uid_t)];
181 uid_t uid;
182 int r;
183
184 /* The user may be already removed. So, first try to parse the string by parse_uid(),
185 * and if it fails, fall back to get_user_creds(). */
186 if (parse_uid(user, &uid) < 0) {
187 r = get_user_creds(&user, &uid, NULL, NULL, NULL, 0);
188 if (r < 0)
189 return log_error_errno(r,
190 r == -ESRCH ? "No such user \"%s\"" :
191 r == -ENOMSG ? "UID \"%s\" is invalid or has an invalid main group"
192 : "Failed to look up user \"%s\": %m",
193 user);
194 }
195
196 xsprintf(runtime_path, "/run/user/" UID_FMT, uid);
197
198 log_debug("Will remove %s", runtime_path);
199 return user_remove_runtime_path(runtime_path);
200 }
201
202 static int apply_tmpfs_quota(
203 char **paths,
204 uid_t uid,
205 uint64_t limit,
206 uint32_t scale) {
207
208 _cleanup_set_free_ Set *processed = NULL;
209 int r;
210
211 assert(uid_is_valid(uid));
212
213 STRV_FOREACH(p, paths) {
214 _cleanup_close_ int fd = open(*p, O_DIRECTORY|O_CLOEXEC);
215 if (fd < 0) {
216 log_warning_errno(errno, "Failed to open '%s' in order to set quota, ignoring: %m", *p);
217 continue;
218 }
219
220 struct stat st;
221 if (fstat(fd, &st) < 0) {
222 log_warning_errno(errno, "Failed to stat '%s' in order to set quota, ignoring: %m", *p);
223 continue;
224 }
225
226 /* Cover for bind mounted or symlinked /var/tmp/ + /tmp/ */
227 if (set_contains(processed, DEVNUM_TO_PTR(st.st_dev))) {
228 log_debug("Not setting quota on '%s', since already processed.", *p);
229 continue;
230 }
231
232 /* Remember we already dealt with this fs, even if the subsequent operation fails, since
233 * there's no point in appyling quota twice, regardless if it succeeds or not. */
234 if (set_ensure_put(&processed, /* hash_ops= */ NULL, DEVNUM_TO_PTR(st.st_dev)) < 0)
235 return log_oom();
236
237 struct statfs sfs;
238 if (fstatfs(fd, &sfs) < 0) {
239 log_warning_errno(errno, "Failed to statfs '%s' in order to set quota, ignoring: %m", *p);
240 continue;
241 }
242
243 if (!is_fs_type(&sfs, TMPFS_MAGIC)) {
244 log_debug("Not setting quota on '%s', since not tmpfs.", *p);
245 continue;
246 }
247
248 struct dqblk req;
249 r = RET_NERRNO(quotactl_fd(fd, QCMD_FIXED(Q_GETQUOTA, USRQUOTA), uid, &req));
250 if (r == -ESRCH)
251 zero(req);
252 else if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) {
253 log_debug_errno(r, "No UID quota support on %s, not setting quota: %m", *p);
254 continue;
255 } else if (ERRNO_IS_NEG_PRIVILEGE(r)) {
256 log_debug_errno(r, "Lacking privileges to query UID quota on %s, not setting quota: %m", *p);
257 continue;
258 } else if (r < 0) {
259 log_warning_errno(r, "Failed to query disk quota on %s for UID " UID_FMT ", ignoring: %m", *p, uid);
260 continue;
261 }
262
263 uint64_t v =
264 (scale == 0) ? 0 :
265 (scale == UINT32_MAX) ? UINT64_MAX :
266 (uint64_t) ((double) (sfs.f_blocks * sfs.f_frsize) * scale / UINT32_MAX);
267
268 v = MIN(v, limit);
269 v /= QIF_DQBLKSIZE;
270
271 if (FLAGS_SET(req.dqb_valid, QIF_BLIMITS) && v == req.dqb_bhardlimit) {
272 /* Shortcut things if everything is set up properly already */
273 log_debug("Configured quota on '%s' already matches the intended setting, not updating quota.", *p);
274 continue;
275 }
276
277 req.dqb_valid = QIF_BLIMITS;
278 req.dqb_bsoftlimit = req.dqb_bhardlimit = v;
279
280 r = RET_NERRNO(quotactl_fd(fd, QCMD_FIXED(Q_SETQUOTA, USRQUOTA), uid, &req));
281 if (r == -ESRCH) {
282 log_debug_errno(r, "Not setting UID quota on %s since UID quota is not supported: %m", *p);
283 continue;
284 } else if (ERRNO_IS_NEG_PRIVILEGE(r)) {
285 log_debug_errno(r, "Lacking privileges to set UID quota on %s, skipping: %m", *p);
286 continue;
287 } else if (r < 0) {
288 log_warning_errno(r, "Failed to set disk quota limit to '%s' on %s for UID " UID_FMT ", ignoring: %m", FORMAT_BYTES(v), *p, uid);
289 continue;
290 }
291
292 log_info("Successfully configured disk quota for UID " UID_FMT " on %s to %s", uid, *p, FORMAT_BYTES(v * QIF_DQBLKSIZE));
293 }
294
295 return 0;
296 }
297
298 static int do_tmpfs_quota(UserRecord *ur) {
299 int r;
300
301 assert(ur);
302
303 if (user_record_is_root(ur)) {
304 log_debug("Not applying tmpfs quota to root user.");
305 return 0;
306 }
307
308 if (!uid_is_valid(ur->uid))
309 return log_error_errno(SYNTHETIC_ERRNO(ENOMSG), "User '%s' lacks UID, refusing.", ur->user_name);
310
311 r = apply_tmpfs_quota(STRV_MAKE("/tmp", "/var/tmp"), ur->uid, ur->tmp_limit.limit, user_record_tmp_limit_scale(ur));
312 if (r < 0)
313 return r;
314
315 r = apply_tmpfs_quota(STRV_MAKE("/dev/shm"), ur->uid, ur->dev_shm_limit.limit, user_record_dev_shm_limit_scale(ur));
316 if (r < 0)
317 return r;
318
319 return 0;
320 }
321
322 static int run(int argc, char *argv[]) {
323 int r;
324
325 log_setup();
326
327 if (argc != 3)
328 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
329 "This program takes two arguments.");
330
331 const char *verb = argv[1], *user = argv[2];
332
333 if (!STR_IN_SET(verb, "start", "stop"))
334 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
335 "First argument must be either \"start\" or \"stop\".");
336
337 umask(0022);
338
339 r = mac_init();
340 if (r < 0)
341 return r;
342
343 if (streq(verb, "start")) {
344 _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
345 r = userdb_by_name(user, /* match= */ NULL, USERDB_PARSE_NUMERIC|USERDB_SUPPRESS_SHADOW, &ur);
346 if (r == -ESRCH)
347 return log_error_errno(r, "User '%s' does not exist: %m", user);
348 if (r < 0)
349 return log_error_errno(r, "Failed to resolve user '%s': %m", user);
350
351 /* We do two things here: mount the per-user XDG_RUNTIME_DIR, and set up tmpfs quota on /tmp/
352 * and /dev/shm/. */
353
354 r = 0;
355 RET_GATHER(r, do_mount(ur));
356 RET_GATHER(r, do_tmpfs_quota(ur));
357 return r;
358 }
359
360 if (streq(verb, "stop"))
361 return do_umount(user);
362
363 assert_not_reached();
364 }
365
366 DEFINE_MAIN_FUNCTION(run);