git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH] apply: refuse touching a file beyond symlink

From
Junio C Hamano <gitster@pobox.com>
Date
Jan 29, 2015, 20:45 UTC
Message-ID
<xmqqa911e2ot.fsf_-_@gitster.dls.corp.google.com>
In-Reply-To
<xmqqtwzadrj8.fsf@gitster.dls.corp.google.com>

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 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 interim result and then either the index or the working tree. The interim results of applying patches are kept track of by fn_table logic for us already, so use it to fiture out if existing a symbolic link will cause problems, if a new symbolic link that will cause problems will appear, etc.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 * At least I convinced myself enough to say that I do not seem to
   be breaking things with this patch, after taking patches out of
   dozens of random pairs of commits from the Linux kernel history
   and applying them using this version ;-) No code change since
   last night's snapshot, but the test script is a bit more thorough
   in this version.
 builtin/apply.c                 | 44 +++++++++++++++++++++++++++++
 t/t4122-apply-symlink-inside.sh | 62 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 106 insertions(+)
diff --git a/builtin/apply.c b/builtin/apply.c
index ef32e4f..dcb44fb 100644
--- a/builtin/apply.c
+++ b/builtin/apply.c
@@ -3483,6 +3483,46 @@ static int check_to_create(const char *new_name, int ok_if_exists)
 	return 0;
 }
 
+static int path_is_beyond_symlink(const char *name_)
+{
+	struct strbuf name = STRBUF_INIT;
+
+	strbuf_addstr(&name, name_);
+	do {
+		struct patch *previous;
+
+		while (--name.len && name.buf[name.len] != '/')
+			; /* scan backwards */
+		if (!name.len)
+			break;
+		name.buf[name.len] = '\0';
+		previous = in_fn_table(name.buf);
+		if (previous) {
+			if (!was_deleted(previous) &&
+			    !to_be_deleted(previous) &&
+			    previous->new_mode &&
+			    S_ISLNK(previous->new_mode))
+				goto symlink_found;
+		} else if (check_index) {
+			int pos = cache_name_pos(name.buf, name.len);
+			if (0 <= pos &&
+			    S_ISLNK(active_cache[pos]->ce_mode))
+				goto symlink_found;
+		} else {
+			struct stat st;
+			if (!lstat(name.buf, &st) && S_ISLNK(st.st_mode))
+				goto symlink_found;
+		}
+	} while (1);
+
+	strbuf_release(&name);
+	return 0;
+symlink_found:
+	strbuf_release(&name);
+	return 1;
+
+}
+
 /*
  * 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 +3610,10 @@ static int check_patch(struct patch *patch)
 		}
 	}
 
+	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;
diff --git a/t/t4122-apply-symlink-inside.sh b/t/t4122-apply-symlink-inside.sh
index 70b3a06..0a8de4a 100755
--- a/t/t4122-apply-symlink-inside.sh
+++ b/t/t4122-apply-symlink-inside.sh
@@ -52,4 +52,66 @@ test_expect_success 'check result' '
 
 '
 
+test_expect_success SYMLINKS 'do not follow symbolic link (setup)' '
+
+	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 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 confusihng symbolic link
+	test_must_fail git apply patch 2>error-wt &&
+	test_i18ngrep "beyond a symbolic link" error-wt &&
+	test ! -e arch/x86_64/dir &&
+	test ! -e arch/i386/dir/file &&
+
+	test_must_fail git apply --index patch 2>error-ix &&
+	test_i18ngrep "beyond a symbolic link" error-ix &&
+	test ! -e arch/x86_64/dir &&
+	test ! -e 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-file &&
+	test_i18ngrep "beyond a symbolic link" error-wt-file &&
+	test ! -e arch/i386/dir/file &&
+
+	test_must_fail git apply --index add_file.patch 2>error-ix-file &&
+	test_i18ngrep "beyond a symbolic link" error-ix-file &&
+	test ! -e 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
-- 
2.3.0-rc2-153-g9e53805
Previous: Junio C HamanoNext: Stefan Beller
Message 16 of 35 in “patch-2.7.3 no longer applies relative symbolic link patches”
  1. Josh BoyerJan 26, 2015
  2. Josh BoyerJan 26, 2015
  3. Linus TorvaldsJan 26, 2015
  4. David KastrupJan 26, 2015
  5. Josh BoyerJan 26, 2015
  6. Linus TorvaldsJan 26, 2015
  7. Junio C HamanoJan 26, 2015
  8. Linus TorvaldsJan 26, 2015
  9. Andreas GruenbacherJan 27, 2015
  10. Andreas GruenbacherJan 31, 2015
  11. Josh BoyerJan 26, 2015
  12. Junio C HamanoJan 27, 2015
  13. Junio C HamanoJan 27, 2015
  14. Junio C HamanoJan 29, 2015
  15. Junio C HamanoJan 29, 2015
  16. apply: refuse touching a file beyond symlinkJunio C Hamano, Jan 29, 2015
  17. Stefan BellerJan 29, 2015
  18. 2/1 apply: reject input that touches outside $cwdJunio C Hamano, Jan 29, 2015
  19. Jeff KingJan 30, 2015
  20. Junio C HamanoJan 30, 2015
  21. Jeff KingJan 30, 2015
  22. Christian CouderJan 30, 2015
  23. Jeff KingJan 30, 2015
  24. Junio C HamanoJan 30, 2015
  25. Jeff KingJan 30, 2015
  26. Junio C HamanoJan 30, 2015
  27. Jeff KingJan 30, 2015
  28. Junio C HamanoJan 30, 2015
  29. Junio C HamanoJan 30, 2015
  30. Jeff KingJan 30, 2015
  31. Junio C HamanoJan 30, 2015
  32. Jeff KingJan 30, 2015
  33. Junio C HamanoJan 30, 2015
  34. Junio C HamanoJan 30, 2015
  35. Andreas GruenbacherJan 27, 2015

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.