Re: [PATCH] pack-bitmap: fix memory leak if `load_bitmap_entries_v1` failed
- From
Taylor Blau <me@ttaylorr.com>
- Date
- May 13, 2025, 17:47 UTC
- Message-ID
- <aCOFqYdnPp1Lne4Y@nand.local>
- In-Reply-To
- <20250512131315.GD1191360@coredump.intra.peff.net>
On Mon, May 12, 2025 at 09:13:15AM -0400, Jeff King wrote:
Show 11 quoted lines
> On Mon, May 12, 2025 at 12:22:10PM +0000, Lidong Yan via GitGitGadget wrote: > > > From: Lidong Yan <502024330056@smail.nju.edu.cn> > > > > In pack-bitmap.c:load_bitmap_entries_v1, the function `read_bitmap_1` > > allocates a bitmap and reads index data into it. However, if any of > > the validation checks following the allocation fail, the allocated bitmap > > is not freed, resulting in a memory leak. To avoid this, the validation > > checks should be performed before the bitmap is allocated. > > Thanks, this looks correct to me.
It looks correct to me as well, and is a strict improvement. But I think there is a leak outside of this function as well that is not touched by this patch.
Show 15 quoted lines
> > @@ -388,10 +388,6 @@ static int load_bitmap_entries_v1(struct bitmap_index *index)
> > return error(_("corrupt ewah bitmap: commit index %u out of range"),
> > (unsigned)commit_idx_pos);
> >
> > - bitmap = read_bitmap_1(index);
> > - if (!bitmap)
> > - return -1;
> > -
> > if (xor_offset > MAX_XOR_OFFSET || xor_offset > i)
> > return error(_("corrupted bitmap pack index"));
>
> I noticed that this code is also within a loop, so we could still return
> early on the next loop iteration. But by that point we will have called
> store_bitmap() on the result, so we only have to worry about leaking the
> bitmap from the current loop iteration.That's right, though I think there is still a leak here.
After going through the "failed" label, load_bitmap() will return -1, and its caller (either prepare_bitmap_walk() or prepare_bitmap_git()) will then call free_bitmap_index().
That function would have done:
struct stored_bitmap *sb;
kh_foreach_value(b->bitmaps, sb {
ewah_pool_free(sb->root);
free(sb);
});, but won't since load_bitmap() already called kh_destroy_oid_map() and NULL'd the "bitmaps" pointer from within its "failed" label.
So I think if you got part of the way through loading bitmap entries and then failed, you would leak all of the previous entries that you were able to load successfully.
I suspect the fix looks something like:
--- 8< ---
diff --git a/pack-bitmap.c b/pack-bitmap.c index 5299f49d59..7f28532a69 100644 --- a/pack-bitmap.c +++ b/pack-bitmap.c @@ -631,41 +631,28 @@ static int load_bitmap(struct repository *r, struct bitmap_index *bitmap_git, bitmap_git->ext_index.positions = kh_init_oid_pos(); if (load_reverse_index(r, bitmap_git)) - goto failed; + return -1; if (!(bitmap_git->commits = read_bitmap_1(bitmap_git)) || !(bitmap_git->trees = read_bitmap_1(bitmap_git)) || !(bitmap_git->blobs = read_bitmap_1(bitmap_git)) || !(bitmap_git->tags = read_bitmap_1(bitmap_git))) - goto failed; + return -1; if (!bitmap_git->table_lookup && load_bitmap_entries_v1(bitmap_git) < 0) - goto failed; + return -1; if (bitmap_git->base) { if (!bitmap_is_midx(bitmap_git)) BUG("non-MIDX bitmap has non-NULL base bitmap index"); if (load_bitmap(r, bitmap_git->base, 1) < 0) - goto failed; + return -1; } if (!recursing) load_all_type_bitmaps(bitmap_git); return 0; - -failed: - munmap(bitmap_git->map, bitmap_git->map_size); - bitmap_git->map = NULL; - bitmap_git->map_size = 0; - - kh_destroy_oid_map(bitmap_git->bitmaps); - bitmap_git->bitmaps = NULL; - - kh_destroy_oid_pos(bitmap_git->ext_index.positions); - bitmap_git->ext_index.positions = NULL; - - return -1; } static int open_pack_bitmap(struct repository *r, --- >8 --- , since all callers of load_bitmap() will themselves call free_bitmap_index(), so there is no need for us to open-code a portion of that function's implementation ourselves. Thanks, Taylor