Skip to content

Commit c3514b0

Browse files
committed
Fix unpack double free
If an element has been cached, but then the call to packfile_unpack_compressed() fails, the very next thing that happens is that its data is freed and then the element is not removed from the cache, which frees the data again. This change sets obj->data to NULL to avoid the double-free. It also stops trying to resolve deltas after two continuous failed rounds of resolution, and adds a test for this.
1 parent 9f7ad3c commit c3514b0

File tree

3 files changed

+68
-8
lines changed

3 files changed

+68
-8
lines changed

src/indexer.c

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -844,6 +844,7 @@ static int fix_thin_pack(git_indexer *idx, git_transfer_progress *stats)
844844
static int resolve_deltas(git_indexer *idx, git_transfer_progress *stats)
845845
{
846846
unsigned int i;
847+
int error;
847848
struct delta_info *delta;
848849
int progressed = 0, non_null = 0, progress_cb_result;
849850

@@ -858,8 +859,13 @@ static int resolve_deltas(git_indexer *idx, git_transfer_progress *stats)
858859

859860
non_null = 1;
860861
idx->off = delta->delta_off;
861-
if (git_packfile_unpack(&obj, idx->pack, &idx->off) < 0)
862-
continue;
862+
if ((error = git_packfile_unpack(&obj, idx->pack, &idx->off)) < 0) {
863+
if (error == GIT_PASSTHROUGH) {
864+
/* We have not seen the base object, we'll try again later. */
865+
continue;
866+
}
867+
return -1;
868+
}
863869

864870
if (hash_and_save(idx, &obj, delta->delta_off) < 0)
865871
continue;

src/pack.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -716,8 +716,11 @@ int git_packfile_unpack(
716716
error = packfile_unpack_compressed(&delta, p, &w_curs, &curpos, elem->size, elem->type);
717717
git_mwindow_close(&w_curs);
718718

719-
if (error < 0)
719+
if (error < 0) {
720+
/* We have transferred ownership of the data to the cache. */
721+
obj->data = NULL;
720722
break;
723+
}
721724

722725
/* the current object becomes the new base, on which we apply the delta */
723726
base = *obj;

tests/pack/indexer.c

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,29 @@ static const unsigned char thin_pack[] = {
4040
};
4141
static const unsigned int thin_pack_len = 78;
4242

43+
/*
44+
* Packfile with one object. It references an object which is not in the
45+
* packfile and has a corrupt length (states the deltified stream is 1 byte
46+
* long, where it is actually 6).
47+
*/
48+
static const unsigned char corrupt_thin_pack[] = {
49+
0x50, 0x41, 0x43, 0x4b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01,
50+
0x71, 0xe6, 0x8f, 0xe8, 0x12, 0x9b, 0x54, 0x6b, 0x10, 0x1a, 0xee, 0x95,
51+
0x10, 0xc5, 0x32, 0x8e, 0x7f, 0x21, 0xca, 0x1d, 0x18, 0x78, 0x9c, 0x63,
52+
0x62, 0x66, 0x4e, 0xcb, 0xcf, 0x07, 0x00, 0x02, 0xac, 0x01, 0x4d, 0x07,
53+
0x67, 0x03, 0xc5, 0x40, 0x99, 0x49, 0xb1, 0x3b, 0x7d, 0xae, 0x9b, 0x0e,
54+
0xdd, 0xde, 0xc6, 0x76, 0x43, 0x24, 0x64
55+
};
56+
static const unsigned int corrupt_thin_pack_len = 67;
57+
58+
/*
59+
* Packfile with a missing trailer.
60+
*/
61+
static const unsigned char missing_trailer_pack[] = {
62+
0x50, 0x41, 0x43, 0x4b, 0x00, 0x00, 0x00, 0x03, 0x00, 0x50, 0xf4, 0x3b,
63+
};
64+
static const unsigned int missing_trailer_pack_len = 12;
65+
4366
/*
4467
* Packfile that causes the packfile stream to open in a way in which it leaks
4568
* the stream reader.
@@ -71,14 +94,14 @@ void test_pack_indexer__out_of_order(void)
7194
git_indexer_free(idx);
7295
}
7396

74-
void test_pack_indexer__leaky(void)
97+
void test_pack_indexer__missing_trailer(void)
7598
{
7699
git_indexer *idx = 0;
77100
git_transfer_progress stats = { 0 };
78101

79102
cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL, NULL));
80103
cl_git_pass(git_indexer_append(
81-
idx, leaky_pack, leaky_pack_len, &stats));
104+
idx, missing_trailer_pack, missing_trailer_pack_len, &stats));
82105
cl_git_fail(git_indexer_commit(idx, &stats));
83106

84107
cl_assert(giterr_last() != NULL);
@@ -87,15 +110,14 @@ void test_pack_indexer__leaky(void)
87110
git_indexer_free(idx);
88111
}
89112

90-
void test_pack_indexer__missing_trailer(void)
113+
void test_pack_indexer__leaky(void)
91114
{
92115
git_indexer *idx = 0;
93116
git_transfer_progress stats = { 0 };
94117

95118
cl_git_pass(git_indexer_new(&idx, ".", 0, NULL, NULL, NULL));
96-
/* Truncate a valid packfile */
97119
cl_git_pass(git_indexer_append(
98-
idx, out_of_order_pack, out_of_order_pack_len - 20, &stats));
120+
idx, leaky_pack, leaky_pack_len, &stats));
99121
cl_git_fail(git_indexer_commit(idx, &stats));
100122

101123
cl_assert(giterr_last() != NULL);
@@ -170,6 +192,35 @@ void test_pack_indexer__fix_thin(void)
170192
}
171193
}
172194

195+
void test_pack_indexer__corrupt_length(void)
196+
{
197+
git_indexer *idx = NULL;
198+
git_transfer_progress stats = { 0 };
199+
git_repository *repo;
200+
git_odb *odb;
201+
git_oid id, should_id;
202+
203+
cl_git_pass(git_repository_init(&repo, "thin.git", true));
204+
cl_git_pass(git_repository_odb(&odb, repo));
205+
206+
/* Store the missing base into your ODB so the indexer can fix the pack */
207+
cl_git_pass(git_odb_write(&id, odb, base_obj, base_obj_len, GIT_OBJ_BLOB));
208+
git_oid_fromstr(&should_id, "e68fe8129b546b101aee9510c5328e7f21ca1d18");
209+
cl_assert_equal_oid(&should_id, &id);
210+
211+
cl_git_pass(git_indexer_new(&idx, ".", 0, odb, NULL, NULL));
212+
cl_git_pass(git_indexer_append(
213+
idx, corrupt_thin_pack, corrupt_thin_pack_len, &stats));
214+
cl_git_fail(git_indexer_commit(idx, &stats));
215+
216+
cl_assert(giterr_last() != NULL);
217+
cl_assert_equal_i(giterr_last()->klass, GITERR_ZLIB);
218+
219+
git_indexer_free(idx);
220+
git_odb_free(odb);
221+
git_repository_free(repo);
222+
}
223+
173224
static int find_tmp_file_recurs(void *opaque, git_buf *path)
174225
{
175226
int error = 0;

0 commit comments

Comments
 (0)