diff mbox series

[v3] sparse index: fix use-after-free bug in cache_tree_verify()

Message ID pull.1053.v3.git.1633630041829.gitgitgadget@gmail.com (mailing list archive)
State Superseded
Commit 2f90c87850592ee4673efed4e53f8bc1de58177e
Headers show
Series [v3] sparse index: fix use-after-free bug in cache_tree_verify() | expand

Commit Message

Phillip Wood Oct. 7, 2021, 6:07 p.m. UTC
From: Phillip Wood <phillip.wood@dunelm.org.uk>

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 <phillip.wood@dunelm.org.uk>
---
    [RFC] sparse index: fix use-after-free bug in cache_tree_verify()
    
    Changes since V2
    
     * Fixed the spelling of Stolee's name (sorry Stolee)
     * Added "-q" to the test to prevent a failure on Microsoft's fork[1]
    
    [1]
    https://lore.kernel.org/git/ebbe8616-0863-812b-e112-103680f7298b@gmail.com/
    
    Thanks for the feedback and help, here are the changes from the RFC
    
     * Updated commit message and comments to make it clear this is
       triggered by a sparse index with a full cache tree based on Stolee's
       explanation.
     * Added a comment and BUG() suggested by Junio
    
    RFC cover letter In a sparse index it is possible for the tree that is
    being verified to be freed while it is being verified. This is an RFC as
    I'm not familiar with the cache tree code. I'm confused as to why this
    bug is triggered by the sequence
    
    unpack_trees()
    prime_cache_tree()
    write_locked_index()
    
    
    but not
    
    unpack_trees()
    write_locked_index()
    
    
    as unpack_trees() appears to update the cache tree with
    
    if (!cache_tree_fully_valid(o->result.cache_tree))
                cache_tree_update(&o->result,
                          WRITE_TREE_SILENT |
                          WRITE_TREE_REPAIR);
    
    
    and I don't understand why the cache tree from prime_cache_tree()
    results in different behavior. It concerns me that this fix is hiding
    another bug.
    
    Best Wishes
    
    Phillip

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1053%2Fphillipwood%2Fwip%2Fsparse-index-fix-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1053/phillipwood/wip/sparse-index-fix-v3
Pull-Request: https://github.com/gitgitgadget/git/pull/1053

Range-diff vs v2:

 1:  4ee972fee2e ! 1:  b3dbe02fcc3 sparse index: fix use-after-free bug in cache_tree_verify()
     @@ Commit message
          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 Derick Stolee for their help analyzing the problem.
     +    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
     @@ t/t1092-sparse-checkout-compatibility.sh: test_expect_success 'checkout and rese
       	init_repos &&
       
      -	for OPERATION in "merge -m merge" cherry-pick rebase
     -+	for OPERATION in "merge -m merge" cherry-pick "rebase --apply" "rebase --merge"
     ++	for OPERATION in "merge -m merge" cherry-pick "rebase --apply -q" "rebase --merge"
       	do
       		test_all_match git checkout -B temp update-deep &&
       		test_all_match git $OPERATION update-folder1 &&


 cache-tree.c                             | 37 +++++++++++++++++++-----
 t/t1092-sparse-checkout-compatibility.sh |  2 +-
 2 files changed, 30 insertions(+), 9 deletions(-)


base-commit: cefe983a320c03d7843ac78e73bd513a27806845

Comments

Junio C Hamano Oct. 7, 2021, 9:23 p.m. UTC | #1
"Phillip Wood via GitGitGadget" <gitgitgadget@gmail.com> writes:

>      * Fixed the spelling of Stolee's name (sorry Stolee)
>      * Added "-q" to the test to prevent a failure on Microsoft's fork[1]
>     
>     [1]
>     https://lore.kernel.org/git/ebbe8616-0863-812b-e112-103680f7298b@gmail.com/

I've seen the exchange, but ...

> -	for OPERATION in "merge -m merge" cherry-pick rebase
> +	for OPERATION in "merge -m merge" cherry-pick "rebase --apply -q" "rebase --merge"
>  	do

... it looks too strange that only one of them requires a "--quiet"
option.  Is it a possibility to get whoever's fork corrected so that
it behaves sensibly without requiring the "-q" option only for the
particular rebase backend?

In the meantime, I'll queue the patch as-is (I actually queued the
previous round with namefix already).

Thanks.
Phillip Wood Oct. 8, 2021, 9:09 a.m. UTC | #2
On 07/10/2021 22:23, Junio C Hamano wrote:
> "Phillip Wood via GitGitGadget" <gitgitgadget@gmail.com> writes:
> 
>>       * Fixed the spelling of Stolee's name (sorry Stolee)
>>       * Added "-q" to the test to prevent a failure on Microsoft's fork[1]
>>      
>>      [1]
>>      https://lore.kernel.org/git/ebbe8616-0863-812b-e112-103680f7298b@gmail.com/
> 
> I've seen the exchange, but ...
> 
>> -	for OPERATION in "merge -m merge" cherry-pick rebase
>> +	for OPERATION in "merge -m merge" cherry-pick "rebase --apply -q" "rebase --merge"
>>   	do
> 
> ... it looks too strange that only one of them requires a "--quiet"
> option.  Is it a possibility to get whoever's fork corrected so that
> it behaves sensibly without requiring the "-q" option only for the
> particular rebase backend?

The issue is caused by a patch that Microsoft is carrying that stops 
apply from creating paths with the skip-worktree bit set. As they're 
upstreaming their sparse index and checkout work I expect it will show 
up on the list sooner or later. I agree the "-q" is odd and it also 
means the test is weaker but I'm not sure what else we can do.

> In the meantime, I'll queue the patch as-is (I actually queued the
> previous round with namefix already).

Thanks

Phillip

> Thanks.
>
Bagas Sanjaya Oct. 8, 2021, 9:38 a.m. UTC | #3
On 08/10/21 01.07, Phillip Wood via GitGitGadget wrote:
> -static void verify_one(struct repository *r,
> -		       struct index_state *istate,
> -		       struct cache_tree *it,
> -		       struct strbuf *path)
> +/*
> + * Returns:
> + *  0 - Verification completed.
> + *  1 - Restart verification - a call to ensure_full_index() freed the cache
> + *      tree that is being verified and verification needs to be restarted from
> + *      the new toplevel cache tree.
> + */
> +static int verify_one(struct repository *r,
> +		      struct index_state *istate,
> +		      struct cache_tree *it,
> +		      struct strbuf *path)
>   {
>   	int i, pos, len = path->len;
>   	struct strbuf tree_buf = STRBUF_INIT;

What is verify_one() doing? I think it worth mentioning it in the 
comment above.
Derrick Stolee Oct. 8, 2021, 6:53 p.m. UTC | #4
On 10/8/2021 5:09 AM, Phillip Wood wrote:
> On 07/10/2021 22:23, Junio C Hamano wrote:
>> "Phillip Wood via GitGitGadget" <gitgitgadget@gmail.com> writes:
>>
>>>       * Fixed the spelling of Stolee's name (sorry Stolee)
>>>       * Added "-q" to the test to prevent a failure on Microsoft's fork[1]
>>>           [1]
>>>      https://lore.kernel.org/git/ebbe8616-0863-812b-e112-103680f7298b@gmail.com/
>>
>> I've seen the exchange, but ...
>>
>>> -    for OPERATION in "merge -m merge" cherry-pick rebase
>>> +    for OPERATION in "merge -m merge" cherry-pick "rebase --apply -q" "rebase --merge"
>>>       do
>>
>> ... it looks too strange that only one of them requires a "--quiet"
>> option.  Is it a possibility to get whoever's fork corrected so that
>> it behaves sensibly without requiring the "-q" option only for the
>> particular rebase backend?
> 
> The issue is caused by a patch that Microsoft is carrying that stops apply from creating paths with the skip-worktree bit set. As they're upstreaming their sparse index and checkout work I expect it will show up on the list sooner or later. I agree the "-q" is odd and it also means the test is weaker but I'm not sure what else we can do.

That particular patch is old and is due to some interactions with
how VFS for Git (ab)uses the skip-worktree bit. I'm not sure it will
ever come upstream. It is probably very much like a recent example [1]
that we tried to upstream only to realize that it should be replaced
with something better.

[1] https://lore.kernel.org/git/65905bf4e001118e8b9ced95c1bcecbacb6334ac.1633013461.git.gitgitgadget@gmail.com/

I'm fine to leave the `-q` out of this patch and I can add it myself
when we take this into microsoft/git. That can also motivate me to
rethink that patch.

Thanks,
-Stolee
Junio C Hamano Oct. 8, 2021, 7:57 p.m. UTC | #5
Phillip Wood <phillip.wood123@gmail.com> writes:

> On 07/10/2021 22:23, Junio C Hamano wrote:
>> "Phillip Wood via GitGitGadget" <gitgitgadget@gmail.com> writes:
>> 
>>>       * Fixed the spelling of Stolee's name (sorry Stolee)
>>>       * Added "-q" to the test to prevent a failure on Microsoft's fork[1]
>>>           [1]
>>>      https://lore.kernel.org/git/ebbe8616-0863-812b-e112-103680f7298b@gmail.com/
>> I've seen the exchange, but ...
>> 
>>> -	for OPERATION in "merge -m merge" cherry-pick rebase
>>> +	for OPERATION in "merge -m merge" cherry-pick "rebase --apply -q" "rebase --merge"
>>>   	do
>> ... it looks too strange that only one of them requires a "--quiet"
>> option.  Is it a possibility to get whoever's fork corrected so that
>> it behaves sensibly without requiring the "-q" option only for the
>> particular rebase backend?
>
> The issue is caused by a patch that Microsoft is carrying that stops
> apply from creating paths with the skip-worktree bit set. As they're 
> upstreaming their sparse index and checkout work I expect it will show
> up on the list sooner or later. I agree the "-q" is odd and it also 
> means the test is weaker but I'm not sure what else we can do.

Perhaps passing "-q" to the other variant of "rebase" would make it
clear that (1) we do not want to worry about traces involved in the
verbose message generation and (2) there is nothing fishy going on
in only one of the "rebase" backends.
Phillip Wood Oct. 14, 2021, 9:40 a.m. UTC | #6
Hi Bagas

On 08/10/2021 10:38, Bagas Sanjaya wrote:
> On 08/10/21 01.07, Phillip Wood via GitGitGadget wrote:
>> -static void verify_one(struct repository *r,
>> -               struct index_state *istate,
>> -               struct cache_tree *it,
>> -               struct strbuf *path)
>> +/*
>> + * Returns:
>> + *  0 - Verification completed.
>> + *  1 - Restart verification - a call to ensure_full_index() freed 
>> the cache
>> + *      tree that is being verified and verification needs to be 
>> restarted from
>> + *      the new toplevel cache tree.
>> + */
>> +static int verify_one(struct repository *r,
>> +              struct index_state *istate,
>> +              struct cache_tree *it,
>> +              struct strbuf *path)
>>   {
>>       int i, pos, len = path->len;
>>       struct strbuf tree_buf = STRBUF_INIT;
> 
> What is verify_one() doing? I think it worth mentioning it in the 
> comment above.

I think it's pretty obvious if you read the code rather than my patch. 
It is a common pattern in git that a function with "one" in the name is 
a helper for another similarly named function without the "one". In this 
case verify_one() is a recursive helper for cache_tree_verify()

Best Wishes

Phillip
Phillip Wood Oct. 14, 2021, 1:34 p.m. UTC | #7
Hi Junio

On 08/10/2021 20:57, Junio C Hamano wrote:
> Phillip Wood <phillip.wood123@gmail.com> writes:
> 
>> On 07/10/2021 22:23, Junio C Hamano wrote:
>>> "Phillip Wood via GitGitGadget" <gitgitgadget@gmail.com> writes:
>>>
>>>>        * Fixed the spelling of Stolee's name (sorry Stolee)
>>>>        * Added "-q" to the test to prevent a failure on Microsoft's fork[1]
>>>>            [1]
>>>>       https://lore.kernel.org/git/ebbe8616-0863-812b-e112-103680f7298b@gmail.com/
>>> I've seen the exchange, but ...
>>>
>>>> -	for OPERATION in "merge -m merge" cherry-pick rebase
>>>> +	for OPERATION in "merge -m merge" cherry-pick "rebase --apply -q" "rebase --merge"
>>>>    	do
>>> ... it looks too strange that only one of them requires a "--quiet"
>>> option.  Is it a possibility to get whoever's fork corrected so that
>>> it behaves sensibly without requiring the "-q" option only for the
>>> particular rebase backend?
>>
>> The issue is caused by a patch that Microsoft is carrying that stops
>> apply from creating paths with the skip-worktree bit set. As they're
>> upstreaming their sparse index and checkout work I expect it will show
>> up on the list sooner or later. I agree the "-q" is odd and it also
>> means the test is weaker but I'm not sure what else we can do.
> 
> Perhaps passing "-q" to the other variant of "rebase" would make it
> clear that (1) we do not want to worry about traces involved in the
> verbose message generation and (2) there is nothing fishy going on
> in only one of the "rebase" backends.

I'm not sure about that. There are really three levels of output from 
rebase - quiet, normal and verbose. I think passing "-q" suppresses 
virtually all the output - there is no indication of which commits have 
been picked. As test appears to be comparing the output of the command 
for the sparse and non-spare case as a proxy for "it behaves the same 
for sparse and non-sparse checkouts/indexes" passing "-q" to rebase 
weakens the test considerably. Stolee indicated [1] that he is happy for 
us to drop the "-q" for the "--apply" case so I'd be inclined to go back 
to your corrected version of V2.

Best Wishes

Phillip

[1] 
https://lore.kernel.org/git/e281c2e2-2044-1a11-e2bc-5ab3ee92c300@gmail.com/
Junio C Hamano Oct. 14, 2021, 4:42 p.m. UTC | #8
Phillip Wood <phillip.wood123@gmail.com> writes:

> I'm not sure about that. There are really three levels of output from
> rebase - quiet, normal and verbose. I think passing "-q" suppresses 
> virtually all the output - there is no indication of which commits
> have been picked. As test appears to be comparing the output of the
> command for the sparse and non-spare case as a proxy for "it behaves
> the same for sparse and non-sparse checkouts/indexes" passing "-q" to
> rebase weakens the test considerably.

True.  Also because the behaviour of "rebase" using different
backends are sufficiently different, I no longer consider it a funny
inconsistency that one backend has to to use "-q" while the other
doesn't.

> Stolee indicated [1] that he is
> happy for us to drop the "-q" for the "--apply" case so I'd be
> inclined to go back to your corrected version of V2.

OK.  Can we have a v4 that is identical to "corrected" v2, then,
please?  That's easier than having to dig v2 up and remember and
apply the "correction" ;-).

Thanks.
diff mbox series

Patch

diff --git a/cache-tree.c b/cache-tree.c
index 90919f9e345..8044e21bcf3 100644
--- a/cache-tree.c
+++ b/cache-tree.c
@@ -826,10 +826,17 @@  static void verify_one_sparse(struct repository *r,
 		    path->buf);
 }
 
-static void verify_one(struct repository *r,
-		       struct index_state *istate,
-		       struct cache_tree *it,
-		       struct strbuf *path)
+/*
+ * Returns:
+ *  0 - Verification completed.
+ *  1 - Restart verification - a call to ensure_full_index() freed the cache
+ *      tree that is being verified and verification needs to be restarted from
+ *      the new toplevel cache tree.
+ */
+static int verify_one(struct repository *r,
+		      struct index_state *istate,
+		      struct cache_tree *it,
+		      struct strbuf *path)
 {
 	int i, pos, len = path->len;
 	struct strbuf tree_buf = STRBUF_INIT;
@@ -837,21 +844,30 @@  static void verify_one(struct repository *r,
 
 	for (i = 0; i < it->subtree_nr; i++) {
 		strbuf_addf(path, "%s/", it->down[i]->name);
-		verify_one(r, istate, it->down[i]->cache_tree, path);
+		if (verify_one(r, istate, it->down[i]->cache_tree, path))
+			return 1;
 		strbuf_setlen(path, len);
 	}
 
 	if (it->entry_count < 0 ||
 	    /* no verification on tests (t7003) that replace trees */
 	    lookup_replace_object(r, &it->oid) != &it->oid)
-		return;
+		return 0;
 
 	if (path->len) {
+		/*
+		 * If the index is sparse and the cache tree is not
+		 * index_name_pos() may trigger ensure_full_index() which will
+		 * free the tree that is being verified.
+		 */
+		int is_sparse = istate->sparse_index;
 		pos = index_name_pos(istate, path->buf, path->len);
+		if (is_sparse && !istate->sparse_index)
+			return 1;
 
 		if (pos >= 0) {
 			verify_one_sparse(r, istate, it, path, pos);
-			return;
+			return 0;
 		}
 
 		pos = -pos - 1;
@@ -899,6 +915,7 @@  static void verify_one(struct repository *r,
 		    oid_to_hex(&new_oid), oid_to_hex(&it->oid));
 	strbuf_setlen(path, len);
 	strbuf_release(&tree_buf);
+	return 0;
 }
 
 void cache_tree_verify(struct repository *r, struct index_state *istate)
@@ -907,6 +924,10 @@  void cache_tree_verify(struct repository *r, struct index_state *istate)
 
 	if (!istate->cache_tree)
 		return;
-	verify_one(r, istate, istate->cache_tree, &path);
+	if (verify_one(r, istate, istate->cache_tree, &path)) {
+		strbuf_reset(&path);
+		if (verify_one(r, istate, istate->cache_tree, &path))
+			BUG("ensure_full_index() called twice while verifying cache tree");
+	}
 	strbuf_release(&path);
 }
diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh
index 886e78715fe..80c77bb432e 100755
--- a/t/t1092-sparse-checkout-compatibility.sh
+++ b/t/t1092-sparse-checkout-compatibility.sh
@@ -484,7 +484,7 @@  test_expect_success 'checkout and reset (mixed) [sparse]' '
 test_expect_success 'merge, cherry-pick, and rebase' '
 	init_repos &&
 
-	for OPERATION in "merge -m merge" cherry-pick rebase
+	for OPERATION in "merge -m merge" cherry-pick "rebase --apply -q" "rebase --merge"
 	do
 		test_all_match git checkout -B temp update-deep &&
 		test_all_match git $OPERATION update-folder1 &&