pack-bitmap.c: avoid uninitialized `pack_int_id` during reuse

When performing multi-pack reuse, reuse_partial_packfile_from_bitmap() is responsible for generating an array of bitmapped_pack structs from which to perform reuse. In the multi-pack case, we loop over the MIDXs packs and copy the result of calling `nth_bitmapped_pack()` to construct the list of reusable paths. But we may also want to do pack-reuse over a single pack, either because we only had one pack to perform reuse over (in the case of single-pack bitmaps), or because we explicitly asked to do single pack reuse even with a MIDX[^1]. When this is the case, the array we generate of reusable packs contains only a single element, which is either (a) the pack attached to the single-pack bitmap, or (b) the MIDX's preferred pack. In 795006fff4 (pack-bitmap: gracefully handle missing BTMP chunks, 2024-04-15), we refactored the reuse_partial_packfile_from_bitmap() function and stopped assigning the pack_int_id field when reusing only the MIDX's preferred pack. This results in an uninitialized read down in try_partial_reuse() like so: ==7474==WARNING: MemorySanitizer: use-of-uninitialized-value #0 0x55c5cd191dde in try_partial_reuse pack-bitmap.c:1887:8 #1 0x55c5cd191dde in reuse_partial_packfile_from_bitmap_1 pack-bitmap.c:2001:8 #2 0x55c5cd191dde in reuse_partial_packfile_from_bitmap pack-bitmap.c:2105:3 #3 0x55c5cce0bd0e in get_object_list_from_bitmap builtin/pack-objects.c:4043:3 #4 0x55c5cce0bd0e in get_object_list builtin/pack-objects.c:4156:27 #5 0x55c5cce0bd0e in cmd_pack_objects builtin/pack-objects.c:4596:3 #6 0x55c5ccc8fac8 in run_builtin git.c:474:11 which happens when try_partial_reuse() tries to call midx_pair_to_pack_pos() when it tries to reject cross-pack deltas. Avoid the uninitialized read by ensuring that the pack_int_id field is set in the single-pack reuse case by setting it to either the MIDX preferred pack's pack_int_id, or '-1', in the case of single-pack bitmaps. In the latter case, we never read the pack_int_id field, so the choice of '-1' is intentional as a "garbage in, garbage out" measure. Guard against further regressions in this area by adding a test which ensures that we do not throw out deltas from the preferred pack as "cross-pack" due to an uninitialized pack_int_id. [^1]: This can happen for a couple of reasons, either because the repository is configured with 'pack.allowPackReuse=(true|single)', or because the MIDX was generated prior to the introduction of the BTMP chunk, which contains information necessary to perform multi-pack reuse. Reported-by: Kyle Lippincott <spectral@google.com> Signed-off-by: Taylor Blau <me@ttaylorr.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Taylor Blau committed Jun 11, 2024 at 13:28 UTC ed4a1d6ae10e2c86ae8e05f485c3c6ad7e6da077
2 files changed +39
pack-bitmap.c
+13
@@ -2073,6 +2073,7 @@ void reuse_partial_packfile_from_bitmap(struct bitmap_index *bitmap_git,
2073 QSORT(packs, packs_nr, bitmapped_pack_cmp);
2074 } else {
2075 struct packed_git *pack;
2076 + uint32_t pack_int_id;
2077
2078 if (bitmap_is_midx(bitmap_git)) {
2079 uint32_t preferred_pack_pos;
@@ -2083,12 +2084,24 @@ void reuse_partial_packfile_from_bitmap(struct bitmap_index *bitmap_git,
2084 }
2085
2086 pack = bitmap_git->midx->packs[preferred_pack_pos];
2087 + pack_int_id = preferred_pack_pos;
2088 } else {
2089 pack = bitmap_git->pack;
2090 + /*
2091 + * Any value for 'pack_int_id' will do here. When we
2092 + * process the pack via try_partial_reuse(), we won't
2093 + * use the `pack_int_id` field since we have a non-MIDX
2094 + * bitmap.
2095 + *
2096 + * Use '-1' as a sentinel value to make it clear
2097 + * that we do not expect to read this field.
2098 + */
2099 + pack_int_id = -1;
2100 }
2101
2102 ALLOC_GROW(packs, packs_nr + 1, packs_alloc);
2103 packs[packs_nr].p = pack;
2104 + packs[packs_nr].pack_int_id = pack_int_id;
2105 packs[packs_nr].bitmap_nr = pack->num_objects;
2106 packs[packs_nr].bitmap_pos = 0;
2107
t/t5332-multi-pack-reuse.sh
+26
@@ -204,4 +204,30 @@ test_expect_success 'omit delta from uninteresting base (cross pack)' '
204 test_pack_objects_reused_all $(($objects_nr - 1)) $packs_nr
205 '
206
207 +test_expect_success 'non-omitted delta in MIDX preferred pack' '
208 + test_config pack.allowPackReuse single &&
209 +
210 + cat >p1.objects <<-EOF &&
211 + $(git rev-parse $base)
212 + ^$(git rev-parse $delta^)
213 + EOF
214 + cat >p2.objects <<-EOF &&
215 + $(git rev-parse F)
216 + EOF
217 +
218 + p1="$(git pack-objects --revs $packdir/pack <p1.objects)" &&
219 + p2="$(git pack-objects --revs $packdir/pack <p2.objects)" &&
220 +
221 + cat >in <<-EOF &&
222 + pack-$p1.idx
223 + pack-$p2.idx
224 + EOF
225 + git multi-pack-index write --bitmap --stdin-packs \
226 + --preferred-pack=pack-$p1.pack <in &&
227 +
228 + git show-index <$packdir/pack-$p1.idx >expect &&
229 +
230 + test_pack_objects_reused_all $(wc -l <expect) 1
231 +'
232 +
233 test_done