summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndreas Gruenbacher <agruen@gnu.org>2015-03-07 01:23:29 +0100
committerAndreas Gruenbacher <agruen@gnu.org>2015-03-07 01:23:29 +0100
commit7a77ae9f81955a8d83f9c8eacc10eee335fc9d46 (patch)
tree2b5759dec5d0398f46e18489d896ce7a3ce187fe
parent274c66c77583be2eb5b0a61a2efc671e1d041314 (diff)
downloadpatch-7a77ae9f81955a8d83f9c8eacc10eee335fc9d46.tar.gz
Allow absolute symlinks that lead back into the working directory
* src/safe.c (cwd_stat_errno, cwd_stat): stat() result of ".". (read_symlink): When a symlink is absolute, check if it leads back into the working directory. If it does, strip off the prefix above the working directory. If the symlink points to the working directory, return an empty path. (traverse_another_path): Recognize empty paths from read_symlink(). * tests/symlinks: Absolute symlink test cases.
-rw-r--r--src/safe.c47
-rw-r--r--tests/symlinks26
2 files changed, 70 insertions, 3 deletions
diff --git a/src/safe.c b/src/safe.c
index 9e93352..cdf9f74 100644
--- a/src/safe.c
+++ b/src/safe.c
@@ -277,6 +277,9 @@ static void pop_symlink (struct symlink **stack)
free (top);
}
+int cwd_stat_errno = -1;
+struct stat cwd_stat;
+
static struct symlink *read_symlink(int dirfd, const char *name)
{
int saved_errno = errno;
@@ -298,11 +301,47 @@ static struct symlink *read_symlink(int dirfd, const char *name)
symlink->path = symlink->buffer;
if (ISSLASH (*symlink->path))
{
- errno = EXDEV;
- goto fail;
+ char *end;
+
+ if (cwd_stat_errno == -1)
+ {
+ cwd_stat_errno = stat (".", &cwd_stat) == 0 ? 0 : errno;
+ if (cwd_stat_errno)
+ goto fail_exdev;
+ }
+ end = symlink->buffer + ret;
+ for (;;)
+ {
+ char slash;
+ int rv;
+
+ slash = *end; *end = 0;
+ rv = stat (symlink->path, &st);
+ *end = slash;
+
+ if (rv == 0
+ && st.st_dev == cwd_stat.st_dev
+ && st.st_ino == cwd_stat.st_ino)
+ {
+ while (ISSLASH (*end))
+ end++;
+ symlink->path = end;
+ return symlink;
+ }
+ end--;
+ if (end == symlink->path)
+ break;
+ while (end != symlink->path + 1 && ! ISSLASH (*end))
+ end--;
+ while (end != symlink->path + 1 && ISSLASH (*(end - 1)))
+ end--;
+ }
+ goto fail_exdev;
}
return symlink;
+fail_exdev:
+ errno = EXDEV;
fail:
free (symlink);
return NULL;
@@ -446,7 +485,7 @@ static int traverse_another_path (const char **pathname, int keepfd)
}
if (stack && ! *stack->path)
pop_symlink (&stack);
- if (symlink)
+ if (symlink && *symlink->path)
{
push_symlink (&stack, symlink);
steps += count_path_components (symlink->path);
@@ -456,6 +495,8 @@ static int traverse_another_path (const char **pathname, int keepfd)
goto fail;
}
}
+ else if (symlink)
+ pop_symlink (&symlink);
if (traversed_symlink && ! stack)
{
traversed_symlink->fd =
diff --git a/tests/symlinks b/tests/symlinks
index cd85b8c..518e446 100644
--- a/tests/symlinks
+++ b/tests/symlinks
@@ -356,6 +356,32 @@ EOF
# --------------------------------------------------------------
+# Absolute symlinks that point back into the working directory
+
+mkdir d
+ln -s $PWD here
+ln -s $PWD/here/d here_d
+
+cat > good-absolute.diff <<EOF
+--- /dev/null
++++ here/d/foo
+@@ -0,0 +1 @@
++foo
+--- /dev/null
++++ here_d/bar
+@@ -0,0 +1 @@
++bar
+EOF
+
+check 'patch -p0 < good-absolute.diff' <<EOF
+patching file here/d/foo
+patching file here_d/bar
+EOF
+
+rm -rf d
+
+# --------------------------------------------------------------
+
# The backup file of a new symlink is an empty regular file.
check 'patch -p1 --backup < create-symlink.diff || echo "Status: $?"' <<EOF