fuzz: port fuzz-parse-attr-line from OSS-Fuzz

Git's fuzz tests are run continuously as part of OSS-Fuzz [1]. Several additional fuzz tests have been contributed directly to OSS-Fuzz; however, these tests are vulnerable to bitrot because they are not built during Git's CI runs, and thus breaking changes are much less likely to be noticed by Git contributors. Port one of these tests back to the Git project: fuzz-parse-attr-line This test was originally written by Eric Sesterhenn as part of a security audit of Git [2]. It was then contributed to the OSS-Fuzz repo in commit c58ac4492 (Git fuzzing: uncomment the existing and add new targets. (#11486), 2024-02-21) by Jaroslav Lobačevski. I (Josh Steadmon) have verified with both Eric and Jaroslav that they're OK with moving this test to the Git project. [1] https://github.com/google/oss-fuzz [2] https://ostif.org/wp-content/uploads/2023/01/X41-OSTIF-Gitlab-Git-Security-Audit-20230117-public.pdf Co-authored-by: Jaroslav Lobačevski <jarlob@gmail.com> Co-authored-by: Josh Steadmon <steadmon@google.com> Signed-off-by: Josh Steadmon <steadmon@google.com> Signed-off-by: Taylor Blau <me@ttaylorr.com>

Eric Sesterhenn committed Oct 14, 2024 at 14:04 UTC 72686d4e5e9a7236b9716368d86fae5bf1ae6156
6 files changed +87 -38
Makefile
+1
@@ -2426,6 +2426,7 @@ FUZZ_OBJS += oss-fuzz/fuzz-credential-from-url-gently.o
2426 FUZZ_OBJS += oss-fuzz/fuzz-date.o
2427 FUZZ_OBJS += oss-fuzz/fuzz-pack-headers.o
2428 FUZZ_OBJS += oss-fuzz/fuzz-pack-idx.o
2429 +FUZZ_OBJS += oss-fuzz/fuzz-parse-attr-line.o
2430 .PHONY: fuzz-objs
2431 fuzz-objs: $(FUZZ_OBJS)
2432
attr.c
+2 -38
@@ -259,42 +259,6 @@ const struct git_attr *git_attr(const char *name)
259 return git_attr_internal(name, strlen(name));
260 }
261
262 -/* What does a matched pattern decide? */
263 -struct attr_state {
264 - const struct git_attr *attr;
265 - const char *setto;
266 -};
267 -
268 -struct pattern {
269 - const char *pattern;
270 - int patternlen;
271 - int nowildcardlen;
272 - unsigned flags; /* PATTERN_FLAG_* */
273 -};
274 -
275 -/*
276 - * One rule, as from a .gitattributes file.
277 - *
278 - * If is_macro is true, then u.attr is a pointer to the git_attr being
279 - * defined.
280 - *
281 - * If is_macro is false, then u.pat is the filename pattern to which the
282 - * rule applies.
283 - *
284 - * In either case, num_attr is the number of attributes affected by
285 - * this rule, and state is an array listing them. The attributes are
286 - * listed as they appear in the file (macros unexpanded).
287 - */
288 -struct match_attr {
289 - union {
290 - struct pattern pat;
291 - const struct git_attr *attr;
292 - } u;
293 - char is_macro;
294 - size_t num_attr;
295 - struct attr_state state[FLEX_ARRAY];
296 -};
297 -
262 static const char blank[] = " \t\r\n";
263
264 /* Flags usable in read_attr() and parse_attr_line() family of functions. */
@@ -353,8 +317,8 @@ static const char *parse_attr(const char *src, int lineno, const char *cp,
317 return ep + strspn(ep, blank);
318 }
319
356 -static struct match_attr *parse_attr_line(const char *line, const char *src,
357 - int lineno, unsigned flags)
320 +struct match_attr *parse_attr_line(const char *line, const char *src,
321 + int lineno, unsigned flags)
322 {
323 size_t namelen, num_attr, i;
324 const char *cp, *name, *states;
attr.h
+43
@@ -240,4 +240,47 @@ int git_attr_system_is_enabled(void);
240
241 extern char *git_attr_tree;
242
243 +/*
244 + * Exposed for fuzz-testing only.
245 + */
246 +
247 +/* What does a matched pattern decide? */
248 +struct attr_state {
249 + const struct git_attr *attr;
250 + const char *setto;
251 +};
252 +
253 +struct pattern {
254 + const char *pattern;
255 + int patternlen;
256 + int nowildcardlen;
257 + unsigned flags; /* PATTERN_FLAG_* */
258 +};
259 +
260 +/*
261 + * One rule, as from a .gitattributes file.
262 + *
263 + * If is_macro is true, then u.attr is a pointer to the git_attr being
264 + * defined.
265 + *
266 + * If is_macro is false, then u.pat is the filename pattern to which the
267 + * rule applies.
268 + *
269 + * In either case, num_attr is the number of attributes affected by
270 + * this rule, and state is an array listing them. The attributes are
271 + * listed as they appear in the file (macros unexpanded).
272 + */
273 +struct match_attr {
274 + union {
275 + struct pattern pat;
276 + const struct git_attr *attr;
277 + } u;
278 + char is_macro;
279 + size_t num_attr;
280 + struct attr_state state[FLEX_ARRAY];
281 +};
282 +
283 +struct match_attr *parse_attr_line(const char *line, const char *src,
284 + int lineno, unsigned flags);
285 +
286 #endif /* ATTR_H */
ci/run-build-and-minimal-fuzzers.sh
+1
@@ -20,6 +20,7 @@ credential-from-url-gently
20 date
21 pack-headers
22 pack-idx
23 +parse-attr-line
24 "
25
26 for fuzzer in $fuzzers; do
oss-fuzz/.gitignore
+1
@@ -4,3 +4,4 @@ fuzz-credential-from-url-gently
4 fuzz-date
5 fuzz-pack-headers
6 fuzz-pack-idx
7 +fuzz-parse-attr-line
oss-fuzz/fuzz-parse-attr-line.c new
+39
@@ -0,0 +1,39 @@
1 +#include "git-compat-util.h"
2 +#include <stddef.h>
3 +#include <stdlib.h>
4 +#include <stdint.h>
5 +#include <string.h>
6 +#include "attr.h"
7 +
8 +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
9 +
10 +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
11 +{
12 + struct match_attr *res;
13 + char *buf;
14 +
15 + buf = malloc(size + 1);
16 + if (!buf)
17 + return 0;
18 +
19 + memcpy(buf, data, size);
20 + buf[size] = 0;
21 +
22 + res = parse_attr_line(buf, "dummy", 0, 0);
23 +
24 + if (res) {
25 + int j;
26 + for (j = 0; j < res->num_attr; j++) {
27 + const char *setto = res->state[j].setto;
28 + if (ATTR_TRUE(setto) || ATTR_FALSE(setto) ||
29 + ATTR_UNSET(setto))
30 + ;
31 + else
32 + free((char *)setto);
33 + }
34 + free(res);
35 + }
36 + free(buf);
37 +
38 + return 0;
39 +}