diff options
author | Minjae Kim <flowergom@gmail.com> | 2021-03-27 12:11:12 +0900 |
---|---|---|
committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2021-03-31 00:06:23 +0100 |
commit | d3b1daa7af8e5eb88037c4ed16bc519e826d80e1 (patch) | |
tree | 7692c9d0dd222f7e2f08944e0c991da1d6a5ac1b | |
parent | 06b4910c3ada5398faf9369d686b82eee441bb19 (diff) | |
download | poky-d3b1daa7af8e5eb88037c4ed16bc519e826d80e1.tar.gz |
git: fix CVE-2021-21300
checkout: fix bug that makes checkout follow symlinks in leading path
Upstream-Status: Acepted [https://github.com/git/git/commit/684dd4c2b414bcf648505e74498a608f28de4592]
CVE: CVE-2021-21300
(From OE-Core rev: 1b680f6aca14c92d03d32c4974292788140d7a65)
Signed-off-by: Minjae Kim <flowergom@gmail.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
-rw-r--r-- | meta/recipes-devtools/git/git.inc | 4 | ||||
-rw-r--r-- | meta/recipes-devtools/git/git/CVE-2021-21300.patch | 304 |
2 files changed, 307 insertions, 1 deletions
diff --git a/meta/recipes-devtools/git/git.inc b/meta/recipes-devtools/git/git.inc index 0cc40b9378..fb1dddc011 100644 --- a/meta/recipes-devtools/git/git.inc +++ b/meta/recipes-devtools/git/git.inc | |||
@@ -9,7 +9,9 @@ PROVIDES_append_class-native = " git-replacement-native" | |||
9 | 9 | ||
10 | SRC_URI = "${KERNELORG_MIRROR}/software/scm/git/git-${PV}.tar.gz;name=tarball \ | 10 | SRC_URI = "${KERNELORG_MIRROR}/software/scm/git/git-${PV}.tar.gz;name=tarball \ |
11 | ${KERNELORG_MIRROR}/software/scm/git/git-manpages-${PV}.tar.gz;name=manpages \ | 11 | ${KERNELORG_MIRROR}/software/scm/git/git-manpages-${PV}.tar.gz;name=manpages \ |
12 | file://fixsort.patch" | 12 | file://fixsort.patch \ |
13 | file://CVE-2021-21300.patch \ | ||
14 | " | ||
13 | 15 | ||
14 | S = "${WORKDIR}/git-${PV}" | 16 | S = "${WORKDIR}/git-${PV}" |
15 | 17 | ||
diff --git a/meta/recipes-devtools/git/git/CVE-2021-21300.patch b/meta/recipes-devtools/git/git/CVE-2021-21300.patch new file mode 100644 index 0000000000..ec5d98395d --- /dev/null +++ b/meta/recipes-devtools/git/git/CVE-2021-21300.patch | |||
@@ -0,0 +1,304 @@ | |||
1 | From 464431b4155e3ff918709de663aa0195d73c99fd Mon Sep 17 00:00:00 2001 | ||
2 | From: Matheus Tavares <matheus.bernardino@usp.br> | ||
3 | Date: Sat, 27 Mar 2021 11:50:05 +0900 | ||
4 | Subject: [PATCH] checkout: fix bug that makes checkout follow symlinks in | ||
5 | leading path | ||
6 | |||
7 | Before checking out a file, we have to confirm that all of its leading | ||
8 | components are real existing directories. And to reduce the number of | ||
9 | lstat() calls in this process, we cache the last leading path known to | ||
10 | contain only directories. However, when a path collision occurs (e.g. | ||
11 | when checking out case-sensitive files in case-insensitive file | ||
12 | systems), a cached path might have its file type changed on disk, | ||
13 | leaving the cache on an invalid state. Normally, this doesn't bring | ||
14 | any bad consequences as we usually check out files in index order, and | ||
15 | therefore, by the time the cached path becomes outdated, we no longer | ||
16 | need it anyway (because all files in that directory would have already | ||
17 | been written). | ||
18 | |||
19 | But, there are some users of the checkout machinery that do not always | ||
20 | follow the index order. In particular: checkout-index writes the paths | ||
21 | in the same order that they appear on the CLI (or stdin); and the | ||
22 | delayed checkout feature -- used when a long-running filter process | ||
23 | replies with "status=delayed" -- postpones the checkout of some entries, | ||
24 | thus modifying the checkout order. | ||
25 | |||
26 | When we have to check out an out-of-order entry and the lstat() cache is | ||
27 | invalid (due to a previous path collision), checkout_entry() may end up | ||
28 | using the invalid data and thrusting that the leading components are | ||
29 | real directories when, in reality, they are not. In the best case | ||
30 | scenario, where the directory was replaced by a regular file, the user | ||
31 | will get an error: "fatal: unable to create file 'foo/bar': Not a | ||
32 | directory". But if the directory was replaced by a symlink, checkout | ||
33 | could actually end up following the symlink and writing the file at a | ||
34 | wrong place, even outside the repository. Since delayed checkout is | ||
35 | affected by this bug, it could be used by an attacker to write | ||
36 | arbitrary files during the clone of a maliciously crafted repository. | ||
37 | |||
38 | Some candidate solutions considered were to disable the lstat() cache | ||
39 | during unordered checkouts or sort the entries before passing them to | ||
40 | the checkout machinery. But both ideas include some performance penalty | ||
41 | and they don't future-proof the code against new unordered use cases. | ||
42 | |||
43 | Instead, we now manually reset the lstat cache whenever we successfully | ||
44 | remove a directory. Note: We are not even checking whether the directory | ||
45 | was the same as the lstat cache points to because we might face a | ||
46 | scenario where the paths refer to the same location but differ due to | ||
47 | case folding, precomposed UTF-8 issues, or the presence of `..` | ||
48 | components in the path. Two regression tests, with case-collisions and | ||
49 | utf8-collisions, are also added for both checkout-index and delayed | ||
50 | checkout. | ||
51 | |||
52 | Note: to make the previously mentioned clone attack unfeasible, it would | ||
53 | be sufficient to reset the lstat cache only after the remove_subtree() | ||
54 | call inside checkout_entry(). This is the place where we would remove a | ||
55 | directory whose path collides with the path of another entry that we are | ||
56 | currently trying to check out (possibly a symlink). However, in the | ||
57 | interest of a thorough fix that does not leave Git open to | ||
58 | similar-but-not-identical attack vectors, we decided to intercept | ||
59 | all `rmdir()` calls in one fell swoop. | ||
60 | |||
61 | This addresses CVE-2021-21300. | ||
62 | |||
63 | Co-authored-by: Johannes Schindelin <johannes.schindelin@gmx.de> | ||
64 | Signed-off-by: Matheus Tavares <matheus.bernardino@usp.br> | ||
65 | |||
66 | Upstream-Status: Acepted [https://github.com/git/git/commit/684dd4c2b414bcf648505e74498a608f28de4592] | ||
67 | CVE: CVE-2021-21300 | ||
68 | Signed-off-by: Minjae Kim <flowergom@gmail.com> | ||
69 | --- | ||
70 | cache.h | 1 + | ||
71 | compat/mingw.c | 2 ++ | ||
72 | git-compat-util.h | 5 +++++ | ||
73 | symlinks.c | 24 ++++++++++++++++++++ | ||
74 | t/t0021-conversion.sh | 39 ++++++++++++++++++++++++++++++++ | ||
75 | t/t0021/rot13-filter.pl | 21 ++++++++++++++--- | ||
76 | t/t2006-checkout-index-basic.sh | 40 +++++++++++++++++++++++++++++++++ | ||
77 | 7 files changed, 129 insertions(+), 3 deletions(-) | ||
78 | |||
79 | diff --git a/cache.h b/cache.h | ||
80 | index 7109765..83776f3 100644 | ||
81 | --- a/cache.h | ||
82 | +++ b/cache.h | ||
83 | @@ -1657,6 +1657,7 @@ int has_symlink_leading_path(const char *name, int len); | ||
84 | int threaded_has_symlink_leading_path(struct cache_def *, const char *, int); | ||
85 | int check_leading_path(const char *name, int len); | ||
86 | int has_dirs_only_path(const char *name, int len, int prefix_len); | ||
87 | +extern void invalidate_lstat_cache(void); | ||
88 | void schedule_dir_for_removal(const char *name, int len); | ||
89 | void remove_scheduled_dirs(void); | ||
90 | |||
91 | diff --git a/compat/mingw.c b/compat/mingw.c | ||
92 | index a00f331..a435998 100644 | ||
93 | --- a/compat/mingw.c | ||
94 | +++ b/compat/mingw.c | ||
95 | @@ -367,6 +367,8 @@ int mingw_rmdir(const char *pathname) | ||
96 | ask_yes_no_if_possible("Deletion of directory '%s' failed. " | ||
97 | "Should I try again?", pathname)) | ||
98 | ret = _wrmdir(wpathname); | ||
99 | + if (!ret) | ||
100 | + invalidate_lstat_cache(); | ||
101 | return ret; | ||
102 | } | ||
103 | |||
104 | diff --git a/git-compat-util.h b/git-compat-util.h | ||
105 | index 104993b..7d3db43 100644 | ||
106 | --- a/git-compat-util.h | ||
107 | +++ b/git-compat-util.h | ||
108 | @@ -349,6 +349,11 @@ static inline int noop_core_config(const char *var, const char *value, void *cb) | ||
109 | #define platform_core_config noop_core_config | ||
110 | #endif | ||
111 | |||
112 | +int lstat_cache_aware_rmdir(const char *path); | ||
113 | +#if !defined(__MINGW32__) && !defined(_MSC_VER) | ||
114 | +#define rmdir lstat_cache_aware_rmdir | ||
115 | +#endif | ||
116 | + | ||
117 | #ifndef has_dos_drive_prefix | ||
118 | static inline int git_has_dos_drive_prefix(const char *path) | ||
119 | { | ||
120 | diff --git a/symlinks.c b/symlinks.c | ||
121 | index 69d458a..7dbb6b2 100644 | ||
122 | --- a/symlinks.c | ||
123 | +++ b/symlinks.c | ||
124 | @@ -267,6 +267,13 @@ int has_dirs_only_path(const char *name, int len, int prefix_len) | ||
125 | */ | ||
126 | static int threaded_has_dirs_only_path(struct cache_def *cache, const char *name, int len, int prefix_len) | ||
127 | { | ||
128 | + /* | ||
129 | + * Note: this function is used by the checkout machinery, which also | ||
130 | + * takes care to properly reset the cache when it performs an operation | ||
131 | + * that would leave the cache outdated. If this function starts caching | ||
132 | + * anything else besides FL_DIR, remember to also invalidate the cache | ||
133 | + * when creating or deleting paths that might be in the cache. | ||
134 | + */ | ||
135 | return lstat_cache(cache, name, len, | ||
136 | FL_DIR|FL_FULLPATH, prefix_len) & | ||
137 | FL_DIR; | ||
138 | @@ -321,3 +328,20 @@ void remove_scheduled_dirs(void) | ||
139 | { | ||
140 | do_remove_scheduled_dirs(0); | ||
141 | } | ||
142 | + | ||
143 | +void invalidate_lstat_cache(void) | ||
144 | +{ | ||
145 | + reset_lstat_cache(&default_cache); | ||
146 | +} | ||
147 | + | ||
148 | +#undef rmdir | ||
149 | +int lstat_cache_aware_rmdir(const char *path) | ||
150 | +{ | ||
151 | + /* Any change in this function must be made also in `mingw_rmdir()` */ | ||
152 | + int ret = rmdir(path); | ||
153 | + | ||
154 | + if (!ret) | ||
155 | + invalidate_lstat_cache(); | ||
156 | + | ||
157 | + return ret; | ||
158 | +} | ||
159 | diff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh | ||
160 | index f6deaf4..60d34fd 100755 | ||
161 | --- a/t/t0021-conversion.sh | ||
162 | +++ b/t/t0021-conversion.sh | ||
163 | @@ -953,4 +953,43 @@ test_expect_success PERL 'invalid file in delayed checkout' ' | ||
164 | grep "error: external filter .* signaled that .unfiltered. is now available although it has not been delayed earlier" git-stderr.log | ||
165 | ' | ||
166 | |||
167 | +for mode in 'case' 'utf-8' | ||
168 | +do | ||
169 | + case "$mode" in | ||
170 | + case) dir='A' symlink='a' mode_prereq='CASE_INSENSITIVE_FS' ;; | ||
171 | + utf-8) | ||
172 | + dir=$(printf "\141\314\210") symlink=$(printf "\303\244") | ||
173 | + mode_prereq='UTF8_NFD_TO_NFC' ;; | ||
174 | + esac | ||
175 | + | ||
176 | + test_expect_success PERL,SYMLINKS,$mode_prereq \ | ||
177 | + "delayed checkout with $mode-collision don't write to the wrong place" ' | ||
178 | + test_config_global filter.delay.process \ | ||
179 | + "\"$TEST_ROOT/rot13-filter.pl\" --always-delay delayed.log clean smudge delay" && | ||
180 | + test_config_global filter.delay.required true && | ||
181 | + git init $mode-collision && | ||
182 | + ( | ||
183 | + cd $mode-collision && | ||
184 | + mkdir target-dir && | ||
185 | + empty_oid=$(printf "" | git hash-object -w --stdin) && | ||
186 | + symlink_oid=$(printf "%s" "$PWD/target-dir" | git hash-object -w --stdin) && | ||
187 | + attr_oid=$(echo "$dir/z filter=delay" | git hash-object -w --stdin) && | ||
188 | + cat >objs <<-EOF && | ||
189 | + 100644 blob $empty_oid $dir/x | ||
190 | + 100644 blob $empty_oid $dir/y | ||
191 | + 100644 blob $empty_oid $dir/z | ||
192 | + 120000 blob $symlink_oid $symlink | ||
193 | + 100644 blob $attr_oid .gitattributes | ||
194 | + EOF | ||
195 | + git update-index --index-info <objs && | ||
196 | + git commit -m "test commit" | ||
197 | + ) && | ||
198 | + git clone $mode-collision $mode-collision-cloned && | ||
199 | + # Make sure z was really delayed | ||
200 | + grep "IN: smudge $dir/z .* \\[DELAYED\\]" $mode-collision-cloned/delayed.log && | ||
201 | + # Should not create $dir/z at $symlink/z | ||
202 | + test_path_is_missing $mode-collision/target-dir/z | ||
203 | + ' | ||
204 | +done | ||
205 | + | ||
206 | test_done | ||
207 | diff --git a/t/t0021/rot13-filter.pl b/t/t0021/rot13-filter.pl | ||
208 | index cd32a82..7bb9376 100644 | ||
209 | --- a/t/t0021/rot13-filter.pl | ||
210 | +++ b/t/t0021/rot13-filter.pl | ||
211 | @@ -2,9 +2,15 @@ | ||
212 | # Example implementation for the Git filter protocol version 2 | ||
213 | # See Documentation/gitattributes.txt, section "Filter Protocol" | ||
214 | # | ||
215 | -# The first argument defines a debug log file that the script write to. | ||
216 | -# All remaining arguments define a list of supported protocol | ||
217 | -# capabilities ("clean", "smudge", etc). | ||
218 | +# Usage: rot13-filter.pl [--always-delay] <log path> <capabilities> | ||
219 | +# | ||
220 | +# Log path defines a debug log file that the script writes to. The | ||
221 | +# subsequent arguments define a list of supported protocol capabilities | ||
222 | +# ("clean", "smudge", etc). | ||
223 | +# | ||
224 | +# When --always-delay is given all pathnames with the "can-delay" flag | ||
225 | +# that don't appear on the list bellow are delayed with a count of 1 | ||
226 | +# (see more below). | ||
227 | # | ||
228 | # This implementation supports special test cases: | ||
229 | # (1) If data with the pathname "clean-write-fail.r" is processed with | ||
230 | @@ -53,6 +59,13 @@ sub gitperllib { | ||
231 | use Git::Packet; | ||
232 | |||
233 | my $MAX_PACKET_CONTENT_SIZE = 65516; | ||
234 | + | ||
235 | +my $always_delay = 0; | ||
236 | +if ( $ARGV[0] eq '--always-delay' ) { | ||
237 | + $always_delay = 1; | ||
238 | + shift @ARGV; | ||
239 | +} | ||
240 | + | ||
241 | my $log_file = shift @ARGV; | ||
242 | my @capabilities = @ARGV; | ||
243 | |||
244 | @@ -134,6 +147,8 @@ sub rot13 { | ||
245 | if ( $buffer eq "can-delay=1" ) { | ||
246 | if ( exists $DELAY{$pathname} and $DELAY{$pathname}{"requested"} == 0 ) { | ||
247 | $DELAY{$pathname}{"requested"} = 1; | ||
248 | + } elsif ( !exists $DELAY{$pathname} and $always_delay ) { | ||
249 | + $DELAY{$pathname} = { "requested" => 1, "count" => 1 }; | ||
250 | } | ||
251 | } elsif ($buffer =~ /^(ref|treeish|blob)=/) { | ||
252 | print $debug " $buffer"; | ||
253 | diff --git a/t/t2006-checkout-index-basic.sh b/t/t2006-checkout-index-basic.sh | ||
254 | index 8e181db..602d8fe 100755 | ||
255 | --- a/t/t2006-checkout-index-basic.sh | ||
256 | +++ b/t/t2006-checkout-index-basic.sh | ||
257 | @@ -32,4 +32,44 @@ test_expect_success 'checkout-index reports errors (stdin)' ' | ||
258 | test_i18ngrep not.in.the.cache stderr | ||
259 | ' | ||
260 | |||
261 | +for mode in 'case' 'utf-8' | ||
262 | +do | ||
263 | + case "$mode" in | ||
264 | + case) dir='A' symlink='a' mode_prereq='CASE_INSENSITIVE_FS' ;; | ||
265 | + utf-8) | ||
266 | + dir=$(printf "\141\314\210") symlink=$(printf "\303\244") | ||
267 | + mode_prereq='UTF8_NFD_TO_NFC' ;; | ||
268 | + esac | ||
269 | + | ||
270 | + test_expect_success SYMLINKS,$mode_prereq \ | ||
271 | + "checkout-index with $mode-collision don't write to the wrong place" ' | ||
272 | + git init $mode-collision && | ||
273 | + ( | ||
274 | + cd $mode-collision && | ||
275 | + mkdir target-dir && | ||
276 | + empty_obj_hex=$(git hash-object -w --stdin </dev/null) && | ||
277 | + symlink_hex=$(printf "%s" "$PWD/target-dir" | git hash-object -w --stdin) && | ||
278 | + cat >objs <<-EOF && | ||
279 | + 100644 blob ${empty_obj_hex} ${dir}/x | ||
280 | + 100644 blob ${empty_obj_hex} ${dir}/y | ||
281 | + 100644 blob ${empty_obj_hex} ${dir}/z | ||
282 | + 120000 blob ${symlink_hex} ${symlink} | ||
283 | + EOF | ||
284 | + git update-index --index-info <objs && | ||
285 | + # Note: the order is important here to exercise the | ||
286 | + # case where the file at ${dir} has its type changed by | ||
287 | + # the time Git tries to check out ${dir}/z. | ||
288 | + # | ||
289 | + # Also, we use core.precomposeUnicode=false because we | ||
290 | + # want Git to treat the UTF-8 paths transparently on | ||
291 | + # Mac OS, matching what is in the index. | ||
292 | + # | ||
293 | + git -c core.precomposeUnicode=false checkout-index -f \ | ||
294 | + ${dir}/x ${dir}/y ${symlink} ${dir}/z && | ||
295 | + # Should not create ${dir}/z at ${symlink}/z | ||
296 | + test_path_is_missing target-dir/z | ||
297 | + ) | ||
298 | + ' | ||
299 | +done | ||
300 | + | ||
301 | test_done | ||
302 | -- | ||
303 | 2.17.1 | ||
304 | |||