diff options
author | Andreas Gruenbacher <agruen@gnu.org> | 2015-03-07 01:23:29 +0100 |
---|---|---|
committer | Andreas Gruenbacher <agruen@gnu.org> | 2015-03-07 01:23:29 +0100 |
commit | 7a77ae9f81955a8d83f9c8eacc10eee335fc9d46 (patch) | |
tree | 2b5759dec5d0398f46e18489d896ce7a3ce187fe | |
parent | 274c66c77583be2eb5b0a61a2efc671e1d041314 (diff) | |
download | patch-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.c | 47 | ||||
-rw-r--r-- | tests/symlinks | 26 |
2 files changed, 70 insertions, 3 deletions
@@ -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 |