Add a new fuzz test for the commit graph and fix a buffer read-overflow
that it discovered. Additionally, fix the Makefile instructions for
building fuzzers.
Changes since V1:
* Moved the parse_commit_graph() declaration to the header file, since
we don't mind if others use it.
* Moved some unnecessary comments into commit messages.
* Fixed some style issues.
* Added a test case for detecting commit graphs with missing chunk
lookup entries.
* Ævar's comments on the Makefile made me realize the fuzzer build
instructions were using the wrong variable. Added a new commit to
fix this.
Josh Steadmon (3):
commit-graph, fuzz: Add fuzzer for commit-graph
commit-graph: fix buffer read-overflow
Makefile: correct example fuzz build
.gitignore | 1 +
Makefile | 3 +-
commit-graph.c | 67 +++++++++++++++++++++++++++++------------
commit-graph.h | 3 ++
fuzz-commit-graph.c | 16 ++++++++++
t/t5318-commit-graph.sh | 28 +++++++++++++++++
6 files changed, 98 insertions(+), 20 deletions(-)
create mode 100644 fuzz-commit-graph.c
Range-diff against v1:
1: 53e62baaa8 ! 1: 0b57ecbe1b commit-graph, fuzz: Add fuzzer for commit-graph
@@ -4,7 +4,9 @@
Breaks load_commit_graph_one() into a new function,
parse_commit_graph(). The latter function operates on arbitrary
buffers,
- which makes it suitable as a fuzzing target.
+ which makes it suitable as a fuzzing target. Since parse_commit_graph()
+ is only called by load_commit_graph_one() (and the fuzzer described
+ below), we omit error messages that would be duplicated by the caller.
Adds fuzz-commit-graph.c, which provides a fuzzing entry point
compatible with libFuzzer (and possibly other fuzzing engines).
@@ -35,17 +37,6 @@
diff --git a/commit-graph.c b/commit-graph.c
--- a/commit-graph.c
+++ b/commit-graph.c
-@@
- #define GRAPH_MIN_SIZE (GRAPH_HEADER_SIZE + 4 * GRAPH_CHUNKLOOKUP_WIDTH \
- + GRAPH_FANOUT_SIZE + GRAPH_OID_LEN)
-
-+struct commit_graph *parse_commit_graph(void *graph_map, int fd,
-+ size_t graph_size);
-+
-+
- char *get_commit_graph_filename(const char *obj_dir)
- {
- return xstrfmt("%s/info/commit-graph", obj_dir);
@@
struct commit_graph *load_commit_graph_one(const char *graph_file)
{
@@ -70,7 +61,7 @@
graph_map = xmmap(NULL, graph_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ ret = parse_commit_graph(graph_map, fd, graph_size);
+
-+ if (ret == NULL) {
++ if (!ret) {
+ munmap(graph_map, graph_size);
+ close(fd);
+ exit(1);
@@ -79,10 +70,6 @@
+ return ret;
+}
+
-+/*
-+ * This function is intended to be used only from load_commit_graph_one()
or in
-+ * fuzz tests.
-+ */
+struct commit_graph *parse_commit_graph(void *graph_map, int fd,
+ size_t graph_size)
+{
@@ -94,11 +81,9 @@
+ uint32_t graph_signature;
+ unsigned char graph_version, hash_version;
+
-+ /*
-+ * This should already be checked in load_commit_graph_one, but we still
-+ * need a check here for when we're calling parse_commit_graph directly
-+ * from fuzz tests. We can omit the error message in that case.
-+ */
++ if (!graph_map)
++ return NULL;
++
+ if (graph_size < GRAPH_MIN_SIZE)
+ return NULL;
+
@@ -162,12 +147,25 @@
static void prepare_commit_graph_one(struct repository *r, const char
*obj_dir)
+ diff --git a/commit-graph.h b/commit-graph.h
+ --- a/commit-graph.h
+ +++ b/commit-graph.h
+@@
+
+ struct commit_graph *load_commit_graph_one(const char *graph_file);
+
++struct commit_graph *parse_commit_graph(void *graph_map, int fd,
++ size_t graph_size);
++
+ /*
+ * Return 1 if and only if the repository has a commit-graph
+ * file and generation numbers are computed in that file.
+
diff --git a/fuzz-commit-graph.c b/fuzz-commit-graph.c
new file mode 100644
--- /dev/null
+++ b/fuzz-commit-graph.c
@@
-+#include "object-store.h"
+#include "commit-graph.h"
+
+struct commit_graph *parse_commit_graph(void *graph_map, int fd,
@@ -179,9 +177,8 @@
+{
+ struct commit_graph *g;
+
-+ g = parse_commit_graph((void *) data, -1, size);
-+ if (g)
-+ free(g);
++ g = parse_commit_graph((void *)data, -1, size);
++ free(g);
+
+ return 0;
+}
2: ad2e761f44 ! 2: af45c2337f commit-graph: fix buffer read-overflow
@@ -22,7 +22,8 @@
+ uint64_t chunk_offset;
int chunk_repeated = 0;
-+ if (chunk_lookup + GRAPH_CHUNKLOOKUP_WIDTH > data + graph_size)
{
++ if (chunk_lookup + GRAPH_CHUNKLOOKUP_WIDTH >
++ data + graph_size) {
+ error(_("chunk lookup table entry missing; graph file
may be incomplete"));
+ free(graph);
+ return NULL;
@@ -34,3 +35,49 @@
chunk_lookup += GRAPH_CHUNKLOOKUP_WIDTH;
if (chunk_offset > graph_size - GIT_MAX_RAWSZ) {
+
+ diff --git a/t/t5318-commit-graph.sh b/t/t5318-commit-graph.sh
+ --- a/t/t5318-commit-graph.sh
+ +++ b/t/t5318-commit-graph.sh
+@@
+ test_i18ngrep "$grepstr" err
+ }
+
++
++# usage: corrupt_and_zero_graph_then_verify <corrupt_position> <data>
<zero_position> <string>
++# Manipulates the commit-graph file at <corrupt_position> by inserting
the data,
++# then zeros the file starting at <zero_position>. Finally, runs
++# 'git commit-graph verify' and places the output in the file 'err'.
Tests 'err'
++# for the given string.
++corrupt_and_zero_graph_then_verify() {
++ corrupt_pos=$1
++ data="${2:-\0}"
++ zero_pos=$3
++ grepstr=$4
++ orig_size=$(stat --format=%s $objdir/info/commit-graph)
++ cd "$TRASH_DIRECTORY/full" &&
++ test_when_finished mv commit-graph-backup $objdir/info/commit-graph &&
++ cp $objdir/info/commit-graph commit-graph-backup &&
++ printf "$data" | dd of="$objdir/info/commit-graph" bs=1
seek="$corrupt_pos" conv=notrunc &&
++ truncate --size=$zero_pos $objdir/info/commit-graph &&
++ truncate --size=$orig_size $objdir/info/commit-graph &&
++ test_must_fail git commit-graph verify 2>test_err &&
++ grep -v "^+" test_err >err &&
++ test_i18ngrep "$grepstr" err
++}
++
+ test_expect_success 'detect bad signature' '
+ corrupt_graph_and_verify 0 "\0" \
+ "graph signature"
+@@
+ "incorrect checksum"
+ '
+
++test_expect_success 'detect truncated graph' '
++ corrupt_and_zero_graph_then_verify $GRAPH_BYTE_CHUNK_COUNT "\xff" \
++ $GRAPH_CHUNK_LOOKUP_OFFSET "chunk lookup table entry missing"
++'
++
+ test_expect_success 'git fsck (checks commit-graph)' '
+ cd "$TRASH_DIRECTORY/full" &&
+ git fsck &&
-: ---------- > 3: 7519fc76df Makefile: correct example fuzz build
--
2.20.0.rc2.10.g7519fc76df