Skip to content

Commit a4eaf3c

Browse files
committed
fs-util: change chase_symlinks() behaviour in regards to escaping the root dir
Previously, we'd generate an EINVAL error if it is attempted to escape a root directory with relative ".." symlinks. With this commit this is changed so that ".." from the root directory is a NOP, following the kernel's own behaviour where /.. is equivalent to /. As suggested by @keszybz.
1 parent df878e6 commit a4eaf3c

File tree

2 files changed

+11
-9
lines changed

2 files changed

+11
-9
lines changed

src/basic/fs-util.c

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -611,8 +611,8 @@ int chase_symlinks(const char *path, const char *_root, char **ret) {
611611
* symlinks relative to a root directory, instead of the root of the host.
612612
*
613613
* Note that "root" primarily matters if we encounter an absolute symlink. It is also used when following
614-
* relative symlinks to ensure they cannot be used to "escape" the root directory. (For cases where this is
615-
* attempted -EINVAL is returned.). The path parameter passed shall *not* be prefixed by it.
614+
* relative symlinks to ensure they cannot be used to "escape" the root directory. The path parameter passed
615+
* shall *not* be prefixed by it.
616616
*
617617
* Algorithmically this operates on two path buffers: "done" are the components of the path we already
618618
* processed and resolved symlinks, "." and ".." of. "todo" are the components of the path we still need to
@@ -674,18 +674,20 @@ int chase_symlinks(const char *path, const char *_root, char **ret) {
674674
_cleanup_free_ char *parent = NULL;
675675
int fd_parent = -1;
676676

677+
/* If we already are at the top, then going up will not change anything. This is in-line with
678+
* how the kernel handles this. */
677679
if (isempty(done) || path_equal(done, "/"))
678-
return -EINVAL;
680+
continue;
679681

680682
parent = dirname_malloc(done);
681683
if (!parent)
682684
return -ENOMEM;
683685

684-
/* Don't allow this to leave the root dir */
686+
/* Don't allow this to leave the root dir. */
685687
if (root &&
686688
path_startswith(done, root) &&
687689
!path_startswith(parent, root))
688-
return -EINVAL;
690+
continue;
689691

690692
free_and_replace(done, parent);
691693

src/test/test-fs-util.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,21 +97,21 @@ static void test_chase_symlinks(void) {
9797

9898
result = mfree(result);
9999
r = chase_symlinks(p, temp, &result);
100-
assert_se(r == -EINVAL);
100+
assert_se(r == 0 && path_equal(result, temp));
101101

102102
p = strjoina(temp, "/6dotsusr");
103103
assert_se(symlink("../../../usr", p) >= 0);
104104

105105
result = mfree(result);
106106
r = chase_symlinks(p, temp, &result);
107-
assert_se(r == -EINVAL);
107+
assert_se(r == 0 && path_equal(result, q));
108108

109109
p = strjoina(temp, "/top/8dotsusr");
110110
assert_se(symlink("../../../../usr", p) >= 0);
111111

112112
result = mfree(result);
113113
r = chase_symlinks(p, temp, &result);
114-
assert_se(r == -EINVAL);
114+
assert_se(r == 0 && path_equal(result, q));
115115

116116
/* Paths that contain repeated slashes */
117117

@@ -137,7 +137,7 @@ static void test_chase_symlinks(void) {
137137

138138
result = mfree(result);
139139
r = chase_symlinks("/etc/./.././", "/etc", &result);
140-
assert_se(r == -EINVAL);
140+
assert_se(r == 0 && path_equal(result, "/etc"));
141141

142142
result = mfree(result);
143143
r = chase_symlinks("/etc/machine-id/foo", NULL, &result);

0 commit comments

Comments
 (0)