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:
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
|||||||
|
/fuzz-commit-graph
|
||||||
/fuzz_corpora
|
/fuzz_corpora
|
||||||
/fuzz-pack-headers
|
/fuzz-pack-headers
|
||||||
/fuzz-pack-idx
|
/fuzz-pack-idx
|
||||||
|
3
Makefile
3
Makefile
@ -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
|
||||||
#
|
#
|
||||||
|
@ -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)
|
||||||
|
@ -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
16
fuzz-commit-graph.c
Normal 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;
|
||||||
|
}
|
@ -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 &&
|
||||||
|
Reference in New Issue
Block a user