All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 0/4] "git apply" safety
@ 2015-02-02 23:27 Junio C Hamano
  2015-02-02 23:27 ` [PATCH v2 1/4] apply: reject input that touches outside $cwd Junio C Hamano
                   ` (4 more replies)
  0 siblings, 5 replies; 34+ messages in thread
From: Junio C Hamano @ 2015-02-02 23:27 UTC (permalink / raw)
  To: git

"git apply" have been fairly careless about letting the input follow
symbolic links, especially when used without the --index/--cached
options (which was more or less deliberate to mimic what "patch"
used to do).  When the input tells it to modify a/b/c, and lstat(2)
said that there is "a/b/c" that matches the preimage in the input,
we happily overwrote it, even when a/b is a symbolic link that
pointed somewhere, even outside the working tree.

This series tightens things a bit for safety.

 (1) By default, we reject patches to ".git/file", "../some/where",
     "./this/././that", etc., i.e. the names you cannot add to the
     index.  Those who use "git apply" (without --index/--cached) as
     a replacement for GNU patch can use --unsafe-paths option to
     override this safety.  This is what patch 1/4 does.

 (2) We do not allow a patch to depend on a location beyond a
     symbolic link (this includes "a patch to remove a path beyond a
     symbolic link").  This is patch 2/4 and 3/4.

 (3) We do not allow a patch to create result on a location beyond a
     symbolic link.  This is patch 4/4.

There is no knob to override the latter two points, as this is not a
safety but is a correctness issue.  Because Git keeps track of and
can express changes to symbolic links, a patch that expects a file
"a/b/c" to be tracked (either the patch adds it, or it modifies an
existing file tehre) implicitly expects that there is no symbolic
link "a/b", so attempting to apply such a patch to a tree with a
symbolic link at "a/b", even when the link points at some directory,
must detect that the target tree does not match what the patch's
preimage expects and fail.

The previous attempt begins at around here:

  http://thread.gmane.org/gmane.linux.kernel/1874498/focus=1878385

Junio C Hamano (4):
  apply: reject input that touches outside $cwd
  apply: do not read from the filesystem under --index
  apply: do not read from beyond a symbolic link
  apply: do not touch a file beyond a symbolic link

 Documentation/git-apply.txt     |  14 +++-
 builtin/apply.c                 | 139 +++++++++++++++++++++++++++++++++++++++-
 t/t4122-apply-symlink-inside.sh |  89 +++++++++++++++++++++++++
 t/t4139-apply-escape.sh         | 137 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 377 insertions(+), 2 deletions(-)
 create mode 100755 t/t4139-apply-escape.sh

-- 
2.3.0-rc2-164-g799cdce

^ permalink raw reply	[flat|nested] 34+ messages in thread

* [PATCH v2 1/4] apply: reject input that touches outside $cwd
  2015-02-02 23:27 [PATCH v2 0/4] "git apply" safety Junio C Hamano
@ 2015-02-02 23:27 ` Junio C Hamano
  2015-02-03  0:45   ` Jeff King
                     ` (2 more replies)
  2015-02-02 23:27 ` [PATCH v2 2/4] apply: do not read from the filesystem under --index Junio C Hamano
                   ` (3 subsequent siblings)
  4 siblings, 3 replies; 34+ messages in thread
From: Junio C Hamano @ 2015-02-02 23:27 UTC (permalink / raw)
  To: git

By default, a patch that affects outside the working area is
rejected as a mistake (or a mischief); Git itself does not create
such a patch, unless the user bends backwards and specifies a
non-standard prefix to "git diff" and friends.

When `git apply` is used without either `--index` or `--cached`
option as a "better GNU patch", the user can pass `--unsafe-paths`
option to override this safety check.  This cannot be used to escape
outside the working tree when using `--index` or `--cached` to apply
the patch to the index.

The new test was stolen from Jeff King with slight enhancements.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 Documentation/git-apply.txt |  14 ++++-
 builtin/apply.c             |  26 +++++++++
 t/t4139-apply-escape.sh     | 137 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 176 insertions(+), 1 deletion(-)
 create mode 100755 t/t4139-apply-escape.sh

diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt
index f605327..a6e83a9 100644
--- a/Documentation/git-apply.txt
+++ b/Documentation/git-apply.txt
@@ -16,7 +16,7 @@ SYNOPSIS
 	  [--ignore-space-change | --ignore-whitespace ]
 	  [--whitespace=(nowarn|warn|fix|error|error-all)]
 	  [--exclude=<path>] [--include=<path>] [--directory=<root>]
-	  [--verbose] [<patch>...]
+	  [--verbose] [--unsafe-paths] [<patch>...]
 
 DESCRIPTION
 -----------
@@ -229,6 +229,18 @@ For example, a patch that talks about updating `a/git-gui.sh` to `b/git-gui.sh`
 can be applied to the file in the working tree `modules/git-gui/git-gui.sh` by
 running `git apply --directory=modules/git-gui`.
 
+--unsafe-paths::
+	By default, a patch that affects outside the working area is
+	rejected as a mistake (or a mischief); Git itself never
+	creates such a patch unless the user bends backwards and
+	specifies nonstandard prefix to "git diff" and friends.
++
+When `git apply` is used without either `--index` or `--cached`
+option as a "better GNU patch", the user can pass `--unsafe-paths`
+option to override this safety check.  This cannot be used to escape
+outside the working tree when using `--index` or `--cached` to apply
+the patch to the index.
+
 Configuration
 -------------
 
diff --git a/builtin/apply.c b/builtin/apply.c
index ef32e4f..c751ddf 100644
--- a/builtin/apply.c
+++ b/builtin/apply.c
@@ -50,6 +50,7 @@ static int apply_verbosely;
 static int allow_overlap;
 static int no_add;
 static int threeway;
+static int unsafe_paths;
 static const char *fake_ancestor;
 static int line_termination = '\n';
 static unsigned int p_context = UINT_MAX;
@@ -3483,6 +3484,23 @@ static int check_to_create(const char *new_name, int ok_if_exists)
 	return 0;
 }
 
+static void die_on_unsafe_path(struct patch *patch)
+{
+	const char *old_name = NULL;
+	const char *new_name = NULL;
+	if (patch->is_delete)
+		old_name = patch->old_name;
+	else if (!patch->is_new && !patch->is_copy)
+		old_name = patch->old_name;
+	if (!patch->is_delete)
+		new_name = patch->new_name;
+
+	if (old_name && !verify_path(old_name))
+		die(_("invalid path '%s'"), old_name);
+	if (new_name && !verify_path(new_name))
+		die(_("invalid path '%s'"), new_name);
+}
+
 /*
  * Check and apply the patch in-core; leave the result in patch->result
  * for the caller to write it out to the final destination.
@@ -3570,6 +3588,9 @@ static int check_patch(struct patch *patch)
 		}
 	}
 
+	if (!unsafe_paths)
+		die_on_unsafe_path(patch);
+
 	if (apply_data(patch, &st, ce) < 0)
 		return error(_("%s: patch does not apply"), name);
 	patch->rejected = 0;
@@ -4379,6 +4400,8 @@ int cmd_apply(int argc, const char **argv, const char *prefix_)
 			N_("make sure the patch is applicable to the current index")),
 		OPT_BOOL(0, "cached", &cached,
 			N_("apply a patch without touching the working tree")),
+		OPT_BOOL(0, "unsafe-paths", &unsafe_paths,
+			N_("accept a patch to touch outside the working area")),
 		OPT_BOOL(0, "apply", &force_apply,
 			N_("also apply the patch (use with --stat/--summary/--check)")),
 		OPT_BOOL('3', "3way", &threeway,
@@ -4451,6 +4474,9 @@ int cmd_apply(int argc, const char **argv, const char *prefix_)
 			die(_("--cached outside a repository"));
 		check_index = 1;
 	}
+	if (check_index)
+		unsafe_paths = 0;
+
 	for (i = 0; i < argc; i++) {
 		const char *arg = argv[i];
 		int fd;
diff --git a/t/t4139-apply-escape.sh b/t/t4139-apply-escape.sh
new file mode 100755
index 0000000..5e67179
--- /dev/null
+++ b/t/t4139-apply-escape.sh
@@ -0,0 +1,137 @@
+#!/bin/sh
+
+test_description='paths written by git-apply cannot escape the working tree'
+. ./test-lib.sh
+
+# tests will try to write to ../foo, and we do not
+# want them to escape the trash directory when they
+# fail
+test_expect_success 'bump git repo one level down' '
+	mkdir inside &&
+	mv .git inside/ &&
+	cd inside
+'
+
+# $1 = name of file
+# $2 = current path to file (if different)
+mkpatch_add() {
+	rm -f "${2:-$1}" &&
+	cat <<-EOF
+	diff --git a/$1 b/$1
+	new file mode 100644
+	index 0000000..53c74cd
+	--- /dev/null
+	+++ b/$1
+	@@ -0,0 +1 @@
+	+evil
+	EOF
+}
+
+mkpatch_del() {
+	echo evil >"${2:-$1}" &&
+	cat <<-EOF
+	diff --git a/$1 b/$1
+	deleted file mode 100644
+	index 53c74cd..0000000
+	--- a/$1
+	+++ /dev/null
+	@@ -1 +0,0 @@
+	-evil
+	EOF
+}
+
+# $1 = name of file
+# $2 = content of symlink
+mkpatch_symlink() {
+	rm -f "$1" &&
+	cat <<-EOF
+	diff --git a/$1 b/$1
+	new file mode 120000
+	index 0000000..$(printf "%s" "$2" | git hash-object --stdin)
+	--- /dev/null
+	+++ b/$1
+	@@ -0,0 +1 @@
+	+$2
+	\ No newline at end of file
+	EOF
+}
+
+test_expect_success 'cannot add file containing ..' '
+	mkpatch_add ../foo >patch &&
+	test_must_fail git apply patch &&
+	test_path_is_missing ../foo
+'
+
+test_expect_success 'can add file containing .. with --unsafe-paths' '
+	mkpatch_add ../foo >patch &&
+	git apply --unsafe-paths patch &&
+	test_path_is_file ../foo
+'
+
+test_expect_success  'cannot add file containing .. (index)' '
+	mkpatch_add ../foo >patch &&
+	test_must_fail git apply --index patch &&
+	test_path_is_missing ../foo
+'
+
+test_expect_success  'cannot add file containing .. with --unsafe-paths (index)' '
+	mkpatch_add ../foo >patch &&
+	test_must_fail git apply --index --unsafe-paths patch &&
+	test_path_is_missing ../foo
+'
+
+test_expect_success 'cannot del file containing ..' '
+	mkpatch_del ../foo >patch &&
+	test_must_fail git apply patch &&
+	test_path_is_file ../foo
+'
+
+test_expect_success 'can del file containing .. with --unsafe-paths' '
+	mkpatch_del ../foo >patch &&
+	git apply --unsafe-paths patch &&
+	test_path_is_missing ../foo
+'
+
+test_expect_success 'cannot del file containing .. (index)' '
+	mkpatch_del ../foo >patch &&
+	test_must_fail git apply --index patch &&
+	test_path_is_file ../foo
+'
+
+test_expect_failure 'symlink escape via ..' '
+	{
+		mkpatch_symlink tmp .. &&
+		mkpatch_add tmp/foo ../foo
+	} >patch &&
+	test_must_fail git apply patch &&
+	test_path_is_missing ../foo
+'
+
+test_expect_failure 'symlink escape via .. (index)' '
+	{
+		mkpatch_symlink tmp .. &&
+		mkpatch_add tmp/foo ../foo
+	} >patch &&
+	test_must_fail git apply --index patch &&
+	test_path_is_missing ../foo
+'
+
+test_expect_failure 'symlink escape via absolute path' '
+	{
+		mkpatch_symlink tmp "$(pwd)" &&
+		mkpatch_add tmp/foo ../foo
+	} >patch &&
+	test_must_fail git apply patch &&
+	test_path_is_missing ../foo
+'
+
+test_expect_success 'symlink escape via absolute path (index)' '
+	{
+		mkpatch_symlink tmp "$(pwd)" &&
+		mkpatch_add tmp/foo ../foo
+	} >patch &&
+	test_must_fail git apply --index patch &&
+	test_path_is_missing ../foo
+'
+
+test_done
-- 
2.3.0-rc2-164-g799cdce

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH v2 2/4] apply: do not read from the filesystem under --index
  2015-02-02 23:27 [PATCH v2 0/4] "git apply" safety Junio C Hamano
  2015-02-02 23:27 ` [PATCH v2 1/4] apply: reject input that touches outside $cwd Junio C Hamano
@ 2015-02-02 23:27 ` Junio C Hamano
  2015-02-02 23:27 ` [PATCH v2 3/4] apply: do not read from beyond a symbolic link Junio C Hamano
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 34+ messages in thread
From: Junio C Hamano @ 2015-02-02 23:27 UTC (permalink / raw)
  To: git

We currently read the preimage to apply a patch from the index only
when the --cached option is given.  Do so also when the command is
running under the --index option.  With --index, the index entry and
the working tree file for a path that is involved in a patch must be
identical, so this should not affect the result, but by reading from
the index, we will get the protection to avoid reading an unintended
path beyond a symbolic link automatically.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/apply.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/builtin/apply.c b/builtin/apply.c
index c751ddf..05eaf54 100644
--- a/builtin/apply.c
+++ b/builtin/apply.c
@@ -3136,7 +3136,7 @@ static int load_patch_target(struct strbuf *buf,
 			     const char *name,
 			     unsigned expected_mode)
 {
-	if (cached) {
+	if (cached || check_index) {
 		if (read_file_or_gitlink(ce, buf))
 			return error(_("read of %s failed"), name);
 	} else if (name) {
-- 
2.3.0-rc2-164-g799cdce

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH v2 3/4] apply: do not read from beyond a symbolic link
  2015-02-02 23:27 [PATCH v2 0/4] "git apply" safety Junio C Hamano
  2015-02-02 23:27 ` [PATCH v2 1/4] apply: reject input that touches outside $cwd Junio C Hamano
  2015-02-02 23:27 ` [PATCH v2 2/4] apply: do not read from the filesystem under --index Junio C Hamano
@ 2015-02-02 23:27 ` Junio C Hamano
  2015-02-03  0:08   ` Stefan Beller
  2015-02-02 23:27 ` [PATCH v2 4/4] apply: do not touch a file " Junio C Hamano
  2015-02-04  0:44 ` [PATCH v3 0/4] "git apply" safety Junio C Hamano
  4 siblings, 1 reply; 34+ messages in thread
From: Junio C Hamano @ 2015-02-02 23:27 UTC (permalink / raw)
  To: git

We should reject a patch, whether it renames/copies dir/file to
elsewhere with or without modificiation, or updates dir/file in
place, if "dir/" part is actually a symbolic link to elsewhere,
by making sure that the code to read the preimage does not read
from a path that is beyond a symbolic link.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/apply.c                 |  2 ++
 t/t4122-apply-symlink-inside.sh | 19 +++++++++++++++++++
 2 files changed, 21 insertions(+)

diff --git a/builtin/apply.c b/builtin/apply.c
index 05eaf54..60d821c 100644
--- a/builtin/apply.c
+++ b/builtin/apply.c
@@ -3145,6 +3145,8 @@ static int load_patch_target(struct strbuf *buf,
 				return read_file_or_gitlink(ce, buf);
 			else
 				return SUBMODULE_PATCH_WITHOUT_INDEX;
+		} else if (has_symlink_leading_path(name, strlen(name))) {
+			return error(_("reading from '%s' beyond a symbolic link"), name);
 		} else {
 			if (read_old_data(st, name, buf))
 				return error(_("read of %s failed"), name);
diff --git a/t/t4122-apply-symlink-inside.sh b/t/t4122-apply-symlink-inside.sh
index 70b3a06..035c080 100755
--- a/t/t4122-apply-symlink-inside.sh
+++ b/t/t4122-apply-symlink-inside.sh
@@ -52,4 +52,23 @@ test_expect_success 'check result' '
 
 '
 
+test_expect_success SYMLINKS 'do not read from beyond symbolic link' '
+	git reset --hard &&
+	mkdir -p arch/x86_64/dir &&
+	>arch/x86_64/dir/file &&
+	git add arch/x86_64/dir/file &&
+	echo line >arch/x86_64/dir/file &&
+	git diff >patch &&
+	git reset --hard &&
+
+	mkdir arch/i386/dir &&
+	>arch/i386/dir/file &&
+	ln -s ../i386/dir arch/x86_64/dir &&
+
+	test_must_fail git apply patch &&
+	test_must_fail git apply --cached patch &&
+	test_must_fail git apply --index patch
+
+'
+
 test_done
-- 
2.3.0-rc2-164-g799cdce

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH v2 4/4] apply: do not touch a file beyond a symbolic link
  2015-02-02 23:27 [PATCH v2 0/4] "git apply" safety Junio C Hamano
                   ` (2 preceding siblings ...)
  2015-02-02 23:27 ` [PATCH v2 3/4] apply: do not read from beyond a symbolic link Junio C Hamano
@ 2015-02-02 23:27 ` Junio C Hamano
  2015-02-03  1:11   ` Jeff King
  2015-02-04  0:44 ` [PATCH v3 0/4] "git apply" safety Junio C Hamano
  4 siblings, 1 reply; 34+ messages in thread
From: Junio C Hamano @ 2015-02-02 23:27 UTC (permalink / raw)
  To: git

Because Git tracks symbolic links as symbolic links, a path that
has a symbolic link in its leading part (e.g. path/to/dir/file,
where path/to/dir is a symbolic link to somewhere else, be it
inside or outside the working tree) can never appear in a patch
that validly applies, unless the same patch first removes the
symbolic link to allow a directory to be created there.

Detect and reject such a patch.

Things to note:

 - Unfortunately, we cannot reuse the has_symlink_leading_path()
   from dir.c, as that is only about the working tree, but "git
   apply" can be told to apply the patch only to the index or to
   both the index and to the working tree.

 - We cannot directly use has_symlink_leading_path() even when we
   are applying only to the working tree, as an early patch of a
   valid input may remove a symbolic link path/to/dir and then a
   later patch of the input may create a path path/to/dir/file, but
   "git apply" first checks the input without touching either the
   index or the working tree.  The leading symbolic link check must
   be done on the interim result we compute in-core (i.e. after the
   first patch, there is no path/to/dir symbolic link and it is
   perfectly valid to create path/to/dir/file).

   Similarly, when an input creates a symbolic link path/to/dir and
   then creates a file path/to/dir/file, we need to flag it as an
   error without actually creating path/to/dir symbolic link in the
   filesystem.

Instead, for any patch in the input that leaves a path (i.e. a non
deletion) in the result, we check all leading paths against the
resulting tree that the patch would create by inspecting all the
patches in the input and then the target of patch application
(either the index or the working tree).

This way, we catch a mischief or a mistake to add a symbolic link
path/to/dir and a file path/to/dir/file at the same time, while
allowing a valid patch that removes a symbolic link path/to/dir and
then adds a file path/to/dir/file.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/apply.c                 | 109 ++++++++++++++++++++++++++++++++++++++++
 t/t4122-apply-symlink-inside.sh |  70 ++++++++++++++++++++++++++
 t/t4139-apply-escape.sh         |   6 +--
 3 files changed, 182 insertions(+), 3 deletions(-)

diff --git a/builtin/apply.c b/builtin/apply.c
index 60d821c..a760d97 100644
--- a/builtin/apply.c
+++ b/builtin/apply.c
@@ -3486,6 +3486,103 @@ static int check_to_create(const char *new_name, int ok_if_exists)
 	return 0;
 }
 
+/*
+ * We need to keep track of how symlinks in the preimage are
+ * manipulated by the patches.  A patch to add a/b/c where a/b
+ * is a symlink should not be allowed to affect the directory
+ * the symlink points at, but if the same patch removes a/b,
+ * it is perfectly fine, as the patch removes a/b to make room
+ * to create a directory a/b so that a/b/c can be created.
+ */
+static struct string_list symlink_changes;
+#define SYMLINK_GOES_AWAY 01
+#define SYMLINK_IN_RESULT 02
+
+static uintptr_t register_symlink_changes(const char *path, uintptr_t what)
+{
+	struct string_list_item *ent;
+
+	ent = string_list_lookup(&symlink_changes, path);
+	if (!ent) {
+		ent = string_list_insert(&symlink_changes, path);
+		ent->util = (void *)0;
+	}
+	ent->util = (void *)(what | ((uintptr_t)ent->util));
+	return (uintptr_t)ent->util;
+}
+
+static uintptr_t check_symlink_changes(const char *path)
+{
+	struct string_list_item *ent;
+
+	ent = string_list_lookup(&symlink_changes, path);
+	if (!ent)
+		return 0;
+	return (uintptr_t)ent->util;
+}
+
+static void prepare_symlink_changes(struct patch *patch)
+{
+	for ( ; patch; patch = patch->next) {
+		if ((patch->old_name && S_ISLNK(patch->old_mode)) &&
+		    (patch->is_rename || patch->is_delete))
+			/* the symlink at patch->old_name is removed */
+			register_symlink_changes(patch->old_name, SYMLINK_GOES_AWAY);
+
+		if (patch->new_name && S_ISLNK(patch->new_mode))
+			/* the symlink at patch->new_name is created or remains */
+			register_symlink_changes(patch->new_name, SYMLINK_IN_RESULT);
+	}
+}
+
+static int path_is_beyond_symlink_1(struct strbuf *name)
+{
+	do {
+		unsigned int change;
+
+		while (--name->len && name->buf[name->len] != '/')
+			; /* scan backwards */
+		if (!name->len)
+			break;
+		name->buf[name->len] = '\0';
+		change = check_symlink_changes(name->buf);
+		if (change & SYMLINK_IN_RESULT)
+			return 1;
+		if (change & SYMLINK_GOES_AWAY)
+			/*
+			 * This cannot be "return 0", because we may
+			 * see a new one created at a higher level.
+			 */
+			continue;
+
+		/* otherwise, check the preimage */
+		if (check_index) {
+			int pos = cache_name_pos(name->buf, name->len);
+			if (0 <= pos &&
+			    S_ISLNK(active_cache[pos]->ce_mode))
+				return 1;
+		} else {
+			struct stat st;
+			if (!lstat(name->buf, &st) && S_ISLNK(st.st_mode))
+				return 1;
+		}
+	} while (1);
+	return 0;
+}
+
+static int path_is_beyond_symlink(const char *name_)
+{
+	int ret;
+	struct strbuf name = STRBUF_INIT;
+
+	assert(*name_ != '\0');
+	strbuf_addstr(&name, name_);
+	ret = path_is_beyond_symlink_1(&name);
+	strbuf_release(&name);
+
+	return ret;
+}
+
 static void die_on_unsafe_path(struct patch *patch)
 {
 	const char *old_name = NULL;
@@ -3593,6 +3690,17 @@ static int check_patch(struct patch *patch)
 	if (!unsafe_paths)
 		die_on_unsafe_path(patch);
 
+	/*
+	 * An attempt to read from or delete a path that is beyond
+	 * a symbolic link will be prevented by load_patch_target()
+	 * that is called at the beginning of apply_data().  We need
+	 * to make sure that the patch result is not deposited to
+	 * a path that is beyond a symbolic link ourselves.
+	 */
+	if (!patch->is_delete && path_is_beyond_symlink(patch->new_name))
+		return error(_("affected file '%s' is beyond a symbolic link"),
+			     patch->new_name);
+
 	if (apply_data(patch, &st, ce) < 0)
 		return error(_("%s: patch does not apply"), name);
 	patch->rejected = 0;
@@ -3603,6 +3711,7 @@ static int check_patch_list(struct patch *patch)
 {
 	int err = 0;
 
+	prepare_symlink_changes(patch);
 	prepare_fn_table(patch);
 	while (patch) {
 		if (apply_verbosely)
diff --git a/t/t4122-apply-symlink-inside.sh b/t/t4122-apply-symlink-inside.sh
index 035c080..942c5cb 100755
--- a/t/t4122-apply-symlink-inside.sh
+++ b/t/t4122-apply-symlink-inside.sh
@@ -71,4 +71,74 @@ test_expect_success SYMLINKS 'do not read from beyond symbolic link' '
 
 '
 
+test_expect_success SYMLINKS 'do not follow symbolic link (setup)' '
+
+	rm -rf arch/i386/dir arch/x86_64/dir &&
+	git reset --hard &&
+	ln -s ../i386/dir arch/x86_64/dir &&
+	git add arch/x86_64/dir &&
+	git diff HEAD >add_symlink.patch &&
+	git reset --hard &&
+
+	mkdir arch/x86_64/dir &&
+	>arch/x86_64/dir/file &&
+	git add arch/x86_64/dir/file &&
+	git diff HEAD >add_file.patch &&
+	git diff -R HEAD >del_file.patch &&
+	git reset --hard &&
+	rm -fr arch/x86_64/dir &&
+
+	cat add_symlink.patch add_file.patch >patch &&
+
+	mkdir arch/i386/dir
+'
+
+test_expect_success SYMLINKS 'do not follow symbolic link (same input)' '
+
+	# same input creates a confusing symbolic link
+	test_must_fail git apply patch 2>error-wt &&
+	test_i18ngrep "beyond a symbolic link" error-wt &&
+	test_path_is_missing arch/x86_64/dir &&
+	test_path_is_missing arch/i386/dir/file &&
+
+	test_must_fail git apply --index patch 2>error-ix &&
+	test_i18ngrep "beyond a symbolic link" error-ix &&
+	test_path_is_missing arch/x86_64/dir &&
+	test_path_is_missing arch/i386/dir/file &&
+	test_must_fail git ls-files --error-unmatch arch/x86_64/dir &&
+	test_must_fail git ls-files --error-unmatch arch/i386/dir &&
+
+	test_must_fail git apply --cached patch 2>error-ct &&
+	test_i18ngrep "beyond a symbolic link" error-ct &&
+	test_must_fail git ls-files --error-unmatch arch/x86_64/dir &&
+	test_must_fail git ls-files --error-unmatch arch/i386/dir
+'
+
+test_expect_success SYMLINKS 'do not follow symbolic link (existing)' '
+
+	# existing symbolic link
+	git reset --hard &&
+	ln -s ../i386/dir arch/x86_64/dir &&
+	git add arch/x86_64/dir &&
+
+	test_must_fail git apply add_file.patch 2>error-wt-add &&
+	test_i18ngrep "beyond a symbolic link" error-wt-add &&
+	test_path_is_missing arch/i386/dir/file &&
+
+	>arch/i386/dir/file &&
+	test_must_fail git apply del_file.patch 2>error-wt-del &&
+	test_i18ngrep "beyond a symbolic link" error-wt-del &&
+	test_path_is_file arch/i386/dir/file &&
+	rm arch/i386/dir/file &&
+
+	test_must_fail git apply --index add_file.patch 2>error-ix-add &&
+	test_i18ngrep "beyond a symbolic link" error-ix-add &&
+	test_path_is_missing arch/i386/dir/file &&
+	test_must_fail git ls-files --error-unmatch arch/i386/dir &&
+
+	test_must_fail git apply --cached add_file.patch 2>error-ct-file &&
+	test_i18ngrep "beyond a symbolic link" error-ct-file &&
+	test_must_fail git ls-files --error-unmatch arch/i386/dir
+'
+
 test_done
diff --git a/t/t4139-apply-escape.sh b/t/t4139-apply-escape.sh
index 5e67179..25917cc 100755
--- a/t/t4139-apply-escape.sh
+++ b/t/t4139-apply-escape.sh
@@ -98,7 +98,7 @@ test_expect_success 'cannot del file containing .. (index)' '
 	test_path_is_file ../foo
 '
 
-test_expect_failure 'symlink escape via ..' '
+test_expect_success 'symlink escape via ..' '
 	{
 		mkpatch_symlink tmp .. &&
 		mkpatch_add tmp/foo ../foo
@@ -107,7 +107,7 @@ test_expect_failure 'symlink escape via ..' '
 	test_path_is_missing ../foo
 '
 
-test_expect_failure 'symlink escape via .. (index)' '
+test_expect_success 'symlink escape via .. (index)' '
 	{
 		mkpatch_symlink tmp .. &&
 		mkpatch_add tmp/foo ../foo
@@ -116,7 +116,7 @@ test_expect_failure 'symlink escape via .. (index)' '
 	test_path_is_missing ../foo
 '
 
-test_expect_failure 'symlink escape via absolute path' '
+test_expect_success 'symlink escape via absolute path' '
 	{
 		mkpatch_symlink tmp "$(pwd)" &&
 		mkpatch_add tmp/foo ../foo
-- 
2.3.0-rc2-164-g799cdce

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 3/4] apply: do not read from beyond a symbolic link
  2015-02-02 23:27 ` [PATCH v2 3/4] apply: do not read from beyond a symbolic link Junio C Hamano
@ 2015-02-03  0:08   ` Stefan Beller
  2015-02-03 19:37     ` Junio C Hamano
  0 siblings, 1 reply; 34+ messages in thread
From: Stefan Beller @ 2015-02-03  0:08 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

On Mon, Feb 2, 2015 at 3:27 PM, Junio C Hamano <gitster@pobox.com> wrote:
> +       test_must_fail git apply --index patch
> +
> +'

Is the empty line between the last test_must_fail and the closing `'`
intentional?

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 1/4] apply: reject input that touches outside $cwd
  2015-02-02 23:27 ` [PATCH v2 1/4] apply: reject input that touches outside $cwd Junio C Hamano
@ 2015-02-03  0:45   ` Jeff King
  2015-02-03  0:50   ` Jeff King
  2015-02-03  5:56   ` Torsten Bögershausen
  2 siblings, 0 replies; 34+ messages in thread
From: Jeff King @ 2015-02-03  0:45 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

On Mon, Feb 02, 2015 at 03:27:27PM -0800, Junio C Hamano wrote:

> By default, a patch that affects outside the working area is
> rejected as a mistake (or a mischief); Git itself does not create
> such a patch, unless the user bends backwards and specifies a
> non-standard prefix to "git diff" and friends.
> 
> When `git apply` is used without either `--index` or `--cached`
> option as a "better GNU patch", the user can pass `--unsafe-paths`
> option to override this safety check.  This cannot be used to escape
> outside the working tree when using `--index` or `--cached` to apply
> the patch to the index.
> 
> The new test was stolen from Jeff King with slight enhancements.

I notice that this includes the symlink-crossing tests, marked as
failures. Reading the series, I know what is going to happen later, but
do you want to leave a note like:

  Note that we also add tests for leaving the working directory by
  crossing symlink boundaries, which is not addressed in this patch.
  That is a separate issue caused following symlinks, which will come
  later.

or something to help later readers of "git log"?

> +--unsafe-paths::
> +	By default, a patch that affects outside the working area is
> +	rejected as a mistake (or a mischief); Git itself never
> +	creates such a patch unless the user bends backwards and
> +	specifies nonstandard prefix to "git diff" and friends.

Minor wordsmithing: the usual idiom is "bend over backwards", and you
probably want s/specifies/& a/.

> ++
> +When `git apply` is used without either `--index` or `--cached`
> +option as a "better GNU patch", the user can pass `--unsafe-paths`
> +option to override this safety check.

Similarly, probably every instance of "foo option" would read better as
"the foo option".

> This cannot be used to escape
> +outside the working tree when using `--index` or `--cached` to apply
> +the patch to the index.

I had trouble figuring out what this meant. Would it be simpler to just
say:

  This option has no effect when `--index` or `--cached` is in use.

Or is there some other subtlety that you are trying to convey that I am
missing?

-Peff

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 1/4] apply: reject input that touches outside $cwd
  2015-02-02 23:27 ` [PATCH v2 1/4] apply: reject input that touches outside $cwd Junio C Hamano
  2015-02-03  0:45   ` Jeff King
@ 2015-02-03  0:50   ` Jeff King
  2015-02-03 20:23     ` Junio C Hamano
  2015-02-03  5:56   ` Torsten Bögershausen
  2 siblings, 1 reply; 34+ messages in thread
From: Jeff King @ 2015-02-03  0:50 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

On Mon, Feb 02, 2015 at 03:27:27PM -0800, Junio C Hamano wrote:

> +test_expect_failure 'symlink escape via ..' '
> +	{
> +		mkpatch_symlink tmp .. &&
> +		mkpatch_add tmp/foo ../foo
> +	} >patch &&
> +	test_must_fail git apply patch &&
> +	test_path_is_missing ../foo
> +'

By the way, does this patch (and the other symlink-escape ones) need to
be marked with the SYMLINKS prereq? For a pure-index application, it
should work anywhere, but I have a feeling that this "git apply patch"
may try to write the symlink to the filesystem, fail, and report failure
for the wrong reason.  I don't have a SYMLINK-challenged filesystem to
test on, though.

-Peff

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 4/4] apply: do not touch a file beyond a symbolic link
  2015-02-02 23:27 ` [PATCH v2 4/4] apply: do not touch a file " Junio C Hamano
@ 2015-02-03  1:11   ` Jeff King
  2015-02-03  1:56     ` Junio C Hamano
  2015-02-03 21:01     ` Junio C Hamano
  0 siblings, 2 replies; 34+ messages in thread
From: Jeff King @ 2015-02-03  1:11 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

On Mon, Feb 02, 2015 at 03:27:30PM -0800, Junio C Hamano wrote:

> +static struct string_list symlink_changes;

I notice we don't duplicate strings for this list. Are the paths we
register here always stable? I think they should be, as they are part of
the "struct patch".

> +#define SYMLINK_GOES_AWAY 01
> +#define SYMLINK_IN_RESULT 02
> +
> +static uintptr_t register_symlink_changes(const char *path, uintptr_t what)
> +{
> +	struct string_list_item *ent;
> +
> +	ent = string_list_lookup(&symlink_changes, path);
> +	if (!ent) {
> +		ent = string_list_insert(&symlink_changes, path);
> +		ent->util = (void *)0;
> +	}
> +	ent->util = (void *)(what | ((uintptr_t)ent->util));
> +	return (uintptr_t)ent->util;
> +}

I was surprised to see this as a bit-field and not a "current state" as
we walk through the set of patches to apply. I think this means we'll be
overly cautious with a patch that does:

  1. add foo as a symlink

  2. remove foo

  3. add foo/bar

which is perfectly OK, but we'll reject. I suspect this is making things
much simpler for you, because now we don't have to worry about order of
application that we were discussing the other day. If that is the
reason, then I think patches like the above are an acceptable casualty.
It seems rather far-fetched in the first place for a real patch (as
opposed to a mischievous one).

> +	/*
> +	 * An attempt to read from or delete a path that is beyond
> +	 * a symbolic link will be prevented by load_patch_target()
> +	 * that is called at the beginning of apply_data().  We need
> +	 * to make sure that the patch result is not deposited to
> +	 * a path that is beyond a symbolic link ourselves.
> +	 */
> +	if (!patch->is_delete && path_is_beyond_symlink(patch->new_name))
> +		return error(_("affected file '%s' is beyond a symbolic link"),
> +			     patch->new_name);

Do we need to check the patch->is_delete case here (with patch->old_name)?

I had a suspicion that the new patch 3/4 to check the reading-side might
help with that, but the comment here sounds like we do need to check
here, too (and I am not clear on how 3/4 handles deleting from a patch
on the far side of a symlink we have just created).

It does seem to work, though. I'm just not sure how. :)

Here's the test addition I came up with, because it didn't look like we
were covering this case. 

diff --git a/t/t4122-apply-symlink-inside.sh b/t/t4122-apply-symlink-inside.sh
index 942c5cb..fbba8dd 100755
--- a/t/t4122-apply-symlink-inside.sh
+++ b/t/t4122-apply-symlink-inside.sh
@@ -89,6 +89,7 @@ test_expect_success SYMLINKS 'do not follow symbolic link (setup)' '
 	rm -fr arch/x86_64/dir &&
 
 	cat add_symlink.patch add_file.patch >patch &&
+	cat add_symlink.patch del_file.patch >tricky_del &&
 
 	mkdir arch/i386/dir
 '
@@ -112,6 +113,20 @@ test_expect_success SYMLINKS 'do not follow symbolic link (same input)' '
 	test_i18ngrep "beyond a symbolic link" error-ct &&
 	test_must_fail git ls-files --error-unmatch arch/x86_64/dir &&
 	test_must_fail git ls-files --error-unmatch arch/i386/dir
+
+	>arch/i386/dir/file &&
+	git add arch/i386/dir/file &&
+	test_must_fail git apply tricky_del &&
+	test_path_is_file arch/i386/dir/file &&
+
+	test_must_fail git apply --index tricky_del &&
+	test_path_is_file arch/i386/dir/file &&
+	test_must_fail git ls-files --error-unmatch arch/x86_64/dir &&
+	git ls-files --error-unmatch arch/i386/dir &&
+
+	test_must_fail git apply --cached tricky_del &&
+	test_must_fail git ls-files --error-unmatch arch/x86_64/dir &&
+	git ls-files --error-unmatch arch/i386/dir
 '
 
 test_expect_success SYMLINKS 'do not follow symbolic link (existing)' '
@@ -125,6 +140,7 @@ test_expect_success SYMLINKS 'do not follow symbolic link (existing)' '
 	test_i18ngrep "beyond a symbolic link" error-wt-add &&
 	test_path_is_missing arch/i386/dir/file &&
 
+	mkdir arch/i386/dir &&
 	>arch/i386/dir/file &&
 	test_must_fail git apply del_file.patch 2>error-wt-del &&
 	test_i18ngrep "beyond a symbolic link" error-wt-del &&

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 4/4] apply: do not touch a file beyond a symbolic link
  2015-02-03  1:11   ` Jeff King
@ 2015-02-03  1:56     ` Junio C Hamano
  2015-02-03  2:04       ` Jeff King
  2015-02-03 21:01     ` Junio C Hamano
  1 sibling, 1 reply; 34+ messages in thread
From: Junio C Hamano @ 2015-02-03  1:56 UTC (permalink / raw)
  To: Jeff King; +Cc: git

Jeff King <peff@peff.net> writes:

> On Mon, Feb 02, 2015 at 03:27:30PM -0800, Junio C Hamano wrote:
>
>> +static struct string_list symlink_changes;
>
> I notice we don't duplicate strings for this list. Are the paths we
> register here always stable? I think they should be, as they are part of
> the "struct patch".

Yeah, and I also forgot to free this string-list itself.  Needs
fixing.

> I think this means we'll be
> overly cautious with a patch that does:
>
>   1. add foo as a symlink
>
>   2. remove foo
>
>   3. add foo/bar
>
> which is perfectly OK

No, such a patchset is broken.

A valid "git apply" input must *not* depend on the order of patches
in it.  The consequence is that "an input to 'git apply' must not
mention the fate of each path at most once."

"create B by copying A" and "modify A in-place" can appear in the
same patchset in any order, and the new file B will have the
contents from the original A, not the result of modifying A
in-place, which is what will be in the resulting A.  That is how
"git diff" expresses renames and copies, and that is why rearranging
the patchset using "git diff -Oorderfile" is safe.

> but we'll reject. I suspect this is making things
> much simpler for you, because now we don't have to worry about order of
> application that we were discussing the other day.

It is not "now this new decision made things simpler".  "git diff"
output and "git apply" application have been designed to work that
way from day one.  At least from day one of rename/copy feature.

We probably should start thinking about ripping out the fn_table[]
crud.  It fundamentally cannot correctly work on an input that
concatenates more than one "git diff" outputs that have renames
and/or copies of the same file, and it never will, and that is not
due to a bug in the implementation.

The reason why the incremental application is a fundamentally flawed
concept in the context of "git apply" is because you cannot tell the
boundary between the original "git diff" outputs.

Imagine that you have three versions, A, B and C, and the original
two "git diff -M A B" and "git diff -M B C" output said this:

    (A -> B)
    edit X in place and add two lines at the beginning
    create Z by copying X

    (B -> C)
    create Y by renaming X and add a line at the end

If you take "git diff -M A C", it should say:

    (A -> C)
    edit X in place and add two lines at the beginning
    create Y by copying X and add two lines at the beginning
        and a line at the end
    create Z by copying X

Now, if you concatenate two "git diff" outputs and feed it to "git
apply", you would want it to express a patchset that goes from A to
C, but think if you can really get such a semantics.

    edit X in place and add two lines at the beginning
    create Z by copying X
    create Y by renaming X and add a line at the end

You fundamentally cannot tell that Z needs to be a copy of X before
the change to X (which is what the usual "git apply" does), but Y
needs to start from a copy of X after the change to X.  There is no
clue to tell "git apply" that there is a boundary between the first
two operations and the third one.  It is impossible for the
concatenated patch to express the same thing as "(A -> C)" patch
does, without inventng some "I am now switching to a new basis"
marker in the "git apply" input stream.

>> +	/*
>> +	 * An attempt to read from or delete a path that is beyond
>> +	 * a symbolic link will be prevented by load_patch_target()
>> +	 * that is called at the beginning of apply_data().  We need
>> +	 * to make sure that the patch result is not deposited to
>> +	 * a path that is beyond a symbolic link ourselves.
>> +	 */
>> +	if (!patch->is_delete && path_is_beyond_symlink(patch->new_name))
>> +		return error(_("affected file '%s' is beyond a symbolic link"),
>> +			     patch->new_name);
>
> Do we need to check the patch->is_delete case here (with patch->old_name)?

> I had a suspicion that the new patch 3/4 to check the reading-side might
> help with that, but the comment here sounds like we do need to check
> here, too

Hmm, the comment above was meant to tell you that we do not have to
worry about the deletion case (because load_patch_target() will try
to read the original to verify we are deleting what we expect to
delete at the beginning of apply_data(), and it will notice that
old_name is beyond a symbolic link), but we still need to check the
non-deletion case.  Strictly speaking, modify-in-place case does not
have to depend on this code (the same load_patch_target() check will
catch it because it wants to check the preimage).

May need rephrasing to clarify but I thought it was clear enough.

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 4/4] apply: do not touch a file beyond a symbolic link
  2015-02-03  1:56     ` Junio C Hamano
@ 2015-02-03  2:04       ` Jeff King
  0 siblings, 0 replies; 34+ messages in thread
From: Jeff King @ 2015-02-03  2:04 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

On Mon, Feb 02, 2015 at 05:56:55PM -0800, Junio C Hamano wrote:

> > I think this means we'll be
> > overly cautious with a patch that does:
> >
> >   1. add foo as a symlink
> >
> >   2. remove foo
> >
> >   3. add foo/bar
> >
> > which is perfectly OK
> 
> No, such a patchset is broken.
> 
> A valid "git apply" input must *not* depend on the order of patches
> in it.  The consequence is that "an input to 'git apply' must not
> mention the fate of each path at most once."

Ah, right, I forgot we covered this already in the earlier discussion
(but thanks for elaborating; I think the reason I forgot is that I did
not really understand all of the implications).  If we do not have to
worry about that, then it's not a problem.

> >> +	/*
> >> +	 * An attempt to read from or delete a path that is beyond
> >> +	 * a symbolic link will be prevented by load_patch_target()
> >> +	 * that is called at the beginning of apply_data().  We need
> >> +	 * to make sure that the patch result is not deposited to
> >> +	 * a path that is beyond a symbolic link ourselves.
> >> +	 */
> >> +	if (!patch->is_delete && path_is_beyond_symlink(patch->new_name))
> >> +		return error(_("affected file '%s' is beyond a symbolic link"),
> >> +			     patch->new_name);
> >
> > Do we need to check the patch->is_delete case here (with patch->old_name)?
> 
> > I had a suspicion that the new patch 3/4 to check the reading-side might
> > help with that, but the comment here sounds like we do need to check
> > here, too
> 
> Hmm, the comment above was meant to tell you that we do not have to
> worry about the deletion case (because load_patch_target() will try
> to read the original to verify we are deleting what we expect to
> delete at the beginning of apply_data(), and it will notice that
> old_name is beyond a symbolic link), but we still need to check the
> non-deletion case.  Strictly speaking, modify-in-place case does not
> have to depend on this code (the same load_patch_target() check will
> catch it because it wants to check the preimage).
> 
> May need rephrasing to clarify but I thought it was clear enough.

Ah, OK. I totally misread it, thinking that load_patch_target was what
set up the symlink-table, and that was what you were referring to.  It
might be more clear after "...that is called at the beginning of
apply_data()" to add "...so we do not have to worry about that case
here".

-Peff

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 1/4] apply: reject input that touches outside $cwd
  2015-02-02 23:27 ` [PATCH v2 1/4] apply: reject input that touches outside $cwd Junio C Hamano
  2015-02-03  0:45   ` Jeff King
  2015-02-03  0:50   ` Jeff King
@ 2015-02-03  5:56   ` Torsten Bögershausen
  2 siblings, 0 replies; 34+ messages in thread
From: Torsten Bögershausen @ 2015-02-03  5:56 UTC (permalink / raw)
  To: Junio C Hamano, git

If I am allowed to to some load thinking:

The commit msh header says:
  reject input that touches outside $cwd

The commit message says:
   By default, a patch that affects outside the working area

And the new command line option is this:
   --unsafe-paths
(Which may be a good choice to pretend people from using it without
   having read the documentaion)

(And isn't working area the same as "work space" ?)

I have the slight feeling that there may be more unsafe-path situations coming
up in the future..

Will
--allow-outside-ws
or
--patch-outside-ws
be better ?

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 3/4] apply: do not read from beyond a symbolic link
  2015-02-03  0:08   ` Stefan Beller
@ 2015-02-03 19:37     ` Junio C Hamano
  2015-02-03 19:44       ` Stefan Beller
  0 siblings, 1 reply; 34+ messages in thread
From: Junio C Hamano @ 2015-02-03 19:37 UTC (permalink / raw)
  To: Stefan Beller; +Cc: git

Stefan Beller <sbeller@google.com> writes:

> On Mon, Feb 2, 2015 at 3:27 PM, Junio C Hamano <gitster@pobox.com> wrote:
>> +       test_must_fail git apply --index patch
>> +
>> +'
>
> Is the empty line between the last test_must_fail and the closing `'`
> intentional?

I think I just mimicked the previous existing one, but I can go with
the version without.

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 3/4] apply: do not read from beyond a symbolic link
  2015-02-03 19:37     ` Junio C Hamano
@ 2015-02-03 19:44       ` Stefan Beller
  2015-02-03 20:31         ` Junio C Hamano
  0 siblings, 1 reply; 34+ messages in thread
From: Stefan Beller @ 2015-02-03 19:44 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

On Tue, Feb 3, 2015 at 11:37 AM, Junio C Hamano <gitster@pobox.com> wrote:
> Stefan Beller <sbeller@google.com> writes:
>
>> On Mon, Feb 2, 2015 at 3:27 PM, Junio C Hamano <gitster@pobox.com> wrote:
>>> +       test_must_fail git apply --index patch
>>> +
>>> +'
>>
>> Is the empty line between the last test_must_fail and the closing `'`
>> intentional?
>
> I think I just mimicked the previous existing one, but I can go with
> the version without.

It was really a honest question. I've seen and written and sent tests
without such an ending
empty line and it seemed to be ok.

Maybe my email was more of "Note to self: this is also a valid style
used in the test suite
at some places"

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 1/4] apply: reject input that touches outside $cwd
  2015-02-03  0:50   ` Jeff King
@ 2015-02-03 20:23     ` Junio C Hamano
  2015-02-03 21:01       ` Jeff King
  0 siblings, 1 reply; 34+ messages in thread
From: Junio C Hamano @ 2015-02-03 20:23 UTC (permalink / raw)
  To: Jeff King; +Cc: git

Jeff King <peff@peff.net> writes:

> On Mon, Feb 02, 2015 at 03:27:27PM -0800, Junio C Hamano wrote:
>
>> +test_expect_failure 'symlink escape via ..' '
>> +	{
>> +		mkpatch_symlink tmp .. &&
>> +		mkpatch_add tmp/foo ../foo
>> +	} >patch &&
>> +	test_must_fail git apply patch &&
>> +	test_path_is_missing ../foo
>> +'
>
> By the way, does this patch (and the other symlink-escape ones) need to
> be marked with the SYMLINKS prereq? For a pure-index application, it
> should work anywhere, but I have a feeling that this "git apply patch"
> may try to write the symlink to the filesystem, fail, and report failure
> for the wrong reason.  I don't have a SYMLINK-challenged filesystem to
> test on, though.

We check the links to be created by the patch itself in-core before
going to the filesystem, and the symbolic links you are creating
using mkpatch_symlink should be caught before we invoke symlink(2),
I think.

In other words, this series attempts to stick to the "verify
everything in-core before deciding that it is OK to touch the
working tree or the index".

A few new tests in t4122 do try to see that the command is not
fooled by existihng symbolic links on the filesystem and they need
to be marked with SYMLINKS prerequisite.

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 3/4] apply: do not read from beyond a symbolic link
  2015-02-03 19:44       ` Stefan Beller
@ 2015-02-03 20:31         ` Junio C Hamano
  0 siblings, 0 replies; 34+ messages in thread
From: Junio C Hamano @ 2015-02-03 20:31 UTC (permalink / raw)
  To: Stefan Beller; +Cc: git

Stefan Beller <sbeller@google.com> writes:

> On Tue, Feb 3, 2015 at 11:37 AM, Junio C Hamano <gitster@pobox.com> wrote:
>> Stefan Beller <sbeller@google.com> writes:
>>
>>> On Mon, Feb 2, 2015 at 3:27 PM, Junio C Hamano <gitster@pobox.com> wrote:
>>>> +       test_must_fail git apply --index patch
>>>> +
>>>> +'
>>>
>>> Is the empty line between the last test_must_fail and the closing `'`
>>> intentional?
>>
>> I think I just mimicked the previous existing one, but I can go with
>> the version without.
>
> It was really a honest question. I've seen and written and sent
> tests without such an ending empty line and it seemed to be ok.

It was a honest answer, too.

I see existing new-style tests [*1*] that have and/or lack the empty
line at the end of the command, and I do not have particular
preference either way.  Having these variations did not bother me
too much, at least until now.  We might want to make them consistent,
and the time to be careful is when we add new tests, so it was very
valid for you to bring it up against this patch.  But I do not know
if we as the Git developer community have preference either way, and
I myself don't either, so...


[Footnote]

*1* The really ancient style we want to clean-up goes like

        cmd for setup > expect
        
        test_expect_success \
                'title of the test' \
                'command && more command &&
                 yet more command > actual &&
                 test_cmp expect actual'

        cmd for cleanup

    and we want them to read more like

        test_expect_success 'title of the test' '
                test_when_finished "cmd for cleanup" &&
                cmd for setup >expect &&
                command &&
                more command &&
                yet more command >actual &&
                test_cmp expect actual
        '

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 4/4] apply: do not touch a file beyond a symbolic link
  2015-02-03  1:11   ` Jeff King
  2015-02-03  1:56     ` Junio C Hamano
@ 2015-02-03 21:01     ` Junio C Hamano
  2015-02-03 23:40       ` Eric Sunshine
  1 sibling, 1 reply; 34+ messages in thread
From: Junio C Hamano @ 2015-02-03 21:01 UTC (permalink / raw)
  To: Jeff King; +Cc: git

Jeff King <peff@peff.net> writes:

> Here's the test addition I came up with, because it didn't look like we
> were covering this case. 

Thanks.

> diff --git a/t/t4122-apply-symlink-inside.sh b/t/t4122-apply-symlink-inside.sh
> index 942c5cb..fbba8dd 100755
> --- a/t/t4122-apply-symlink-inside.sh
> +++ b/t/t4122-apply-symlink-inside.sh
> @@ -89,6 +89,7 @@ test_expect_success SYMLINKS 'do not follow symbolic link (setup)' '
>  	rm -fr arch/x86_64/dir &&
>  
>  	cat add_symlink.patch add_file.patch >patch &&
> +	cat add_symlink.patch del_file.patch >tricky_del &&

This new patch

 (1) creates a symlink arch/x86_64/dir pointing at ../i386/dir
 (2) deletes arch/x86_64/dir/file

It can be a valid patch to be applied to a tree where arch/x86_64/dir/file
is in the index (either as a regular file, a symlink, or even a submodule)
and nothing else is in arch/x86_64/dir directory.


> @@ -112,6 +113,20 @@ test_expect_success SYMLINKS 'do not follow symbolic link (same input)' '
>  	test_i18ngrep "beyond a symbolic link" error-ct &&
>  	test_must_fail git ls-files --error-unmatch arch/x86_64/dir &&
>  	test_must_fail git ls-files --error-unmatch arch/i386/dir
> +
> +	>arch/i386/dir/file &&
> +	git add arch/i386/dir/file &&

At this point, the target of the patch application has:

	arch/i386/boot/Makefile
	arch/i386/dir/file
	arch/x86_64/boot/Makefile

all of which are regular files.  The index and the working tree
match.

> +	test_must_fail git apply tricky_del &&

The reason why this does not apply has nothing to do with the topic
of this series, I think.  It wants to delete arch/x86_64/dir/file,
which does not exist in the target, and the patch is rejected.

It is a good test to make sure that we do not "incrementally" apply
and get fooled by arch/x86_64/dir that will become a symbolic link,
making arch/x86_64/dir/file to appear as arch/i386/dir/file that
does exist in the preimage.

> +	test_path_is_file arch/i386/dir/file &&

When we reject the entire patch, we do so without touching the
outside world, of course ;-), which is good.

> +	test_must_fail git apply --index tricky_del &&
> +	test_path_is_file arch/i386/dir/file &&
> +	test_must_fail git ls-files --error-unmatch arch/x86_64/dir &&
> +	git ls-files --error-unmatch arch/i386/dir &&
> +
> +	test_must_fail git apply --cached tricky_del &&
> +	test_must_fail git ls-files --error-unmatch arch/x86_64/dir &&
> +	git ls-files --error-unmatch arch/i386/dir
>  '

In both of the above, "git apply" rejects its input for the same
reason.  The file it wants to remove does not exist in the target.

>  test_expect_success SYMLINKS 'do not follow symbolic link (existing)' '
> @@ -125,6 +140,7 @@ test_expect_success SYMLINKS 'do not follow symbolic link (existing)' '
>  	test_i18ngrep "beyond a symbolic link" error-wt-add &&
>  	test_path_is_missing arch/i386/dir/file &&
>  
> +	mkdir arch/i386/dir &&

Thanks for spotting this one ;-)

>  	>arch/i386/dir/file &&
>  	test_must_fail git apply del_file.patch 2>error-wt-del &&

del_file.patch wants to remove arch/x86_64/dir/file, and arch/x86_64/dir
is a symbolic link to ../i386/dir in the target at this point, so it
is trying to delete beyond the symbolic link, which gets rejected by
this series.  Good.

>  	test_i18ngrep "beyond a symbolic link" error-wt-del &&

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 1/4] apply: reject input that touches outside $cwd
  2015-02-03 20:23     ` Junio C Hamano
@ 2015-02-03 21:01       ` Jeff King
  2015-02-03 21:23         ` Junio C Hamano
  0 siblings, 1 reply; 34+ messages in thread
From: Jeff King @ 2015-02-03 21:01 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

On Tue, Feb 03, 2015 at 12:23:28PM -0800, Junio C Hamano wrote:

> > By the way, does this patch (and the other symlink-escape ones) need to
> > be marked with the SYMLINKS prereq? For a pure-index application, it
> > should work anywhere, but I have a feeling that this "git apply patch"
> > may try to write the symlink to the filesystem, fail, and report failure
> > for the wrong reason.  I don't have a SYMLINK-challenged filesystem to
> > test on, though.
> 
> We check the links to be created by the patch itself in-core before
> going to the filesystem, and the symbolic links you are creating
> using mkpatch_symlink should be caught before we invoke symlink(2),
> I think.
> 
> In other words, this series attempts to stick to the "verify
> everything in-core before deciding that it is OK to touch the
> working tree or the index".

Right, I do not think these tests will _fail_ when the filesystem does
not support symlinks. But nor are they actually testing anything
interesting. They would pass on such a system even without your patch,
as we would fail to apply even the symlink creation part of the patch.

I can live with leaving them unmarked, though. It gets the code
exercised on more systems, which gives a slightly higher chance of
catching some other unexpected breakage.

-Peff

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 1/4] apply: reject input that touches outside $cwd
  2015-02-03 21:01       ` Jeff King
@ 2015-02-03 21:23         ` Junio C Hamano
  2015-02-03 21:24           ` Jeff King
  0 siblings, 1 reply; 34+ messages in thread
From: Junio C Hamano @ 2015-02-03 21:23 UTC (permalink / raw)
  To: Jeff King; +Cc: git

Jeff King <peff@peff.net> writes:

> Right, I do not think these tests will _fail_ when the filesystem does
> not support symlinks. But nor are they actually testing anything
> interesting. They would pass on such a system even without your patch,
> as we would fail to apply even the symlink creation part of the patch.

I thought we write out the contents of the symbolic link as a
regular file on such a filesystem, and as long as we do not expect
"test -h expected-to-be-symlink-we-just-created" to succeed we would
be fine.

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 1/4] apply: reject input that touches outside $cwd
  2015-02-03 21:23         ` Junio C Hamano
@ 2015-02-03 21:24           ` Jeff King
  2015-02-03 21:40             ` Junio C Hamano
  0 siblings, 1 reply; 34+ messages in thread
From: Jeff King @ 2015-02-03 21:24 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

On Tue, Feb 03, 2015 at 01:23:15PM -0800, Junio C Hamano wrote:

> Jeff King <peff@peff.net> writes:
> 
> > Right, I do not think these tests will _fail_ when the filesystem does
> > not support symlinks. But nor are they actually testing anything
> > interesting. They would pass on such a system even without your patch,
> > as we would fail to apply even the symlink creation part of the patch.
> 
> I thought we write out the contents of the symbolic link as a
> regular file on such a filesystem, and as long as we do not expect
> "test -h expected-to-be-symlink-we-just-created" to succeed we would
> be fine.

But wouldn't we still fail writing "foo/bar" at that point if "foo" is a
regular file (again, we should never do this, but that is the point of
the test).

-Peff

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 1/4] apply: reject input that touches outside $cwd
  2015-02-03 21:24           ` Jeff King
@ 2015-02-03 21:40             ` Junio C Hamano
  2015-02-03 21:50               ` Jeff King
  0 siblings, 1 reply; 34+ messages in thread
From: Junio C Hamano @ 2015-02-03 21:40 UTC (permalink / raw)
  To: Jeff King; +Cc: git

Jeff King <peff@peff.net> writes:

> But wouldn't we still fail writing "foo/bar" at that point if "foo" is a
> regular file (again, we should never do this, but that is the point of
> the test).

The point of the test is not to create foo, whether it is a symlink
or an emulating regular file, in the first place.

In this piece:

>> +test_expect_failure 'symlink escape via ..' '
>> +	{
>> +		mkpatch_symlink tmp .. &&

This is a patch to create "tmp" that points at ".."

>> +		mkpatch_add tmp/foo ../foo

And this is a patch to create "tmp/foo", and make sure ../foo does
not exist in the filesystem to prepare for the test.

>> +	} >patch &&
>> +	test_must_fail git apply patch &&

And this patch, if the rejection logic were to be broken in the
future, might create "tmp" and then try to follow it to affect
"../foo".

>> +	test_path_is_missing ../foo

So if this test makes sure if "tmp" is missing, then it would be
alright, no?  The "follow the symlink to affect ../foo" may not
happen on a filesystem without symlinks, but verifying that "tmp"
is missing will assure us that the patch application is atomic,
i.e. if we see "tmp" on the filesystem after seeing "git apply"
failed, that is a sign that "git apply" failed to be atomic.

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 1/4] apply: reject input that touches outside $cwd
  2015-02-03 21:40             ` Junio C Hamano
@ 2015-02-03 21:50               ` Jeff King
  2015-02-03 22:11                 ` Junio C Hamano
  0 siblings, 1 reply; 34+ messages in thread
From: Jeff King @ 2015-02-03 21:50 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

On Tue, Feb 03, 2015 at 01:40:12PM -0800, Junio C Hamano wrote:

> Jeff King <peff@peff.net> writes:
> 
> > But wouldn't we still fail writing "foo/bar" at that point if "foo" is a
> > regular file (again, we should never do this, but that is the point of
> > the test).
> 
> The point of the test is not to create foo, whether it is a symlink
> or an emulating regular file, in the first place.

I thought the point was not to create "../bar", when "foo" points to
"..". I agree that the way you have implemented it is that we would
never even write "foo", and the test checks for that, but to me that is
the least interesting bit of what is being tested. Crossing a symlink
boundary and escaping from the tree are interesting, and the atomicity
is a side note. We could also realize that treating "foo" as a file
would fail and cancel the whole operation atomically, too.

But I think we are getting into contrasting our mental models, which is
probably not productive. I am OK with leaving it without the SYMLINKS
flag, as it should pass on systems that do not handle symlinks. And if
it does not, then that will be a good cross-check that our analysis was
sane. :)

-Peff

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 1/4] apply: reject input that touches outside $cwd
  2015-02-03 21:50               ` Jeff King
@ 2015-02-03 22:11                 ` Junio C Hamano
  0 siblings, 0 replies; 34+ messages in thread
From: Junio C Hamano @ 2015-02-03 22:11 UTC (permalink / raw)
  To: Jeff King; +Cc: git

Jeff King <peff@peff.net> writes:

> On Tue, Feb 03, 2015 at 01:40:12PM -0800, Junio C Hamano wrote:
>
>> Jeff King <peff@peff.net> writes:
>> 
>> > But wouldn't we still fail writing "foo/bar" at that point if "foo" is a
>> > regular file (again, we should never do this, but that is the point of
>> > the test).
>> 
>> The point of the test is not to create foo, whether it is a symlink
>> or an emulating regular file, in the first place.
>
> I thought the point was not to create "../bar", when "foo" points to
> "..".

That is not what the new test you added was doing, though.  You are
calling "tmp" in that test "foo" and "../foo" in the test is called
"../bar" in the message I am responding to, so that is confusing,
but in these tests you added, I do not see anything that prepares a
symbolic link ON the filesystem and wait for "git apply" to get
fooled.

> I agree that the way you have implemented it is that we would
> never even write "foo", and the test checks for that, but to me that is
> the least interesting bit of what is being tested.

They are both interesting.  When rejecting an input, "git apply"
must be atomic.  When checking an input, "git apply" should notice
and reject a patch that tries to follow a symbolic link.

Taken together:

 (1) If a patchset tries to create a symbolic link and then tries to
     follow it, the latter principle should make "git apply" to
     reject the patchset, and the former should make sure there is
     no external effect.

 (2) If a patchset tries to affect a path, and the target of the
     patch application has a symlink that may divert the operation
     to the original path the patchset wants to affect, the latter
     principle should make "git apply" to reject the patchset, too.

And my observation is that the new tests that are not protected by
SYMLINKS prerequisite (i.e. all of them) in your new test 4139, are
all the former kind.  As "git aplly" must be atomic, rejection must
be decided without touching the filesystem at all.  Hence there is
no need for the filesystem to even support symbolic links.

But some bozo may try to break "git apply" in the future and try to
incrementally apply the patch in a patchset, and at that point, the
existing "test_must_fail git apply" may pass not because we
correctly decide not to follow symbolic links but because his broken
version of "git apply" would try to create a symbolic link (which we
would turn into a regular file) and then the filesystem would fail
to follow that symbolic link mimic, and as the result the test may
still pass.

In order to prevent that from happening, we may want to make sure
that

 - "test_must_fail git apply"

 - There is no "foo" (or "tmp"); the input to 'git apply' is the
   only thing that could create, as you do not create symlinks as
   traps before running 'git apply', so this will catch the 'make it
   incremental and then break the do-not-follow logic'.

 - There is no "../bar" (or "../foo").

The second one is missing from your tests, I think, and it would be
a very good addition, even on a platform with SYMLINKS prerequisite
satisfied.  The future change might be trying to make it incremental
and impelent do-not-follow logic by observing what is in the
filesystem, and we do want to catch such a broken implementation.




	

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH v2 4/4] apply: do not touch a file beyond a symbolic link
  2015-02-03 21:01     ` Junio C Hamano
@ 2015-02-03 23:40       ` Eric Sunshine
  0 siblings, 0 replies; 34+ messages in thread
From: Eric Sunshine @ 2015-02-03 23:40 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Jeff King, Git List

On Tue, Feb 3, 2015 at 4:01 PM, Junio C Hamano <gitster@pobox.com> wrote:
> Jeff King <peff@peff.net> writes:
>> diff --git a/t/t4122-apply-symlink-inside.sh b/t/t4122-apply-symlink-inside.sh
>> index 942c5cb..fbba8dd 100755
>> --- a/t/t4122-apply-symlink-inside.sh
>> +++ b/t/t4122-apply-symlink-inside.sh
>> @@ -112,6 +113,20 @@ test_expect_success SYMLINKS 'do not follow symbolic link (same input)' '
>>       test_i18ngrep "beyond a symbolic link" error-ct &&
>>       test_must_fail git ls-files --error-unmatch arch/x86_64/dir &&
>>       test_must_fail git ls-files --error-unmatch arch/i386/dir

Broken &&-chain.

>> +
>> +     >arch/i386/dir/file &&
>> +     git add arch/i386/dir/file &&
>
> At this point, the target of the patch application has:
>
>         arch/i386/boot/Makefile
>         arch/i386/dir/file
>         arch/x86_64/boot/Makefile
>
> all of which are regular files.  The index and the working tree
> match.

^ permalink raw reply	[flat|nested] 34+ messages in thread

* [PATCH v3 0/4] "git apply" safety
  2015-02-02 23:27 [PATCH v2 0/4] "git apply" safety Junio C Hamano
                   ` (3 preceding siblings ...)
  2015-02-02 23:27 ` [PATCH v2 4/4] apply: do not touch a file " Junio C Hamano
@ 2015-02-04  0:44 ` Junio C Hamano
  2015-02-04  0:44   ` [PATCH v3 1/4] apply: reject input that touches outside the working area Junio C Hamano
                     ` (4 more replies)
  4 siblings, 5 replies; 34+ messages in thread
From: Junio C Hamano @ 2015-02-04  0:44 UTC (permalink / raw)
  To: git

The design and implementation haven't changed.

This round primarily is to update documentation and comments with
rephrases and grammofixes and tighten some tests.

The previous round begins here:

  http://thread.gmane.org/gmane.comp.version-control.git/263291

Junio C Hamano (4):
  apply: reject input that touches outside the working area
  apply: do not read from the filesystem under --index
  apply: do not read from beyond a symbolic link
  apply: do not touch a file beyond a symbolic link

 Documentation/git-apply.txt     |  15 ++++-
 builtin/apply.c                 | 141 +++++++++++++++++++++++++++++++++++++++-
 t/t4122-apply-symlink-inside.sh | 106 ++++++++++++++++++++++++++++++
 t/t4139-apply-escape.sh         | 137 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 397 insertions(+), 2 deletions(-)
 create mode 100755 t/t4139-apply-escape.sh

-- 
2.3.0-rc2-168-g106c876

^ permalink raw reply	[flat|nested] 34+ messages in thread

* [PATCH v3 1/4] apply: reject input that touches outside the working area
  2015-02-04  0:44 ` [PATCH v3 0/4] "git apply" safety Junio C Hamano
@ 2015-02-04  0:44   ` Junio C Hamano
  2015-02-04  0:44   ` [PATCH v3 2/4] apply: do not read from the filesystem under --index Junio C Hamano
                     ` (3 subsequent siblings)
  4 siblings, 0 replies; 34+ messages in thread
From: Junio C Hamano @ 2015-02-04  0:44 UTC (permalink / raw)
  To: git

By default, a patch that affects outside the working area
(either a Git controlled working tree, or the current working
directory when "git apply" is used as a replacement of GNU
patch) rejected as a mistake (or a mischief); Git itself does
not create such a patch, unless the user bends over backwards
and specifies a non-standard prefix to "git diff" and friends.

When `git apply` is used without either the `--index` or the
`--cached` option as a "better GNU patch", the user can pass
the `--unsafe-paths` option to override this safety check. This
option has no effect when `--index` or `--cached` is in use.

The new test was stolen from Jeff King with slight enhancements.
Note that a few new tests for touching outside the working area by
following a symbolic link are still expected to fail at this step,
but will be fixed in later steps.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---

 * Documentation rephrased with grammofixes courtesy of Peff.

 Documentation/git-apply.txt |  15 ++++-
 builtin/apply.c             |  26 +++++++++
 t/t4139-apply-escape.sh     | 137 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 177 insertions(+), 1 deletion(-)
 create mode 100755 t/t4139-apply-escape.sh

diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt
index f605327..a2cdc4a 100644
--- a/Documentation/git-apply.txt
+++ b/Documentation/git-apply.txt
@@ -16,7 +16,7 @@ SYNOPSIS
 	  [--ignore-space-change | --ignore-whitespace ]
 	  [--whitespace=(nowarn|warn|fix|error|error-all)]
 	  [--exclude=<path>] [--include=<path>] [--directory=<root>]
-	  [--verbose] [<patch>...]
+	  [--verbose] [--unsafe-paths] [<patch>...]
 
 DESCRIPTION
 -----------
@@ -229,6 +229,19 @@ For example, a patch that talks about updating `a/git-gui.sh` to `b/git-gui.sh`
 can be applied to the file in the working tree `modules/git-gui/git-gui.sh` by
 running `git apply --directory=modules/git-gui`.
 
+--unsafe-paths::
+	By default, a patch that affects outside the working area
+	(either a Git controlled working tree, or the current working
+	directory when "git apply" is used as a replacement of GNU
+	patch) rejected as a mistake (or a mischief); Git itself does
+	not create such a patch, unless the user bends over backwards
+	and specifies a nonstandard prefix to "git diff" and friends.
++
+When `git apply` is used without either the `--index` or the `--cached`
+option as a "better GNU patch", the user can pass the `--unsafe-paths`
+option to override this safety check.  This option has no effect when
+`--index` or `--cached` is in use.
+
 Configuration
 -------------
 
diff --git a/builtin/apply.c b/builtin/apply.c
index ef32e4f..c751ddf 100644
--- a/builtin/apply.c
+++ b/builtin/apply.c
@@ -50,6 +50,7 @@ static int apply_verbosely;
 static int allow_overlap;
 static int no_add;
 static int threeway;
+static int unsafe_paths;
 static const char *fake_ancestor;
 static int line_termination = '\n';
 static unsigned int p_context = UINT_MAX;
@@ -3483,6 +3484,23 @@ static int check_to_create(const char *new_name, int ok_if_exists)
 	return 0;
 }
 
+static void die_on_unsafe_path(struct patch *patch)
+{
+	const char *old_name = NULL;
+	const char *new_name = NULL;
+	if (patch->is_delete)
+		old_name = patch->old_name;
+	else if (!patch->is_new && !patch->is_copy)
+		old_name = patch->old_name;
+	if (!patch->is_delete)
+		new_name = patch->new_name;
+
+	if (old_name && !verify_path(old_name))
+		die(_("invalid path '%s'"), old_name);
+	if (new_name && !verify_path(new_name))
+		die(_("invalid path '%s'"), new_name);
+}
+
 /*
  * Check and apply the patch in-core; leave the result in patch->result
  * for the caller to write it out to the final destination.
@@ -3570,6 +3588,9 @@ static int check_patch(struct patch *patch)
 		}
 	}
 
+	if (!unsafe_paths)
+		die_on_unsafe_path(patch);
+
 	if (apply_data(patch, &st, ce) < 0)
 		return error(_("%s: patch does not apply"), name);
 	patch->rejected = 0;
@@ -4379,6 +4400,8 @@ int cmd_apply(int argc, const char **argv, const char *prefix_)
 			N_("make sure the patch is applicable to the current index")),
 		OPT_BOOL(0, "cached", &cached,
 			N_("apply a patch without touching the working tree")),
+		OPT_BOOL(0, "unsafe-paths", &unsafe_paths,
+			N_("accept a patch to touch outside the working area")),
 		OPT_BOOL(0, "apply", &force_apply,
 			N_("also apply the patch (use with --stat/--summary/--check)")),
 		OPT_BOOL('3', "3way", &threeway,
@@ -4451,6 +4474,9 @@ int cmd_apply(int argc, const char **argv, const char *prefix_)
 			die(_("--cached outside a repository"));
 		check_index = 1;
 	}
+	if (check_index)
+		unsafe_paths = 0;
+
 	for (i = 0; i < argc; i++) {
 		const char *arg = argv[i];
 		int fd;
diff --git a/t/t4139-apply-escape.sh b/t/t4139-apply-escape.sh
new file mode 100755
index 0000000..5e67179
--- /dev/null
+++ b/t/t4139-apply-escape.sh
@@ -0,0 +1,137 @@
+#!/bin/sh
+
+test_description='paths written by git-apply cannot escape the working tree'
+. ./test-lib.sh
+
+# tests will try to write to ../foo, and we do not
+# want them to escape the trash directory when they
+# fail
+test_expect_success 'bump git repo one level down' '
+	mkdir inside &&
+	mv .git inside/ &&
+	cd inside
+'
+
+# $1 = name of file
+# $2 = current path to file (if different)
+mkpatch_add() {
+	rm -f "${2:-$1}" &&
+	cat <<-EOF
+	diff --git a/$1 b/$1
+	new file mode 100644
+	index 0000000..53c74cd
+	--- /dev/null
+	+++ b/$1
+	@@ -0,0 +1 @@
+	+evil
+	EOF
+}
+
+mkpatch_del() {
+	echo evil >"${2:-$1}" &&
+	cat <<-EOF
+	diff --git a/$1 b/$1
+	deleted file mode 100644
+	index 53c74cd..0000000
+	--- a/$1
+	+++ /dev/null
+	@@ -1 +0,0 @@
+	-evil
+	EOF
+}
+
+# $1 = name of file
+# $2 = content of symlink
+mkpatch_symlink() {
+	rm -f "$1" &&
+	cat <<-EOF
+	diff --git a/$1 b/$1
+	new file mode 120000
+	index 0000000..$(printf "%s" "$2" | git hash-object --stdin)
+	--- /dev/null
+	+++ b/$1
+	@@ -0,0 +1 @@
+	+$2
+	\ No newline at end of file
+	EOF
+}
+
+test_expect_success 'cannot add file containing ..' '
+	mkpatch_add ../foo >patch &&
+	test_must_fail git apply patch &&
+	test_path_is_missing ../foo
+'
+
+test_expect_success 'can add file containing .. with --unsafe-paths' '
+	mkpatch_add ../foo >patch &&
+	git apply --unsafe-paths patch &&
+	test_path_is_file ../foo
+'
+
+test_expect_success  'cannot add file containing .. (index)' '
+	mkpatch_add ../foo >patch &&
+	test_must_fail git apply --index patch &&
+	test_path_is_missing ../foo
+'
+
+test_expect_success  'cannot add file containing .. with --unsafe-paths (index)' '
+	mkpatch_add ../foo >patch &&
+	test_must_fail git apply --index --unsafe-paths patch &&
+	test_path_is_missing ../foo
+'
+
+test_expect_success 'cannot del file containing ..' '
+	mkpatch_del ../foo >patch &&
+	test_must_fail git apply patch &&
+	test_path_is_file ../foo
+'
+
+test_expect_success 'can del file containing .. with --unsafe-paths' '
+	mkpatch_del ../foo >patch &&
+	git apply --unsafe-paths patch &&
+	test_path_is_missing ../foo
+'
+
+test_expect_success 'cannot del file containing .. (index)' '
+	mkpatch_del ../foo >patch &&
+	test_must_fail git apply --index patch &&
+	test_path_is_file ../foo
+'
+
+test_expect_failure 'symlink escape via ..' '
+	{
+		mkpatch_symlink tmp .. &&
+		mkpatch_add tmp/foo ../foo
+	} >patch &&
+	test_must_fail git apply patch &&
+	test_path_is_missing ../foo
+'
+
+test_expect_failure 'symlink escape via .. (index)' '
+	{
+		mkpatch_symlink tmp .. &&
+		mkpatch_add tmp/foo ../foo
+	} >patch &&
+	test_must_fail git apply --index patch &&
+	test_path_is_missing ../foo
+'
+
+test_expect_failure 'symlink escape via absolute path' '
+	{
+		mkpatch_symlink tmp "$(pwd)" &&
+		mkpatch_add tmp/foo ../foo
+	} >patch &&
+	test_must_fail git apply patch &&
+	test_path_is_missing ../foo
+'
+
+test_expect_success 'symlink escape via absolute path (index)' '
+	{
+		mkpatch_symlink tmp "$(pwd)" &&
+		mkpatch_add tmp/foo ../foo
+	} >patch &&
+	test_must_fail git apply --index patch &&
+	test_path_is_missing ../foo
+'
+
+test_done
-- 
2.3.0-rc2-168-g106c876

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH v3 2/4] apply: do not read from the filesystem under --index
  2015-02-04  0:44 ` [PATCH v3 0/4] "git apply" safety Junio C Hamano
  2015-02-04  0:44   ` [PATCH v3 1/4] apply: reject input that touches outside the working area Junio C Hamano
@ 2015-02-04  0:44   ` Junio C Hamano
  2015-02-04  0:44   ` [PATCH v3 3/4] apply: do not read from beyond a symbolic link Junio C Hamano
                     ` (2 subsequent siblings)
  4 siblings, 0 replies; 34+ messages in thread
From: Junio C Hamano @ 2015-02-04  0:44 UTC (permalink / raw)
  To: git

We currently read the preimage to apply a patch from the index only
when the --cached option is given.  Do so also when the command is
running under the --index option.  With --index, the index entry and
the working tree file for a path that is involved in a patch must be
identical, so this should not affect the result, but by reading from
the index, we will get the protection to avoid reading an unintended
path beyond a symbolic link automatically.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---

 * Same as v2

 builtin/apply.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/builtin/apply.c b/builtin/apply.c
index c751ddf..05eaf54 100644
--- a/builtin/apply.c
+++ b/builtin/apply.c
@@ -3136,7 +3136,7 @@ static int load_patch_target(struct strbuf *buf,
 			     const char *name,
 			     unsigned expected_mode)
 {
-	if (cached) {
+	if (cached || check_index) {
 		if (read_file_or_gitlink(ce, buf))
 			return error(_("read of %s failed"), name);
 	} else if (name) {
-- 
2.3.0-rc2-168-g106c876

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH v3 3/4] apply: do not read from beyond a symbolic link
  2015-02-04  0:44 ` [PATCH v3 0/4] "git apply" safety Junio C Hamano
  2015-02-04  0:44   ` [PATCH v3 1/4] apply: reject input that touches outside the working area Junio C Hamano
  2015-02-04  0:44   ` [PATCH v3 2/4] apply: do not read from the filesystem under --index Junio C Hamano
@ 2015-02-04  0:44   ` Junio C Hamano
  2015-02-04  0:44   ` [PATCH v3 4/4] apply: do not touch a file " Junio C Hamano
  2015-02-10 22:36   ` [PATCH v4 0/4] "git apply" safety Junio C Hamano
  4 siblings, 0 replies; 34+ messages in thread
From: Junio C Hamano @ 2015-02-04  0:44 UTC (permalink / raw)
  To: git

We should reject a patch, whether it renames/copies dir/file to
elsewhere with or without modificiation, or updates dir/file in
place, if "dir/" part is actually a symbolic link to elsewhere,
by making sure that the code to read the preimage does not read
from a path that is beyond a symbolic link.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---

 * Same as v2

 builtin/apply.c                 |  2 ++
 t/t4122-apply-symlink-inside.sh | 19 +++++++++++++++++++
 2 files changed, 21 insertions(+)

diff --git a/builtin/apply.c b/builtin/apply.c
index 05eaf54..60d821c 100644
--- a/builtin/apply.c
+++ b/builtin/apply.c
@@ -3145,6 +3145,8 @@ static int load_patch_target(struct strbuf *buf,
 				return read_file_or_gitlink(ce, buf);
 			else
 				return SUBMODULE_PATCH_WITHOUT_INDEX;
+		} else if (has_symlink_leading_path(name, strlen(name))) {
+			return error(_("reading from '%s' beyond a symbolic link"), name);
 		} else {
 			if (read_old_data(st, name, buf))
 				return error(_("read of %s failed"), name);
diff --git a/t/t4122-apply-symlink-inside.sh b/t/t4122-apply-symlink-inside.sh
index 70b3a06..035c080 100755
--- a/t/t4122-apply-symlink-inside.sh
+++ b/t/t4122-apply-symlink-inside.sh
@@ -52,4 +52,23 @@ test_expect_success 'check result' '
 
 '
 
+test_expect_success SYMLINKS 'do not read from beyond symbolic link' '
+	git reset --hard &&
+	mkdir -p arch/x86_64/dir &&
+	>arch/x86_64/dir/file &&
+	git add arch/x86_64/dir/file &&
+	echo line >arch/x86_64/dir/file &&
+	git diff >patch &&
+	git reset --hard &&
+
+	mkdir arch/i386/dir &&
+	>arch/i386/dir/file &&
+	ln -s ../i386/dir arch/x86_64/dir &&
+
+	test_must_fail git apply patch &&
+	test_must_fail git apply --cached patch &&
+	test_must_fail git apply --index patch
+
+'
+
 test_done
-- 
2.3.0-rc2-168-g106c876

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH v3 4/4] apply: do not touch a file beyond a symbolic link
  2015-02-04  0:44 ` [PATCH v3 0/4] "git apply" safety Junio C Hamano
                     ` (2 preceding siblings ...)
  2015-02-04  0:44   ` [PATCH v3 3/4] apply: do not read from beyond a symbolic link Junio C Hamano
@ 2015-02-04  0:44   ` Junio C Hamano
  2015-02-10 22:36   ` [PATCH v4 0/4] "git apply" safety Junio C Hamano
  4 siblings, 0 replies; 34+ messages in thread
From: Junio C Hamano @ 2015-02-04  0:44 UTC (permalink / raw)
  To: git

Because Git tracks symbolic links as symbolic links, a path that
has a symbolic link in its leading part (e.g. path/to/dir/file,
where path/to/dir is a symbolic link to somewhere else, be it
inside or outside the working tree) can never appear in a patch
that validly applies, unless the same patch first removes the
symbolic link to allow a directory to be created there.

Detect and reject such a patch.

Things to note:

 - Unfortunately, we cannot reuse the has_symlink_leading_path()
   from dir.c, as that is only about the working tree, but "git
   apply" can be told to apply the patch only to the index or to
   both the index and to the working tree.

 - We cannot directly use has_symlink_leading_path() even when we
   are applying only to the working tree, as an early patch of a
   valid input may remove a symbolic link path/to/dir and then a
   later patch of the input may create a path path/to/dir/file, but
   "git apply" first checks the input without touching either the
   index or the working tree.  The leading symbolic link check must
   be done on the interim result we compute in-core (i.e. after the
   first patch, there is no path/to/dir symbolic link and it is
   perfectly valid to create path/to/dir/file).

   Similarly, when an input creates a symbolic link path/to/dir and
   then creates a file path/to/dir/file, we need to flag it as an
   error without actually creating path/to/dir symbolic link in the
   filesystem.

Instead, for any patch in the input that leaves a path (i.e. a non
deletion) in the result, we check all leading paths against the
resulting tree that the patch would create by inspecting all the
patches in the input and then the target of patch application
(either the index or the working tree).

This way, we catch a mischief or a mistake to add a symbolic link
path/to/dir and a file path/to/dir/file at the same time, while
allowing a valid patch that removes a symbolic link path/to/dir and
then adds a file path/to/dir/file.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---

 * In code comment to check_patch() rephrased to clarify why we
   don't have to check a is_delete patch there while we have to
   check others.

   An additional test case stolen from Peff.

 builtin/apply.c                 | 111 ++++++++++++++++++++++++++++++++++++++++
 t/t4122-apply-symlink-inside.sh |  87 +++++++++++++++++++++++++++++++
 t/t4139-apply-escape.sh         |   6 +--
 3 files changed, 201 insertions(+), 3 deletions(-)

diff --git a/builtin/apply.c b/builtin/apply.c
index 60d821c..28975e6 100644
--- a/builtin/apply.c
+++ b/builtin/apply.c
@@ -3486,6 +3486,103 @@ static int check_to_create(const char *new_name, int ok_if_exists)
 	return 0;
 }
 
+/*
+ * We need to keep track of how symlinks in the preimage are
+ * manipulated by the patches.  A patch to add a/b/c where a/b
+ * is a symlink should not be allowed to affect the directory
+ * the symlink points at, but if the same patch removes a/b,
+ * it is perfectly fine, as the patch removes a/b to make room
+ * to create a directory a/b so that a/b/c can be created.
+ */
+static struct string_list symlink_changes;
+#define SYMLINK_GOES_AWAY 01
+#define SYMLINK_IN_RESULT 02
+
+static uintptr_t register_symlink_changes(const char *path, uintptr_t what)
+{
+	struct string_list_item *ent;
+
+	ent = string_list_lookup(&symlink_changes, path);
+	if (!ent) {
+		ent = string_list_insert(&symlink_changes, path);
+		ent->util = (void *)0;
+	}
+	ent->util = (void *)(what | ((uintptr_t)ent->util));
+	return (uintptr_t)ent->util;
+}
+
+static uintptr_t check_symlink_changes(const char *path)
+{
+	struct string_list_item *ent;
+
+	ent = string_list_lookup(&symlink_changes, path);
+	if (!ent)
+		return 0;
+	return (uintptr_t)ent->util;
+}
+
+static void prepare_symlink_changes(struct patch *patch)
+{
+	for ( ; patch; patch = patch->next) {
+		if ((patch->old_name && S_ISLNK(patch->old_mode)) &&
+		    (patch->is_rename || patch->is_delete))
+			/* the symlink at patch->old_name is removed */
+			register_symlink_changes(patch->old_name, SYMLINK_GOES_AWAY);
+
+		if (patch->new_name && S_ISLNK(patch->new_mode))
+			/* the symlink at patch->new_name is created or remains */
+			register_symlink_changes(patch->new_name, SYMLINK_IN_RESULT);
+	}
+}
+
+static int path_is_beyond_symlink_1(struct strbuf *name)
+{
+	do {
+		unsigned int change;
+
+		while (--name->len && name->buf[name->len] != '/')
+			; /* scan backwards */
+		if (!name->len)
+			break;
+		name->buf[name->len] = '\0';
+		change = check_symlink_changes(name->buf);
+		if (change & SYMLINK_IN_RESULT)
+			return 1;
+		if (change & SYMLINK_GOES_AWAY)
+			/*
+			 * This cannot be "return 0", because we may
+			 * see a new one created at a higher level.
+			 */
+			continue;
+
+		/* otherwise, check the preimage */
+		if (check_index) {
+			int pos = cache_name_pos(name->buf, name->len);
+			if (0 <= pos &&
+			    S_ISLNK(active_cache[pos]->ce_mode))
+				return 1;
+		} else {
+			struct stat st;
+			if (!lstat(name->buf, &st) && S_ISLNK(st.st_mode))
+				return 1;
+		}
+	} while (1);
+	return 0;
+}
+
+static int path_is_beyond_symlink(const char *name_)
+{
+	int ret;
+	struct strbuf name = STRBUF_INIT;
+
+	assert(*name_ != '\0');
+	strbuf_addstr(&name, name_);
+	ret = path_is_beyond_symlink_1(&name);
+	strbuf_release(&name);
+
+	return ret;
+}
+
 static void die_on_unsafe_path(struct patch *patch)
 {
 	const char *old_name = NULL;
@@ -3593,6 +3690,19 @@ static int check_patch(struct patch *patch)
 	if (!unsafe_paths)
 		die_on_unsafe_path(patch);
 
+	/*
+	 * An attempt to read from or delete a path that is beyond a
+	 * symbolic link will be prevented by load_patch_target() that
+	 * is called at the beginning of apply_data() so we do not
+	 * have to worry about a patch marked with "is_delete" bit
+	 * here.  We however need to make sure that the patch result
+	 * is not deposited to a path that is beyond a symbolic link
+	 * here.
+	 */
+	if (!patch->is_delete && path_is_beyond_symlink(patch->new_name))
+		return error(_("affected file '%s' is beyond a symbolic link"),
+			     patch->new_name);
+
 	if (apply_data(patch, &st, ce) < 0)
 		return error(_("%s: patch does not apply"), name);
 	patch->rejected = 0;
@@ -3603,6 +3713,7 @@ static int check_patch_list(struct patch *patch)
 {
 	int err = 0;
 
+	prepare_symlink_changes(patch);
 	prepare_fn_table(patch);
 	while (patch) {
 		if (apply_verbosely)
diff --git a/t/t4122-apply-symlink-inside.sh b/t/t4122-apply-symlink-inside.sh
index 035c080..1779c0a 100755
--- a/t/t4122-apply-symlink-inside.sh
+++ b/t/t4122-apply-symlink-inside.sh
@@ -71,4 +71,91 @@ test_expect_success SYMLINKS 'do not read from beyond symbolic link' '
 
 '
 
+test_expect_success SYMLINKS 'do not follow symbolic link (setup)' '
+
+	rm -rf arch/i386/dir arch/x86_64/dir &&
+	git reset --hard &&
+	ln -s ../i386/dir arch/x86_64/dir &&
+	git add arch/x86_64/dir &&
+	git diff HEAD >add_symlink.patch &&
+	git reset --hard &&
+
+	mkdir arch/x86_64/dir &&
+	>arch/x86_64/dir/file &&
+	git add arch/x86_64/dir/file &&
+	git diff HEAD >add_file.patch &&
+	git diff -R HEAD >del_file.patch &&
+	git reset --hard &&
+	rm -fr arch/x86_64/dir &&
+
+	cat add_symlink.patch add_file.patch >patch &&
+	cat add_symlink.patch del_file.patch >tricky_del &&
+
+	mkdir arch/i386/dir
+'
+
+test_expect_success SYMLINKS 'do not follow symbolic link (same input)' '
+
+	# same input creates a confusing symbolic link
+	test_must_fail git apply patch 2>error-wt &&
+	test_i18ngrep "beyond a symbolic link" error-wt &&
+	test_path_is_missing arch/x86_64/dir &&
+	test_path_is_missing arch/i386/dir/file &&
+
+	test_must_fail git apply --index patch 2>error-ix &&
+	test_i18ngrep "beyond a symbolic link" error-ix &&
+	test_path_is_missing arch/x86_64/dir &&
+	test_path_is_missing arch/i386/dir/file &&
+	test_must_fail git ls-files --error-unmatch arch/x86_64/dir &&
+	test_must_fail git ls-files --error-unmatch arch/i386/dir &&
+
+	test_must_fail git apply --cached patch 2>error-ct &&
+	test_i18ngrep "beyond a symbolic link" error-ct &&
+	test_must_fail git ls-files --error-unmatch arch/x86_64/dir &&
+	test_must_fail git ls-files --error-unmatch arch/i386/dir &&
+
+	>arch/i386/dir/file &&
+	git add arch/i386/dir/file &&
+
+	test_must_fail git apply tricky_del &&
+	test_path_is_file arch/i386/dir/file &&
+
+	test_must_fail git apply --index tricky_del &&
+	test_path_is_file arch/i386/dir/file &&
+	test_must_fail git ls-files --error-unmatch arch/x86_64/dir &&
+	git ls-files --error-unmatch arch/i386/dir &&
+
+	test_must_fail git apply --cached tricky_del &&
+	test_must_fail git ls-files --error-unmatch arch/x86_64/dir &&
+	git ls-files --error-unmatch arch/i386/dir
+'
+
+test_expect_success SYMLINKS 'do not follow symbolic link (existing)' '
+
+	# existing symbolic link
+	git reset --hard &&
+	ln -s ../i386/dir arch/x86_64/dir &&
+	git add arch/x86_64/dir &&
+
+	test_must_fail git apply add_file.patch 2>error-wt-add &&
+	test_i18ngrep "beyond a symbolic link" error-wt-add &&
+	test_path_is_missing arch/i386/dir/file &&
+
+	mkdir arch/i386/dir &&
+	>arch/i386/dir/file &&
+	test_must_fail git apply del_file.patch 2>error-wt-del &&
+	test_i18ngrep "beyond a symbolic link" error-wt-del &&
+	test_path_is_file arch/i386/dir/file &&
+	rm arch/i386/dir/file &&
+
+	test_must_fail git apply --index add_file.patch 2>error-ix-add &&
+	test_i18ngrep "beyond a symbolic link" error-ix-add &&
+	test_path_is_missing arch/i386/dir/file &&
+	test_must_fail git ls-files --error-unmatch arch/i386/dir &&
+
+	test_must_fail git apply --cached add_file.patch 2>error-ct-file &&
+	test_i18ngrep "beyond a symbolic link" error-ct-file &&
+	test_must_fail git ls-files --error-unmatch arch/i386/dir
+'
+
 test_done
diff --git a/t/t4139-apply-escape.sh b/t/t4139-apply-escape.sh
index 5e67179..25917cc 100755
--- a/t/t4139-apply-escape.sh
+++ b/t/t4139-apply-escape.sh
@@ -98,7 +98,7 @@ test_expect_success 'cannot del file containing .. (index)' '
 	test_path_is_file ../foo
 '
 
-test_expect_failure 'symlink escape via ..' '
+test_expect_success 'symlink escape via ..' '
 	{
 		mkpatch_symlink tmp .. &&
 		mkpatch_add tmp/foo ../foo
@@ -107,7 +107,7 @@ test_expect_failure 'symlink escape via ..' '
 	test_path_is_missing ../foo
 '
 
-test_expect_failure 'symlink escape via .. (index)' '
+test_expect_success 'symlink escape via .. (index)' '
 	{
 		mkpatch_symlink tmp .. &&
 		mkpatch_add tmp/foo ../foo
@@ -116,7 +116,7 @@ test_expect_failure 'symlink escape via .. (index)' '
 	test_path_is_missing ../foo
 '
 
-test_expect_failure 'symlink escape via absolute path' '
+test_expect_success 'symlink escape via absolute path' '
 	{
 		mkpatch_symlink tmp "$(pwd)" &&
 		mkpatch_add tmp/foo ../foo
-- 
2.3.0-rc2-168-g106c876

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH v4 0/4]  "git apply" safety
  2015-02-04  0:44 ` [PATCH v3 0/4] "git apply" safety Junio C Hamano
                     ` (3 preceding siblings ...)
  2015-02-04  0:44   ` [PATCH v3 4/4] apply: do not touch a file " Junio C Hamano
@ 2015-02-10 22:36   ` Junio C Hamano
  2015-02-10 22:36     ` [PATCH v4 1/4] apply: reject input that touches outside the working area Junio C Hamano
                       ` (3 more replies)
  4 siblings, 4 replies; 34+ messages in thread
From: Junio C Hamano @ 2015-02-10 22:36 UTC (permalink / raw)
  To: git

Git tracks symbolic links; e.g. you can remove files that have been
tracked in a directory "dir/file*" and then creates a symbolic link
at "dir" to point elsewhere, express such a change as a patchset and
then apply it to the original tree.  Consequently, applying a patch
to update dir/file, when you have "dir" as a symbolic link pointing
somewhere, must fail, just like a patch whose preimage does not
match what you have in tree you are trying to apply the patch to
gets rejected.  Also, we fundamentally do not like to touch a path
that contains ".git" as a path component.

This round uses cache_file_exists() in the last patch to cope with
case insensitive filesystems better.

The previous round begins here:

  http://thread.gmane.org/gmane.comp.version-control.git/263341

Junio C Hamano (4):
  apply: reject input that touches outside the working area
  apply: do not read from the filesystem under --index
  apply: do not read from beyond a symbolic link
  apply: do not touch a file beyond a symbolic link

 Documentation/git-apply.txt     |  12 +++-
 builtin/apply.c                 | 142 +++++++++++++++++++++++++++++++++++++++-
 t/t4122-apply-symlink-inside.sh | 106 ++++++++++++++++++++++++++++++
 t/t4139-apply-escape.sh         | 141 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 399 insertions(+), 2 deletions(-)
 create mode 100755 t/t4139-apply-escape.sh

-- 
2.3.0-185-g073f588

^ permalink raw reply	[flat|nested] 34+ messages in thread

* [PATCH v4 1/4] apply: reject input that touches outside the working area
  2015-02-10 22:36   ` [PATCH v4 0/4] "git apply" safety Junio C Hamano
@ 2015-02-10 22:36     ` Junio C Hamano
  2015-02-10 22:36     ` [PATCH v4 2/4] apply: do not read from the filesystem under --index Junio C Hamano
                       ` (2 subsequent siblings)
  3 siblings, 0 replies; 34+ messages in thread
From: Junio C Hamano @ 2015-02-10 22:36 UTC (permalink / raw)
  To: git

By default, a patch that affects outside the working area (either a
Git controlled working tree, or the current working directory when
"git apply" is used as a replacement of GNU patch) is rejected as a
mistake (or a mischief).  Git itself does not create such a patch,
unless the user bends over backwards and specifies a non-standard
prefix to "git diff" and friends.

When `git apply` is used as a "better GNU patch", the user can pass
the `--unsafe-paths` option to override this safety check. This
option has no effect when `--index` or `--cached` is in use.

The new test was stolen from Jeff King with slight enhancements.
Note that a few new tests for touching outside the working area by
following a symbolic link are still expected to fail at this step,
but will be fixed in later steps.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 Documentation/git-apply.txt |  12 +++-
 builtin/apply.c             |  26 ++++++++
 t/t4139-apply-escape.sh     | 141 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 178 insertions(+), 1 deletion(-)
 create mode 100755 t/t4139-apply-escape.sh

diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt
index f605327..9489664 100644
--- a/Documentation/git-apply.txt
+++ b/Documentation/git-apply.txt
@@ -16,7 +16,7 @@ SYNOPSIS
 	  [--ignore-space-change | --ignore-whitespace ]
 	  [--whitespace=(nowarn|warn|fix|error|error-all)]
 	  [--exclude=<path>] [--include=<path>] [--directory=<root>]
-	  [--verbose] [<patch>...]
+	  [--verbose] [--unsafe-paths] [<patch>...]
 
 DESCRIPTION
 -----------
@@ -229,6 +229,16 @@ For example, a patch that talks about updating `a/git-gui.sh` to `b/git-gui.sh`
 can be applied to the file in the working tree `modules/git-gui/git-gui.sh` by
 running `git apply --directory=modules/git-gui`.
 
+--unsafe-paths::
+	By default, a patch that affects outside the working area
+	(either a Git controlled working tree, or the current working
+	directory when "git apply" is used as a replacement of GNU
+	patch) is rejected as a mistake (or a mischief).
++
+When `git apply` is used as a "better GNU patch", the user can pass
+the `--unsafe-paths` option to override this safety check.  This option
+has no effect when `--index` or `--cached` is in use.
+
 Configuration
 -------------
 
diff --git a/builtin/apply.c b/builtin/apply.c
index ef32e4f..8561236 100644
--- a/builtin/apply.c
+++ b/builtin/apply.c
@@ -50,6 +50,7 @@ static int apply_verbosely;
 static int allow_overlap;
 static int no_add;
 static int threeway;
+static int unsafe_paths;
 static const char *fake_ancestor;
 static int line_termination = '\n';
 static unsigned int p_context = UINT_MAX;
@@ -3483,6 +3484,23 @@ static int check_to_create(const char *new_name, int ok_if_exists)
 	return 0;
 }
 
+static void die_on_unsafe_path(struct patch *patch)
+{
+	const char *old_name = NULL;
+	const char *new_name = NULL;
+	if (patch->is_delete)
+		old_name = patch->old_name;
+	else if (!patch->is_new && !patch->is_copy)
+		old_name = patch->old_name;
+	if (!patch->is_delete)
+		new_name = patch->new_name;
+
+	if (old_name && !verify_path(old_name))
+		die(_("invalid path '%s'"), old_name);
+	if (new_name && !verify_path(new_name))
+		die(_("invalid path '%s'"), new_name);
+}
+
 /*
  * Check and apply the patch in-core; leave the result in patch->result
  * for the caller to write it out to the final destination.
@@ -3570,6 +3588,9 @@ static int check_patch(struct patch *patch)
 		}
 	}
 
+	if (!unsafe_paths)
+		die_on_unsafe_path(patch);
+
 	if (apply_data(patch, &st, ce) < 0)
 		return error(_("%s: patch does not apply"), name);
 	patch->rejected = 0;
@@ -4379,6 +4400,8 @@ int cmd_apply(int argc, const char **argv, const char *prefix_)
 			N_("make sure the patch is applicable to the current index")),
 		OPT_BOOL(0, "cached", &cached,
 			N_("apply a patch without touching the working tree")),
+		OPT_BOOL(0, "unsafe-paths", &unsafe_paths,
+			N_("accept a patch that touches outside the working area")),
 		OPT_BOOL(0, "apply", &force_apply,
 			N_("also apply the patch (use with --stat/--summary/--check)")),
 		OPT_BOOL('3', "3way", &threeway,
@@ -4451,6 +4474,9 @@ int cmd_apply(int argc, const char **argv, const char *prefix_)
 			die(_("--cached outside a repository"));
 		check_index = 1;
 	}
+	if (check_index)
+		unsafe_paths = 0;
+
 	for (i = 0; i < argc; i++) {
 		const char *arg = argv[i];
 		int fd;
diff --git a/t/t4139-apply-escape.sh b/t/t4139-apply-escape.sh
new file mode 100755
index 0000000..02b97b3
--- /dev/null
+++ b/t/t4139-apply-escape.sh
@@ -0,0 +1,141 @@
+#!/bin/sh
+
+test_description='paths written by git-apply cannot escape the working tree'
+. ./test-lib.sh
+
+# tests will try to write to ../foo, and we do not
+# want them to escape the trash directory when they
+# fail
+test_expect_success 'bump git repo one level down' '
+	mkdir inside &&
+	mv .git inside/ &&
+	cd inside
+'
+
+# $1 = name of file
+# $2 = current path to file (if different)
+mkpatch_add () {
+	rm -f "${2:-$1}" &&
+	cat <<-EOF
+	diff --git a/$1 b/$1
+	new file mode 100644
+	index 0000000..53c74cd
+	--- /dev/null
+	+++ b/$1
+	@@ -0,0 +1 @@
+	+evil
+	EOF
+}
+
+mkpatch_del () {
+	echo evil >"${2:-$1}" &&
+	cat <<-EOF
+	diff --git a/$1 b/$1
+	deleted file mode 100644
+	index 53c74cd..0000000
+	--- a/$1
+	+++ /dev/null
+	@@ -1 +0,0 @@
+	-evil
+	EOF
+}
+
+# $1 = name of file
+# $2 = content of symlink
+mkpatch_symlink () {
+	rm -f "$1" &&
+	cat <<-EOF
+	diff --git a/$1 b/$1
+	new file mode 120000
+	index 0000000..$(printf "%s" "$2" | git hash-object --stdin)
+	--- /dev/null
+	+++ b/$1
+	@@ -0,0 +1 @@
+	+$2
+	\ No newline at end of file
+	EOF
+}
+
+test_expect_success 'cannot create file containing ..' '
+	mkpatch_add ../foo >patch &&
+	test_must_fail git apply patch &&
+	test_path_is_missing ../foo
+'
+
+test_expect_success 'can create file containing .. with --unsafe-paths' '
+	mkpatch_add ../foo >patch &&
+	git apply --unsafe-paths patch &&
+	test_path_is_file ../foo
+'
+
+test_expect_success  'cannot create file containing .. (index)' '
+	mkpatch_add ../foo >patch &&
+	test_must_fail git apply --index patch &&
+	test_path_is_missing ../foo
+'
+
+test_expect_success  'cannot create file containing .. with --unsafe-paths (index)' '
+	mkpatch_add ../foo >patch &&
+	test_must_fail git apply --index --unsafe-paths patch &&
+	test_path_is_missing ../foo
+'
+
+test_expect_success 'cannot delete file containing ..' '
+	mkpatch_del ../foo >patch &&
+	test_must_fail git apply patch &&
+	test_path_is_file ../foo
+'
+
+test_expect_success 'can delete file containing .. with --unsafe-paths' '
+	mkpatch_del ../foo >patch &&
+	git apply --unsafe-paths patch &&
+	test_path_is_missing ../foo
+'
+
+test_expect_success 'cannot delete file containing .. (index)' '
+	mkpatch_del ../foo >patch &&
+	test_must_fail git apply --index patch &&
+	test_path_is_file ../foo
+'
+
+test_expect_failure SYMLINKS 'symlink escape via ..' '
+	{
+		mkpatch_symlink tmp .. &&
+		mkpatch_add tmp/foo ../foo
+	} >patch &&
+	test_must_fail git apply patch &&
+	test_path_is_missing tmp &&
+	test_path_is_missing ../foo
+'
+
+test_expect_failure SYMLINKS 'symlink escape via .. (index)' '
+	{
+		mkpatch_symlink tmp .. &&
+		mkpatch_add tmp/foo ../foo
+	} >patch &&
+	test_must_fail git apply --index patch &&
+	test_path_is_missing tmp &&
+	test_path_is_missing ../foo
+'
+
+test_expect_failure SYMLINKS 'symlink escape via absolute path' '
+	{
+		mkpatch_symlink tmp "$(pwd)" &&
+		mkpatch_add tmp/foo ../foo
+	} >patch &&
+	test_must_fail git apply patch &&
+	test_path_is_missing tmp &&
+	test_path_is_missing ../foo
+'
+
+test_expect_failure SYMLINKS 'symlink escape via absolute path (index)' '
+	{
+		mkpatch_symlink tmp "$(pwd)" &&
+		mkpatch_add tmp/foo ../foo
+	} >patch &&
+	test_must_fail git apply --index patch &&
+	test_path_is_missing tmp &&
+	test_path_is_missing ../foo
+'
+
+test_done
-- 
2.3.0-185-g073f588

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH v4 2/4] apply: do not read from the filesystem under --index
  2015-02-10 22:36   ` [PATCH v4 0/4] "git apply" safety Junio C Hamano
  2015-02-10 22:36     ` [PATCH v4 1/4] apply: reject input that touches outside the working area Junio C Hamano
@ 2015-02-10 22:36     ` Junio C Hamano
  2015-02-10 22:36     ` [PATCH v4 3/4] apply: do not read from beyond a symbolic link Junio C Hamano
  2015-02-10 22:36     ` [PATCH v4 4/4] apply: do not touch a file " Junio C Hamano
  3 siblings, 0 replies; 34+ messages in thread
From: Junio C Hamano @ 2015-02-10 22:36 UTC (permalink / raw)
  To: git

We currently read the preimage to apply a patch from the index only
when the --cached option is given.  Do so also when the command is
running under the --index option.  With --index, the index entry and
the working tree file for a path that is involved in a patch must be
identical, so this should not affect the result, but by reading from
the index, we will get the protection to avoid reading an unintended
path beyond a symbolic link automatically.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/apply.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/builtin/apply.c b/builtin/apply.c
index 8561236..21e45a0 100644
--- a/builtin/apply.c
+++ b/builtin/apply.c
@@ -3136,7 +3136,7 @@ static int load_patch_target(struct strbuf *buf,
 			     const char *name,
 			     unsigned expected_mode)
 {
-	if (cached) {
+	if (cached || check_index) {
 		if (read_file_or_gitlink(ce, buf))
 			return error(_("read of %s failed"), name);
 	} else if (name) {
-- 
2.3.0-185-g073f588

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH v4 3/4] apply: do not read from beyond a symbolic link
  2015-02-10 22:36   ` [PATCH v4 0/4] "git apply" safety Junio C Hamano
  2015-02-10 22:36     ` [PATCH v4 1/4] apply: reject input that touches outside the working area Junio C Hamano
  2015-02-10 22:36     ` [PATCH v4 2/4] apply: do not read from the filesystem under --index Junio C Hamano
@ 2015-02-10 22:36     ` Junio C Hamano
  2015-02-10 22:36     ` [PATCH v4 4/4] apply: do not touch a file " Junio C Hamano
  3 siblings, 0 replies; 34+ messages in thread
From: Junio C Hamano @ 2015-02-10 22:36 UTC (permalink / raw)
  To: git

We should reject a patch, whether it renames/copies dir/file to
elsewhere with or without modificiation, or updates dir/file in
place, if "dir/" part is actually a symbolic link to elsewhere,
by making sure that the code to read the preimage does not read
from a path that is beyond a symbolic link.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/apply.c                 |  2 ++
 t/t4122-apply-symlink-inside.sh | 19 +++++++++++++++++++
 2 files changed, 21 insertions(+)

diff --git a/builtin/apply.c b/builtin/apply.c
index 21e45a0..422e4ce 100644
--- a/builtin/apply.c
+++ b/builtin/apply.c
@@ -3145,6 +3145,8 @@ static int load_patch_target(struct strbuf *buf,
 				return read_file_or_gitlink(ce, buf);
 			else
 				return SUBMODULE_PATCH_WITHOUT_INDEX;
+		} else if (has_symlink_leading_path(name, strlen(name))) {
+			return error(_("reading from '%s' beyond a symbolic link"), name);
 		} else {
 			if (read_old_data(st, name, buf))
 				return error(_("read of %s failed"), name);
diff --git a/t/t4122-apply-symlink-inside.sh b/t/t4122-apply-symlink-inside.sh
index 70b3a06..035c080 100755
--- a/t/t4122-apply-symlink-inside.sh
+++ b/t/t4122-apply-symlink-inside.sh
@@ -52,4 +52,23 @@ test_expect_success 'check result' '
 
 '
 
+test_expect_success SYMLINKS 'do not read from beyond symbolic link' '
+	git reset --hard &&
+	mkdir -p arch/x86_64/dir &&
+	>arch/x86_64/dir/file &&
+	git add arch/x86_64/dir/file &&
+	echo line >arch/x86_64/dir/file &&
+	git diff >patch &&
+	git reset --hard &&
+
+	mkdir arch/i386/dir &&
+	>arch/i386/dir/file &&
+	ln -s ../i386/dir arch/x86_64/dir &&
+
+	test_must_fail git apply patch &&
+	test_must_fail git apply --cached patch &&
+	test_must_fail git apply --index patch
+
+'
+
 test_done
-- 
2.3.0-185-g073f588

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH v4 4/4] apply: do not touch a file beyond a symbolic link
  2015-02-10 22:36   ` [PATCH v4 0/4] "git apply" safety Junio C Hamano
                       ` (2 preceding siblings ...)
  2015-02-10 22:36     ` [PATCH v4 3/4] apply: do not read from beyond a symbolic link Junio C Hamano
@ 2015-02-10 22:36     ` Junio C Hamano
  3 siblings, 0 replies; 34+ messages in thread
From: Junio C Hamano @ 2015-02-10 22:36 UTC (permalink / raw)
  To: git

Because Git tracks symbolic links as symbolic links, a path that
has a symbolic link in its leading part (e.g. path/to/dir/file,
where path/to/dir is a symbolic link to somewhere else, be it
inside or outside the working tree) can never appear in a patch
that validly applies, unless the same patch first removes the
symbolic link to allow a directory to be created there.

Detect and reject such a patch.

Things to note:

 - Unfortunately, we cannot reuse the has_symlink_leading_path()
   from dir.c, as that is only about the working tree, but "git
   apply" can be told to apply the patch only to the index or to
   both the index and to the working tree.

 - We cannot directly use has_symlink_leading_path() even when we
   are applying only to the working tree, as an early patch of a
   valid input may remove a symbolic link path/to/dir and then a
   later patch of the input may create a path path/to/dir/file, but
   "git apply" first checks the input without touching either the
   index or the working tree.  The leading symbolic link check must
   be done on the interim result we compute in-core (i.e. after the
   first patch, there is no path/to/dir symbolic link and it is
   perfectly valid to create path/to/dir/file).

   Similarly, when an input creates a symbolic link path/to/dir and
   then creates a file path/to/dir/file, we need to flag it as an
   error without actually creating path/to/dir symbolic link in the
   filesystem.

Instead, for any patch in the input that leaves a path (i.e. a non
deletion) in the result, we check all leading paths against the
resulting tree that the patch would create by inspecting all the
patches in the input and then the target of patch application
(either the index or the working tree).

This way, we catch a mischief or a mistake to add a symbolic link
path/to/dir and a file path/to/dir/file at the same time, while
allowing a valid patch that removes a symbolic link path/to/dir and
then adds a file path/to/dir/file.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/apply.c                 | 112 ++++++++++++++++++++++++++++++++++++++++
 t/t4122-apply-symlink-inside.sh |  87 +++++++++++++++++++++++++++++++
 t/t4139-apply-escape.sh         |   8 +--
 3 files changed, 203 insertions(+), 4 deletions(-)

diff --git a/builtin/apply.c b/builtin/apply.c
index 422e4ce..c2c6fda 100644
--- a/builtin/apply.c
+++ b/builtin/apply.c
@@ -3486,6 +3486,104 @@ static int check_to_create(const char *new_name, int ok_if_exists)
 	return 0;
 }
 
+/*
+ * We need to keep track of how symlinks in the preimage are
+ * manipulated by the patches.  A patch to add a/b/c where a/b
+ * is a symlink should not be allowed to affect the directory
+ * the symlink points at, but if the same patch removes a/b,
+ * it is perfectly fine, as the patch removes a/b to make room
+ * to create a directory a/b so that a/b/c can be created.
+ */
+static struct string_list symlink_changes;
+#define SYMLINK_GOES_AWAY 01
+#define SYMLINK_IN_RESULT 02
+
+static uintptr_t register_symlink_changes(const char *path, uintptr_t what)
+{
+	struct string_list_item *ent;
+
+	ent = string_list_lookup(&symlink_changes, path);
+	if (!ent) {
+		ent = string_list_insert(&symlink_changes, path);
+		ent->util = (void *)0;
+	}
+	ent->util = (void *)(what | ((uintptr_t)ent->util));
+	return (uintptr_t)ent->util;
+}
+
+static uintptr_t check_symlink_changes(const char *path)
+{
+	struct string_list_item *ent;
+
+	ent = string_list_lookup(&symlink_changes, path);
+	if (!ent)
+		return 0;
+	return (uintptr_t)ent->util;
+}
+
+static void prepare_symlink_changes(struct patch *patch)
+{
+	for ( ; patch; patch = patch->next) {
+		if ((patch->old_name && S_ISLNK(patch->old_mode)) &&
+		    (patch->is_rename || patch->is_delete))
+			/* the symlink at patch->old_name is removed */
+			register_symlink_changes(patch->old_name, SYMLINK_GOES_AWAY);
+
+		if (patch->new_name && S_ISLNK(patch->new_mode))
+			/* the symlink at patch->new_name is created or remains */
+			register_symlink_changes(patch->new_name, SYMLINK_IN_RESULT);
+	}
+}
+
+static int path_is_beyond_symlink_1(struct strbuf *name)
+{
+	do {
+		unsigned int change;
+
+		while (--name->len && name->buf[name->len] != '/')
+			; /* scan backwards */
+		if (!name->len)
+			break;
+		name->buf[name->len] = '\0';
+		change = check_symlink_changes(name->buf);
+		if (change & SYMLINK_IN_RESULT)
+			return 1;
+		if (change & SYMLINK_GOES_AWAY)
+			/*
+			 * This cannot be "return 0", because we may
+			 * see a new one created at a higher level.
+			 */
+			continue;
+
+		/* otherwise, check the preimage */
+		if (check_index) {
+			struct cache_entry *ce;
+
+			ce = cache_file_exists(name->buf, name->len, ignore_case);
+			if (ce && S_ISLNK(ce->ce_mode))
+				return 1;
+		} else {
+			struct stat st;
+			if (!lstat(name->buf, &st) && S_ISLNK(st.st_mode))
+				return 1;
+		}
+	} while (1);
+	return 0;
+}
+
+static int path_is_beyond_symlink(const char *name_)
+{
+	int ret;
+	struct strbuf name = STRBUF_INIT;
+
+	assert(*name_ != '\0');
+	strbuf_addstr(&name, name_);
+	ret = path_is_beyond_symlink_1(&name);
+	strbuf_release(&name);
+
+	return ret;
+}
+
 static void die_on_unsafe_path(struct patch *patch)
 {
 	const char *old_name = NULL;
@@ -3593,6 +3691,19 @@ static int check_patch(struct patch *patch)
 	if (!unsafe_paths)
 		die_on_unsafe_path(patch);
 
+	/*
+	 * An attempt to read from or delete a path that is beyond a
+	 * symbolic link will be prevented by load_patch_target() that
+	 * is called at the beginning of apply_data() so we do not
+	 * have to worry about a patch marked with "is_delete" bit
+	 * here.  We however need to make sure that the patch result
+	 * is not deposited to a path that is beyond a symbolic link
+	 * here.
+	 */
+	if (!patch->is_delete && path_is_beyond_symlink(patch->new_name))
+		return error(_("affected file '%s' is beyond a symbolic link"),
+			     patch->new_name);
+
 	if (apply_data(patch, &st, ce) < 0)
 		return error(_("%s: patch does not apply"), name);
 	patch->rejected = 0;
@@ -3603,6 +3714,7 @@ static int check_patch_list(struct patch *patch)
 {
 	int err = 0;
 
+	prepare_symlink_changes(patch);
 	prepare_fn_table(patch);
 	while (patch) {
 		if (apply_verbosely)
diff --git a/t/t4122-apply-symlink-inside.sh b/t/t4122-apply-symlink-inside.sh
index 035c080..1779c0a 100755
--- a/t/t4122-apply-symlink-inside.sh
+++ b/t/t4122-apply-symlink-inside.sh
@@ -71,4 +71,91 @@ test_expect_success SYMLINKS 'do not read from beyond symbolic link' '
 
 '
 
+test_expect_success SYMLINKS 'do not follow symbolic link (setup)' '
+
+	rm -rf arch/i386/dir arch/x86_64/dir &&
+	git reset --hard &&
+	ln -s ../i386/dir arch/x86_64/dir &&
+	git add arch/x86_64/dir &&
+	git diff HEAD >add_symlink.patch &&
+	git reset --hard &&
+
+	mkdir arch/x86_64/dir &&
+	>arch/x86_64/dir/file &&
+	git add arch/x86_64/dir/file &&
+	git diff HEAD >add_file.patch &&
+	git diff -R HEAD >del_file.patch &&
+	git reset --hard &&
+	rm -fr arch/x86_64/dir &&
+
+	cat add_symlink.patch add_file.patch >patch &&
+	cat add_symlink.patch del_file.patch >tricky_del &&
+
+	mkdir arch/i386/dir
+'
+
+test_expect_success SYMLINKS 'do not follow symbolic link (same input)' '
+
+	# same input creates a confusing symbolic link
+	test_must_fail git apply patch 2>error-wt &&
+	test_i18ngrep "beyond a symbolic link" error-wt &&
+	test_path_is_missing arch/x86_64/dir &&
+	test_path_is_missing arch/i386/dir/file &&
+
+	test_must_fail git apply --index patch 2>error-ix &&
+	test_i18ngrep "beyond a symbolic link" error-ix &&
+	test_path_is_missing arch/x86_64/dir &&
+	test_path_is_missing arch/i386/dir/file &&
+	test_must_fail git ls-files --error-unmatch arch/x86_64/dir &&
+	test_must_fail git ls-files --error-unmatch arch/i386/dir &&
+
+	test_must_fail git apply --cached patch 2>error-ct &&
+	test_i18ngrep "beyond a symbolic link" error-ct &&
+	test_must_fail git ls-files --error-unmatch arch/x86_64/dir &&
+	test_must_fail git ls-files --error-unmatch arch/i386/dir &&
+
+	>arch/i386/dir/file &&
+	git add arch/i386/dir/file &&
+
+	test_must_fail git apply tricky_del &&
+	test_path_is_file arch/i386/dir/file &&
+
+	test_must_fail git apply --index tricky_del &&
+	test_path_is_file arch/i386/dir/file &&
+	test_must_fail git ls-files --error-unmatch arch/x86_64/dir &&
+	git ls-files --error-unmatch arch/i386/dir &&
+
+	test_must_fail git apply --cached tricky_del &&
+	test_must_fail git ls-files --error-unmatch arch/x86_64/dir &&
+	git ls-files --error-unmatch arch/i386/dir
+'
+
+test_expect_success SYMLINKS 'do not follow symbolic link (existing)' '
+
+	# existing symbolic link
+	git reset --hard &&
+	ln -s ../i386/dir arch/x86_64/dir &&
+	git add arch/x86_64/dir &&
+
+	test_must_fail git apply add_file.patch 2>error-wt-add &&
+	test_i18ngrep "beyond a symbolic link" error-wt-add &&
+	test_path_is_missing arch/i386/dir/file &&
+
+	mkdir arch/i386/dir &&
+	>arch/i386/dir/file &&
+	test_must_fail git apply del_file.patch 2>error-wt-del &&
+	test_i18ngrep "beyond a symbolic link" error-wt-del &&
+	test_path_is_file arch/i386/dir/file &&
+	rm arch/i386/dir/file &&
+
+	test_must_fail git apply --index add_file.patch 2>error-ix-add &&
+	test_i18ngrep "beyond a symbolic link" error-ix-add &&
+	test_path_is_missing arch/i386/dir/file &&
+	test_must_fail git ls-files --error-unmatch arch/i386/dir &&
+
+	test_must_fail git apply --cached add_file.patch 2>error-ct-file &&
+	test_i18ngrep "beyond a symbolic link" error-ct-file &&
+	test_must_fail git ls-files --error-unmatch arch/i386/dir
+'
+
 test_done
diff --git a/t/t4139-apply-escape.sh b/t/t4139-apply-escape.sh
index 02b97b3..45b5660 100755
--- a/t/t4139-apply-escape.sh
+++ b/t/t4139-apply-escape.sh
@@ -98,7 +98,7 @@ test_expect_success 'cannot delete file containing .. (index)' '
 	test_path_is_file ../foo
 '
 
-test_expect_failure SYMLINKS 'symlink escape via ..' '
+test_expect_success SYMLINKS 'symlink escape via ..' '
 	{
 		mkpatch_symlink tmp .. &&
 		mkpatch_add tmp/foo ../foo
@@ -108,7 +108,7 @@ test_expect_failure SYMLINKS 'symlink escape via ..' '
 	test_path_is_missing ../foo
 '
 
-test_expect_failure SYMLINKS 'symlink escape via .. (index)' '
+test_expect_success SYMLINKS 'symlink escape via .. (index)' '
 	{
 		mkpatch_symlink tmp .. &&
 		mkpatch_add tmp/foo ../foo
@@ -118,7 +118,7 @@ test_expect_failure SYMLINKS 'symlink escape via .. (index)' '
 	test_path_is_missing ../foo
 '
 
-test_expect_failure SYMLINKS 'symlink escape via absolute path' '
+test_expect_success SYMLINKS 'symlink escape via absolute path' '
 	{
 		mkpatch_symlink tmp "$(pwd)" &&
 		mkpatch_add tmp/foo ../foo
@@ -128,7 +128,7 @@ test_expect_failure SYMLINKS 'symlink escape via absolute path' '
 	test_path_is_missing ../foo
 '
 
-test_expect_failure SYMLINKS 'symlink escape via absolute path (index)' '
+test_expect_success SYMLINKS 'symlink escape via absolute path (index)' '
 	{
 		mkpatch_symlink tmp "$(pwd)" &&
 		mkpatch_add tmp/foo ../foo
-- 
2.3.0-185-g073f588

^ permalink raw reply related	[flat|nested] 34+ messages in thread

end of thread, other threads:[~2015-02-10 22:36 UTC | newest]

Thread overview: 34+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-02-02 23:27 [PATCH v2 0/4] "git apply" safety Junio C Hamano
2015-02-02 23:27 ` [PATCH v2 1/4] apply: reject input that touches outside $cwd Junio C Hamano
2015-02-03  0:45   ` Jeff King
2015-02-03  0:50   ` Jeff King
2015-02-03 20:23     ` Junio C Hamano
2015-02-03 21:01       ` Jeff King
2015-02-03 21:23         ` Junio C Hamano
2015-02-03 21:24           ` Jeff King
2015-02-03 21:40             ` Junio C Hamano
2015-02-03 21:50               ` Jeff King
2015-02-03 22:11                 ` Junio C Hamano
2015-02-03  5:56   ` Torsten Bögershausen
2015-02-02 23:27 ` [PATCH v2 2/4] apply: do not read from the filesystem under --index Junio C Hamano
2015-02-02 23:27 ` [PATCH v2 3/4] apply: do not read from beyond a symbolic link Junio C Hamano
2015-02-03  0:08   ` Stefan Beller
2015-02-03 19:37     ` Junio C Hamano
2015-02-03 19:44       ` Stefan Beller
2015-02-03 20:31         ` Junio C Hamano
2015-02-02 23:27 ` [PATCH v2 4/4] apply: do not touch a file " Junio C Hamano
2015-02-03  1:11   ` Jeff King
2015-02-03  1:56     ` Junio C Hamano
2015-02-03  2:04       ` Jeff King
2015-02-03 21:01     ` Junio C Hamano
2015-02-03 23:40       ` Eric Sunshine
2015-02-04  0:44 ` [PATCH v3 0/4] "git apply" safety Junio C Hamano
2015-02-04  0:44   ` [PATCH v3 1/4] apply: reject input that touches outside the working area Junio C Hamano
2015-02-04  0:44   ` [PATCH v3 2/4] apply: do not read from the filesystem under --index Junio C Hamano
2015-02-04  0:44   ` [PATCH v3 3/4] apply: do not read from beyond a symbolic link Junio C Hamano
2015-02-04  0:44   ` [PATCH v3 4/4] apply: do not touch a file " Junio C Hamano
2015-02-10 22:36   ` [PATCH v4 0/4] "git apply" safety Junio C Hamano
2015-02-10 22:36     ` [PATCH v4 1/4] apply: reject input that touches outside the working area Junio C Hamano
2015-02-10 22:36     ` [PATCH v4 2/4] apply: do not read from the filesystem under --index Junio C Hamano
2015-02-10 22:36     ` [PATCH v4 3/4] apply: do not read from beyond a symbolic link Junio C Hamano
2015-02-10 22:36     ` [PATCH v4 4/4] apply: do not touch a file " Junio C Hamano

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.