Skip to content

Commit f4a783f

Browse files
phillipwoodgitster
authored andcommitted
sparse index: fix use-after-free bug in cache_tree_verify()
In a sparse index it is possible for the tree that is being verified to be freed while it is being verified. This happens when index_name_pos() looks up a entry that is missing from the index and that would be a descendant of a sparse entry. That triggers a call to ensure_full_index() which frees the cache tree that is being verified. Carrying on trying to verify the tree after this results in a use-after-free bug. Instead restart the verification if a sparse index is converted to a full index. This bug is triggered by a call to reset_head() in "git rebase --apply". Thanks to René Scharfe for his help analyzing the problem. ==74345==ERROR: AddressSanitizer: heap-use-after-free on address 0x606000001b20 at pc 0x557cbe82d3a2 bp 0x7ffdfee08090 sp 0x7ffdfee08080 READ of size 4 at 0x606000001b20 thread T0 #0 0x557cbe82d3a1 in verify_one /home/phil/src/git/cache-tree.c:863 #1 0x557cbe82ca9d in verify_one /home/phil/src/git/cache-tree.c:840 #2 0x557cbe82ca9d in verify_one /home/phil/src/git/cache-tree.c:840 #3 0x557cbe82ca9d in verify_one /home/phil/src/git/cache-tree.c:840 #4 0x557cbe830a2b in cache_tree_verify /home/phil/src/git/cache-tree.c:910 #5 0x557cbea53741 in write_locked_index /home/phil/src/git/read-cache.c:3250 #6 0x557cbeab7fdd in reset_head /home/phil/src/git/reset.c:87 #7 0x557cbe72147f in cmd_rebase builtin/rebase.c:2074 #8 0x557cbe5bd151 in run_builtin /home/phil/src/git/git.c:461 #9 0x557cbe5bd151 in handle_builtin /home/phil/src/git/git.c:714 #10 0x557cbe5c0503 in run_argv /home/phil/src/git/git.c:781 #11 0x557cbe5c0503 in cmd_main /home/phil/src/git/git.c:912 #12 0x557cbe5bad28 in main /home/phil/src/git/common-main.c:52 #13 0x7fdd4b82eb24 in __libc_start_main (/usr/lib/libc.so.6+0x27b24) #14 0x557cbe5bcb8d in _start (/home/phil/src/git/git+0x1b9b8d) 0x606000001b20 is located 0 bytes inside of 56-byte region [0x606000001b20,0x606000001b58) freed by thread T0 here: #0 0x7fdd4bacff19 in __interceptor_free /build/gcc/src/gcc/libsanitizer/asan/asan_malloc_linux.cpp:127 #1 0x557cbe82af60 in cache_tree_free /home/phil/src/git/cache-tree.c:35 #2 0x557cbe82aee5 in cache_tree_free /home/phil/src/git/cache-tree.c:31 #3 0x557cbe82aee5 in cache_tree_free /home/phil/src/git/cache-tree.c:31 #4 0x557cbe82aee5 in cache_tree_free /home/phil/src/git/cache-tree.c:31 #5 0x557cbeb2557a in ensure_full_index /home/phil/src/git/sparse-index.c:310 #6 0x557cbea45c4a in index_name_stage_pos /home/phil/src/git/read-cache.c:588 #7 0x557cbe82ce37 in verify_one /home/phil/src/git/cache-tree.c:850 #8 0x557cbe82ca9d in verify_one /home/phil/src/git/cache-tree.c:840 #9 0x557cbe82ca9d in verify_one /home/phil/src/git/cache-tree.c:840 #10 0x557cbe82ca9d in verify_one /home/phil/src/git/cache-tree.c:840 #11 0x557cbe830a2b in cache_tree_verify /home/phil/src/git/cache-tree.c:910 #12 0x557cbea53741 in write_locked_index /home/phil/src/git/read-cache.c:3250 #13 0x557cbeab7fdd in reset_head /home/phil/src/git/reset.c:87 #14 0x557cbe72147f in cmd_rebase builtin/rebase.c:2074 #15 0x557cbe5bd151 in run_builtin /home/phil/src/git/git.c:461 #16 0x557cbe5bd151 in handle_builtin /home/phil/src/git/git.c:714 #17 0x557cbe5c0503 in run_argv /home/phil/src/git/git.c:781 #18 0x557cbe5c0503 in cmd_main /home/phil/src/git/git.c:912 #19 0x557cbe5bad28 in main /home/phil/src/git/common-main.c:52 #20 0x7fdd4b82eb24 in __libc_start_main (/usr/lib/libc.so.6+0x27b24) previously allocated by thread T0 here: #0 0x7fdd4bad0459 in __interceptor_calloc /build/gcc/src/gcc/libsanitizer/asan/asan_malloc_linux.cpp:154 #1 0x557cbebc1807 in xcalloc /home/phil/src/git/wrapper.c:140 #2 0x557cbe82b7d8 in cache_tree /home/phil/src/git/cache-tree.c:17 #3 0x557cbe82b7d8 in prime_cache_tree_rec /home/phil/src/git/cache-tree.c:763 #4 0x557cbe82b837 in prime_cache_tree_rec /home/phil/src/git/cache-tree.c:764 #5 0x557cbe82b837 in prime_cache_tree_rec /home/phil/src/git/cache-tree.c:764 #6 0x557cbe8304e1 in prime_cache_tree /home/phil/src/git/cache-tree.c:779 #7 0x557cbeab7fa7 in reset_head /home/phil/src/git/reset.c:85 #8 0x557cbe72147f in cmd_rebase builtin/rebase.c:2074 #9 0x557cbe5bd151 in run_builtin /home/phil/src/git/git.c:461 #10 0x557cbe5bd151 in handle_builtin /home/phil/src/git/git.c:714 #11 0x557cbe5c0503 in run_argv /home/phil/src/git/git.c:781 #12 0x557cbe5c0503 in cmd_main /home/phil/src/git/git.c:912 #13 0x557cbe5bad28 in main /home/phil/src/git/common-main.c:52 #14 0x7fdd4b82eb24 in __libc_start_main (/usr/lib/libc.so.6+0x27b24) Signed-off-by: Phillip Wood <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 2a233d4 commit f4a783f

File tree

2 files changed

+22
-9
lines changed

2 files changed

+22
-9
lines changed

cache-tree.c

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -826,32 +826,41 @@ static void verify_one_sparse(struct repository *r,
826826
path->buf);
827827
}
828828

829-
static void verify_one(struct repository *r,
830-
struct index_state *istate,
831-
struct cache_tree *it,
832-
struct strbuf *path)
829+
static int verify_one(struct repository *r,
830+
struct index_state *istate,
831+
struct cache_tree *it,
832+
struct strbuf *path)
833833
{
834834
int i, pos, len = path->len;
835835
struct strbuf tree_buf = STRBUF_INIT;
836836
struct object_id new_oid;
837837

838838
for (i = 0; i < it->subtree_nr; i++) {
839839
strbuf_addf(path, "%s/", it->down[i]->name);
840-
verify_one(r, istate, it->down[i]->cache_tree, path);
840+
if (verify_one(r, istate, it->down[i]->cache_tree, path))
841+
return 1;
841842
strbuf_setlen(path, len);
842843
}
843844

844845
if (it->entry_count < 0 ||
845846
/* no verification on tests (t7003) that replace trees */
846847
lookup_replace_object(r, &it->oid) != &it->oid)
847-
return;
848+
return 0;
848849

849850
if (path->len) {
851+
/*
852+
* If the index is sparse index_name_pos() may trigger
853+
* ensure_full_index() which will free the tree that is being
854+
* verified.
855+
*/
856+
int is_sparse = istate->sparse_index;
850857
pos = index_name_pos(istate, path->buf, path->len);
858+
if (is_sparse && !istate->sparse_index)
859+
return 1;
851860

852861
if (pos >= 0) {
853862
verify_one_sparse(r, istate, it, path, pos);
854-
return;
863+
return 0;
855864
}
856865

857866
pos = -pos - 1;
@@ -899,6 +908,7 @@ static void verify_one(struct repository *r,
899908
oid_to_hex(&new_oid), oid_to_hex(&it->oid));
900909
strbuf_setlen(path, len);
901910
strbuf_release(&tree_buf);
911+
return 0;
902912
}
903913

904914
void cache_tree_verify(struct repository *r, struct index_state *istate)
@@ -907,6 +917,9 @@ void cache_tree_verify(struct repository *r, struct index_state *istate)
907917

908918
if (!istate->cache_tree)
909919
return;
910-
verify_one(r, istate, istate->cache_tree, &path);
920+
if (verify_one(r, istate, istate->cache_tree, &path)) {
921+
strbuf_reset(&path);
922+
verify_one(r, istate, istate->cache_tree, &path);
923+
}
911924
strbuf_release(&path);
912925
}

t/t1092-sparse-checkout-compatibility.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,7 @@ test_expect_success 'checkout and reset (mixed) [sparse]' '
484484
test_expect_success 'merge, cherry-pick, and rebase' '
485485
init_repos &&
486486
487-
for OPERATION in "merge -m merge" cherry-pick rebase
487+
for OPERATION in "merge -m merge" cherry-pick "rebase --apply" "rebase --merge"
488488
do
489489
test_all_match git checkout -B temp update-deep &&
490490
test_all_match git $OPERATION update-folder1 &&

0 commit comments

Comments
 (0)