Skip to content

Commit 26b42b4

Browse files
committed
Merge branch 'mt/delta-base-cache-races'
A race that leads to an access to a free'd data was corrected in the codepath that reads pack files. * mt/delta-base-cache-races: packfile: fix memory leak in add_delta_base_cache() packfile: fix race condition on unpack_entry()
2 parents 2fa8aac + bda959c commit 26b42b4

File tree

1 file changed

+29
-19
lines changed

1 file changed

+29
-19
lines changed

packfile.c

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1475,16 +1475,18 @@ void clear_delta_base_cache(void)
14751475
static void add_delta_base_cache(struct packed_git *p, off_t base_offset,
14761476
void *base, unsigned long base_size, enum object_type type)
14771477
{
1478-
struct delta_base_cache_entry *ent = xmalloc(sizeof(*ent));
1478+
struct delta_base_cache_entry *ent;
14791479
struct list_head *lru, *tmp;
14801480

14811481
/*
14821482
* Check required to avoid redundant entries when more than one thread
14831483
* is unpacking the same object, in unpack_entry() (since its phases I
14841484
* and III might run concurrently across multiple threads).
14851485
*/
1486-
if (in_delta_base_cache(p, base_offset))
1486+
if (in_delta_base_cache(p, base_offset)) {
1487+
free(base);
14871488
return;
1489+
}
14881490

14891491
delta_base_cached += base_size;
14901492

@@ -1496,6 +1498,7 @@ static void add_delta_base_cache(struct packed_git *p, off_t base_offset,
14961498
release_delta_base_cache(f);
14971499
}
14981500

1501+
ent = xmalloc(sizeof(*ent));
14991502
ent->key.p = p;
15001503
ent->key.base_offset = base_offset;
15011504
ent->type = type;
@@ -1776,12 +1779,10 @@ void *unpack_entry(struct repository *r, struct packed_git *p, off_t obj_offset,
17761779
void *external_base = NULL;
17771780
unsigned long delta_size, base_size = size;
17781781
int i;
1782+
off_t base_obj_offset = obj_offset;
17791783

17801784
data = NULL;
17811785

1782-
if (base)
1783-
add_delta_base_cache(p, obj_offset, base, base_size, type);
1784-
17851786
if (!base) {
17861787
/*
17871788
* We're probably in deep shit, but let's try to fetch
@@ -1819,24 +1820,33 @@ void *unpack_entry(struct repository *r, struct packed_git *p, off_t obj_offset,
18191820
"at offset %"PRIuMAX" from %s",
18201821
(uintmax_t)curpos, p->pack_name);
18211822
data = NULL;
1822-
free(external_base);
1823-
continue;
1824-
}
1823+
} else {
1824+
data = patch_delta(base, base_size, delta_data,
1825+
delta_size, &size);
18251826

1826-
data = patch_delta(base, base_size,
1827-
delta_data, delta_size,
1828-
&size);
1827+
/*
1828+
* We could not apply the delta; warn the user, but
1829+
* keep going. Our failure will be noticed either in
1830+
* the next iteration of the loop, or if this is the
1831+
* final delta, in the caller when we return NULL.
1832+
* Those code paths will take care of making a more
1833+
* explicit warning and retrying with another copy of
1834+
* the object.
1835+
*/
1836+
if (!data)
1837+
error("failed to apply delta");
1838+
}
18291839

18301840
/*
1831-
* We could not apply the delta; warn the user, but keep going.
1832-
* Our failure will be noticed either in the next iteration of
1833-
* the loop, or if this is the final delta, in the caller when
1834-
* we return NULL. Those code paths will take care of making
1835-
* a more explicit warning and retrying with another copy of
1836-
* the object.
1841+
* We delay adding `base` to the cache until the end of the loop
1842+
* because unpack_compressed_entry() momentarily releases the
1843+
* obj_read_mutex, giving another thread the chance to access
1844+
* the cache. Therefore, if `base` was already there, this other
1845+
* thread could free() it (e.g. to make space for another entry)
1846+
* before we are done using it.
18371847
*/
1838-
if (!data)
1839-
error("failed to apply delta");
1848+
if (!external_base)
1849+
add_delta_base_cache(p, base_obj_offset, base, base_size, type);
18401850

18411851
free(delta_data);
18421852
free(external_base);

0 commit comments

Comments
 (0)