]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/basic/chase-symlinks.c
man: explicitly document that "reboot -f" is different from "systemctl reboot -f"
[thirdparty/systemd.git] / src / basic / chase-symlinks.c
CommitLineData
f4351959
LP
1/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3#include <linux/magic.h>
4
5#include "alloc-util.h"
6#include "chase-symlinks.h"
7#include "fd-util.h"
01bebba3 8#include "fileio.h"
f4351959
LP
9#include "fs-util.h"
10#include "glyph-util.h"
11#include "log.h"
12#include "path-util.h"
13#include "string-util.h"
14#include "user-util.h"
15
16bool unsafe_transition(const struct stat *a, const struct stat *b) {
17 /* Returns true if the transition from a to b is safe, i.e. that we never transition from unprivileged to
18 * privileged files or directories. Why bother? So that unprivileged code can't symlink to privileged files
19 * making us believe we read something safe even though it isn't safe in the specific context we open it in. */
20
21 if (a->st_uid == 0) /* Transitioning from privileged to unprivileged is always fine */
22 return false;
23
24 return a->st_uid != b->st_uid; /* Otherwise we need to stay within the same UID */
25}
26
8f47f880 27static int log_unsafe_transition(int a, int b, const char *path, ChaseSymlinksFlags flags) {
f4351959
LP
28 _cleanup_free_ char *n1 = NULL, *n2 = NULL, *user_a = NULL, *user_b = NULL;
29 struct stat st;
30
31 if (!FLAGS_SET(flags, CHASE_WARN))
32 return -ENOLINK;
33
34 (void) fd_get_path(a, &n1);
35 (void) fd_get_path(b, &n2);
36
37 if (fstat(a, &st) == 0)
38 user_a = uid_to_name(st.st_uid);
39 if (fstat(b, &st) == 0)
40 user_b = uid_to_name(st.st_uid);
41
42 return log_warning_errno(SYNTHETIC_ERRNO(ENOLINK),
43 "Detected unsafe path transition %s (owned by %s) %s %s (owned by %s) during canonicalization of %s.",
fc03e80c 44 strna(n1), strna(user_a), special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), strna(n2), strna(user_b), path);
f4351959
LP
45}
46
8f47f880 47static int log_autofs_mount_point(int fd, const char *path, ChaseSymlinksFlags flags) {
f4351959
LP
48 _cleanup_free_ char *n1 = NULL;
49
50 if (!FLAGS_SET(flags, CHASE_WARN))
51 return -EREMOTE;
52
53 (void) fd_get_path(fd, &n1);
54
55 return log_warning_errno(SYNTHETIC_ERRNO(EREMOTE),
56 "Detected autofs mount point %s during canonicalization of %s.",
57 strna(n1), path);
58}
59
8f47f880
LP
60int chase_symlinks(
61 const char *path,
62 const char *original_root,
63 ChaseSymlinksFlags flags,
64 char **ret_path,
65 int *ret_fd) {
66
f4351959
LP
67 _cleanup_free_ char *buffer = NULL, *done = NULL, *root = NULL;
68 _cleanup_close_ int fd = -1;
69 unsigned max_follow = CHASE_SYMLINKS_MAX; /* how many symlinks to follow before giving up and returning ELOOP */
70 bool exists = true, append_trail_slash = false;
71 struct stat previous_stat;
72 const char *todo;
73 int r;
74
75 assert(path);
76
77 /* Either the file may be missing, or we return an fd to the final object, but both make no sense */
78 if ((flags & CHASE_NONEXISTENT) && ret_fd)
79 return -EINVAL;
80
81 if ((flags & CHASE_STEP) && ret_fd)
82 return -EINVAL;
83
84 if (isempty(path))
85 return -EINVAL;
86
7b9be862
LP
87 /* We don't support relative paths in combination with a root directory */
88 if (FLAGS_SET(flags, CHASE_PREFIX_ROOT) && !path_is_absolute(path))
89 return -EINVAL;
90
f4351959
LP
91 /* This is a lot like canonicalize_file_name(), but takes an additional "root" parameter, that allows following
92 * symlinks relative to a root directory, instead of the root of the host.
93 *
94 * Note that "root" primarily matters if we encounter an absolute symlink. It is also used when following
95 * relative symlinks to ensure they cannot be used to "escape" the root directory. The path parameter passed is
96 * assumed to be already prefixed by it, except if the CHASE_PREFIX_ROOT flag is set, in which case it is first
97 * prefixed accordingly.
98 *
99 * Algorithmically this operates on two path buffers: "done" are the components of the path we already
100 * processed and resolved symlinks, "." and ".." of. "todo" are the components of the path we still need to
101 * process. On each iteration, we move one component from "todo" to "done", processing it's special meaning
102 * each time. The "todo" path always starts with at least one slash, the "done" path always ends in no
103 * slash. We always keep an O_PATH fd to the component we are currently processing, thus keeping lookup races
104 * to a minimum.
105 *
106 * Suggested usage: whenever you want to canonicalize a path, use this function. Pass the absolute path you got
107 * as-is: fully qualified and relative to your host's root. Optionally, specify the root parameter to tell this
108 * function what to do when encountering a symlink with an absolute path as directory: prefix it by the
109 * specified path.
110 *
111 * There are five ways to invoke this function:
112 *
113 * 1. Without CHASE_STEP or ret_fd: in this case the path is resolved and the normalized path is
114 * returned in `ret_path`. The return value is < 0 on error. If CHASE_NONEXISTENT is also set, 0
115 * is returned if the file doesn't exist, > 0 otherwise. If CHASE_NONEXISTENT is not set, >= 0 is
116 * returned if the destination was found, -ENOENT if it wasn't.
117 *
118 * 2. With ret_fd: in this case the destination is opened after chasing it as O_PATH and this file
119 * descriptor is returned as return value. This is useful to open files relative to some root
120 * directory. Note that the returned O_PATH file descriptors must be converted into a regular one (using
121 * fd_reopen() or such) before it can be used for reading/writing. ret_fd may not be combined with
122 * CHASE_NONEXISTENT.
123 *
124 * 3. With CHASE_STEP: in this case only a single step of the normalization is executed, i.e. only the first
125 * symlink or ".." component of the path is resolved, and the resulting path is returned. This is useful if
126 * a caller wants to trace the path through the file system verbosely. Returns < 0 on error, > 0 if the
127 * path is fully normalized, and == 0 for each normalization step. This may be combined with
128 * CHASE_NONEXISTENT, in which case 1 is returned when a component is not found.
129 *
130 * 4. With CHASE_SAFE: in this case the path must not contain unsafe transitions, i.e. transitions from
131 * unprivileged to privileged files or directories. In such cases the return value is -ENOLINK. If
132 * CHASE_WARN is also set, a warning describing the unsafe transition is emitted.
133 *
134 * 5. With CHASE_NO_AUTOFS: in this case if an autofs mount point is encountered, path normalization
135 * is aborted and -EREMOTE is returned. If CHASE_WARN is also set, a warning showing the path of
136 * the mount point is emitted.
137 */
138
139 /* A root directory of "/" or "" is identical to none */
140 if (empty_or_root(original_root))
141 original_root = NULL;
142
143 if (!original_root && !ret_path && !(flags & (CHASE_NONEXISTENT|CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_STEP)) && ret_fd) {
144 /* Shortcut the ret_fd case if the caller isn't interested in the actual path and has no root set
145 * and doesn't care about any of the other special features we provide either. */
146 r = open(path, O_PATH|O_CLOEXEC|((flags & CHASE_NOFOLLOW) ? O_NOFOLLOW : 0));
147 if (r < 0)
148 return -errno;
149
150 *ret_fd = r;
151 return 0;
152 }
153
154 if (original_root) {
155 r = path_make_absolute_cwd(original_root, &root);
156 if (r < 0)
157 return r;
158
159 /* Simplify the root directory, so that it has no duplicate slashes and nothing at the
160 * end. While we won't resolve the root path we still simplify it. Note that dropping the
161 * trailing slash should not change behaviour, since when opening it we specify O_DIRECTORY
162 * anyway. Moreover at the end of this function after processing everything we'll always turn
163 * the empty string back to "/". */
164 delete_trailing_chars(root, "/");
165 path_simplify(root);
166
167 if (flags & CHASE_PREFIX_ROOT) {
7b9be862
LP
168 buffer = path_join(root, path);
169 if (!buffer)
170 return -ENOMEM;
f4351959
LP
171 }
172 }
173
7b9be862
LP
174 if (!buffer) {
175 r = path_make_absolute_cwd(path, &buffer);
176 if (r < 0)
177 return r;
178 }
f4351959 179
69cf392f 180 fd = open(empty_to_root(root), O_CLOEXEC|O_DIRECTORY|O_PATH);
f4351959
LP
181 if (fd < 0)
182 return -errno;
183
184 if (flags & CHASE_SAFE)
185 if (fstat(fd, &previous_stat) < 0)
186 return -errno;
187
188 if (flags & CHASE_TRAIL_SLASH)
189 append_trail_slash = endswith(buffer, "/") || endswith(buffer, "/.");
190
191 if (root) {
192 /* If we are operating on a root directory, let's take the root directory as it is. */
193
194 todo = path_startswith(buffer, root);
195 if (!todo)
196 return log_full_errno(flags & CHASE_WARN ? LOG_WARNING : LOG_DEBUG,
197 SYNTHETIC_ERRNO(ECHRNG),
198 "Specified path '%s' is outside of specified root directory '%s', refusing to resolve.",
199 path, root);
200
201 done = strdup(root);
202 } else {
203 todo = buffer;
204 done = strdup("/");
205 }
0ac6cdd6
LP
206 if (!done)
207 return -ENOMEM;
f4351959
LP
208
209 for (;;) {
210 _cleanup_free_ char *first = NULL;
211 _cleanup_close_ int child = -1;
212 struct stat st;
213 const char *e;
214
860f4c6a 215 r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e);
f4351959
LP
216 if (r < 0)
217 return r;
218 if (r == 0) { /* We reached the end. */
219 if (append_trail_slash)
220 if (!strextend(&done, "/"))
221 return -ENOMEM;
222 break;
223 }
224
225 first = strndup(e, r);
226 if (!first)
227 return -ENOMEM;
228
229 /* Two dots? Then chop off the last bit of what we already found out. */
230 if (path_equal(first, "..")) {
231 _cleanup_free_ char *parent = NULL;
232 _cleanup_close_ int fd_parent = -1;
233
234 /* If we already are at the top, then going up will not change anything. This is in-line with
235 * how the kernel handles this. */
236 if (empty_or_root(done))
237 continue;
238
57f9ca3a
LP
239 r = path_extract_directory(done, &parent);
240 if (r < 0)
241 return r;
f4351959
LP
242
243 /* Don't allow this to leave the root dir. */
244 if (root &&
245 path_startswith(done, root) &&
246 !path_startswith(parent, root))
247 continue;
248
249 free_and_replace(done, parent);
250
251 if (flags & CHASE_STEP)
252 goto chased_one;
253
254 fd_parent = openat(fd, "..", O_CLOEXEC|O_NOFOLLOW|O_PATH);
255 if (fd_parent < 0)
256 return -errno;
257
258 if (flags & CHASE_SAFE) {
259 if (fstat(fd_parent, &st) < 0)
260 return -errno;
261
262 if (unsafe_transition(&previous_stat, &st))
263 return log_unsafe_transition(fd, fd_parent, path, flags);
264
265 previous_stat = st;
266 }
267
268 safe_close(fd);
269 fd = TAKE_FD(fd_parent);
270
271 continue;
272 }
273
274 /* Otherwise let's see what this is. */
275 child = openat(fd, first, O_CLOEXEC|O_NOFOLLOW|O_PATH);
276 if (child < 0) {
277 if (errno == ENOENT &&
278 (flags & CHASE_NONEXISTENT) &&
279 (isempty(todo) || path_is_safe(todo))) {
280 /* If CHASE_NONEXISTENT is set, and the path does not exist, then
281 * that's OK, return what we got so far. But don't allow this if the
282 * remaining path contains "../" or something else weird. */
283
284 if (!path_extend(&done, first, todo))
285 return -ENOMEM;
286
287 exists = false;
288 break;
289 }
290
291 return -errno;
292 }
293
294 if (fstat(child, &st) < 0)
295 return -errno;
296 if ((flags & CHASE_SAFE) &&
297 unsafe_transition(&previous_stat, &st))
298 return log_unsafe_transition(fd, child, path, flags);
299
300 previous_stat = st;
301
302 if ((flags & CHASE_NO_AUTOFS) &&
303 fd_is_fs_type(child, AUTOFS_SUPER_MAGIC) > 0)
304 return log_autofs_mount_point(child, path, flags);
305
306 if (S_ISLNK(st.st_mode) && !((flags & CHASE_NOFOLLOW) && isempty(todo))) {
307 _cleanup_free_ char *destination = NULL;
308
309 /* This is a symlink, in this case read the destination. But let's make sure we
310 * don't follow symlinks without bounds. */
311 if (--max_follow <= 0)
312 return -ELOOP;
313
314 r = readlinkat_malloc(fd, first, &destination);
315 if (r < 0)
316 return r;
317 if (isempty(destination))
318 return -EINVAL;
319
320 if (path_is_absolute(destination)) {
321
322 /* An absolute destination. Start the loop from the beginning, but use the root
323 * directory as base. */
324
325 safe_close(fd);
69cf392f 326 fd = open(empty_to_root(root), O_CLOEXEC|O_DIRECTORY|O_PATH);
f4351959
LP
327 if (fd < 0)
328 return -errno;
329
330 if (flags & CHASE_SAFE) {
331 if (fstat(fd, &st) < 0)
332 return -errno;
333
334 if (unsafe_transition(&previous_stat, &st))
335 return log_unsafe_transition(child, fd, path, flags);
336
337 previous_stat = st;
338 }
339
340 /* Note that we do not revalidate the root, we take it as is. */
341 r = free_and_strdup(&done, empty_to_root(root));
342 if (r < 0)
343 return r;
344 }
345
346 /* Prefix what's left to do with what we just read, and start the loop again, but
347 * remain in the current directory. */
348 if (!path_extend(&destination, todo))
349 return -ENOMEM;
350
351 free_and_replace(buffer, destination);
352 todo = buffer;
353
354 if (flags & CHASE_STEP)
355 goto chased_one;
356
357 continue;
358 }
359
360 /* If this is not a symlink, then let's just add the name we read to what we already verified. */
361 if (!path_extend(&done, first))
362 return -ENOMEM;
363
364 /* And iterate again, but go one directory further down. */
365 safe_close(fd);
366 fd = TAKE_FD(child);
367 }
368
369 if (ret_path)
370 *ret_path = TAKE_PTR(done);
371
372 if (ret_fd) {
373 /* Return the O_PATH fd we currently are looking to the caller. It can translate it to a
374 * proper fd by opening /proc/self/fd/xyz. */
375
376 assert(fd >= 0);
377 *ret_fd = TAKE_FD(fd);
378 }
379
380 if (flags & CHASE_STEP)
381 return 1;
382
383 return exists;
384
385chased_one:
386 if (ret_path) {
387 const char *e;
388
389 /* todo may contain slashes at the beginning. */
860f4c6a 390 r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e);
f4351959
LP
391 if (r < 0)
392 return r;
393 if (r == 0)
394 *ret_path = TAKE_PTR(done);
395 else {
396 char *c;
397
398 c = path_join(done, e);
399 if (!c)
400 return -ENOMEM;
401
402 *ret_path = c;
403 }
404 }
405
406 return 0;
407}
408
409int chase_symlinks_and_open(
410 const char *path,
411 const char *root,
8f47f880 412 ChaseSymlinksFlags chase_flags,
f4351959
LP
413 int open_flags,
414 char **ret_path) {
415
416 _cleanup_close_ int path_fd = -1;
417 _cleanup_free_ char *p = NULL;
418 int r;
419
81a7eac1 420 if (chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))
f4351959
LP
421 return -EINVAL;
422
423 if (empty_or_root(root) && !ret_path && (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE)) == 0) {
424 /* Shortcut this call if none of the special features of this call are requested */
69570232 425 r = open(path, open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0));
f4351959
LP
426 if (r < 0)
427 return -errno;
428
429 return r;
430 }
431
432 r = chase_symlinks(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
433 if (r < 0)
434 return r;
435 assert(path_fd >= 0);
436
437 r = fd_reopen(path_fd, open_flags);
438 if (r < 0)
439 return r;
440
441 if (ret_path)
442 *ret_path = TAKE_PTR(p);
443
444 return r;
445}
446
447int chase_symlinks_and_opendir(
448 const char *path,
449 const char *root,
8f47f880 450 ChaseSymlinksFlags chase_flags,
f4351959
LP
451 char **ret_path,
452 DIR **ret_dir) {
453
454 _cleanup_close_ int path_fd = -1;
455 _cleanup_free_ char *p = NULL;
456 DIR *d;
457 int r;
458
459 if (!ret_dir)
460 return -EINVAL;
81a7eac1 461 if (chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))
f4351959
LP
462 return -EINVAL;
463
464 if (empty_or_root(root) && !ret_path && (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE)) == 0) {
465 /* Shortcut this call if none of the special features of this call are requested */
466 d = opendir(path);
467 if (!d)
468 return -errno;
469
470 *ret_dir = d;
471 return 0;
472 }
473
474 r = chase_symlinks(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
475 if (r < 0)
476 return r;
477 assert(path_fd >= 0);
478
479 d = opendir(FORMAT_PROC_FD_PATH(path_fd));
480 if (!d)
481 return -errno;
482
483 if (ret_path)
484 *ret_path = TAKE_PTR(p);
485
486 *ret_dir = d;
487 return 0;
488}
489
490int chase_symlinks_and_stat(
491 const char *path,
492 const char *root,
8f47f880 493 ChaseSymlinksFlags chase_flags,
f4351959
LP
494 char **ret_path,
495 struct stat *ret_stat,
496 int *ret_fd) {
497
498 _cleanup_close_ int path_fd = -1;
499 _cleanup_free_ char *p = NULL;
500 int r;
501
502 assert(path);
503 assert(ret_stat);
504
81a7eac1 505 if (chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))
f4351959
LP
506 return -EINVAL;
507
37b9bc56 508 if (empty_or_root(root) && !ret_path && (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE)) == 0 && !ret_fd) {
f4351959 509 /* Shortcut this call if none of the special features of this call are requested */
69570232
LP
510
511 if (fstatat(AT_FDCWD, path, ret_stat, FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0) < 0)
f4351959
LP
512 return -errno;
513
514 return 1;
515 }
516
517 r = chase_symlinks(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
518 if (r < 0)
519 return r;
520 assert(path_fd >= 0);
521
522 if (fstat(path_fd, ret_stat) < 0)
523 return -errno;
524
525 if (ret_path)
526 *ret_path = TAKE_PTR(p);
2b2caea2
LP
527 if (ret_fd)
528 *ret_fd = TAKE_FD(path_fd);
529
530 return 1;
531}
532
533int chase_symlinks_and_access(
534 const char *path,
535 const char *root,
536 ChaseSymlinksFlags chase_flags,
537 int access_mode,
538 char **ret_path,
539 int *ret_fd) {
540
541 _cleanup_close_ int path_fd = -1;
542 _cleanup_free_ char *p = NULL;
543 int r;
544
545 assert(path);
546
547 if (chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))
548 return -EINVAL;
549
550 if (empty_or_root(root) && !ret_path && (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE)) == 0 && !ret_fd) {
551 /* Shortcut this call if none of the special features of this call are requested */
552
553 if (faccessat(AT_FDCWD, path, access_mode, FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0) < 0)
554 return -errno;
555
556 return 1;
557 }
558
559 r = chase_symlinks(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
560 if (r < 0)
561 return r;
562 assert(path_fd >= 0);
563
564 r = access_fd(path_fd, access_mode);
565 if (r < 0)
566 return r;
567
568 if (ret_path)
569 *ret_path = TAKE_PTR(p);
f4351959
LP
570 if (ret_fd)
571 *ret_fd = TAKE_FD(path_fd);
572
573 return 1;
574}
01bebba3
LP
575
576int chase_symlinks_and_fopen_unlocked(
577 const char *path,
578 const char *root,
8f47f880 579 ChaseSymlinksFlags chase_flags,
01bebba3
LP
580 const char *open_flags,
581 char **ret_path,
582 FILE **ret_file) {
583
584 _cleanup_free_ char *final_path = NULL;
585 _cleanup_close_ int fd = -1;
586 int mode_flags, r;
587
588 assert(path);
589 assert(open_flags);
590 assert(ret_file);
591
592 mode_flags = fopen_mode_to_flags(open_flags);
593 if (mode_flags < 0)
594 return mode_flags;
595
596 fd = chase_symlinks_and_open(path, root, chase_flags, mode_flags, ret_path ? &final_path : NULL);
597 if (fd < 0)
598 return fd;
599
600 r = take_fdopen_unlocked(&fd, open_flags, ret_file);
601 if (r < 0)
602 return r;
603
604 if (ret_path)
605 *ret_path = TAKE_PTR(final_path);
606
607 return 0;
608}