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.
Adds fuzz-commit-graph.c, which provides a fuzzing entry point
compatible with libFuzzer (and possibly other fuzzing engines).
Signed-off-by: Josh Steadmon <redacted>
---
.gitignore | 1 +
Makefile | 1 +
commit-graph.c | 63 +++++++++++++++++++++++++++++++++------------
fuzz-commit-graph.c | 18 +++++++++++++
4 files changed, 66 insertions(+), 17 deletions(-)
create mode 100644 fuzz-commit-graph.c
@@ -84,16 +88,10 @@ static int commit_graph_compatible(struct repository *r)structcommit_graph*load_commit_graph_one(constchar*graph_file){void*graph_map;-constunsignedchar*data,*chunk_lookup;size_tgraph_size;structstatst;-uint32_ti;-structcommit_graph*graph;+structcommit_graph*ret;intfd=git_open(graph_file);-uint64_tlast_chunk_offset;-uint32_tlast_chunk_id;-uint32_tgraph_signature;-unsignedchargraph_version,hash_version;if(fd<0)returnNULL;
@@ -108,27 +106,61 @@ struct commit_graph *load_commit_graph_one(const char *graph_file)die(_("graph file %s is too small"),graph_file);}graph_map=xmmap(NULL,graph_size,PROT_READ,MAP_PRIVATE,fd,0);+ret=parse_commit_graph(graph_map,fd,graph_size);++if(ret==NULL){+munmap(graph_map,graph_size);+close(fd);+exit(1);+}++returnret;+}++/*+*Thisfunctionisintendedtobeusedonlyfromload_commit_graph_one()orin+*fuzztests.+*/+structcommit_graph*parse_commit_graph(void*graph_map,intfd,+size_tgraph_size)+{+constunsignedchar*data,*chunk_lookup;+uint32_ti;+structcommit_graph*graph;+uint64_tlast_chunk_offset;+uint32_tlast_chunk_id;+uint32_tgraph_signature;+unsignedchargraph_version,hash_version;++/*+*Thisshouldalreadybecheckedinload_commit_graph_one,butwestill+*needacheckhereforwhenwe'recallingparse_commit_graphdirectly+*fromfuzztests.Wecanomittheerrormessageinthatcase.+*/+if(graph_size<GRAPH_MIN_SIZE)+returnNULL;+data=(constunsignedchar*)graph_map;graph_signature=get_be32(data);if(graph_signature!=GRAPH_SIGNATURE){error(_("graph signature %X does not match signature %X"),graph_signature,GRAPH_SIGNATURE);-gotocleanup_fail;+returnNULL;}graph_version=*(unsignedchar*)(data+4);if(graph_version!=GRAPH_VERSION){error(_("graph version %X does not match version %X"),graph_version,GRAPH_VERSION);-gotocleanup_fail;+returnNULL;}hash_version=*(unsignedchar*)(data+5);if(hash_version!=GRAPH_OID_VERSION){error(_("hash version %X does not match version %X"),hash_version,GRAPH_OID_VERSION);-gotocleanup_fail;+returnNULL;}graph=alloc_commit_graph();
fuzz-commit-graph identified a case where Git will read past the end of
a buffer containing a commit graph if the graph's header has an
incorrect chunk count. A simple bounds check in parse_commit_graph()
prevents this.
Signed-off-by: Josh Steadmon <redacted>
Helped-by: Derrick Stolee [off-list ref]
---
commit-graph.c | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
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.
Adds fuzz-commit-graph.c, which provides a fuzzing entry point
compatible with libFuzzer (and possibly other fuzzing engines).
Signed-off-by: Josh Steadmon <redacted>
---
.gitignore | 1 +
Makefile | 1 +
commit-graph.c | 63 +++++++++++++++++++++++++++++++++------------
fuzz-commit-graph.c | 18 +++++++++++++
4 files changed, 66 insertions(+), 17 deletions(-)
create mode 100644 fuzz-commit-graph.c
I hadn't looked at this before, but see your 5e47215080 ("fuzz: add
basic fuzz testing target.", 2018-10-12) for some prior art.
There's instructions there for a very long "make" invocation. Would be
nice if this were friendlier and we could just do "make test-fuzz" or
something...
On 2018.12.05 23:48, Ævar Arnfjörð Bjarmason wrote:
On Wed, Dec 05 2018, Josh Steadmon wrote:
quoted
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.
Adds fuzz-commit-graph.c, which provides a fuzzing entry point
compatible with libFuzzer (and possibly other fuzzing engines).
Signed-off-by: Josh Steadmon <redacted>
---
.gitignore | 1 +
Makefile | 1 +
commit-graph.c | 63 +++++++++++++++++++++++++++++++++------------
fuzz-commit-graph.c | 18 +++++++++++++
4 files changed, 66 insertions(+), 17 deletions(-)
create mode 100644 fuzz-commit-graph.c
I hadn't looked at this before, but see your 5e47215080 ("fuzz: add
basic fuzz testing target.", 2018-10-12) for some prior art.
There's instructions there for a very long "make" invocation. Would be
nice if this were friendlier and we could just do "make test-fuzz" or
something...
Yeah, the problem is that there are too many combinations of fuzzing
engine, sanitizer, and compiler to make any reasonable default here.
Even if you just stick with libFuzzer, address sanitizer, and clang, the
flags change radically depending on which version of clang you're using.
+ if (chunk_lookup + GRAPH_CHUNKLOOKUP_WIDTH > data + graph_size) {
+ error(_("chunk lookup table entry missing; graph file may be incomplete"));
+ free(graph);
+ return NULL;
+ }
Something I forgot earlier: there are several tests in
t5318-commit-graph.sh that use 'git commit-graph verify' to ensure we
hit these error conditions on a corrupted commit-graph file. Could you
try adding a test there that looks for this error message?
Thanks,
-Stolee
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. 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).
Signed-off-by: Josh Steadmon <redacted>
---
.gitignore | 1 +
Makefile | 1 +
commit-graph.c | 53 ++++++++++++++++++++++++++++++---------------
commit-graph.h | 3 +++
fuzz-commit-graph.c | 16 ++++++++++++++
5 files changed, 57 insertions(+), 17 deletions(-)
create mode 100644 fuzz-commit-graph.c
@@ -84,16 +84,10 @@ static int commit_graph_compatible(struct repository *r)structcommit_graph*load_commit_graph_one(constchar*graph_file){void*graph_map;-constunsignedchar*data,*chunk_lookup;size_tgraph_size;structstatst;-uint32_ti;-structcommit_graph*graph;+structcommit_graph*ret;intfd=git_open(graph_file);-uint64_tlast_chunk_offset;-uint32_tlast_chunk_id;-uint32_tgraph_signature;-unsignedchargraph_version,hash_version;if(fd<0)returnNULL;
@@ -108,27 +102,55 @@ struct commit_graph *load_commit_graph_one(const char *graph_file)die(_("graph file %s is too small"),graph_file);}graph_map=xmmap(NULL,graph_size,PROT_READ,MAP_PRIVATE,fd,0);+ret=parse_commit_graph(graph_map,fd,graph_size);++if(!ret){+munmap(graph_map,graph_size);+close(fd);+exit(1);+}++returnret;+}++structcommit_graph*parse_commit_graph(void*graph_map,intfd,+size_tgraph_size)+{+constunsignedchar*data,*chunk_lookup;+uint32_ti;+structcommit_graph*graph;+uint64_tlast_chunk_offset;+uint32_tlast_chunk_id;+uint32_tgraph_signature;+unsignedchargraph_version,hash_version;++if(!graph_map)+returnNULL;++if(graph_size<GRAPH_MIN_SIZE)+returnNULL;+data=(constunsignedchar*)graph_map;graph_signature=get_be32(data);if(graph_signature!=GRAPH_SIGNATURE){error(_("graph signature %X does not match signature %X"),graph_signature,GRAPH_SIGNATURE);-gotocleanup_fail;+returnNULL;}graph_version=*(unsignedchar*)(data+4);if(graph_version!=GRAPH_VERSION){error(_("graph version %X does not match version %X"),graph_version,GRAPH_VERSION);-gotocleanup_fail;+returnNULL;}hash_version=*(unsignedchar*)(data+5);if(hash_version!=GRAPH_OID_VERSION){error(_("hash version %X does not match version %X"),hash_version,GRAPH_OID_VERSION);-gotocleanup_fail;+returnNULL;}graph=alloc_commit_graph();
fuzz-commit-graph identified a case where Git will read past the end of
a buffer containing a commit graph if the graph's header has an
incorrect chunk count. A simple bounds check in parse_commit_graph()
prevents this.
Signed-off-by: Josh Steadmon <redacted>
---
commit-graph.c | 14 ++++++++++++--
t/t5318-commit-graph.sh | 28 ++++++++++++++++++++++++++++
2 files changed, 40 insertions(+), 2 deletions(-)
@@ -384,6 +384,29 @@ corrupt_graph_and_verify() {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_finishedmvcommit-graph-backup$objdir/info/commit-graph&&+cp$objdir/info/commit-graphcommit-graph-backup&&+printf"$data"|ddof="$objdir/info/commit-graph"bs=1seek="$corrupt_pos"conv=notrunc&&+truncate--size=$zero_pos$objdir/info/commit-graph&&+truncate--size=$orig_size$objdir/info/commit-graph&&+test_must_failgitcommit-graphverify2>test_err&&+grep-v"^+"test_err>err&&+test_i18ngrep"$grepstr"err+}+ test_expect_success'detect bad signature''corrupt_graph_and_verify0"\0"\"graph signature"
@@ -3104,7 +3104,7 @@ cover_db_html: cover_db# An example command to build against libFuzzer from LLVM 4.0.0:## make CC=clang CXX=clang++ \-# FUZZ_CXXFLAGS="-fsanitize-coverage=trace-pc-guard -fsanitize=address" \+# CFLAGS="-fsanitize-coverage=trace-pc-guard -fsanitize=address" \# LIB_FUZZING_ENGINE=/usr/lib/llvm-4.0/lib/libFuzzer.a \# fuzz-all#
@@ -165,10 +165,20 @@ struct commit_graph *parse_commit_graph(void *graph_map, int fd,last_chunk_offset=8;chunk_lookup=data+8;for(i=0;i<graph->num_chunks;i++){-uint32_tchunk_id=get_be32(chunk_lookup+0);-uint64_tchunk_offset=get_be64(chunk_lookup+4);+uint32_tchunk_id;+uint64_tchunk_offset;intchunk_repeated=0;+if(chunk_lookup+GRAPH_CHUNKLOOKUP_WIDTH>+data+graph_size){+error(_("chunk lookup table entry missing; graph file may be incomplete"));+free(graph);+returnNULL;+}
Is it possible to overflow the addition here? E.g., if I'm on a 32-bit
system and the truncated chunk appears right at the 4GB limit, in which
case we wrap back around? I guess that's pretty implausible, since it
would mean that the mmap is bumping up against the end of the address
space. I didn't check, but I wouldn't be surprised if sane operating
systems avoid allocating those addresses.
But I think you could write this as:
if (data + graph_size - chunk_lookup < GRAPH_CHUNKLOOKUP_WIDTH)
to avoid overflow (we know that "data + graph_size" is sane because
that's our mmap, and chunk_lookup is somewhere between "data" and "data
+ graph_size", so the result is between 0 and graph_size).
I dunno. I think I've convinced myself it's a non-issue here, but it may
be good to get in the habit of writing these sorts of offset checks in
an overflow-proof order.
-Peff
+
+# 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() {
This method is very similar to to 'corrupt_graph_and_verify()', the only
difference being the zero_pos, which zeroes the graph.
Could it instead be a modification of corrupt_graph_and_verify() where
$4 is interpreted as zero_pos, and if it is blank we don't do the
truncation?
Thanks for this! I think it's valuable to keep explicit tests around
that were discovered from your fuzz tests. Specifically, I can repeat
the test when I get around to the next file format.
Thanks,
-Stolee
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. 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).
Signed-off-by: Josh Steadmon <redacted>
---
.gitignore | 1 +
Makefile | 1 +
commit-graph.c | 53 ++++++++++++++++++++++++++++++---------------
commit-graph.h | 3 +++
fuzz-commit-graph.c | 16 ++++++++++++++
5 files changed, 57 insertions(+), 17 deletions(-)
create mode 100644 fuzz-commit-graph.c
@@ -84,16 +84,10 @@ static int commit_graph_compatible(struct repository *r)structcommit_graph*load_commit_graph_one(constchar*graph_file){void*graph_map;-constunsignedchar*data,*chunk_lookup;size_tgraph_size;structstatst;-uint32_ti;-structcommit_graph*graph;+structcommit_graph*ret;intfd=git_open(graph_file);-uint64_tlast_chunk_offset;-uint32_tlast_chunk_id;-uint32_tgraph_signature;-unsignedchargraph_version,hash_version;if(fd<0)returnNULL;
@@ -108,27 +102,55 @@ struct commit_graph *load_commit_graph_one(const char *graph_file)die(_("graph file %s is too small"),graph_file);}graph_map=xmmap(NULL,graph_size,PROT_READ,MAP_PRIVATE,fd,0);+ret=parse_commit_graph(graph_map,fd,graph_size);++if(!ret){+munmap(graph_map,graph_size);+close(fd);+exit(1);+}++returnret;+}++structcommit_graph*parse_commit_graph(void*graph_map,intfd,+size_tgraph_size)+{+constunsignedchar*data,*chunk_lookup;+uint32_ti;+structcommit_graph*graph;+uint64_tlast_chunk_offset;+uint32_tlast_chunk_id;+uint32_tgraph_signature;+unsignedchargraph_version,hash_version;++if(!graph_map)+returnNULL;++if(graph_size<GRAPH_MIN_SIZE)+returnNULL;+data=(constunsignedchar*)graph_map;graph_signature=get_be32(data);if(graph_signature!=GRAPH_SIGNATURE){error(_("graph signature %X does not match signature %X"),graph_signature,GRAPH_SIGNATURE);-gotocleanup_fail;+returnNULL;}graph_version=*(unsignedchar*)(data+4);if(graph_version!=GRAPH_VERSION){error(_("graph version %X does not match version %X"),graph_version,GRAPH_VERSION);-gotocleanup_fail;+returnNULL;}hash_version=*(unsignedchar*)(data+5);if(hash_version!=GRAPH_OID_VERSION){error(_("hash version %X does not match version %X"),hash_version,GRAPH_OID_VERSION);-gotocleanup_fail;+returnNULL;}graph=alloc_commit_graph();
fuzz-commit-graph identified a case where Git will read past the end of
a buffer containing a commit graph if the graph's header has an
incorrect chunk count. A simple bounds check in parse_commit_graph()
prevents this.
Signed-off-by: Josh Steadmon <redacted>
---
commit-graph.c | 14 ++++++++++++--
t/t5318-commit-graph.sh | 15 +++++++++++++--
2 files changed, 25 insertions(+), 4 deletions(-)
@@ -366,24 +366,30 @@ GRAPH_OCTOPUS_DATA_OFFSET=$(($GRAPH_COMMIT_DATA_OFFSET + \GRAPH_BYTE_OCTOPUS=$(($GRAPH_OCTOPUS_DATA_OFFSET+4))GRAPH_BYTE_FOOTER=$(($GRAPH_OCTOPUS_DATA_OFFSET+4*$NUM_OCTOPUS_EDGES))-# usage: corrupt_graph_and_verify <position> <data> <string>+# usage: corrupt_graph_and_verify <position> <data> <string> [<zero_pos>]# Manipulates the commit-graph file at the position-# by inserting the data, then runs 'git commit-graph verify'+# by inserting the data, optionally zeroing the file+# starting at <zero_pos>, then runs 'git commit-graph verify'# and places the output in the file 'err'. Test 'err' for# the given string. corrupt_graph_and_verify(){pos=$1data="${2:-\0}"grepstr=$3+orig_size=$(stat--format=%s$objdir/info/commit-graph)+zero_pos=${4:-${orig_size}}cd"$TRASH_DIRECTORY/full"&&test_when_finishedmvcommit-graph-backup$objdir/info/commit-graph&&cp$objdir/info/commit-graphcommit-graph-backup&&printf"$data"|ddof="$objdir/info/commit-graph"bs=1seek="$pos"conv=notrunc&&+truncate--size=$zero_pos$objdir/info/commit-graph&&+truncate--size=$orig_size$objdir/info/commit-graph&&test_must_failgitcommit-graphverify2>test_err&&grep-v"^+"test_err>errtest_i18ngrep"$grepstr"err}+ test_expect_success'detect bad signature''corrupt_graph_and_verify0"\0"\"graph signature"
@@ -3104,7 +3104,7 @@ cover_db_html: cover_db# An example command to build against libFuzzer from LLVM 4.0.0:## make CC=clang CXX=clang++ \-# FUZZ_CXXFLAGS="-fsanitize-coverage=trace-pc-guard -fsanitize=address" \+# CFLAGS="-fsanitize-coverage=trace-pc-guard -fsanitize=address" \# LIB_FUZZING_ENGINE=/usr/lib/llvm-4.0/lib/libFuzzer.a \# fuzz-all#
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. 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).
Signed-off-by: Josh Steadmon <redacted>
---
.gitignore | 1 +
Makefile | 1 +
commit-graph.c | 53 ++++++++++++++++++++++++++++++---------------
commit-graph.h | 3 +++
fuzz-commit-graph.c | 16 ++++++++++++++
5 files changed, 57 insertions(+), 17 deletions(-)
create mode 100644 fuzz-commit-graph.c
@@ -84,16 +84,10 @@ static int commit_graph_compatible(struct repository *r)structcommit_graph*load_commit_graph_one(constchar*graph_file){void*graph_map;-constunsignedchar*data,*chunk_lookup;size_tgraph_size;structstatst;-uint32_ti;-structcommit_graph*graph;+structcommit_graph*ret;intfd=git_open(graph_file);-uint64_tlast_chunk_offset;-uint32_tlast_chunk_id;-uint32_tgraph_signature;-unsignedchargraph_version,hash_version;if(fd<0)returnNULL;
@@ -108,27 +102,55 @@ struct commit_graph *load_commit_graph_one(const char *graph_file)die(_("graph file %s is too small"),graph_file);}graph_map=xmmap(NULL,graph_size,PROT_READ,MAP_PRIVATE,fd,0);+ret=parse_commit_graph(graph_map,fd,graph_size);++if(!ret){+munmap(graph_map,graph_size);+close(fd);+exit(1);+}++returnret;+}++structcommit_graph*parse_commit_graph(void*graph_map,intfd,+size_tgraph_size)+{+constunsignedchar*data,*chunk_lookup;+uint32_ti;+structcommit_graph*graph;+uint64_tlast_chunk_offset;+uint32_tlast_chunk_id;+uint32_tgraph_signature;+unsignedchargraph_version,hash_version;++if(!graph_map)+returnNULL;++if(graph_size<GRAPH_MIN_SIZE)+returnNULL;+data=(constunsignedchar*)graph_map;graph_signature=get_be32(data);if(graph_signature!=GRAPH_SIGNATURE){error(_("graph signature %X does not match signature %X"),graph_signature,GRAPH_SIGNATURE);-gotocleanup_fail;+returnNULL;}graph_version=*(unsignedchar*)(data+4);if(graph_version!=GRAPH_VERSION){error(_("graph version %X does not match version %X"),graph_version,GRAPH_VERSION);-gotocleanup_fail;+returnNULL;}hash_version=*(unsignedchar*)(data+5);if(hash_version!=GRAPH_OID_VERSION){error(_("hash version %X does not match version %X"),hash_version,GRAPH_OID_VERSION);-gotocleanup_fail;+returnNULL;}graph=alloc_commit_graph();
fuzz-commit-graph identified a case where Git will read past the end of
a buffer containing a commit graph if the graph's header has an
incorrect chunk count. A simple bounds check in parse_commit_graph()
prevents this.
Signed-off-by: Josh Steadmon <redacted>
---
commit-graph.c | 14 ++++++++++++--
t/t5318-commit-graph.sh | 16 +++++++++++++---
2 files changed, 25 insertions(+), 5 deletions(-)
@@ -366,21 +366,26 @@ GRAPH_OCTOPUS_DATA_OFFSET=$(($GRAPH_COMMIT_DATA_OFFSET + \GRAPH_BYTE_OCTOPUS=$(($GRAPH_OCTOPUS_DATA_OFFSET+4))GRAPH_BYTE_FOOTER=$(($GRAPH_OCTOPUS_DATA_OFFSET+4*$NUM_OCTOPUS_EDGES))-# usage: corrupt_graph_and_verify <position> <data> <string>+# usage: corrupt_graph_and_verify <position> <data> <string> [<zero_pos>]# Manipulates the commit-graph file at the position-# by inserting the data, then runs 'git commit-graph verify'+# by inserting the data, optionally zeroing the file+# starting at <zero_pos>, then runs 'git commit-graph verify'# and places the output in the file 'err'. Test 'err' for# the given string. corrupt_graph_and_verify(){pos=$1data="${2:-\0}"grepstr=$3+orig_size=$(wc-c<$objdir/info/commit-graph)&&+zero_pos=${4:-${orig_size}}&&cd"$TRASH_DIRECTORY/full"&&test_when_finishedmvcommit-graph-backup$objdir/info/commit-graph&&cp$objdir/info/commit-graphcommit-graph-backup&&printf"$data"|ddof="$objdir/info/commit-graph"bs=1seek="$pos"conv=notrunc&&+ddof="$objdir/info/commit-graph"bs=1seek="$zero_pos"count=0&&+ddif=/dev/zeroof="$objdir/info/commit-graph"bs=1seek="$zero_pos"count=$(($orig_size-$zero_pos))&&test_must_failgitcommit-graphverify2>test_err&&-grep-v"^+"test_err>err+grep-v"^+"test_err>err&&test_i18ngrep"$grepstr"err}
@@ -3104,7 +3104,7 @@ cover_db_html: cover_db# An example command to build against libFuzzer from LLVM 4.0.0:## make CC=clang CXX=clang++ \-# FUZZ_CXXFLAGS="-fsanitize-coverage=trace-pc-guard -fsanitize=address" \+# CFLAGS="-fsanitize-coverage=trace-pc-guard -fsanitize=address" \# LIB_FUZZING_ENGINE=/usr/lib/llvm-4.0/lib/libFuzzer.a \# fuzz-all#
From: Jeff King <hidden> Date: 2018-12-18 17:35:43
On Thu, Dec 13, 2018 at 11:43:55AM -0800, Josh Steadmon wrote:
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 V3:
* Improve portability of the new test functionality.
I thought there was some question about /dev/zero, which I think is
in this version (I don't actually know whether there are portability
issues or not, but somebody did mention it).
-Peff
On Thu, Dec 13, 2018 at 11:43:55AM -0800, Josh Steadmon wrote:
quoted
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 V3:
* Improve portability of the new test functionality.
I thought there was some question about /dev/zero, which I think is
in this version (I don't actually know whether there are portability
issues or not, but somebody did mention it).
-Peff
From: Jeff King <hidden> Date: 2018-12-19 15:51:11
On Tue, Dec 18, 2018 at 01:05:51PM -0800, Josh Steadmon wrote:
On 2018.12.18 12:35, Jeff King wrote:
quoted
On Thu, Dec 13, 2018 at 11:43:55AM -0800, Josh Steadmon wrote:
quoted
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 V3:
* Improve portability of the new test functionality.
I thought there was some question about /dev/zero, which I think is
in this version (I don't actually know whether there are portability
issues or not, but somebody did mention it).
-Peff
I've only found one reference [1] (from 1999) of OS X Server not having
a /dev/zero. It appears to be present as of 2010 though [2].
Thanks for digging. That seems like enough to assume we should try it
and see if any macOS people complain.
I do wonder if we'll run into problems on Windows, though.
-Peff
From: Johannes Schindelin <hidden> Date: 2018-12-20 19:36:31
Hi Peff,
On Wed, 19 Dec 2018, Jeff King wrote:
On Tue, Dec 18, 2018 at 01:05:51PM -0800, Josh Steadmon wrote:
quoted
On 2018.12.18 12:35, Jeff King wrote:
quoted
On Thu, Dec 13, 2018 at 11:43:55AM -0800, Josh Steadmon wrote:
quoted
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 V3:
* Improve portability of the new test functionality.
I thought there was some question about /dev/zero, which I think is
in this version (I don't actually know whether there are portability
issues or not, but somebody did mention it).
-Peff
I've only found one reference [1] (from 1999) of OS X Server not having
a /dev/zero. It appears to be present as of 2010 though [2].
Thanks for digging. That seems like enough to assume we should try it
and see if any macOS people complain.
I do wonder if we'll run into problems on Windows, though.
As long as we're talking about Unix shell scripts, /dev/zero should be
fine, as we are essentially running in a variant of Cygwin.
If you try to pass /dev/zero as an argument to a Git command, that's an
entirely different thing: this most likely won't work.
Ciao,
Dscho
From: Jeff King <hidden> Date: 2018-12-20 20:13:00
On Thu, Dec 20, 2018 at 08:35:57PM +0100, Johannes Schindelin wrote:
quoted
I do wonder if we'll run into problems on Windows, though.
As long as we're talking about Unix shell scripts, /dev/zero should be
fine, as we are essentially running in a variant of Cygwin.
If you try to pass /dev/zero as an argument to a Git command, that's an
entirely different thing: this most likely won't work.
Thanks for confirming. We're talking about passing it to dd here, so I
think it should be OK.
-Peff
@@ -366,21 +366,26 @@ GRAPH_OCTOPUS_DATA_OFFSET=$(($GRAPH_COMMIT_DATA_OFFSET + \GRAPH_BYTE_OCTOPUS=$(($GRAPH_OCTOPUS_DATA_OFFSET+4))GRAPH_BYTE_FOOTER=$(($GRAPH_OCTOPUS_DATA_OFFSET+4*$NUM_OCTOPUS_EDGES))-# usage: corrupt_graph_and_verify <position> <data> <string>+# usage: corrupt_graph_and_verify <position> <data> <string> [<zero_pos>]# Manipulates the commit-graph file at the position-# by inserting the data, then runs 'git commit-graph verify'+# by inserting the data, optionally zeroing the file+# starting at <zero_pos>, then runs 'git commit-graph verify'# and places the output in the file 'err'. Test 'err' for# the given string. corrupt_graph_and_verify(){pos=$1data="${2:-\0}"grepstr=$3+orig_size=$(wc-c<$objdir/info/commit-graph)&&
A minor nit: this test script is unusually prudent about which
directory/repository each test is executed in, as the first thing each
test does is to 'cd' into the right directory. (I think this is a
Good Thing, and other test scripts should follow suit if they use a
repo other than $TRASH_DIRECTORY.) Though it doesn't cause any
immediate issues (the previous test happens to use the same
repository), the above line violates this, as it accesses the
'.git/.../commit-graph' file ...
+ zero_pos=${4:-${orig_size}} &&
cd "$TRASH_DIRECTORY/full" &&
... before this line could ensure that it's in the right repository.
@@ -366,21 +366,26 @@ GRAPH_OCTOPUS_DATA_OFFSET=$(($GRAPH_COMMIT_DATA_OFFSET + \GRAPH_BYTE_OCTOPUS=$(($GRAPH_OCTOPUS_DATA_OFFSET+4))GRAPH_BYTE_FOOTER=$(($GRAPH_OCTOPUS_DATA_OFFSET+4*$NUM_OCTOPUS_EDGES))-# usage: corrupt_graph_and_verify <position> <data> <string>+# usage: corrupt_graph_and_verify <position> <data> <string> [<zero_pos>]# Manipulates the commit-graph file at the position-# by inserting the data, then runs 'git commit-graph verify'+# by inserting the data, optionally zeroing the file+# starting at <zero_pos>, then runs 'git commit-graph verify'# and places the output in the file 'err'. Test 'err' for# the given string. corrupt_graph_and_verify(){pos=$1data="${2:-\0}"grepstr=$3+orig_size=$(wc-c<$objdir/info/commit-graph)&&
A minor nit: this test script is unusually prudent about which
directory/repository each test is executed in, as the first thing each
test does is to 'cd' into the right directory. (I think this is a
Good Thing, and other test scripts should follow suit if they use a
repo other than $TRASH_DIRECTORY.) Though it doesn't cause any
immediate issues (the previous test happens to use the same
repository), the above line violates this, as it accesses the
'.git/.../commit-graph' file ...
quoted
+ zero_pos=${4:-${orig_size}} &&
cd "$TRASH_DIRECTORY/full" &&
... before this line could ensure that it's in the right repository.
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. 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).
Signed-off-by: Josh Steadmon <redacted>
---
.gitignore | 1 +
Makefile | 1 +
commit-graph.c | 53 ++++++++++++++++++++++++++++++---------------
commit-graph.h | 3 +++
fuzz-commit-graph.c | 16 ++++++++++++++
5 files changed, 57 insertions(+), 17 deletions(-)
create mode 100644 fuzz-commit-graph.c
@@ -84,16 +84,10 @@ static int commit_graph_compatible(struct repository *r)structcommit_graph*load_commit_graph_one(constchar*graph_file){void*graph_map;-constunsignedchar*data,*chunk_lookup;size_tgraph_size;structstatst;-uint32_ti;-structcommit_graph*graph;+structcommit_graph*ret;intfd=git_open(graph_file);-uint64_tlast_chunk_offset;-uint32_tlast_chunk_id;-uint32_tgraph_signature;-unsignedchargraph_version,hash_version;if(fd<0)returnNULL;
@@ -108,27 +102,55 @@ struct commit_graph *load_commit_graph_one(const char *graph_file)die(_("graph file %s is too small"),graph_file);}graph_map=xmmap(NULL,graph_size,PROT_READ,MAP_PRIVATE,fd,0);+ret=parse_commit_graph(graph_map,fd,graph_size);++if(!ret){+munmap(graph_map,graph_size);+close(fd);+exit(1);+}++returnret;+}++structcommit_graph*parse_commit_graph(void*graph_map,intfd,+size_tgraph_size)+{+constunsignedchar*data,*chunk_lookup;+uint32_ti;+structcommit_graph*graph;+uint64_tlast_chunk_offset;+uint32_tlast_chunk_id;+uint32_tgraph_signature;+unsignedchargraph_version,hash_version;++if(!graph_map)+returnNULL;++if(graph_size<GRAPH_MIN_SIZE)+returnNULL;+data=(constunsignedchar*)graph_map;graph_signature=get_be32(data);if(graph_signature!=GRAPH_SIGNATURE){error(_("graph signature %X does not match signature %X"),graph_signature,GRAPH_SIGNATURE);-gotocleanup_fail;+returnNULL;}graph_version=*(unsignedchar*)(data+4);if(graph_version!=GRAPH_VERSION){error(_("graph version %X does not match version %X"),graph_version,GRAPH_VERSION);-gotocleanup_fail;+returnNULL;}hash_version=*(unsignedchar*)(data+5);if(hash_version!=GRAPH_OID_VERSION){error(_("hash version %X does not match version %X"),hash_version,GRAPH_OID_VERSION);-gotocleanup_fail;+returnNULL;}graph=alloc_commit_graph();
fuzz-commit-graph identified a case where Git will read past the end of
a buffer containing a commit graph if the graph's header has an
incorrect chunk count. A simple bounds check in parse_commit_graph()
prevents this.
Signed-off-by: Josh Steadmon <redacted>
---
commit-graph.c | 14 ++++++++++++--
t/t5318-commit-graph.sh | 16 +++++++++++++---
2 files changed, 25 insertions(+), 5 deletions(-)
@@ -366,9 +366,10 @@ GRAPH_OCTOPUS_DATA_OFFSET=$(($GRAPH_COMMIT_DATA_OFFSET + \GRAPH_BYTE_OCTOPUS=$(($GRAPH_OCTOPUS_DATA_OFFSET+4))GRAPH_BYTE_FOOTER=$(($GRAPH_OCTOPUS_DATA_OFFSET+4*$NUM_OCTOPUS_EDGES))-# usage: corrupt_graph_and_verify <position> <data> <string>+# usage: corrupt_graph_and_verify <position> <data> <string> [<zero_pos>]# Manipulates the commit-graph file at the position-# by inserting the data, then runs 'git commit-graph verify'+# by inserting the data, optionally zeroing the file+# starting at <zero_pos>, then runs 'git commit-graph verify'# and places the output in the file 'err'. Test 'err' for# the given string. corrupt_graph_and_verify(){
@@ -3104,7 +3104,7 @@ cover_db_html: cover_db# An example command to build against libFuzzer from LLVM 4.0.0:## make CC=clang CXX=clang++ \-# FUZZ_CXXFLAGS="-fsanitize-coverage=trace-pc-guard -fsanitize=address" \+# CFLAGS="-fsanitize-coverage=trace-pc-guard -fsanitize=address" \# LIB_FUZZING_ENGINE=/usr/lib/llvm-4.0/lib/libFuzzer.a \# fuzz-all#
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 V5:
* Conform to commit message standards for the 1st patch in the series.
* Clarify commit message for the 3rd patch in the series.
Changes since V4:
* Ensure that corrupt_graph_and_verify() in t5318 changes to the
proper directory before accessing any files.
Changes since V3:
* Improve portability of the new test functionality.
* Fix broken &&-chains in tests.
Changes since V2:
* Avoid pointer arithmetic overflow when checking the graph's chunk
count.
* Merge the corrupt_graph_and_verify and
corrupt_and_zero_graph_then_verify test functions.
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 | 16 ++++++++--
6 files changed, 83 insertions(+), 23 deletions(-)
create mode 100644 fuzz-commit-graph.c
Range-diff against v5:
1: 0b57ecbe1b ! 1: c4ec3fc3fc commit-graph, fuzz: Add fuzzer for commit-graph
@@ -2,11 +2,11 @@
commit-graph, fuzz: Add fuzzer for commit-graph
- 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. 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.
+ Break 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. 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).
2: a3b5d33c4b = 2: d7b137650f commit-graph: fix buffer read-overflow
3: 350ea5f7c9 ! 3: c06e0667fa Makefile: correct example fuzz build
@@ -2,6 +2,15 @@
Makefile: correct example fuzz build
+ The comment explaining how to build the fuzzers was broken in
+ 927c77e7d4d ("Makefile: use FUZZ_CXXFLAGS for linking fuzzers",
+ 2018-11-14).
+
+ When building fuzzers, all .c files must be compiled with coverage
+ tracing enabled. This is not possible when using only FUZZ_CXXFLAGS, as
+ that flag is only applied to the fuzzers themselves. Switching back to
+ CFLAGS fixes the issue.
+
diff --git a/Makefile b/Makefile
--
2.20.1.97.g81188d93c3-goog
Break 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. 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).
Signed-off-by: Josh Steadmon <redacted>
---
.gitignore | 1 +
Makefile | 1 +
commit-graph.c | 53 ++++++++++++++++++++++++++++++---------------
commit-graph.h | 3 +++
fuzz-commit-graph.c | 16 ++++++++++++++
5 files changed, 57 insertions(+), 17 deletions(-)
create mode 100644 fuzz-commit-graph.c
@@ -84,16 +84,10 @@ static int commit_graph_compatible(struct repository *r)structcommit_graph*load_commit_graph_one(constchar*graph_file){void*graph_map;-constunsignedchar*data,*chunk_lookup;size_tgraph_size;structstatst;-uint32_ti;-structcommit_graph*graph;+structcommit_graph*ret;intfd=git_open(graph_file);-uint64_tlast_chunk_offset;-uint32_tlast_chunk_id;-uint32_tgraph_signature;-unsignedchargraph_version,hash_version;if(fd<0)returnNULL;
@@ -108,27 +102,55 @@ struct commit_graph *load_commit_graph_one(const char *graph_file)die(_("graph file %s is too small"),graph_file);}graph_map=xmmap(NULL,graph_size,PROT_READ,MAP_PRIVATE,fd,0);+ret=parse_commit_graph(graph_map,fd,graph_size);++if(!ret){+munmap(graph_map,graph_size);+close(fd);+exit(1);+}++returnret;+}++structcommit_graph*parse_commit_graph(void*graph_map,intfd,+size_tgraph_size)+{+constunsignedchar*data,*chunk_lookup;+uint32_ti;+structcommit_graph*graph;+uint64_tlast_chunk_offset;+uint32_tlast_chunk_id;+uint32_tgraph_signature;+unsignedchargraph_version,hash_version;++if(!graph_map)+returnNULL;++if(graph_size<GRAPH_MIN_SIZE)+returnNULL;+data=(constunsignedchar*)graph_map;graph_signature=get_be32(data);if(graph_signature!=GRAPH_SIGNATURE){error(_("graph signature %X does not match signature %X"),graph_signature,GRAPH_SIGNATURE);-gotocleanup_fail;+returnNULL;}graph_version=*(unsignedchar*)(data+4);if(graph_version!=GRAPH_VERSION){error(_("graph version %X does not match version %X"),graph_version,GRAPH_VERSION);-gotocleanup_fail;+returnNULL;}hash_version=*(unsignedchar*)(data+5);if(hash_version!=GRAPH_OID_VERSION){error(_("hash version %X does not match version %X"),hash_version,GRAPH_OID_VERSION);-gotocleanup_fail;+returnNULL;}graph=alloc_commit_graph();
fuzz-commit-graph identified a case where Git will read past the end of
a buffer containing a commit graph if the graph's header has an
incorrect chunk count. A simple bounds check in parse_commit_graph()
prevents this.
Signed-off-by: Josh Steadmon <redacted>
---
commit-graph.c | 14 ++++++++++++--
t/t5318-commit-graph.sh | 16 +++++++++++++---
2 files changed, 25 insertions(+), 5 deletions(-)
@@ -366,9 +366,10 @@ GRAPH_OCTOPUS_DATA_OFFSET=$(($GRAPH_COMMIT_DATA_OFFSET + \GRAPH_BYTE_OCTOPUS=$(($GRAPH_OCTOPUS_DATA_OFFSET+4))GRAPH_BYTE_FOOTER=$(($GRAPH_OCTOPUS_DATA_OFFSET+4*$NUM_OCTOPUS_EDGES))-# usage: corrupt_graph_and_verify <position> <data> <string>+# usage: corrupt_graph_and_verify <position> <data> <string> [<zero_pos>]# Manipulates the commit-graph file at the position-# by inserting the data, then runs 'git commit-graph verify'+# by inserting the data, optionally zeroing the file+# starting at <zero_pos>, then runs 'git commit-graph verify'# and places the output in the file 'err'. Test 'err' for# the given string. corrupt_graph_and_verify(){
The comment explaining how to build the fuzzers was broken in
927c77e7d4d ("Makefile: use FUZZ_CXXFLAGS for linking fuzzers",
2018-11-14).
When building fuzzers, all .c files must be compiled with coverage
tracing enabled. This is not possible when using only FUZZ_CXXFLAGS, as
that flag is only applied to the fuzzers themselves. Switching back to
CFLAGS fixes the issue.
Signed-off-by: Josh Steadmon <redacted>
---
Makefile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
@@ -3104,7 +3104,7 @@ cover_db_html: cover_db# An example command to build against libFuzzer from LLVM 4.0.0:## make CC=clang CXX=clang++ \-# FUZZ_CXXFLAGS="-fsanitize-coverage=trace-pc-guard -fsanitize=address" \+# CFLAGS="-fsanitize-coverage=trace-pc-guard -fsanitize=address" \# LIB_FUZZING_ENGINE=/usr/lib/llvm-4.0/lib/libFuzzer.a \# fuzz-all#
fuzz-commit-graph identified a case where Git will read past the end of
a buffer containing a commit graph if the graph's header has an
incorrect chunk count. A simple bounds check in parse_commit_graph()
prevents this.
@@ -366,9 +366,10 @@ GRAPH_OCTOPUS_DATA_OFFSET=$(($GRAPH_COMMIT_DATA_OFFSET + \GRAPH_BYTE_OCTOPUS=$(($GRAPH_OCTOPUS_DATA_OFFSET+4))GRAPH_BYTE_FOOTER=$(($GRAPH_OCTOPUS_DATA_OFFSET+4*$NUM_OCTOPUS_EDGES))-# usage: corrupt_graph_and_verify <position> <data> <string>+# usage: corrupt_graph_and_verify <position> <data> <string> [<zero_pos>]# Manipulates the commit-graph file at the position-# by inserting the data, then runs 'git commit-graph verify'+# by inserting the data, optionally zeroing the file+# starting at <zero_pos>, then runs 'git commit-graph verify'# and places the output in the file 'err'. Test 'err' for# the given string. corrupt_graph_and_verify(){
In the limited time I had to dig it starts failing at test 46, when
count=0 is given. dd on NetBSD exits with 127 when given count=0 it
seems.
So the first 'dd' is supposed to truncate the commit-graph file at
$zero_pos. I don't think we need 'count=0' for that: in the absence
of the 'if=...' operand, 'dd' reads from standard input, which is
redirected from /dev/null in our test scripts, i.e. there is nothing
to read, and, consequently, there is nothing to write, either.
Though not strictly necessary, I would feel more comfortable if
'if=/dev/null' would be explicitly specified, and even more so with a
"# truncate at $zero_pos" comment above that command.
As to the second 'dd', I think we should not run it at all when count
would be zero, i.e. when $orig_size = $zero_pos, because in
combination with 'if=/dev/zero' it's asking for trouble. According to
POSIX [1]:
count=n
Copy only n input blocks. If n is zero, it is unspecified
whether no blocks or all blocks are copied.
Imagine a 'dd' that implements the second option: there are infinite
blocks in /dev/zero to copy! OTOH, if an implementation chooses the
first option (e.g. the usual Linux 'dd' from coreutils), then both of
these 'dd' invocations will leave the commit-graph file as-is, so it
doesn't matter whether we run them or not.
[1] http://pubs.opengroup.org/onlinepubs/9699919799/utilities/dd.html