Skip to content

Commit 920f400

Browse files
peffgitster
authored andcommitted
commit-graph: check bounds when accessing BDAT chunk
When loading Bloom filters from a commit-graph file, we use the offset values in the BIDX chunk to index into the memory mapped for the BDAT chunk. But since we don't record how big the BDAT chunk is, we just trust that the BIDX offsets won't cause us to read outside of the chunk memory. A corrupted or malicious commit-graph file will cause us to segfault (in practice this isn't a very interesting attack, since commit-graph files are local-only, and the worst case is an out-of-bounds read). We can't fix this by checking the chunk size during parsing, since the data in the BDAT chunk doesn't have a fixed size (that's why we need the BIDX in the first place). So we'll fix it in two parts: 1. Record the BDAT chunk size during parsing, and then later check that the BIDX offsets we look up are within bounds. 2. Because the offsets are relative to the end of the BDAT header, we must also make sure that the BDAT chunk is at least as large as the expected header size. Otherwise, we overflow when trying to move past the header, even for an offset of "0". We can check this early, during the parsing stage. The error messages are rather verbose, but since this is not something you'd expect to see outside of severe bugs or corruption, it makes sense to err on the side of too many details. Sadly we can't mention the filename during the chunk-parsing stage, as we haven't set g->filename at this point, nor passed it down through the stack. Signed-off-by: Jeff King <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent ee6a792 commit 920f400

File tree

4 files changed

+63
-0
lines changed

4 files changed

+63
-0
lines changed

bloom.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,26 @@ static inline unsigned char get_bitmask(uint32_t pos)
2929
return ((unsigned char)1) << (pos & (BITS_PER_WORD - 1));
3030
}
3131

32+
static int check_bloom_offset(struct commit_graph *g, uint32_t pos,
33+
uint32_t offset)
34+
{
35+
/*
36+
* Note that we allow offsets equal to the data size, which would set
37+
* our pointers at one past the end of the chunk memory. This is
38+
* necessary because the on-disk index points to the end of the
39+
* entries (so we can compute size by comparing adjacent ones). And
40+
* naturally the final entry's end is one-past-the-end of the chunk.
41+
*/
42+
if (offset <= g->chunk_bloom_data_size - BLOOMDATA_CHUNK_HEADER_SIZE)
43+
return 0;
44+
45+
warning("ignoring out-of-range offset (%"PRIuMAX") for changed-path"
46+
" filter at pos %"PRIuMAX" of %s (chunk size: %"PRIuMAX")",
47+
(uintmax_t)offset, (uintmax_t)pos,
48+
g->filename, (uintmax_t)g->chunk_bloom_data_size);
49+
return -1;
50+
}
51+
3252
static int load_bloom_filter_from_graph(struct commit_graph *g,
3353
struct bloom_filter *filter,
3454
uint32_t graph_pos)
@@ -51,6 +71,10 @@ static int load_bloom_filter_from_graph(struct commit_graph *g,
5171
else
5272
start_index = 0;
5373

74+
if (check_bloom_offset(g, lex_pos, end_index) < 0 ||
75+
check_bloom_offset(g, lex_pos - 1, start_index) < 0)
76+
return 0;
77+
5478
filter->len = end_index - start_index;
5579
filter->data = (unsigned char *)(g->chunk_bloom_data +
5680
sizeof(unsigned char) * start_index +

commit-graph.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,17 @@ static int graph_read_bloom_data(const unsigned char *chunk_start,
365365
{
366366
struct commit_graph *g = data;
367367
uint32_t hash_version;
368+
369+
if (chunk_size < BLOOMDATA_CHUNK_HEADER_SIZE) {
370+
warning("ignoring too-small changed-path chunk"
371+
" (%"PRIuMAX" < %"PRIuMAX") in commit-graph file",
372+
(uintmax_t)chunk_size,
373+
(uintmax_t)BLOOMDATA_CHUNK_HEADER_SIZE);
374+
return -1;
375+
}
376+
368377
g->chunk_bloom_data = chunk_start;
378+
g->chunk_bloom_data_size = chunk_size;
369379
hash_version = get_be32(chunk_start);
370380

371381
if (hash_version != 1)

commit-graph.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ struct commit_graph {
101101
size_t chunk_base_graphs_size;
102102
const unsigned char *chunk_bloom_indexes;
103103
const unsigned char *chunk_bloom_data;
104+
size_t chunk_bloom_data_size;
104105

105106
struct topo_level_slab *topo_levels;
106107
struct bloom_filter_settings *bloom_filter_settings;

t/t4216-log-bloom.sh

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
55
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
66

77
. ./test-lib.sh
8+
. "$TEST_DIRECTORY"/lib-chunk.sh
89

910
GIT_TEST_COMMIT_GRAPH=0
1011
GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS=0
@@ -404,4 +405,31 @@ test_expect_success 'Bloom generation backfills empty commits' '
404405
)
405406
'
406407

408+
corrupt_graph () {
409+
graph=.git/objects/info/commit-graph &&
410+
test_when_finished "rm -rf $graph" &&
411+
git commit-graph write --reachable --changed-paths &&
412+
corrupt_chunk_file $graph "$@"
413+
}
414+
415+
check_corrupt_graph () {
416+
corrupt_graph "$@" &&
417+
git -c core.commitGraph=false log -- A/B/file2 >expect.out &&
418+
git -c core.commitGraph=true log -- A/B/file2 >out 2>err &&
419+
test_cmp expect.out out
420+
}
421+
422+
test_expect_success 'Bloom reader notices too-small data chunk' '
423+
check_corrupt_graph BDAT clear 00000000 &&
424+
echo "warning: ignoring too-small changed-path chunk" \
425+
"(4 < 12) in commit-graph file" >expect.err &&
426+
test_cmp expect.err err
427+
'
428+
429+
test_expect_success 'Bloom reader notices out-of-bounds filter offsets' '
430+
check_corrupt_graph BIDX 12 FFFFFFFF &&
431+
# use grep to avoid depending on exact chunk size
432+
grep "warning: ignoring out-of-range offset (4294967295) for changed-path filter at pos 3 of .git/objects/info/commit-graph" err
433+
'
434+
407435
test_done

0 commit comments

Comments
 (0)