summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/basic/fs-util.c22
-rw-r--r--src/basic/fs-util.h1
-rw-r--r--src/test/test-fs-util.c56
3 files changed, 66 insertions, 13 deletions
diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c
index 0a3e983631..0ca4656fdd 100644
--- a/src/basic/fs-util.c
+++ b/src/basic/fs-util.c
@@ -602,6 +602,7 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
_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;
@@ -707,8 +708,25 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
/* 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_NON_EXISTING) &&
+ (isempty(todo) || path_is_safe(todo))) {
+
+ /* If CHASE_NON_EXISTING 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;
@@ -793,5 +811,5 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
*ret = done;
done = NULL;
- return 0;
+ return exists;
}
diff --git a/src/basic/fs-util.h b/src/basic/fs-util.h
index ee3d6bf7af..3931534a42 100644
--- a/src/basic/fs-util.h
+++ b/src/basic/fs-util.h
@@ -80,6 +80,7 @@ int inotify_add_watch_fd(int fd, int what, uint32_t mask);
enum {
CHASE_PREFIX_ROOT = 1, /* If set, the specified path will be prefixed by the specified root before beginning the iteration */
+ CHASE_NON_EXISTING = 2, /* If set, it's OK if the path doesn't actually exist. */
};
int chase_symlinks(const char *path_with_prefix, const char *root, unsigned flags, char **ret);
diff --git a/src/test/test-fs-util.c b/src/test/test-fs-util.c
index fac3a1d089..2570bc5859 100644
--- a/src/test/test-fs-util.c
+++ b/src/test/test-fs-util.c
@@ -63,7 +63,7 @@ static void test_chase_symlinks(void) {
/* Paths that use symlinks underneath the "root" */
r = chase_symlinks(p, NULL, 0, &result);
- assert_se(r >= 0);
+ assert_se(r > 0);
assert_se(path_equal(result, "/usr"));
result = mfree(result);
@@ -71,10 +71,15 @@ static void test_chase_symlinks(void) {
assert_se(r == -ENOENT);
q = strjoina(temp, "/usr");
+
+ r = chase_symlinks(p, temp, CHASE_NON_EXISTING, &result);
+ assert_se(r == 0);
+ assert_se(path_equal(result, q));
+
assert_se(mkdir(q, 0700) >= 0);
r = chase_symlinks(p, temp, 0, &result);
- assert_se(r >= 0);
+ assert_se(r > 0);
assert_se(path_equal(result, q));
p = strjoina(temp, "/slash");
@@ -82,12 +87,12 @@ static void test_chase_symlinks(void) {
result = mfree(result);
r = chase_symlinks(p, NULL, 0, &result);
- assert_se(r >= 0);
+ assert_se(r > 0);
assert_se(path_equal(result, "/"));
result = mfree(result);
r = chase_symlinks(p, temp, 0, &result);
- assert_se(r >= 0);
+ assert_se(r > 0);
assert_se(path_equal(result, temp));
/* Paths that would "escape" outside of the "root" */
@@ -97,21 +102,21 @@ static void test_chase_symlinks(void) {
result = mfree(result);
r = chase_symlinks(p, temp, 0, &result);
- assert_se(r == 0 && path_equal(result, temp));
+ assert_se(r > 0 && path_equal(result, temp));
p = strjoina(temp, "/6dotsusr");
assert_se(symlink("../../../usr", p) >= 0);
result = mfree(result);
r = chase_symlinks(p, temp, 0, &result);
- assert_se(r == 0 && path_equal(result, q));
+ assert_se(r > 0 && path_equal(result, q));
p = strjoina(temp, "/top/8dotsusr");
assert_se(symlink("../../../../usr", p) >= 0);
result = mfree(result);
r = chase_symlinks(p, temp, 0, &result);
- assert_se(r == 0 && path_equal(result, q));
+ assert_se(r > 0 && path_equal(result, q));
/* Paths that contain repeated slashes */
@@ -120,24 +125,24 @@ static void test_chase_symlinks(void) {
result = mfree(result);
r = chase_symlinks(p, NULL, 0, &result);
- assert_se(r >= 0);
+ assert_se(r > 0);
assert_se(path_equal(result, "/usr"));
result = mfree(result);
r = chase_symlinks(p, temp, 0, &result);
- assert_se(r >= 0);
+ assert_se(r > 0);
assert_se(path_equal(result, q));
/* Paths using . */
result = mfree(result);
r = chase_symlinks("/etc/./.././", NULL, 0, &result);
- assert_se(r >= 0);
+ assert_se(r > 0);
assert_se(path_equal(result, "/"));
result = mfree(result);
r = chase_symlinks("/etc/./.././", "/etc", 0, &result);
- assert_se(r == 0 && path_equal(result, "/etc"));
+ assert_se(r > 0 && path_equal(result, "/etc"));
result = mfree(result);
r = chase_symlinks("/etc/machine-id/foo", NULL, 0, &result);
@@ -151,6 +156,35 @@ static void test_chase_symlinks(void) {
r = chase_symlinks(p, NULL, 0, &result);
assert_se(r == -ELOOP);
+ /* Path which doesn't exist */
+
+ p = strjoina(temp, "/idontexist");
+ r = chase_symlinks(p, NULL, 0, &result);
+ assert_se(r == -ENOENT);
+
+ r = chase_symlinks(p, NULL, CHASE_NON_EXISTING, &result);
+ assert_se(r == 0);
+ assert_se(path_equal(result, p));
+ result = mfree(result);
+
+ p = strjoina(temp, "/idontexist/meneither");
+ r = chase_symlinks(p, NULL, 0, &result);
+ assert_se(r == -ENOENT);
+
+ r = chase_symlinks(p, NULL, CHASE_NON_EXISTING, &result);
+ assert_se(r == 0);
+ assert_se(path_equal(result, p));
+ result = mfree(result);
+
+ /* Path which doesn't exist, but contains weird stuff */
+
+ p = strjoina(temp, "/idontexist/..");
+ r = chase_symlinks(p, NULL, 0, &result);
+ assert_se(r == -ENOENT);
+
+ r = chase_symlinks(p, NULL, CHASE_NON_EXISTING, &result);
+ assert_se(r == -ENOENT);
+
assert_se(rm_rf(temp, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
}