Skip to content

Commit f86f769

Browse files
peffgitster
authored andcommitted
compute pack .idx byte offsets using size_t
A pack and its matching .idx file are limited to 2^32 objects, because the pack format contains a 32-bit field to store the number of objects. Hence we use uint32_t in the code. But the byte count of even a .idx file can be much larger than that, because it stores at least a hash and an offset for each object. So using SHA-1, a v2 .idx file will cross the 4GB boundary at 153,391,650 objects. This confuses load_idx(), which computes the minimum size like this: unsigned long min_size = 8 + 4*256 + nr*(hashsz + 4 + 4) + hashsz + hashsz; Even though min_size will be big enough on most 64-bit platforms, the actual arithmetic is done as a uint32_t, resulting in a truncation. We actually exceed that min_size, but then we do: unsigned long max_size = min_size; if (nr) max_size += (nr - 1)*8; to account for the variable-sized table. That computation doesn't overflow quite so low, but with the truncation for min_size, we end up with a max_size that is much smaller than our actual size. So we complain that the idx is invalid, and can't find any of its objects. We can fix this case by casting "nr" to a size_t, which will do the multiplication in 64-bits (assuming you're on a 64-bit platform; this will never work on a 32-bit system since we couldn't map the whole .idx anyway). Likewise, we don't have to worry about further additions, because adding a smaller number to a size_t will convert the other side to a size_t. A few notes: - obviously we could just declare "nr" as a size_t in the first place (and likewise, packed_git.num_objects). But it's conceptually a uint32_t because of the on-disk format, and we correctly treat it that way in other contexts that don't need to compute byte offsets (e.g., iterating over the set of objects should and generally does use a uint32_t). Switching to size_t would make all of those other cases look wrong. - it could be argued that the proper type is off_t to represent the file offset. But in practice the .idx file must fit within memory, because we mmap the whole thing. And the rest of the code (including the idx_size variable we're comparing against) uses size_t. - we'll add the same cast to the max_size arithmetic line. Even though we're adding to a larger type, which will convert our result, the multiplication is still done as a 32-bit value and can itself overflow. I didn't check this with my test case, since it would need an even larger pack (~530M objects), but looking at compiler output shows that it works this way. The standard should agree, but I couldn't find anything explicit in 6.3.1.8 ("usual arithmetic conversions"). The case in load_idx() was the most immediate one that I was able to trigger. After fixing it, looking up actual objects (including the very last one in sha1 order) works in a test repo with 153,725,110 objects. That's because bsearch_hash() works with uint32_t entry indices, and the actual byte access: int cmp = hashcmp(table + mi * stride, sha1); is done with "stride" as a size_t, causing the uint32_t "mi" to be promoted to a size_t. This is the way most code will access the index data. However, I audited all of the other byte-wise accesses of packed_git.index_data, and many of the others are suspect (they are similar to the max_size one, where we are adding to a properly sized offset or directly to a pointer, but the multiplication in the sub-expression can overflow). I didn't trigger any of these in practice, but I believe they're potential problems, and certainly adding in the cast is not going to hurt anything here. Signed-off-by: Jeff King <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 898f807 commit f86f769

File tree

4 files changed

+9
-9
lines changed

4 files changed

+9
-9
lines changed

builtin/index-pack.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1597,7 +1597,7 @@ static void read_v2_anomalous_offsets(struct packed_git *p,
15971597

15981598
/* The address of the 4-byte offset table */
15991599
idx1 = (((const uint32_t *)((const uint8_t *)p->index_data + p->crc_offset))
1600-
+ p->num_objects /* CRC32 table */
1600+
+ (size_t)p->num_objects /* CRC32 table */
16011601
);
16021602

16031603
/* The address of the 8-byte offset table */

pack-check.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ int check_pack_crc(struct packed_git *p, struct pack_window **w_curs,
3939
} while (len);
4040

4141
index_crc = p->index_data;
42-
index_crc += 2 + 256 + p->num_objects * (the_hash_algo->rawsz/4) + nr;
42+
index_crc += 2 + 256 + (size_t)p->num_objects * (the_hash_algo->rawsz/4) + nr;
4343

4444
return data_crc != ntohl(*index_crc);
4545
}

pack-revindex.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ static void create_pack_revindex(struct packed_git *p)
130130

131131
if (p->index_version > 1) {
132132
const uint32_t *off_32 =
133-
(uint32_t *)(index + 8 + p->num_objects * (hashsz + 4));
133+
(uint32_t *)(index + 8 + (size_t)p->num_objects * (hashsz + 4));
134134
const uint32_t *off_64 = off_32 + p->num_objects;
135135
for (i = 0; i < num_ent; i++) {
136136
const uint32_t off = ntohl(*off_32++);

packfile.c

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ int load_idx(const char *path, const unsigned int hashsz, void *idx_map,
148148
* - hash of the packfile
149149
* - file checksum
150150
*/
151-
if (idx_size != 4 * 256 + nr * (hashsz + 4) + hashsz + hashsz)
151+
if (idx_size != 4 * 256 + (size_t)nr * (hashsz + 4) + hashsz + hashsz)
152152
return error("wrong index v1 file size in %s", path);
153153
} else if (version == 2) {
154154
/*
@@ -164,10 +164,10 @@ int load_idx(const char *path, const unsigned int hashsz, void *idx_map,
164164
* variable sized table containing 8-byte entries
165165
* for offsets larger than 2^31.
166166
*/
167-
unsigned long min_size = 8 + 4*256 + nr*(hashsz + 4 + 4) + hashsz + hashsz;
167+
unsigned long min_size = 8 + 4*256 + (size_t)nr*(hashsz + 4 + 4) + hashsz + hashsz;
168168
unsigned long max_size = min_size;
169169
if (nr)
170-
max_size += (nr - 1)*8;
170+
max_size += ((size_t)nr - 1)*8;
171171
if (idx_size < min_size || idx_size > max_size)
172172
return error("wrong index v2 file size in %s", path);
173173
if (idx_size != min_size &&
@@ -1933,14 +1933,14 @@ off_t nth_packed_object_offset(const struct packed_git *p, uint32_t n)
19331933
const unsigned int hashsz = the_hash_algo->rawsz;
19341934
index += 4 * 256;
19351935
if (p->index_version == 1) {
1936-
return ntohl(*((uint32_t *)(index + (hashsz + 4) * n)));
1936+
return ntohl(*((uint32_t *)(index + (hashsz + 4) * (size_t)n)));
19371937
} else {
19381938
uint32_t off;
1939-
index += 8 + p->num_objects * (hashsz + 4);
1939+
index += 8 + (size_t)p->num_objects * (hashsz + 4);
19401940
off = ntohl(*((uint32_t *)(index + 4 * n)));
19411941
if (!(off & 0x80000000))
19421942
return off;
1943-
index += p->num_objects * 4 + (off & 0x7fffffff) * 8;
1943+
index += (size_t)p->num_objects * 4 + (off & 0x7fffffff) * 8;
19441944
check_pack_index_ptr(p, index);
19451945
return get_be64(index);
19461946
}

0 commit comments

Comments
 (0)