+/* SPDX-License-Identifier: LGPL-2.1+ */
/***
This file is part of systemd.
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
-#include <dirent.h>
#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
+#include <linux/magic.h>
#include <time.h>
#include <unistd.h>
return 0;
}
-
int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) {
struct stat buf;
int ret;
return 0;
}
-int readlink_and_canonicalize(const char *p, char **r) {
+int readlink_and_canonicalize(const char *p, const char *root, char **ret) {
char *t, *s;
- int j;
+ int r;
assert(p);
- assert(r);
+ assert(ret);
- j = readlink_and_make_absolute(p, &t);
- if (j < 0)
- return j;
+ r = readlink_and_make_absolute(p, &t);
+ if (r < 0)
+ return r;
- s = canonicalize_file_name(t);
- if (s) {
+ r = chase_symlinks(t, root, 0, &s);
+ if (r < 0)
+ /* If we can't follow up, then let's return the original string, slightly cleaned up. */
+ *ret = path_kill_slashes(t);
+ else {
+ *ret = s;
free(t);
- *r = s;
- } else
- *r = t;
-
- path_kill_slashes(*r);
+ }
return 0;
}
if (st.st_mode & 0002)
log_warning("Configuration file %s is marked world-writable. Please remove world writability permission bits. Proceeding anyway.", path);
- if (getpid() == 1 && (st.st_mode & 0044) != 0044)
+ if (getpid_cached() == 1 && (st.st_mode & 0044) != 0044)
log_warning("Configuration file %s is marked world-inaccessible. This has no effect as configuration data is accessible via APIs without restrictions. Proceeding anyway.", path);
return 0;
mkdir_parents(path, 0755);
fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY,
- (mode == 0 || mode == MODE_INVALID) ? 0644 : mode);
+ IN_SET(mode, 0, MODE_INVALID) ? 0644 : mode);
if (fd < 0)
return -errno;
}
int symlink_idempotent(const char *from, const char *to) {
- _cleanup_free_ char *p = NULL;
int r;
assert(from);
assert(to);
if (symlink(from, to) < 0) {
+ _cleanup_free_ char *p = NULL;
+
if (errno != EEXIST)
return -errno;
r = readlink_malloc(to, &p);
- if (r < 0)
+ if (r == -EINVAL) /* Not a symlink? In that case return the original error we encountered: -EEXIST */
+ return -EEXIST;
+ if (r < 0) /* Any other error? In that case propagate it as is */
return r;
- if (!streq(p, from))
- return -EINVAL;
+ if (!streq(p, from)) /* Not the symlink we want it to be? In that case, propagate the original -EEXIST */
+ return -EEXIST;
}
return 0;
int get_files_in_directory(const char *path, char ***list) {
_cleanup_closedir_ DIR *d = NULL;
+ struct dirent *de;
size_t bufsize = 0, n = 0;
_cleanup_strv_free_ char **l = NULL;
if (!d)
return -errno;
- for (;;) {
- struct dirent *de;
-
- errno = 0;
- de = readdir(d);
- if (!de && errno > 0)
- return -errno;
- if (!de)
- break;
-
+ FOREACH_DIRENT_ALL(de, d, return -errno) {
dirent_ensure_type(d, de);
if (!dirent_is_file(de))
return r;
}
-int chase_symlinks(const char *path, const char *_root, char **ret) {
+int chase_symlinks(const char *path, const char *original_root, unsigned flags, char **ret) {
_cleanup_free_ char *buffer = NULL, *done = NULL, *root = NULL;
_cleanup_close_ int fd = -1;
unsigned max_follow = 32; /* how many symlinks to follow before giving up and returning ELOOP */
+ bool exists = true;
char *todo;
int r;
/* This is a lot like canonicalize_file_name(), but takes an additional "root" parameter, that allows following
* symlinks relative to a root directory, instead of the root of the host.
*
- * Note that "root" matters only if we encounter an absolute symlink, it's unused otherwise. Most importantly
- * this means the path parameter passed in is not prefixed by it.
+ * Note that "root" primarily matters if we encounter an absolute symlink. It is also used when following
+ * relative symlinks to ensure they cannot be used to "escape" the root directory. The path parameter passed is
+ * assumed to be already prefixed by it, except if the CHASE_PREFIX_ROOT flag is set, in which case it is first
+ * prefixed accordingly.
*
* Algorithmically this operates on two path buffers: "done" are the components of the path we already
* processed and resolved symlinks, "." and ".." of. "todo" are the components of the path we still need to
* process. On each iteration, we move one component from "todo" to "done", processing it's special meaning
* each time. The "todo" path always starts with at least one slash, the "done" path always ends in no
* slash. We always keep an O_PATH fd to the component we are currently processing, thus keeping lookup races
- * at a minimum. */
-
- r = path_make_absolute_cwd(path, &buffer);
- if (r < 0)
- return r;
+ * at a minimum.
+ *
+ * Suggested usage: whenever you want to canonicalize a path, use this function. Pass the absolute path you got
+ * as-is: fully qualified and relative to your host's root. Optionally, specify the root parameter to tell this
+ * function what to do when encountering a symlink with an absolute path as directory: prefix it by the
+ * specified path.
+ *
+ * Note: there's also chase_symlinks_prefix() (see below), which as first step prefixes the passed path by the
+ * passed root. */
- if (_root) {
- r = path_make_absolute_cwd(_root, &root);
+ if (original_root) {
+ r = path_make_absolute_cwd(original_root, &root);
if (r < 0)
return r;
+
+ if (flags & CHASE_PREFIX_ROOT)
+ path = prefix_roota(root, path);
}
+ r = path_make_absolute_cwd(path, &buffer);
+ if (r < 0)
+ return r;
+
fd = open("/", O_CLOEXEC|O_NOFOLLOW|O_PATH);
if (fd < 0)
return -errno;
_cleanup_free_ char *parent = NULL;
int fd_parent = -1;
+ /* If we already are at the top, then going up will not change anything. This is in-line with
+ * how the kernel handles this. */
if (isempty(done) || path_equal(done, "/"))
- return -EINVAL;
+ continue;
parent = dirname_malloc(done);
if (!parent)
return -ENOMEM;
- /* Don't allow this to leave the root dir */
+ /* Don't allow this to leave the root dir. */
if (root &&
path_startswith(done, root) &&
!path_startswith(parent, root))
- return -EINVAL;
+ continue;
free_and_replace(done, parent);
/* Otherwise let's see what this is. */
child = openat(fd, first + n, O_CLOEXEC|O_NOFOLLOW|O_PATH);
- if (child < 0)
+ if (child < 0) {
+
+ if (errno == ENOENT &&
+ (flags & CHASE_NONEXISTENT) &&
+ (isempty(todo) || path_is_safe(todo))) {
+
+ /* If CHASE_NONEXISTENT is set, and the path does not exist, then that's OK, return
+ * what we got so far. But don't allow this if the remaining path contains "../ or "./"
+ * or something else weird. */
+
+ if (!strextend(&done, first, todo, NULL))
+ return -ENOMEM;
+
+ exists = false;
+ break;
+ }
+
return -errno;
+ }
if (fstat(child, &st) < 0)
return -errno;
+ if ((flags & CHASE_NO_AUTOFS) &&
+ fd_check_fstype(child, AUTOFS_SUPER_MAGIC) > 0)
+ return -EREMOTE;
if (S_ISLNK(st.st_mode)) {
+ char *joined;
+
_cleanup_free_ char *destination = NULL;
/* This is a symlink, in this case read the destination. But let's make sure we don't follow
if (fd < 0)
return -errno;
- free_and_replace(buffer, destination);
-
- todo = buffer;
free(done);
/* Note that we do not revalidate the root, we take it as is. */
return -ENOMEM;
}
- } else {
- char *joined;
-
- /* A relative destination. If so, this is what we'll prefix what's left to do with what
- * we just read, and start the loop again, but remain in the current directory. */
-
- joined = strjoin("/", destination, todo, NULL);
- if (!joined)
- return -ENOMEM;
+ /* Prefix what's left to do with what we just read, and start the loop again, but
+ * remain in the current directory. */
+ joined = strjoin(destination, todo);
+ } else
+ joined = strjoin("/", destination, todo);
+ if (!joined)
+ return -ENOMEM;
- free(buffer);
- todo = buffer = joined;
- }
+ free(buffer);
+ todo = buffer = joined;
continue;
}
return -ENOMEM;
}
- *ret = done;
- done = NULL;
+ if (ret) {
+ *ret = done;
+ done = NULL;
+ }
- return 0;
+ return exists;
+}
+
+int access_fd(int fd, int mode) {
+ char p[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(fd) + 1];
+ int r;
+
+ /* Like access() but operates on an already open fd */
+
+ xsprintf(p, "/proc/self/fd/%i", fd);
+
+ r = access(p, mode);
+ if (r < 0)
+ r = -errno;
+
+ return r;
}