tummychow / git-absorb

git commit --fixup, but automatic
https://crates.io/crates/git-absorb
BSD 3-Clause "New" or "Revised" License
3.35k stars 59 forks source link

double free detected in tcache 2 #69

Closed karlding closed 1 year ago

karlding commented 1 year ago

I'm not sure if this is the same issue described in #62 given there's no reproduction case available, but I encountered a similar segfault when using binaries built from the Cargo.lock file at the current HEAD (219e386ff665b4fd360b397752ce51a658c5e1d6). This is also the case with the latest prebuilt binaries from GitHub (git-absorb-0.6.7-x86_64-unknown-linux-musl.tar.gz).

Unfortunately I can't share the repository to reproduce this either, but I did manage to find a solution to my problem.

I was seeing a segmentation fault on a specific repository:

# in the repo with changes
$ git add -u .
$ git absorb
Segmentation fault (core dumped)

(Also apologies if I got to my conclusions in a roundabout way, this was my first time looking at Rust code, so do let me know if there's a better way of doing things)

Debugging

Since the prebuilt binaries don't have debug symbols, I tried building from source and ensured that things were still failing. This gave the following message:

# in the tummychow/git-absorb repo
$ cargo clean && cargo build --locked

# in the repo with changes
$ git absorb
free(): double free detected in tcache 2
Aborted (core dumped)

Running this under rust-gdb gives the following backtrace:

 >>> bt
 #0  0x00007ffff7dac00b in raise () from /lib/x86_64-linux-gnu/libc.so.6
 #1  0x00007ffff7d8b859 in abort () from /lib/x86_64-linux-gnu/libc.so.6
 #2  0x00007ffff7df626e in ?? () from /lib/x86_64-linux-gnu/libc.so.6
 #3  0x00007ffff7dfe2fc in ?? () from /lib/x86_64-linux-gnu/libc.so.6
 #4  0x00007ffff7dfff6d in ?? () from /lib/x86_64-linux-gnu/libc.so.6
 #5  0x000055555564fe5b in stdalloc__free (ptr=0x5555559ee2e0) at libgit2/src/allocators/stdalloc.c:104
 #6  0x0000555555611277 in git_mwindow_close_lru_window () at libgit2/src/mwindow.c:269
 #7  0x00005555556114c2 in new_window (fd=7, size=92396285398, offset=49063184399) at libgit2/src/mwindow.c:337
 #8  0x00005555556116df in git_mwindow_open (mwf=0x5555559d50e0, cursor=0x7fffffff1f58, offset=49063184399, extra=20, left=0x7fffffff1ee0) at libgit2/src/mwindow.c:407
 #9  0x000055555564c156 in git_packfile_unpack_header (size_p=0x7fffffff1f70, type_p=0x7fffffff1f4c, mwf=0x5555559d50e0, w_curs=0x7fffffff1f58, curpos=0x7fffffff1f60) at libgit2/src/pack.c:455
 #10 0x000055555564c6c2 in pack_dependency_chain (chain_out=0x7fffffff2040, cached_out=0x7fffffff2010, cached_off=0x7fffffff2920, small_stack=0x7fffffff20a0, stack_sz=0x7fffffff2018, p=0x5555559d50e0, obj_offset=49063184399) at libgit2/src/pack.c:581
 #11 0x000055555564c8bc in git_packfile_unpack (obj=0x7fffffff2900, p=0x5555559d50e0, obj_offset=0x7fffffff2920) at libgit2/src/pack.c:637
 #12 0x0000555555667e74 in pack_backend__read (buffer_p=0x7fffffff29b0, len_p=0x7fffffff29b8, type_p=0x7fffffff29c0, backend=0x5555559c4f90, oid=0x555555abb145) at libgit2/src/odb_pack.c:400
 #13 0x000055555564292d in odb_read_1 (out=0x7fffffff2a88, db=0x5555559ca3d0, id=0x555555abb145, only_refreshed=false) at libgit2/src/odb.c:1065
 #14 0x0000555555642b3c in git_odb_read (out=0x7fffffff2a88, db=0x5555559ca3d0, id=0x555555abb145) at libgit2/src/odb.c:1116
 #15 0x0000555555611ff5 in git_object_lookup_prefix (object_out=0x7fffffff2b50, repo=0x5555559b4fe0, id=0x555555abb145, len=40, type=GIT_OBJECT_TREE) at libgit2/src/object.c:222
 #16 0x00005555556120c7 in git_object_lookup (object_out=0x7fffffff2b50, repo=0x5555559b4fe0, id=0x555555abb145, type=GIT_OBJECT_TREE) at libgit2/src/object.c:253
 #17 0x00005555556097aa in git_tree_lookup (out=0x7fffffff2b50, repo=0x5555559b4fe0, id=0x555555abb145) at libgit2/src/object_api.c:56
 #18 0x000055555566ab6c in tree_iterator_frame_push (iter=0x5555559cdd70, entry=0x555555ab6c48) at libgit2/src/iterator.c:660
 #19 0x000055555566b130 in tree_iterator_advance (out=0x7fffffff2ca8, i=0x5555559cdd70) at libgit2/src/iterator.c:813
 #20 0x0000555555625006 in git_iterator_advance (entry=0x7fffffff2ca8, iter=0x5555559cdd70) at libgit2/src/iterator.h:184
 #21 0x000055555562739c in iterator_advance (entry=0x7fffffff2ca8, iterator=0x5555559cdd70) at libgit2/src/diff_generate.c:925
 #22 0x0000555555627aaf in handle_matched_item (diff=0x5555559cb200, info=0x7fffffff2c90) at libgit2/src/diff_generate.c:1179
 #23 0x0000555555627cf7 in git_diff__from_iterators (out=0x7fffffff2d20, repo=0x5555559b4fe0, old_iter=0x5555559cdd70, new_iter=0x5555559ca200, opts=0x7fffffff3730) at libgit2/src/diff_generate.c:1249
 #24 0x00005555556280a0 in git_diff_tree_to_tree (out=0x7fffffff2e08, repo=0x5555559b4fe0, old_tree=0x5555559d8f50, new_tree=0x5555559cc120, opts=0x7fffffff3730) at libgit2/src/diff_generate.c:1319
 #25 0x00005555555eb58e in git2::repo::Repository::diff_tree_to_tree (self=0x7fffffff34c0, old_tree=..., new_tree=..., opts=...) at src/repo.rs:2374
 #26 0x00005555555b5791 in git_absorb::run (config=0x7fffffffce00) at src/lib.rs:42
 #27 0x000055555559c416 in git_absorb::main () at src/main.rs:102

The backtrace was somewhere in libgit2, and I noticed that the pinned version was rather old. So the first thing I tried was updating the dependencies.

After updating dependencies via cargo update, the double free no longer occurs.

# in the tummychow/git-absorb repo
$ cargo clean && cargo update && cargo build --locked

$ git diff | grep -A3 'libgit2-sys'
name = "libgit2-sys"
-version = "0.12.13+1.0.1"
+version = "0.12.26+1.3.0"

# in the repo with changes
$ git absorb
(success)

This updated the transitive libgit2-sys dependency in the Cargo.lock file from 0.12.13+1.0.1 to 0.12.26+1.3.0. Since this works, it indicates that the issue I was seeing was fixed upstream already.

I then tried explicitly adding libgit2-sys as a dependency and setting an exact version to override the Cargo dependency resolver (without updating git2). The first libgit2-sys after the current version in the Cargo.lock file that fixes this seems to be =0.12.14+1.1.0.

$ nvim Cargo.toml
# add libgit2-sys = "=0.12.14+1.1.0" under [dependencies]

# regenerate the Cargo.lock file + rebuild

That release bumps the libgit2 dependency, so most likely some change in libgit2 between 1.0.0 and 1.1.0 (there's 335 commits here).

At this point, I was happy that things were working so I stopped looking further into this to see what commit in libgit2 fixes the issue.

TL; DR

Updating Cargo.lock to pull in a newer upstream libgit2 dependency seems to fix a double free