Skip to content

Commit e9317e5

Browse files
phillipwoodderrickstolee
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 the index is sparse but the cache tree is not and index_name_pos() looks up a path from the cache tree that is a descendant of a sparse index 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 and Derrick Stolee for their 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: Derrick Stolee <[email protected]>
1 parent 6df9339 commit e9317e5

File tree

2 files changed

+30
-9
lines changed

2 files changed

+30
-9
lines changed

cache-tree.c

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -923,32 +923,48 @@ static void verify_one_sparse(struct repository *r,
923923
path->buf);
924924
}
925925

926-
static void verify_one(struct repository *r,
927-
struct index_state *istate,
928-
struct cache_tree *it,
929-
struct strbuf *path)
926+
/*
927+
* Returns:
928+
* 0 - Verification completed.
929+
* 1 - Restart verification - a call to ensure_full_index() freed the cache
930+
* tree that is being verified and verification needs to be restarted from
931+
* the new toplevel cache tree.
932+
*/
933+
static int verify_one(struct repository *r,
934+
struct index_state *istate,
935+
struct cache_tree *it,
936+
struct strbuf *path)
930937
{
931938
int i, pos, len = path->len;
932939
struct strbuf tree_buf = STRBUF_INIT;
933940
struct object_id new_oid;
934941

935942
for (i = 0; i < it->subtree_nr; i++) {
936943
strbuf_addf(path, "%s/", it->down[i]->name);
937-
verify_one(r, istate, it->down[i]->cache_tree, path);
944+
if (verify_one(r, istate, it->down[i]->cache_tree, path))
945+
return 1;
938946
strbuf_setlen(path, len);
939947
}
940948

941949
if (it->entry_count < 0 ||
942950
/* no verification on tests (t7003) that replace trees */
943951
lookup_replace_object(r, &it->oid) != &it->oid)
944-
return;
952+
return 0;
945953

946954
if (path->len) {
955+
/*
956+
* If the index is sparse and the cache tree is not
957+
* index_name_pos() may trigger ensure_full_index() which will
958+
* free the tree that is being verified.
959+
*/
960+
int is_sparse = istate->sparse_index;
947961
pos = index_name_pos(istate, path->buf, path->len);
962+
if (is_sparse && !istate->sparse_index)
963+
return 1;
948964

949965
if (pos >= 0) {
950966
verify_one_sparse(r, istate, it, path, pos);
951-
return;
967+
return 0;
952968
}
953969

954970
pos = -pos - 1;
@@ -996,6 +1012,7 @@ static void verify_one(struct repository *r,
9961012
oid_to_hex(&new_oid), oid_to_hex(&it->oid));
9971013
strbuf_setlen(path, len);
9981014
strbuf_release(&tree_buf);
1015+
return 0;
9991016
}
10001017

10011018
void cache_tree_verify(struct repository *r, struct index_state *istate)
@@ -1004,6 +1021,10 @@ void cache_tree_verify(struct repository *r, struct index_state *istate)
10041021

10051022
if (!istate->cache_tree)
10061023
return;
1007-
verify_one(r, istate, istate->cache_tree, &path);
1024+
if (verify_one(r, istate, istate->cache_tree, &path)) {
1025+
strbuf_reset(&path);
1026+
if (verify_one(r, istate, istate->cache_tree, &path))
1027+
BUG("ensure_full_index() called twice while verifying cache tree");
1028+
}
10081029
strbuf_release(&path);
10091030
}

t/t1092-sparse-checkout-compatibility.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -890,7 +890,7 @@ test_expect_success 'read-tree --merge with directory-file conflicts' '
890890
test_expect_success 'merge, cherry-pick, and rebase' '
891891
init_repos &&
892892
893-
for OPERATION in "merge -m merge" cherry-pick rebase
893+
for OPERATION in "merge -m merge" cherry-pick "rebase -q --apply" "rebase --merge"
894894
do
895895
test_all_match git checkout -B temp update-deep &&
896896
test_all_match git $OPERATION update-folder1 &&

0 commit comments

Comments
 (0)