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-graphmaint
						commit
						19a504d92b
					
				|  | @ -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. | ||||||
|  |  | ||||||
|  | @ -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 && | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	 Junio C Hamano
						Junio C Hamano