Skip to content

Commit c9b9fef

Browse files
peffgitster
authored andcommitted
midx: enforce chunk alignment on reading
The midx reader assumes chunks are aligned to a 4-byte boundary: we treat the fanout chunk as an array of uint32_t, indexing it to feed the results to ntohl(). Without aligning the chunks, we may violate the CPU's alignment constraints. Though many platforms allow this, some do not. And certanily UBSan will complain, since it is undefined behavior. Even though most chunks are naturally 4-byte-aligned (because they are storing uint32_t or larger types), PNAM is not. It stores NUL-terminated pack names, so you can have a valid chunk with any length. The writing side handles this by 4-byte-aligning the chunk, introducing a few extra NULs as necessary. But since we don't check this on the reading side, we may end up with a misaligned fanout and trigger the undefined behavior. We have two options here: 1. Swap out ntohl(fanout[i]) for get_be32(fanout+i) everywhere. The latter handles alignment itself. It's possible that it's slightly slower (though in practice I'm not sure how true that is, especially for these code paths which then go on to do a binary search). 2. Enforce the alignment when reading the chunks. This is easy to do, since the table-of-contents reader can check it in one spot. I went with the second option here, just because it places less burden on maintenance going forward (it is OK to continue using ntohl), and we know it can't have any performance impact on the actual reads. The commit-graph code uses the same chunk API. It's usually also 4-byte aligned, but some chunks are not (like Bloom filter BDAT chunks). So we'll pass "1" here to allow any alignment. It doesn't suffer from the same problem as midx with its fanout because the fanout chunk is always the first (and the rest of the format dictates that the first chunk will start aligned). The new test shows the effect on a midx with a misaligned PNAM chunk. Note that the midx-reading code treats chunk-toc errors as soft, falling back to the non-midx path rather than calling die(), as we do for other parsing errors. Arguably we should make all of these behave the same, but that's out of scope for this patch. For now the test just expects the fallback behavior. Signed-off-by: Jeff King <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 72a9a08 commit c9b9fef

File tree

5 files changed

+26
-4
lines changed

5 files changed

+26
-4
lines changed

chunk-format.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@ int read_table_of_contents(struct chunkfile *cf,
102102
const unsigned char *mfile,
103103
size_t mfile_size,
104104
uint64_t toc_offset,
105-
int toc_length)
105+
int toc_length,
106+
unsigned expected_alignment)
106107
{
107108
int i;
108109
uint32_t chunk_id;
@@ -120,6 +121,11 @@ int read_table_of_contents(struct chunkfile *cf,
120121
error(_("terminating chunk id appears earlier than expected"));
121122
return 1;
122123
}
124+
if (chunk_offset % expected_alignment != 0) {
125+
error(_("chunk id %"PRIx32" not %d-byte aligned"),
126+
chunk_id, expected_alignment);
127+
return 1;
128+
}
123129

124130
table_of_contents += CHUNK_TOC_ENTRY_SIZE;
125131
next_chunk_offset = get_be64(table_of_contents + 4);

chunk-format.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ int read_table_of_contents(struct chunkfile *cf,
3636
const unsigned char *mfile,
3737
size_t mfile_size,
3838
uint64_t toc_offset,
39-
int toc_length);
39+
int toc_length,
40+
unsigned expected_alignment);
4041

4142
#define CHUNK_NOT_FOUND (-2)
4243

commit-graph.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@ struct commit_graph *parse_commit_graph(struct repo_settings *s,
417417
cf = init_chunkfile(NULL);
418418

419419
if (read_table_of_contents(cf, graph->data, graph_size,
420-
GRAPH_HEADER_SIZE, graph->num_chunks))
420+
GRAPH_HEADER_SIZE, graph->num_chunks, 1))
421421
goto free_and_return;
422422

423423
read_chunk(cf, GRAPH_CHUNKID_OIDFANOUT, graph_read_oid_fanout, graph);

midx.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,8 @@ struct multi_pack_index *load_multi_pack_index(const char *object_dir, int local
154154
cf = init_chunkfile(NULL);
155155

156156
if (read_table_of_contents(cf, m->data, midx_size,
157-
MIDX_HEADER_SIZE, m->num_chunks))
157+
MIDX_HEADER_SIZE, m->num_chunks,
158+
MIDX_CHUNK_ALIGNMENT))
158159
goto cleanup_fail;
159160

160161
if (pair_chunk(cf, MIDX_CHUNKID_PACKNAMES, &m->chunk_pack_names, &m->chunk_pack_names_len))

t/t5319-multi-pack-index.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1094,4 +1094,18 @@ test_expect_success 'reader notices too-small pack names chunk' '
10941094
test_cmp expect err
10951095
'
10961096

1097+
test_expect_success 'reader handles unaligned chunks' '
1098+
# A 9-byte PNAM means all of the subsequent chunks
1099+
# will no longer be 4-byte aligned, but it is still
1100+
# a valid one-pack chunk on its own (it is "foo.pack\0").
1101+
corrupt_chunk PNAM clear 666f6f2e7061636b00 &&
1102+
git -c core.multipackindex=false log >expect.out &&
1103+
git -c core.multipackindex=true log >out 2>err &&
1104+
test_cmp expect.out out &&
1105+
cat >expect.err <<-\EOF &&
1106+
error: chunk id 4f494446 not 4-byte aligned
1107+
EOF
1108+
test_cmp expect.err err
1109+
'
1110+
10971111
test_done

0 commit comments

Comments
 (0)