debian: apply security fixes from 2.24.1
[git/debian.git] / debian / patches / 0008-clone-recurse-submodules-prevent-name-squatting-on-Wi.diff
bloba9adb6ecebbe3e05e8bd0efe0d257993dde9d53c
1 From d6c490804c9cff3b195946a40c3d953ed9770f76 Mon Sep 17 00:00:00 2001
2 From: Johannes Schindelin <johannes.schindelin@gmx.de>
3 Date: Thu, 12 Sep 2019 14:20:39 +0200
4 Subject: clone --recurse-submodules: prevent name squatting on Windows
6 In addition to preventing `.git` from being tracked by Git, on Windows
7 we also have to prevent `git~1` from being tracked, as the default NTFS
8 short name (also known as the "8.3 filename") for the file name `.git`
9 is `git~1`, otherwise it would be possible for malicious repositories to
10 write directly into the `.git/` directory, e.g. a `post-checkout` hook
11 that would then be executed _during_ a recursive clone.
13 When we implemented appropriate protections in 2b4c6efc821 (read-cache:
14 optionally disallow NTFS .git variants, 2014-12-16), we had analyzed
15 carefully that the `.git` directory or file would be guaranteed to be
16 the first directory entry to be written. Otherwise it would be possible
17 e.g. for a file named `..git` to be assigned the short name `git~1` and
18 subsequently, the short name generated for `.git` would be `git~2`. Or
19 `git~3`. Or even `~9999999` (for a detailed explanation of the lengths
20 we have to go to protect `.gitmodules`, see the commit message of
21 e7cb0b4455c (is_ntfs_dotgit: match other .git files, 2018-05-11)).
23 However, by exploiting two issues (that will be addressed in a related
24 patch series close by), it is currently possible to clone a submodule
25 into a non-empty directory:
27 - On Windows, file names cannot end in a space or a period (for
28 historical reasons: the period separating the base name from the file
29 extension was not actually written to disk, and the base name/file
30 extension was space-padded to the full 8/3 characters, respectively).
31 Helpfully, when creating a directory under the name, say, `sub.`, that
32 trailing period is trimmed automatically and the actual name on disk
33 is `sub`.
35 This means that while Git thinks that the submodule names `sub` and
36 `sub.` are different, they both access `.git/modules/sub/`.
38 - While the backslash character is a valid file name character on Linux,
39 it is not so on Windows. As Git tries to be cross-platform, it
40 therefore allows backslash characters in the file names stored in tree
41 objects.
43 Which means that it is totally possible that a submodule `c` sits next
44 to a file `c\..git`, and on Windows, during recursive clone a file
45 called `..git` will be written into `c/`, of course _before_ the
46 submodule is cloned.
48 Note that the actual exploit is not quite as simple as having a
49 submodule `c` next to a file `c\..git`, as we have to make sure that the
50 directory `.git/modules/b` already exists when the submodule is checked
51 out, otherwise a different code path is taken in `module_clone()` that
52 does _not_ allow a non-empty submodule directory to exist already.
54 Even if we will address both issues nearby (the next commit will
55 disallow backslash characters in tree entries' file names on Windows,
56 and another patch will disallow creating directories/files with trailing
57 spaces or periods), it is a wise idea to defend in depth against this
58 sort of attack vector: when submodules are cloned recursively, we now
59 _require_ the directory to be empty, addressing CVE-2019-1349.
61 Note: the code path we patch is shared with the code path of `git
62 submodule update --init`, which must not expect, in general, that the
63 directory is empty. Hence we have to introduce the new option
64 `--force-init` and hand it all the way down from `git submodule` to the
65 actual `git submodule--helper` process that performs the initial clone.
67 Reported-by: Nicolas Joly <Nicolas.Joly@microsoft.com>
68 Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
69 (cherry picked from commit 0060fd1511b94c918928fa3708f69a3f33895a4a)
70 Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
71 ---
72 builtin/clone.c | 2 +-
73 builtin/submodule--helper.c | 14 ++++++++++++--
74 git-submodule.sh | 6 ++++++
75 t/t7415-submodule-names.sh | 31 +++++++++++++++++++++++++++++++
76 4 files changed, 50 insertions(+), 3 deletions(-)
78 diff --git a/builtin/clone.c b/builtin/clone.c
79 index c46ee29f0a..53e04b14b3 100644
80 --- a/builtin/clone.c
81 +++ b/builtin/clone.c
82 @@ -789,7 +789,7 @@ static int checkout(int submodule_progress)
84 if (!err && (option_recurse_submodules.nr > 0)) {
85 struct argv_array args = ARGV_ARRAY_INIT;
86 - argv_array_pushl(&args, "submodule", "update", "--init", "--recursive", NULL);
87 + argv_array_pushl(&args, "submodule", "update", "--require-init", "--recursive", NULL);
89 if (option_shallow_submodules == 1)
90 argv_array_push(&args, "--depth=1");
91 diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
92 index 2c2395a620..2b74f3d589 100644
93 --- a/builtin/submodule--helper.c
94 +++ b/builtin/submodule--helper.c
95 @@ -19,6 +19,7 @@
96 #include "diffcore.h"
97 #include "diff.h"
98 #include "object-store.h"
99 +#include "dir.h"
101 #define OPT_QUIET (1 << 0)
102 #define OPT_CACHED (1 << 1)
103 @@ -1359,7 +1360,7 @@ static int module_clone(int argc, const char **argv, const char *prefix)
104 char *p, *path = NULL, *sm_gitdir;
105 struct strbuf sb = STRBUF_INIT;
106 struct string_list reference = STRING_LIST_INIT_NODUP;
107 - int dissociate = 0;
108 + int dissociate = 0, require_init = 0;
109 char *sm_alternate = NULL, *error_strategy = NULL;
111 struct option module_clone_options[] = {
112 @@ -1386,6 +1387,8 @@ static int module_clone(int argc, const char **argv, const char *prefix)
113 OPT__QUIET(&quiet, "Suppress output for cloning a submodule"),
114 OPT_BOOL(0, "progress", &progress,
115 N_("force cloning progress")),
116 + OPT_BOOL(0, "require-init", &require_init,
117 + N_("disallow cloning into non-empty directory")),
118 OPT_END()
121 @@ -1424,6 +1427,8 @@ static int module_clone(int argc, const char **argv, const char *prefix)
122 die(_("clone of '%s' into submodule path '%s' failed"),
123 url, path);
124 } else {
125 + if (require_init && !access(path, X_OK) && !is_empty_dir(path))
126 + die(_("directory not empty: '%s'"), path);
127 if (safe_create_leading_directories_const(path) < 0)
128 die(_("could not create directory '%s'"), path);
129 strbuf_addf(&sb, "%s/index", sm_gitdir);
130 @@ -1536,6 +1541,7 @@ struct submodule_update_clone {
131 int recommend_shallow;
132 struct string_list references;
133 int dissociate;
134 + unsigned require_init;
135 const char *depth;
136 const char *recursive_prefix;
137 const char *prefix;
138 @@ -1554,7 +1560,7 @@ struct submodule_update_clone {
139 int max_jobs;
141 #define SUBMODULE_UPDATE_CLONE_INIT {0, MODULE_LIST_INIT, 0, \
142 - SUBMODULE_UPDATE_STRATEGY_INIT, 0, 0, -1, STRING_LIST_INIT_DUP, 0, \
143 + SUBMODULE_UPDATE_STRATEGY_INIT, 0, 0, -1, STRING_LIST_INIT_DUP, 0, 0, \
144 NULL, NULL, NULL, \
145 NULL, 0, 0, 0, NULL, 0, 0, 1}
147 @@ -1681,6 +1687,8 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
148 argv_array_pushl(&child->args, "--prefix", suc->prefix, NULL);
149 if (suc->recommend_shallow && sub->recommend_shallow == 1)
150 argv_array_push(&child->args, "--depth=1");
151 + if (suc->require_init)
152 + argv_array_push(&child->args, "--require-init");
153 argv_array_pushl(&child->args, "--path", sub->path, NULL);
154 argv_array_pushl(&child->args, "--name", sub->name, NULL);
155 argv_array_pushl(&child->args, "--url", url, NULL);
156 @@ -1870,6 +1878,8 @@ static int update_clone(int argc, const char **argv, const char *prefix)
157 OPT__QUIET(&suc.quiet, N_("don't print cloning progress")),
158 OPT_BOOL(0, "progress", &suc.progress,
159 N_("force cloning progress")),
160 + OPT_BOOL(0, "require-init", &suc.require_init,
161 + N_("disallow cloning into non-empty directory")),
162 OPT_END()
165 diff --git a/git-submodule.sh b/git-submodule.sh
166 index c7f58c5756..58713c5467 100755
167 --- a/git-submodule.sh
168 +++ b/git-submodule.sh
169 @@ -36,6 +36,7 @@ reference=
170 cached=
171 recursive=
172 init=
173 +require_init=
174 files=
175 remote=
176 nofetch=
177 @@ -466,6 +467,10 @@ cmd_update()
178 -i|--init)
179 init=1
181 + --require-init)
182 + init=1
183 + require_init=1
184 + ;;
185 --remote)
186 remote=1
188 @@ -548,6 +553,7 @@ cmd_update()
189 ${reference:+"$reference"} \
190 ${dissociate:+"--dissociate"} \
191 ${depth:+--depth "$depth"} \
192 + ${require_init:+--require-init} \
193 $recommend_shallow \
194 $jobs \
195 -- \
196 diff --git a/t/t7415-submodule-names.sh b/t/t7415-submodule-names.sh
197 index 49a37efe9c..75dcdff173 100755
198 --- a/t/t7415-submodule-names.sh
199 +++ b/t/t7415-submodule-names.sh
200 @@ -191,4 +191,35 @@ test_expect_success 'fsck detects corrupt .gitmodules' '
204 +test_expect_success MINGW 'prevent git~1 squatting on Windows' '
205 + git init squatting &&
207 + cd squatting &&
208 + mkdir a &&
209 + touch a/..git &&
210 + git add a/..git &&
211 + test_tick &&
212 + git commit -m initial &&
214 + modules="$(test_write_lines \
215 + "[submodule \"b.\"]" "url = ." "path = c" \
216 + "[submodule \"b\"]" "url = ." "path = d\\\\a" |
217 + git hash-object -w --stdin)" &&
218 + rev="$(git rev-parse --verify HEAD)" &&
219 + hash="$(echo x | git hash-object -w --stdin)" &&
220 + git update-index --add \
221 + --cacheinfo 100644,$modules,.gitmodules \
222 + --cacheinfo 160000,$rev,c \
223 + --cacheinfo 160000,$rev,d\\a \
224 + --cacheinfo 100644,$hash,d./a/x \
225 + --cacheinfo 100644,$hash,d./a/..git &&
226 + test_tick &&
227 + git commit -m "module"
228 + ) &&
229 + test_must_fail git \
230 + clone --recurse-submodules squatting squatting-clone 2>err &&
231 + test_i18ngrep "directory not empty" err &&
232 + ! grep gitdir squatting-clone/d/a/git~2
235 test_done
237 2.24.0.393.g34dc348eaf