mirror of
https://github.com/git/git.git
synced 2024-10-28 12:59:41 +01:00
c9b9fefc13
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 <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
220 lines
4.9 KiB
C
220 lines
4.9 KiB
C
#include "git-compat-util.h"
|
|
#include "chunk-format.h"
|
|
#include "csum-file.h"
|
|
#include "gettext.h"
|
|
#include "hash.h"
|
|
#include "trace2.h"
|
|
|
|
/*
|
|
* When writing a chunk-based file format, collect the chunks in
|
|
* an array of chunk_info structs. The size stores the _expected_
|
|
* amount of data that will be written by write_fn.
|
|
*/
|
|
struct chunk_info {
|
|
uint32_t id;
|
|
uint64_t size;
|
|
chunk_write_fn write_fn;
|
|
|
|
const void *start;
|
|
};
|
|
|
|
struct chunkfile {
|
|
struct hashfile *f;
|
|
|
|
struct chunk_info *chunks;
|
|
size_t chunks_nr;
|
|
size_t chunks_alloc;
|
|
};
|
|
|
|
struct chunkfile *init_chunkfile(struct hashfile *f)
|
|
{
|
|
struct chunkfile *cf = xcalloc(1, sizeof(*cf));
|
|
cf->f = f;
|
|
return cf;
|
|
}
|
|
|
|
void free_chunkfile(struct chunkfile *cf)
|
|
{
|
|
if (!cf)
|
|
return;
|
|
free(cf->chunks);
|
|
free(cf);
|
|
}
|
|
|
|
int get_num_chunks(struct chunkfile *cf)
|
|
{
|
|
return cf->chunks_nr;
|
|
}
|
|
|
|
void add_chunk(struct chunkfile *cf,
|
|
uint32_t id,
|
|
size_t size,
|
|
chunk_write_fn fn)
|
|
{
|
|
ALLOC_GROW(cf->chunks, cf->chunks_nr + 1, cf->chunks_alloc);
|
|
|
|
cf->chunks[cf->chunks_nr].id = id;
|
|
cf->chunks[cf->chunks_nr].write_fn = fn;
|
|
cf->chunks[cf->chunks_nr].size = size;
|
|
cf->chunks_nr++;
|
|
}
|
|
|
|
int write_chunkfile(struct chunkfile *cf, void *data)
|
|
{
|
|
int i, result = 0;
|
|
uint64_t cur_offset = hashfile_total(cf->f);
|
|
|
|
trace2_region_enter("chunkfile", "write", the_repository);
|
|
|
|
/* Add the table of contents to the current offset */
|
|
cur_offset += (cf->chunks_nr + 1) * CHUNK_TOC_ENTRY_SIZE;
|
|
|
|
for (i = 0; i < cf->chunks_nr; i++) {
|
|
hashwrite_be32(cf->f, cf->chunks[i].id);
|
|
hashwrite_be64(cf->f, cur_offset);
|
|
|
|
cur_offset += cf->chunks[i].size;
|
|
}
|
|
|
|
/* Trailing entry marks the end of the chunks */
|
|
hashwrite_be32(cf->f, 0);
|
|
hashwrite_be64(cf->f, cur_offset);
|
|
|
|
for (i = 0; i < cf->chunks_nr; i++) {
|
|
off_t start_offset = hashfile_total(cf->f);
|
|
result = cf->chunks[i].write_fn(cf->f, data);
|
|
|
|
if (result)
|
|
goto cleanup;
|
|
|
|
if (hashfile_total(cf->f) - start_offset != cf->chunks[i].size)
|
|
BUG("expected to write %"PRId64" bytes to chunk %"PRIx32", but wrote %"PRId64" instead",
|
|
cf->chunks[i].size, cf->chunks[i].id,
|
|
hashfile_total(cf->f) - start_offset);
|
|
}
|
|
|
|
cleanup:
|
|
trace2_region_leave("chunkfile", "write", the_repository);
|
|
return result;
|
|
}
|
|
|
|
int read_table_of_contents(struct chunkfile *cf,
|
|
const unsigned char *mfile,
|
|
size_t mfile_size,
|
|
uint64_t toc_offset,
|
|
int toc_length,
|
|
unsigned expected_alignment)
|
|
{
|
|
int i;
|
|
uint32_t chunk_id;
|
|
const unsigned char *table_of_contents = mfile + toc_offset;
|
|
|
|
ALLOC_GROW(cf->chunks, toc_length, cf->chunks_alloc);
|
|
|
|
while (toc_length--) {
|
|
uint64_t chunk_offset, next_chunk_offset;
|
|
|
|
chunk_id = get_be32(table_of_contents);
|
|
chunk_offset = get_be64(table_of_contents + 4);
|
|
|
|
if (!chunk_id) {
|
|
error(_("terminating chunk id appears earlier than expected"));
|
|
return 1;
|
|
}
|
|
if (chunk_offset % expected_alignment != 0) {
|
|
error(_("chunk id %"PRIx32" not %d-byte aligned"),
|
|
chunk_id, expected_alignment);
|
|
return 1;
|
|
}
|
|
|
|
table_of_contents += CHUNK_TOC_ENTRY_SIZE;
|
|
next_chunk_offset = get_be64(table_of_contents + 4);
|
|
|
|
if (next_chunk_offset < chunk_offset ||
|
|
next_chunk_offset > mfile_size - the_hash_algo->rawsz) {
|
|
error(_("improper chunk offset(s) %"PRIx64" and %"PRIx64""),
|
|
chunk_offset, next_chunk_offset);
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < cf->chunks_nr; i++) {
|
|
if (cf->chunks[i].id == chunk_id) {
|
|
error(_("duplicate chunk ID %"PRIx32" found"),
|
|
chunk_id);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
cf->chunks[cf->chunks_nr].id = chunk_id;
|
|
cf->chunks[cf->chunks_nr].start = mfile + chunk_offset;
|
|
cf->chunks[cf->chunks_nr].size = next_chunk_offset - chunk_offset;
|
|
cf->chunks_nr++;
|
|
}
|
|
|
|
chunk_id = get_be32(table_of_contents);
|
|
if (chunk_id) {
|
|
error(_("final chunk has non-zero id %"PRIx32""), chunk_id);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct pair_chunk_data {
|
|
const unsigned char **p;
|
|
size_t *size;
|
|
};
|
|
|
|
static int pair_chunk_fn(const unsigned char *chunk_start,
|
|
size_t chunk_size,
|
|
void *data)
|
|
{
|
|
struct pair_chunk_data *pcd = data;
|
|
*pcd->p = chunk_start;
|
|
*pcd->size = chunk_size;
|
|
return 0;
|
|
}
|
|
|
|
int pair_chunk(struct chunkfile *cf,
|
|
uint32_t chunk_id,
|
|
const unsigned char **p,
|
|
size_t *size)
|
|
{
|
|
struct pair_chunk_data pcd = { .p = p, .size = size };
|
|
return read_chunk(cf, chunk_id, pair_chunk_fn, &pcd);
|
|
}
|
|
|
|
int pair_chunk_unsafe(struct chunkfile *cf,
|
|
uint32_t chunk_id,
|
|
const unsigned char **p)
|
|
{
|
|
size_t dummy;
|
|
return pair_chunk(cf, chunk_id, p, &dummy);
|
|
}
|
|
|
|
int read_chunk(struct chunkfile *cf,
|
|
uint32_t chunk_id,
|
|
chunk_read_fn fn,
|
|
void *data)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < cf->chunks_nr; i++) {
|
|
if (cf->chunks[i].id == chunk_id)
|
|
return fn(cf->chunks[i].start, cf->chunks[i].size, data);
|
|
}
|
|
|
|
return CHUNK_NOT_FOUND;
|
|
}
|
|
|
|
uint8_t oid_version(const struct git_hash_algo *algop)
|
|
{
|
|
switch (hash_algo_by_ptr(algop)) {
|
|
case GIT_HASH_SHA1:
|
|
return 1;
|
|
case GIT_HASH_SHA256:
|
|
return 2;
|
|
default:
|
|
die(_("invalid hash version"));
|
|
}
|
|
}
|