Merge branch 'js/commit-graph-chunk-table-fix'

The codepath to read from the commit-graph file attempted to read
past the end of it when the file's table-of-contents was corrupt.

* js/commit-graph-chunk-table-fix:
  Makefile: correct example fuzz build
  commit-graph: fix buffer read-overflow
  commit-graph, fuzz: add fuzzer for commit-graph
This commit is contained in:
Junio C Hamano
2019-02-05 14:26:11 -08:00
6 changed files with 83 additions and 23 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
/fuzz-commit-graph
/fuzz_corpora /fuzz_corpora
/fuzz-pack-headers /fuzz-pack-headers
/fuzz-pack-idx /fuzz-pack-idx

View File

@ -690,6 +690,7 @@ SCRIPTS = $(SCRIPT_SH_INS) \
ETAGS_TARGET = TAGS ETAGS_TARGET = TAGS
FUZZ_OBJS += fuzz-commit-graph.o
FUZZ_OBJS += fuzz-pack-headers.o FUZZ_OBJS += fuzz-pack-headers.o
FUZZ_OBJS += fuzz-pack-idx.o FUZZ_OBJS += fuzz-pack-idx.o
@ -3125,7 +3126,7 @@ cover_db_html: cover_db
# An example command to build against libFuzzer from LLVM 4.0.0: # An example command to build against libFuzzer from LLVM 4.0.0:
# #
# make CC=clang CXX=clang++ \ # 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 \ # LIB_FUZZING_ENGINE=/usr/lib/llvm-4.0/lib/libFuzzer.a \
# fuzz-all # fuzz-all
# #

View File

@ -83,16 +83,10 @@ static int commit_graph_compatible(struct repository *r)
struct commit_graph *load_commit_graph_one(const char *graph_file) struct commit_graph *load_commit_graph_one(const char *graph_file)
{ {
void *graph_map; void *graph_map;
const unsigned char *data, *chunk_lookup;
size_t graph_size; size_t graph_size;
struct stat st; struct stat st;
uint32_t i; struct commit_graph *ret;
struct commit_graph *graph;
int fd = git_open(graph_file); int fd = git_open(graph_file);
uint64_t last_chunk_offset;
uint32_t last_chunk_id;
uint32_t graph_signature;
unsigned char graph_version, hash_version;
if (fd < 0) if (fd < 0)
return NULL; return NULL;
@ -107,27 +101,55 @@ struct commit_graph *load_commit_graph_one(const char *graph_file)
die(_("graph file %s is too small"), graph_file); die(_("graph file %s is too small"), graph_file);
} }
graph_map = xmmap(NULL, graph_size, PROT_READ, MAP_PRIVATE, fd, 0); 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);
}
return ret;
}
struct commit_graph *parse_commit_graph(void *graph_map, int fd,
size_t graph_size)
{
const unsigned char *data, *chunk_lookup;
uint32_t i;
struct commit_graph *graph;
uint64_t last_chunk_offset;
uint32_t last_chunk_id;
uint32_t graph_signature;
unsigned char graph_version, hash_version;
if (!graph_map)
return NULL;
if (graph_size < GRAPH_MIN_SIZE)
return NULL;
data = (const unsigned char *)graph_map; data = (const unsigned char *)graph_map;
graph_signature = get_be32(data); graph_signature = get_be32(data);
if (graph_signature != GRAPH_SIGNATURE) { if (graph_signature != GRAPH_SIGNATURE) {
error(_("graph signature %X does not match signature %X"), error(_("graph signature %X does not match signature %X"),
graph_signature, GRAPH_SIGNATURE); graph_signature, GRAPH_SIGNATURE);
goto cleanup_fail; return NULL;
} }
graph_version = *(unsigned char*)(data + 4); graph_version = *(unsigned char*)(data + 4);
if (graph_version != GRAPH_VERSION) { if (graph_version != GRAPH_VERSION) {
error(_("graph version %X does not match version %X"), error(_("graph version %X does not match version %X"),
graph_version, GRAPH_VERSION); graph_version, GRAPH_VERSION);
goto cleanup_fail; return NULL;
} }
hash_version = *(unsigned char*)(data + 5); hash_version = *(unsigned char*)(data + 5);
if (hash_version != oid_version()) { if (hash_version != oid_version()) {
error(_("hash version %X does not match version %X"), error(_("hash version %X does not match version %X"),
hash_version, oid_version()); hash_version, oid_version());
goto cleanup_fail; return NULL;
} }
graph = alloc_commit_graph(); graph = alloc_commit_graph();
@ -142,16 +164,27 @@ struct commit_graph *load_commit_graph_one(const char *graph_file)
last_chunk_offset = 8; last_chunk_offset = 8;
chunk_lookup = data + 8; chunk_lookup = data + 8;
for (i = 0; i < graph->num_chunks; i++) { for (i = 0; i < graph->num_chunks; i++) {
uint32_t chunk_id = get_be32(chunk_lookup + 0); uint32_t chunk_id;
uint64_t chunk_offset = get_be64(chunk_lookup + 4); uint64_t chunk_offset;
int chunk_repeated = 0; int chunk_repeated = 0;
if (data + graph_size - chunk_lookup <
GRAPH_CHUNKLOOKUP_WIDTH) {
error(_("chunk lookup table entry missing; graph file may be incomplete"));
free(graph);
return NULL;
}
chunk_id = get_be32(chunk_lookup + 0);
chunk_offset = get_be64(chunk_lookup + 4);
chunk_lookup += GRAPH_CHUNKLOOKUP_WIDTH; chunk_lookup += GRAPH_CHUNKLOOKUP_WIDTH;
if (chunk_offset > graph_size - the_hash_algo->rawsz) { if (chunk_offset > graph_size - the_hash_algo->rawsz) {
error(_("improper chunk offset %08x%08x"), (uint32_t)(chunk_offset >> 32), error(_("improper chunk offset %08x%08x"), (uint32_t)(chunk_offset >> 32),
(uint32_t)chunk_offset); (uint32_t)chunk_offset);
goto cleanup_fail; free(graph);
return NULL;
} }
switch (chunk_id) { switch (chunk_id) {
@ -186,7 +219,8 @@ struct commit_graph *load_commit_graph_one(const char *graph_file)
if (chunk_repeated) { if (chunk_repeated) {
error(_("chunk id %08x appears multiple times"), chunk_id); error(_("chunk id %08x appears multiple times"), chunk_id);
goto cleanup_fail; free(graph);
return NULL;
} }
if (last_chunk_id == GRAPH_CHUNKID_OIDLOOKUP) if (last_chunk_id == GRAPH_CHUNKID_OIDLOOKUP)
@ -200,11 +234,6 @@ struct commit_graph *load_commit_graph_one(const char *graph_file)
} }
return graph; return graph;
cleanup_fail:
munmap(graph_map, graph_size);
close(fd);
exit(1);
} }
static void prepare_commit_graph_one(struct repository *r, const char *obj_dir) static void prepare_commit_graph_one(struct repository *r, const char *obj_dir)

View File

@ -54,6 +54,9 @@ struct commit_graph {
struct commit_graph *load_commit_graph_one(const char *graph_file); 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 * Return 1 if and only if the repository has a commit-graph
* file and generation numbers are computed in that file. * file and generation numbers are computed in that file.

16
fuzz-commit-graph.c Normal file
View File

@ -0,0 +1,16 @@
#include "commit-graph.h"
struct commit_graph *parse_commit_graph(void *graph_map, int fd,
size_t graph_size);
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
{
struct commit_graph *g;
g = parse_commit_graph((void *)data, -1, size);
free(g);
return 0;
}

View File

@ -366,9 +366,10 @@ GRAPH_OCTOPUS_DATA_OFFSET=$(($GRAPH_COMMIT_DATA_OFFSET + \
GRAPH_BYTE_OCTOPUS=$(($GRAPH_OCTOPUS_DATA_OFFSET + 4)) GRAPH_BYTE_OCTOPUS=$(($GRAPH_OCTOPUS_DATA_OFFSET + 4))
GRAPH_BYTE_FOOTER=$(($GRAPH_OCTOPUS_DATA_OFFSET + 4 * $NUM_OCTOPUS_EDGES)) 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 # 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 # and places the output in the file 'err'. Test 'err' for
# the given string. # the given string.
corrupt_graph_and_verify() { corrupt_graph_and_verify() {
@ -376,11 +377,15 @@ corrupt_graph_and_verify() {
data="${2:-\0}" data="${2:-\0}"
grepstr=$3 grepstr=$3
cd "$TRASH_DIRECTORY/full" && cd "$TRASH_DIRECTORY/full" &&
orig_size=$(wc -c < $objdir/info/commit-graph) &&
zero_pos=${4:-${orig_size}} &&
test_when_finished mv commit-graph-backup $objdir/info/commit-graph && test_when_finished mv commit-graph-backup $objdir/info/commit-graph &&
cp $objdir/info/commit-graph commit-graph-backup && cp $objdir/info/commit-graph commit-graph-backup &&
printf "$data" | dd of="$objdir/info/commit-graph" bs=1 seek="$pos" conv=notrunc && printf "$data" | dd of="$objdir/info/commit-graph" bs=1 seek="$pos" conv=notrunc &&
dd of="$objdir/info/commit-graph" bs=1 seek="$zero_pos" count=0 &&
dd if=/dev/zero of="$objdir/info/commit-graph" bs=1 seek="$zero_pos" count=$(($orig_size - $zero_pos)) &&
test_must_fail git commit-graph verify 2>test_err && test_must_fail git commit-graph verify 2>test_err &&
grep -v "^+" test_err >err grep -v "^+" test_err >err &&
test_i18ngrep "$grepstr" err test_i18ngrep "$grepstr" err
} }
@ -484,6 +489,11 @@ test_expect_success 'detect invalid checksum hash' '
"incorrect checksum" "incorrect checksum"
' '
test_expect_success 'detect incorrect chunk count' '
corrupt_graph_and_verify $GRAPH_BYTE_CHUNK_COUNT "\377" \
"chunk lookup table entry missing" $GRAPH_CHUNK_LOOKUP_OFFSET
'
test_expect_success 'git fsck (checks commit-graph)' ' test_expect_success 'git fsck (checks commit-graph)' '
cd "$TRASH_DIRECTORY/full" && cd "$TRASH_DIRECTORY/full" &&
git fsck && git fsck &&