Skip to content

Commit 9622610

Browse files
peffgitster
authored andcommitted
commit-graph: detect out-of-bounds extra-edges pointers
If an entry in a commit-graph file has more than 2 parents, the fixed-size parent fields instead point to an offset within an "extra edges" chunk. We blindly follow these, assuming that the chunk is present and sufficiently large; this can lead to an out-of-bounds read for a corrupt or malicious file. We can fix this by recording the size of the chunk and adding a bounds-check in fill_commit_in_graph(). There are a few tricky bits: 1. We'll switch from working with a pointer to an offset. This makes some corner cases just fall out naturally: a. If we did not find an EDGE chunk at all, our size will correctly be zero (so everything is "out of bounds"). b. Comparing "size / 4" lets us make sure we have at least 4 bytes to read, and we never compute a pointer more than one element past the end of the array (computing a larger pointer is probably OK in practice, but is technically undefined behavior). c. The current code casts to "uint32_t *". Replacing it with an offset avoids any comparison between different types of pointer (since the chunk is stored as "unsigned char *"). 2. This is the first case in which fill_commit_in_graph() may return anything but success. We need to make sure to roll back the "parsed" flag (and any parents we might have added before running out of buffer) so that the caller can cleanly fall back to loading the commit object itself. It's a little non-trivial to do this, and we might benefit from factoring it out. But we can wait on that until we actually see a second case where we return an error. As a bonus, this lets us drop the st_mult() call. Since we've already done a bounds check, we know there won't be any integer overflow (it would imply our buffer is larger than a size_t can hold). The included test does not actually segfault before this patch (though you could construct a case where it does). Instead, it reads garbage from the next chunk which results in it complaining about a bogus parent id. This is sufficient for our needs, though (we care that the fallback succeeds, and that stderr mentions the out-of-bounds read). Signed-off-by: Jeff King <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent b72df61 commit 9622610

File tree

3 files changed

+23
-6
lines changed

3 files changed

+23
-6
lines changed

commit-graph.c

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,8 @@ struct commit_graph *parse_commit_graph(struct repo_settings *s,
433433
read_chunk(cf, GRAPH_CHUNKID_OIDFANOUT, graph_read_oid_fanout, graph);
434434
read_chunk(cf, GRAPH_CHUNKID_OIDLOOKUP, graph_read_oid_lookup, graph);
435435
read_chunk(cf, GRAPH_CHUNKID_DATA, graph_read_commit_data, graph);
436-
pair_chunk_unsafe(cf, GRAPH_CHUNKID_EXTRAEDGES, &graph->chunk_extra_edges);
436+
pair_chunk(cf, GRAPH_CHUNKID_EXTRAEDGES, &graph->chunk_extra_edges,
437+
&graph->chunk_extra_edges_size);
437438
pair_chunk_unsafe(cf, GRAPH_CHUNKID_BASE, &graph->chunk_base_graphs);
438439

439440
if (s->commit_graph_generation_version >= 2) {
@@ -899,7 +900,7 @@ static int fill_commit_in_graph(struct repository *r,
899900
struct commit_graph *g, uint32_t pos)
900901
{
901902
uint32_t edge_value;
902-
uint32_t *parent_data_ptr;
903+
uint32_t parent_data_pos;
903904
struct commit_list **pptr;
904905
const unsigned char *commit_data;
905906
uint32_t lex_index;
@@ -931,14 +932,21 @@ static int fill_commit_in_graph(struct repository *r,
931932
return 1;
932933
}
933934

934-
parent_data_ptr = (uint32_t*)(g->chunk_extra_edges +
935-
st_mult(4, edge_value & GRAPH_EDGE_LAST_MASK));
935+
parent_data_pos = edge_value & GRAPH_EDGE_LAST_MASK;
936936
do {
937-
edge_value = get_be32(parent_data_ptr);
937+
if (g->chunk_extra_edges_size / sizeof(uint32_t) <= parent_data_pos) {
938+
error("commit-graph extra-edges pointer out of bounds");
939+
free_commit_list(item->parents);
940+
item->parents = NULL;
941+
item->object.parsed = 0;
942+
return 0;
943+
}
944+
edge_value = get_be32(g->chunk_extra_edges +
945+
sizeof(uint32_t) * parent_data_pos);
938946
pptr = insert_parent_or_die(r, g,
939947
edge_value & GRAPH_EDGE_LAST_MASK,
940948
pptr);
941-
parent_data_ptr++;
949+
parent_data_pos++;
942950
} while (!(edge_value & GRAPH_LAST_EDGE));
943951

944952
return 1;

commit-graph.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ struct commit_graph {
9595
const unsigned char *chunk_generation_data;
9696
const unsigned char *chunk_generation_data_overflow;
9797
const unsigned char *chunk_extra_edges;
98+
size_t chunk_extra_edges_size;
9899
const unsigned char *chunk_base_graphs;
99100
const unsigned char *chunk_bloom_indexes;
100101
const unsigned char *chunk_bloom_data;

t/t5318-commit-graph.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -879,4 +879,12 @@ test_expect_success 'reader notices too-small commit data chunk' '
879879
test_cmp expect.err err
880880
'
881881

882+
test_expect_success 'reader notices out-of-bounds extra edge' '
883+
check_corrupt_chunk EDGE clear &&
884+
cat >expect.err <<-\EOF &&
885+
error: commit-graph extra-edges pointer out of bounds
886+
EOF
887+
test_cmp expect.err err
888+
'
889+
882890
test_done

0 commit comments

Comments
 (0)