Skip to content

Commit fade728

Browse files
pks-tgitster
authored andcommitted
apply: fix writing behind newly created symbolic links
When writing files git-apply(1) initially makes sure that none of the files it is about to create are behind a symlink: ``` $ git init repo Initialized empty Git repository in /tmp/repo/.git/ $ cd repo/ $ ln -s dir symlink $ git apply - <<EOF diff --git a/symlink/file b/symlink/file new file mode 100644 index 0000000..e69de29 EOF error: affected file 'symlink/file' is beyond a symbolic link ``` This safety mechanism is crucial to ensure that we don't write outside of the repository's working directory. It can be fooled though when the patch that is being applied creates the symbolic link in the first place, which can lead to writing files in arbitrary locations. Fix this by checking whether the path we're about to create is beyond a symlink or not. Tightening these checks like this should be fine as we already have these precautions in Git as explained above. Ideally, we should update the check we do up-front before starting to reflect the computed changes to the working tree so that we catch this case as well, but as part of embargoed security work, adding an equivalent check just before we try to write out a file should serve us well as a reasonable first step. Digging back into history shows that this vulnerability has existed since at least Git v2.9.0. As Git v2.8.0 and older don't build on my system anymore I cannot tell whether older versions are affected, as well. Reported-by: Joern Schneeweisz <[email protected]> Signed-off-by: Patrick Steinhardt <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 0227130 commit fade728

File tree

2 files changed

+108
-0
lines changed

2 files changed

+108
-0
lines changed

apply.c

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4400,6 +4400,33 @@ static int create_one_file(struct apply_state *state,
44004400
if (state->cached)
44014401
return 0;
44024402

4403+
/*
4404+
* We already try to detect whether files are beyond a symlink in our
4405+
* up-front checks. But in the case where symlinks are created by any
4406+
* of the intermediate hunks it can happen that our up-front checks
4407+
* didn't yet see the symlink, but at the point of arriving here there
4408+
* in fact is one. We thus repeat the check for symlinks here.
4409+
*
4410+
* Note that this does not make the up-front check obsolete as the
4411+
* failure mode is different:
4412+
*
4413+
* - The up-front checks cause us to abort before we have written
4414+
* anything into the working directory. So when we exit this way the
4415+
* working directory remains clean.
4416+
*
4417+
* - The checks here happen in the middle of the action where we have
4418+
* already started to apply the patch. The end result will be a dirty
4419+
* working directory.
4420+
*
4421+
* Ideally, we should update the up-front checks to catch what would
4422+
* happen when we apply the patch before we damage the working tree.
4423+
* We have all the information necessary to do so. But for now, as a
4424+
* part of embargoed security work, having this check would serve as a
4425+
* reasonable first step.
4426+
*/
4427+
if (path_is_beyond_symlink(state, path))
4428+
return error(_("affected file '%s' is beyond a symbolic link"), path);
4429+
44034430
res = try_create_file(state, path, mode, buf, size);
44044431
if (res < 0)
44054432
return -1;

t/t4115-apply-symlink.sh

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,85 @@ test_expect_success 'apply --index symlink patch' '
4444
4545
'
4646

47+
test_expect_success 'symlink setup' '
48+
ln -s .git symlink &&
49+
git add symlink &&
50+
git commit -m "add symlink"
51+
'
52+
53+
test_expect_success SYMLINKS 'symlink escape when creating new files' '
54+
test_when_finished "git reset --hard && git clean -dfx" &&
55+
56+
cat >patch <<-EOF &&
57+
diff --git a/symlink b/renamed-symlink
58+
similarity index 100%
59+
rename from symlink
60+
rename to renamed-symlink
61+
--
62+
diff --git /dev/null b/renamed-symlink/create-me
63+
new file mode 100644
64+
index 0000000..039727e
65+
--- /dev/null
66+
+++ b/renamed-symlink/create-me
67+
@@ -0,0 +1,1 @@
68+
+busted
69+
EOF
70+
71+
test_must_fail git apply patch 2>stderr &&
72+
cat >expected_stderr <<-EOF &&
73+
error: affected file ${SQ}renamed-symlink/create-me${SQ} is beyond a symbolic link
74+
EOF
75+
test_cmp expected_stderr stderr &&
76+
! test_path_exists .git/create-me
77+
'
78+
79+
test_expect_success SYMLINKS 'symlink escape when modifying file' '
80+
test_when_finished "git reset --hard && git clean -dfx" &&
81+
touch .git/modify-me &&
82+
83+
cat >patch <<-EOF &&
84+
diff --git a/symlink b/renamed-symlink
85+
similarity index 100%
86+
rename from symlink
87+
rename to renamed-symlink
88+
--
89+
diff --git a/renamed-symlink/modify-me b/renamed-symlink/modify-me
90+
index 1111111..2222222 100644
91+
--- a/renamed-symlink/modify-me
92+
+++ b/renamed-symlink/modify-me
93+
@@ -0,0 +1,1 @@
94+
+busted
95+
EOF
96+
97+
test_must_fail git apply patch 2>stderr &&
98+
cat >expected_stderr <<-EOF &&
99+
error: renamed-symlink/modify-me: No such file or directory
100+
EOF
101+
test_cmp expected_stderr stderr &&
102+
test_must_be_empty .git/modify-me
103+
'
104+
105+
test_expect_success SYMLINKS 'symlink escape when deleting file' '
106+
test_when_finished "git reset --hard && git clean -dfx && rm .git/delete-me" &&
107+
touch .git/delete-me &&
108+
109+
cat >patch <<-EOF &&
110+
diff --git a/symlink b/renamed-symlink
111+
similarity index 100%
112+
rename from symlink
113+
rename to renamed-symlink
114+
--
115+
diff --git a/renamed-symlink/delete-me b/renamed-symlink/delete-me
116+
deleted file mode 100644
117+
index 1111111..0000000 100644
118+
EOF
119+
120+
test_must_fail git apply patch 2>stderr &&
121+
cat >expected_stderr <<-EOF &&
122+
error: renamed-symlink/delete-me: No such file or directory
123+
EOF
124+
test_cmp expected_stderr stderr &&
125+
test_path_is_file .git/delete-me
126+
'
127+
47128
test_done

0 commit comments

Comments
 (0)