diff options
Diffstat (limited to 'meta/recipes-devtools/git/files/CVE-2022-23521.patch')
-rw-r--r-- | meta/recipes-devtools/git/files/CVE-2022-23521.patch | 367 |
1 files changed, 367 insertions, 0 deletions
diff --git a/meta/recipes-devtools/git/files/CVE-2022-23521.patch b/meta/recipes-devtools/git/files/CVE-2022-23521.patch new file mode 100644 index 0000000000..974546013d --- /dev/null +++ b/meta/recipes-devtools/git/files/CVE-2022-23521.patch | |||
@@ -0,0 +1,367 @@ | |||
1 | From eb22e7dfa23da6bd9aed9bd1dad69e1e8e167d24 Mon Sep 17 00:00:00 2001 | ||
2 | From: Patrick Steinhardt <ps@pks.im> | ||
3 | Date: Thu, 1 Dec 2022 15:45:15 +0100 | ||
4 | Subject: [PATCH] CVE-2022-23521 | ||
5 | |||
6 | attr: fix overflow when upserting attribute with overly long name | ||
7 | |||
8 | The function `git_attr_internal()` is called to upsert attributes into | ||
9 | the global map. And while all callers pass a `size_t`, the function | ||
10 | itself accepts an `int` as the attribute name's length. This can lead to | ||
11 | an integer overflow in case the attribute name is longer than `INT_MAX`. | ||
12 | |||
13 | Now this overflow seems harmless as the first thing we do is to call | ||
14 | `attr_name_valid()`, and that function only succeeds in case all chars | ||
15 | in the range of `namelen` match a certain small set of chars. We thus | ||
16 | can't do an out-of-bounds read as NUL is not part of that set and all | ||
17 | strings passed to this function are NUL-terminated. And furthermore, we | ||
18 | wouldn't ever read past the current attribute name anyway due to the | ||
19 | same reason. And if validation fails we will return early. | ||
20 | |||
21 | On the other hand it feels fragile to rely on this behaviour, even more | ||
22 | so given that we pass `namelen` to `FLEX_ALLOC_MEM()`. So let's instead | ||
23 | just do the correct thing here and accept a `size_t` as line length. | ||
24 | |||
25 | Upstream-Status: Backport [https://github.com/git/git/commit/eb22e7dfa23da6bd9aed9bd1dad69e1e8e167d24 &https://github.com/git/git/commit/8d0d48cf2157cfb914db1f53b3fe40785b86f3aa & https://github.com/git/git/commit/24557209500e6ed618f04a8795a111a0c491a29c & https://github.com/git/git/commit/34ace8bad02bb14ecc5b631f7e3daaa7a9bba7d9 & https://github.com/git/git/commit/447ac906e189535e77dcb1f4bbe3f1bc917d4c12 & https://github.com/git/git/commit/e1e12e97ac73ded85f7d000da1063a774b3cc14f & https://github.com/git/git/commit/a60a66e409c265b2944f18bf43581c146812586d & https://github.com/git/git/commit/d74b1fd54fdbc45966d12ea907dece11e072fb2b & https://github.com/git/git/commit/dfa6b32b5e599d97448337ed4fc18dd50c90758f & https://github.com/git/git/commit/3c50032ff5289cc45659f21949c8d09e52164579 | ||
26 | |||
27 | CVE: CVE-2022-23521 | ||
28 | |||
29 | Reviewed-by: Sylvain Beucler <beuc@debian.org> | ||
30 | Signed-off-by: Patrick Steinhardt <ps@pks.im> | ||
31 | Signed-off-by: Junio C Hamano <gitster@pobox.com> | ||
32 | Signed-off-by: Hitendra Prajapati <hprajapati@mvista.com> | ||
33 | --- | ||
34 | attr.c | 97 +++++++++++++++++++++++++++---------------- | ||
35 | attr.h | 12 ++++++ | ||
36 | t/t0003-attributes.sh | 59 ++++++++++++++++++++++++++ | ||
37 | 3 files changed, 132 insertions(+), 36 deletions(-) | ||
38 | |||
39 | diff --git a/attr.c b/attr.c | ||
40 | index 11f19b5..63484ab 100644 | ||
41 | --- a/attr.c | ||
42 | +++ b/attr.c | ||
43 | @@ -29,7 +29,7 @@ static const char git_attr__unknown[] = "(builtin)unknown"; | ||
44 | #endif | ||
45 | |||
46 | struct git_attr { | ||
47 | - int attr_nr; /* unique attribute number */ | ||
48 | + unsigned int attr_nr; /* unique attribute number */ | ||
49 | char name[FLEX_ARRAY]; /* attribute name */ | ||
50 | }; | ||
51 | |||
52 | @@ -221,7 +221,7 @@ static void report_invalid_attr(const char *name, size_t len, | ||
53 | * dictionary. If no entry is found, create a new attribute and store it in | ||
54 | * the dictionary. | ||
55 | */ | ||
56 | -static const struct git_attr *git_attr_internal(const char *name, int namelen) | ||
57 | +static const struct git_attr *git_attr_internal(const char *name, size_t namelen) | ||
58 | { | ||
59 | struct git_attr *a; | ||
60 | |||
61 | @@ -237,8 +237,8 @@ static const struct git_attr *git_attr_internal(const char *name, int namelen) | ||
62 | a->attr_nr = hashmap_get_size(&g_attr_hashmap.map); | ||
63 | |||
64 | attr_hashmap_add(&g_attr_hashmap, a->name, namelen, a); | ||
65 | - assert(a->attr_nr == | ||
66 | - (hashmap_get_size(&g_attr_hashmap.map) - 1)); | ||
67 | + if (a->attr_nr != hashmap_get_size(&g_attr_hashmap.map) - 1) | ||
68 | + die(_("unable to add additional attribute")); | ||
69 | } | ||
70 | |||
71 | hashmap_unlock(&g_attr_hashmap); | ||
72 | @@ -283,7 +283,7 @@ struct match_attr { | ||
73 | const struct git_attr *attr; | ||
74 | } u; | ||
75 | char is_macro; | ||
76 | - unsigned num_attr; | ||
77 | + size_t num_attr; | ||
78 | struct attr_state state[FLEX_ARRAY]; | ||
79 | }; | ||
80 | |||
81 | @@ -300,7 +300,7 @@ static const char *parse_attr(const char *src, int lineno, const char *cp, | ||
82 | struct attr_state *e) | ||
83 | { | ||
84 | const char *ep, *equals; | ||
85 | - int len; | ||
86 | + size_t len; | ||
87 | |||
88 | ep = cp + strcspn(cp, blank); | ||
89 | equals = strchr(cp, '='); | ||
90 | @@ -344,8 +344,7 @@ static const char *parse_attr(const char *src, int lineno, const char *cp, | ||
91 | static struct match_attr *parse_attr_line(const char *line, const char *src, | ||
92 | int lineno, int macro_ok) | ||
93 | { | ||
94 | - int namelen; | ||
95 | - int num_attr, i; | ||
96 | + size_t namelen, num_attr, i; | ||
97 | const char *cp, *name, *states; | ||
98 | struct match_attr *res = NULL; | ||
99 | int is_macro; | ||
100 | @@ -356,6 +355,11 @@ static struct match_attr *parse_attr_line(const char *line, const char *src, | ||
101 | return NULL; | ||
102 | name = cp; | ||
103 | |||
104 | + if (strlen(line) >= ATTR_MAX_LINE_LENGTH) { | ||
105 | + warning(_("ignoring overly long attributes line %d"), lineno); | ||
106 | + return NULL; | ||
107 | + } | ||
108 | + | ||
109 | if (*cp == '"' && !unquote_c_style(&pattern, name, &states)) { | ||
110 | name = pattern.buf; | ||
111 | namelen = pattern.len; | ||
112 | @@ -392,10 +396,9 @@ static struct match_attr *parse_attr_line(const char *line, const char *src, | ||
113 | goto fail_return; | ||
114 | } | ||
115 | |||
116 | - res = xcalloc(1, | ||
117 | - sizeof(*res) + | ||
118 | - sizeof(struct attr_state) * num_attr + | ||
119 | - (is_macro ? 0 : namelen + 1)); | ||
120 | + res = xcalloc(1, st_add3(sizeof(*res), | ||
121 | + st_mult(sizeof(struct attr_state), num_attr), | ||
122 | + is_macro ? 0 : namelen + 1)); | ||
123 | if (is_macro) { | ||
124 | res->u.attr = git_attr_internal(name, namelen); | ||
125 | } else { | ||
126 | @@ -458,11 +461,12 @@ struct attr_stack { | ||
127 | |||
128 | static void attr_stack_free(struct attr_stack *e) | ||
129 | { | ||
130 | - int i; | ||
131 | + unsigned i; | ||
132 | free(e->origin); | ||
133 | for (i = 0; i < e->num_matches; i++) { | ||
134 | struct match_attr *a = e->attrs[i]; | ||
135 | - int j; | ||
136 | + size_t j; | ||
137 | + | ||
138 | for (j = 0; j < a->num_attr; j++) { | ||
139 | const char *setto = a->state[j].setto; | ||
140 | if (setto == ATTR__TRUE || | ||
141 | @@ -671,8 +675,8 @@ static void handle_attr_line(struct attr_stack *res, | ||
142 | a = parse_attr_line(line, src, lineno, macro_ok); | ||
143 | if (!a) | ||
144 | return; | ||
145 | - ALLOC_GROW(res->attrs, res->num_matches + 1, res->alloc); | ||
146 | - res->attrs[res->num_matches++] = a; | ||
147 | + ALLOC_GROW_BY(res->attrs, res->num_matches, 1, res->alloc); | ||
148 | + res->attrs[res->num_matches - 1] = a; | ||
149 | } | ||
150 | |||
151 | static struct attr_stack *read_attr_from_array(const char **list) | ||
152 | @@ -711,21 +715,37 @@ void git_attr_set_direction(enum git_attr_direction new_direction) | ||
153 | |||
154 | static struct attr_stack *read_attr_from_file(const char *path, int macro_ok) | ||
155 | { | ||
156 | + struct strbuf buf = STRBUF_INIT; | ||
157 | FILE *fp = fopen_or_warn(path, "r"); | ||
158 | struct attr_stack *res; | ||
159 | - char buf[2048]; | ||
160 | int lineno = 0; | ||
161 | + int fd; | ||
162 | + struct stat st; | ||
163 | |||
164 | if (!fp) | ||
165 | return NULL; | ||
166 | - res = xcalloc(1, sizeof(*res)); | ||
167 | - while (fgets(buf, sizeof(buf), fp)) { | ||
168 | - char *bufp = buf; | ||
169 | - if (!lineno) | ||
170 | - skip_utf8_bom(&bufp, strlen(bufp)); | ||
171 | - handle_attr_line(res, bufp, path, ++lineno, macro_ok); | ||
172 | + | ||
173 | + fd = fileno(fp); | ||
174 | + if (fstat(fd, &st)) { | ||
175 | + warning_errno(_("cannot fstat gitattributes file '%s'"), path); | ||
176 | + fclose(fp); | ||
177 | + return NULL; | ||
178 | } | ||
179 | + if (st.st_size >= ATTR_MAX_FILE_SIZE) { | ||
180 | + warning(_("ignoring overly large gitattributes file '%s'"), path); | ||
181 | + fclose(fp); | ||
182 | + return NULL; | ||
183 | + } | ||
184 | + | ||
185 | + CALLOC_ARRAY(res, 1); | ||
186 | + while (strbuf_getline(&buf, fp) != EOF) { | ||
187 | + if (!lineno && starts_with(buf.buf, utf8_bom)) | ||
188 | + strbuf_remove(&buf, 0, strlen(utf8_bom)); | ||
189 | + handle_attr_line(res, buf.buf, path, ++lineno, macro_ok); | ||
190 | + } | ||
191 | + | ||
192 | fclose(fp); | ||
193 | + strbuf_release(&buf); | ||
194 | return res; | ||
195 | } | ||
196 | |||
197 | @@ -736,13 +756,18 @@ static struct attr_stack *read_attr_from_index(const struct index_state *istate, | ||
198 | struct attr_stack *res; | ||
199 | char *buf, *sp; | ||
200 | int lineno = 0; | ||
201 | + size_t size; | ||
202 | |||
203 | if (!istate) | ||
204 | return NULL; | ||
205 | |||
206 | - buf = read_blob_data_from_index(istate, path, NULL); | ||
207 | + buf = read_blob_data_from_index(istate, path, &size); | ||
208 | if (!buf) | ||
209 | return NULL; | ||
210 | + if (size >= ATTR_MAX_FILE_SIZE) { | ||
211 | + warning(_("ignoring overly large gitattributes blob '%s'"), path); | ||
212 | + return NULL; | ||
213 | + } | ||
214 | |||
215 | res = xcalloc(1, sizeof(*res)); | ||
216 | for (sp = buf; *sp; ) { | ||
217 | @@ -1012,12 +1037,12 @@ static int macroexpand_one(struct all_attrs_item *all_attrs, int nr, int rem); | ||
218 | static int fill_one(const char *what, struct all_attrs_item *all_attrs, | ||
219 | const struct match_attr *a, int rem) | ||
220 | { | ||
221 | - int i; | ||
222 | + size_t i; | ||
223 | |||
224 | - for (i = a->num_attr - 1; rem > 0 && i >= 0; i--) { | ||
225 | - const struct git_attr *attr = a->state[i].attr; | ||
226 | + for (i = a->num_attr; rem > 0 && i > 0; i--) { | ||
227 | + const struct git_attr *attr = a->state[i - 1].attr; | ||
228 | const char **n = &(all_attrs[attr->attr_nr].value); | ||
229 | - const char *v = a->state[i].setto; | ||
230 | + const char *v = a->state[i - 1].setto; | ||
231 | |||
232 | if (*n == ATTR__UNKNOWN) { | ||
233 | debug_set(what, | ||
234 | @@ -1036,11 +1061,11 @@ static int fill(const char *path, int pathlen, int basename_offset, | ||
235 | struct all_attrs_item *all_attrs, int rem) | ||
236 | { | ||
237 | for (; rem > 0 && stack; stack = stack->prev) { | ||
238 | - int i; | ||
239 | + unsigned i; | ||
240 | const char *base = stack->origin ? stack->origin : ""; | ||
241 | |||
242 | - for (i = stack->num_matches - 1; 0 < rem && 0 <= i; i--) { | ||
243 | - const struct match_attr *a = stack->attrs[i]; | ||
244 | + for (i = stack->num_matches; 0 < rem && 0 < i; i--) { | ||
245 | + const struct match_attr *a = stack->attrs[i - 1]; | ||
246 | if (a->is_macro) | ||
247 | continue; | ||
248 | if (path_matches(path, pathlen, basename_offset, | ||
249 | @@ -1071,11 +1096,11 @@ static void determine_macros(struct all_attrs_item *all_attrs, | ||
250 | const struct attr_stack *stack) | ||
251 | { | ||
252 | for (; stack; stack = stack->prev) { | ||
253 | - int i; | ||
254 | - for (i = stack->num_matches - 1; i >= 0; i--) { | ||
255 | - const struct match_attr *ma = stack->attrs[i]; | ||
256 | + unsigned i; | ||
257 | + for (i = stack->num_matches; i > 0; i--) { | ||
258 | + const struct match_attr *ma = stack->attrs[i - 1]; | ||
259 | if (ma->is_macro) { | ||
260 | - int n = ma->u.attr->attr_nr; | ||
261 | + unsigned int n = ma->u.attr->attr_nr; | ||
262 | if (!all_attrs[n].macro) { | ||
263 | all_attrs[n].macro = ma; | ||
264 | } | ||
265 | @@ -1127,7 +1152,7 @@ void git_check_attr(const struct index_state *istate, | ||
266 | collect_some_attrs(istate, path, check); | ||
267 | |||
268 | for (i = 0; i < check->nr; i++) { | ||
269 | - size_t n = check->items[i].attr->attr_nr; | ||
270 | + unsigned int n = check->items[i].attr->attr_nr; | ||
271 | const char *value = check->all_attrs[n].value; | ||
272 | if (value == ATTR__UNKNOWN) | ||
273 | value = ATTR__UNSET; | ||
274 | diff --git a/attr.h b/attr.h | ||
275 | index b0378bf..f424285 100644 | ||
276 | --- a/attr.h | ||
277 | +++ b/attr.h | ||
278 | @@ -1,6 +1,18 @@ | ||
279 | #ifndef ATTR_H | ||
280 | #define ATTR_H | ||
281 | |||
282 | +/** | ||
283 | + * The maximum line length for a gitattributes file. If the line exceeds this | ||
284 | + * length we will ignore it. | ||
285 | + */ | ||
286 | +#define ATTR_MAX_LINE_LENGTH 2048 | ||
287 | + | ||
288 | + /** | ||
289 | + * The maximum size of the giattributes file. If the file exceeds this size we | ||
290 | + * will ignore it. | ||
291 | + */ | ||
292 | +#define ATTR_MAX_FILE_SIZE (100 * 1024 * 1024) | ||
293 | + | ||
294 | struct index_state; | ||
295 | |||
296 | /* An attribute is a pointer to this opaque structure */ | ||
297 | diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh | ||
298 | index 71e63d8..556245b 100755 | ||
299 | --- a/t/t0003-attributes.sh | ||
300 | +++ b/t/t0003-attributes.sh | ||
301 | @@ -342,4 +342,63 @@ test_expect_success 'query binary macro directly' ' | ||
302 | test_cmp expect actual | ||
303 | ' | ||
304 | |||
305 | +test_expect_success 'large attributes line ignored in tree' ' | ||
306 | + test_when_finished "rm .gitattributes" && | ||
307 | + printf "path %02043d" 1 >.gitattributes && | ||
308 | + git check-attr --all path >actual 2>err && | ||
309 | + echo "warning: ignoring overly long attributes line 1" >expect && | ||
310 | + test_cmp expect err && | ||
311 | + test_must_be_empty actual | ||
312 | +' | ||
313 | + | ||
314 | +test_expect_success 'large attributes line ignores trailing content in tree' ' | ||
315 | + test_when_finished "rm .gitattributes" && | ||
316 | + # older versions of Git broke lines at 2048 bytes; the 2045 bytes | ||
317 | + # of 0-padding here is accounting for the three bytes of "a 1", which | ||
318 | + # would knock "trailing" to the "next" line, where it would be | ||
319 | + # erroneously parsed. | ||
320 | + printf "a %02045dtrailing attribute\n" 1 >.gitattributes && | ||
321 | + git check-attr --all trailing >actual 2>err && | ||
322 | + echo "warning: ignoring overly long attributes line 1" >expect && | ||
323 | + test_cmp expect err && | ||
324 | + test_must_be_empty actual | ||
325 | +' | ||
326 | + | ||
327 | +test_expect_success EXPENSIVE 'large attributes file ignored in tree' ' | ||
328 | + test_when_finished "rm .gitattributes" && | ||
329 | + dd if=/dev/zero of=.gitattributes bs=101M count=1 2>/dev/null && | ||
330 | + git check-attr --all path >/dev/null 2>err && | ||
331 | + echo "warning: ignoring overly large gitattributes file ${SQ}.gitattributes${SQ}" >expect && | ||
332 | + test_cmp expect err | ||
333 | +' | ||
334 | + | ||
335 | +test_expect_success 'large attributes line ignored in index' ' | ||
336 | + test_when_finished "git update-index --remove .gitattributes" && | ||
337 | + blob=$(printf "path %02043d" 1 | git hash-object -w --stdin) && | ||
338 | + git update-index --add --cacheinfo 100644,$blob,.gitattributes && | ||
339 | + git check-attr --cached --all path >actual 2>err && | ||
340 | + echo "warning: ignoring overly long attributes line 1" >expect && | ||
341 | + test_cmp expect err && | ||
342 | + test_must_be_empty actual | ||
343 | +' | ||
344 | + | ||
345 | +test_expect_success 'large attributes line ignores trailing content in index' ' | ||
346 | + test_when_finished "git update-index --remove .gitattributes" && | ||
347 | + blob=$(printf "a %02045dtrailing attribute\n" 1 | git hash-object -w --stdin) && | ||
348 | + git update-index --add --cacheinfo 100644,$blob,.gitattributes && | ||
349 | + git check-attr --cached --all trailing >actual 2>err && | ||
350 | + echo "warning: ignoring overly long attributes line 1" >expect && | ||
351 | + test_cmp expect err && | ||
352 | + test_must_be_empty actual | ||
353 | +' | ||
354 | + | ||
355 | +test_expect_success EXPENSIVE 'large attributes file ignored in index' ' | ||
356 | + test_when_finished "git update-index --remove .gitattributes" && | ||
357 | + blob=$(dd if=/dev/zero bs=101M count=1 2>/dev/null | git hash-object -w --stdin) && | ||
358 | + git update-index --add --cacheinfo 100644,$blob,.gitattributes && | ||
359 | + git check-attr --cached --all path >/dev/null 2>err && | ||
360 | + echo "warning: ignoring overly large gitattributes blob ${SQ}.gitattributes${SQ}" >expect && | ||
361 | + test_cmp expect err | ||
362 | +' | ||
363 | + | ||
364 | test_done | ||
365 | -- | ||
366 | 2.25.1 | ||
367 | |||