]> git.ipfire.org Git - thirdparty/git.git/commitdiff
branch: fix branch_checked_out() leaks
authorDerrick Stolee <derrickstolee@github.com>
Tue, 14 Jun 2022 19:27:33 +0000 (19:27 +0000)
committerJunio C Hamano <gitster@pobox.com>
Wed, 15 Jun 2022 17:47:19 +0000 (10:47 -0700)
The branch_checked_out() method populates a strmap linking a refname to
a worktree that has that branch checked out. While unlikely, it is
possible that a bug or filesystem manipulation could create a scenario
where the same ref is checked out in multiple places. Further, there are
some states in an interactive rebase where HEAD and REBASE_HEAD point to
the same ref, leading to multiple insertions into the strmap. In either
case, the strmap_put() method returns the old value which is leaked.

Update branch_checked_out() to consume that pointer and free it.

Add a test in t2407 that checks this erroneous case. The test "checks
itself" by first confirming that the filesystem manipulations it makes
trigger the branch_checked_out() logic, and then sets up similar
manipulations to make it look like there are multiple worktrees pointing
to the same ref.

While TEST_PASSES_SANITIZE_LEAK would be helpful to demonstrate the
leakage and prevent it in the future, t2407 uses helpers such as 'git
clone' that cause the test to fail under that mode.

Signed-off-by: Derrick Stolee <derrickstolee@github.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
branch.c
t/t2407-worktree-heads.sh

index 34597c9554b9931595db831522d054795ee44882..526e8237673f0a1a262bda74f73e6a4c70b3f551 100644 (file)
--- a/branch.c
+++ b/branch.c
@@ -362,25 +362,29 @@ static void prepare_checked_out_branches(void)
        worktrees = get_worktrees();
 
        while (worktrees[i]) {
+               char *old;
                struct wt_status_state state = { 0 };
                struct worktree *wt = worktrees[i++];
 
                if (wt->is_bare)
                        continue;
 
-               if (wt->head_ref)
-                       strmap_put(&current_checked_out_branches,
-                                  wt->head_ref,
-                                  xstrdup(wt->path));
+               if (wt->head_ref) {
+                       old = strmap_put(&current_checked_out_branches,
+                                        wt->head_ref,
+                                        xstrdup(wt->path));
+                       free(old);
+               }
 
                if (wt_status_check_rebase(wt, &state) &&
                    (state.rebase_in_progress || state.rebase_interactive_in_progress) &&
                    state.branch) {
                        struct strbuf ref = STRBUF_INIT;
                        strbuf_addf(&ref, "refs/heads/%s", state.branch);
-                       strmap_put(&current_checked_out_branches,
-                                  ref.buf,
-                                  xstrdup(wt->path));
+                       old = strmap_put(&current_checked_out_branches,
+                                        ref.buf,
+                                        xstrdup(wt->path));
+                       free(old);
                        strbuf_release(&ref);
                }
                wt_status_state_free_buffers(&state);
@@ -389,9 +393,10 @@ static void prepare_checked_out_branches(void)
                    state.branch) {
                        struct strbuf ref = STRBUF_INIT;
                        strbuf_addf(&ref, "refs/heads/%s", state.branch);
-                       strmap_put(&current_checked_out_branches,
-                                  ref.buf,
-                                  xstrdup(wt->path));
+                       old = strmap_put(&current_checked_out_branches,
+                                        ref.buf,
+                                        xstrdup(wt->path));
+                       free(old);
                        strbuf_release(&ref);
                }
                wt_status_state_free_buffers(&state);
index a5aec1486c5a3cda713e3c34d0e7070aeebdbc85..b6be42f74a2b891bfd13a07b004cfa98aeb0f9a4 100755 (executable)
@@ -98,4 +98,32 @@ test_expect_success !SANITIZE_LEAK 'refuse to fetch over ref: worktree in rebase
        grep "refusing to fetch into branch" err
 '
 
+test_expect_success 'refuse to overwrite when in error states' '
+       test_when_finished rm -rf .git/worktrees/wt-*/rebase-merge &&
+       test_when_finished rm -rf .git/worktrees/wt-*/BISECT_* &&
+
+       # Both branches are currently under rebase.
+       mkdir -p .git/worktrees/wt-3/rebase-merge &&
+       touch .git/worktrees/wt-3/rebase-merge/interactive &&
+       echo refs/heads/fake-1 >.git/worktrees/wt-3/rebase-merge/head-name &&
+       echo refs/heads/fake-2 >.git/worktrees/wt-3/rebase-merge/onto &&
+       mkdir -p .git/worktrees/wt-4/rebase-merge &&
+       touch .git/worktrees/wt-4/rebase-merge/interactive &&
+       echo refs/heads/fake-2 >.git/worktrees/wt-4/rebase-merge/head-name &&
+       echo refs/heads/fake-1 >.git/worktrees/wt-4/rebase-merge/onto &&
+
+       # Both branches are currently under bisect.
+       touch .git/worktrees/wt-4/BISECT_LOG &&
+       echo refs/heads/fake-2 >.git/worktrees/wt-4/BISECT_START &&
+       touch .git/worktrees/wt-1/BISECT_LOG &&
+       echo refs/heads/fake-1 >.git/worktrees/wt-1/BISECT_START &&
+
+       for i in 1 2
+       do
+               test_must_fail git branch -f fake-$i HEAD 2>err &&
+               grep "cannot force update the branch '\''fake-$i'\'' checked out at" err ||
+                       return 1
+       done
+'
+
 test_done