merge-tree: load default git config
PATCH 1/1 merge-tree: load default git config
From: Derrick Stolee <[email protected]>
The 'git merge-tree' command handles creating root trees for merges without using the worktree. This is a critical operation in many Git hosts, as they typically store bare repositories.
This builtin does not load the default Git config, which can have several important ramifications.
In particular, one config that is loaded by default is core.useReplaceRefs. This is typically disabled in Git hosts due to the ability to spoof commits in strange ways.
Since this config is not loaded specifically during merge-tree, users were previously able to use refs/replace/ references to make pull requests that looked valid but introduced malicious content. The resulting merge commit would have the correct commit history, but the malicious content would exist in the root tree of the merge.
The fix is simple: load the default Git config in cmd_merge_tree(). This may also fix other behaviors that are effected by reading default config. The only possible downside is a little extra computation time spent reading config. The config parsing is placed after basic argument parsing so it does not slow down usage errors.
---
merge-tree: load default git config
This patch was reviewed on the Git security list, but the impact seemed limited to Git forges using merge-ort to create merge commits. The forges represented on the list have deployed versions of this patch and thus are no longer vulnerable.
Thanks, -Stolee
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1530%2Fderrickstolee%2Fstolee%2Frefs-replace-upstream-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1530/derrickstolee/stolee/refs-replace-upstream-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/1530
builtin/merge-tree.c | 3 +++ t/t4300-merge-tree.sh | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+)
builtin/merge-tree.c +3 -0
@@ -17,6 +17,7 @@#include "merge-blobs.h" #include "quote.h" #include "tree.h" +#include "config.h" static int line_termination ='\n' ;
@@ -628,6 +629,8 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)if (argc != expected_remaining_argc)usage_with_options (merge_tree_usage, mt_options); +git_config (git_default_config,NULL ); +/* Do the relevant type of merge */ if (o.mode ==MODE_REAL )return real_merge (&o, merge_base, argv[0 ], argv[1 ], prefix);
t/t4300-merge-tree.sh +18 -0
@@ -334,4 +334,22 @@ test_expect_success 'turn tree to file' ' test_cmp expect actual ' +test_expect_success 'merge-tree respects core.useReplaceRefs=false' ' + test_commit merge-to && + test_commit valid base && + git reset --hard HEAD^ && + test_commit malicious base && + + test_when_finished "git replace -d $(git rev-parse valid^0)" && + git replace valid^0 malicious^0 && + + tree=$(git -c core.useReplaceRefs=true merge-tree --write-tree merge-to valid) && + merged=$(git cat-file -p $tree:base) && + test malicious = $merged && + + tree=$(git -c core.useReplaceRefs=false merge-tree --write-tree merge-to valid) && + merged=$(git cat-file -p $tree:base) && + test valid = $merged +' + test_done
base-commit: 5597cfdf47db94825213fefe78c4485e6a5702d8
Signature · 1 line
gitgitgadget
Quoted 4 lines · Derrick Stolee via GitGitGadget
This patch was reviewed on the Git security list, but the impact seemed limited to Git forges using merge-ort to create merge commits. The forges represented on the list have deployed versions of this patch and thus are no longer vulnerable.
Let's queue directly on 'next' (unlike 'master', where we want to merge only commits that had exposure in 'next' for a week or so, there is no formal requirement for topics to enter 'next' before spending any time in 'seen') and fast-track to 'master', as I've seen it already reviewed adequately over there.
Thanks.
Quoted 7 lines · Junio C Hamano
Quoted 4 lines · Derrick Stolee via GitGitGadget
This patch was reviewed on the Git security list, but the impact seemed limited to Git forges using merge-ort to create merge commits. The forges represented on the list have deployed versions of this patch and thus are no longer vulnerable.
Quoted 5 lines
Let's queue directly on 'next' (unlike 'master', where we want to merge only commits that had exposure in 'next' for a week or so, there is no formal requirement for topics to enter 'next' before spending any time in 'seen') and fast-track to 'master', as I've seen it already reviewed adequately over there.
Thanks for picking it up so quickly. I did not mean to imply that we should merge to master without giving the public list time to digest the patch. It is a rather simple one, though.
Thanks,
-Stolee
Quoted 7 lines · Junio C Hamano
Quoted 4 lines · Derrick Stolee via GitGitGadget
This patch was reviewed on the Git security list, but the impact seemed limited to Git forges using merge-ort to create merge commits. The forges represented on the list have deployed versions of this patch and thus are no longer vulnerable.
Quoted 5 lines
Let's queue directly on 'next' (unlike 'master', where we want to merge only commits that had exposure in 'next' for a week or so, there is no formal requirement for topics to enter 'next' before spending any time in 'seen') and fast-track to 'master', as I've seen it already reviewed adequately over there.
Agreed. I also participated in the earlier rounds of review and the resulting patch looks obviously correct to me. I would be happy to see it merged.
I added Elijah to the CC list, since he is likely to be interested in this topic and may have thoughts to share.
Thanks,
Taylor
Quoted 15 lines · Taylor Blau
Quoted 7 lines · Junio C Hamano
Quoted 4 lines · Derrick Stolee via GitGitGadget
This patch was reviewed on the Git security list, but the impact seemed limited to Git forges using merge-ort to create merge commits. The forges represented on the list have deployed versions of this patch and thus are no longer vulnerable.
Quoted 5 lines
Let's queue directly on 'next' (unlike 'master', where we want to merge only commits that had exposure in 'next' for a week or so, there is no formal requirement for topics to enter 'next' before spending any time in 'seen') and fast-track to 'master', as I've seen it already reviewed adequately over there.
Quoted 6 lines
Agreed. I also participated in the earlier rounds of review and the resulting patch looks obviously correct to me. I would be happy to see it merged.
I added Elijah to the CC list, since he is likely to be interested in this topic and may have thoughts to share.
Thanks. I took a look and left some comments (it looks like the merge machinery already parses _most_ relevant merge-related config unconditionally, each time we set up a merge), but I had more questions than answers. :-)
Quoted 8 lines · Derrick Stolee via GitGitGadget
From: Derrick Stolee <[email protected]>
The 'git merge-tree' command handles creating root trees for merges without using the worktree. This is a critical operation in many Git hosts, as they typically store bare repositories.
This builtin does not load the default Git config, which can have several important ramifications.
For the record, I had already sent a better version of this patch almost 2 years ago [1], not just for git merge-tree, but other commands as well.
The obvious fix was completely ignored by the maintainer.
The reason why it should be git_xmerge_config and not git_default_config, is that merge.conflictstyle would not be parsed if you call git_default_config.
Quoted 13 lines
builtin/merge-tree.c +1
-0@@ -17,6 +17,7 @@#include "merge-blobs.h" #include "quote.h" #include "tree.h" +#include "config.h" static int line_termination ='\n' ;
builtin/merge-tree.c int cmd_merge_tree(int argc, const char **argv, const char *prefix) · line 632
Quoted 4 lines
builtin/merge-tree.c +1
-0@@ -628,6 +629,8 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)if (argc != expected_remaining_argc)usage_with_options (merge_tree_usage, mt_options); +git_config (git_default_config,NULL );
It should be git_xmerge_config.
builtin/merge-tree.c int cmd_merge_tree(int argc, const char **argv, const char *prefix) · line 636
Quoted 4 lines
builtin/merge-tree.c +1
-0+/* Do the relevant type of merge */ if (o.mode ==MODE_REAL )return real_merge (&o, merge_base, argv[0 ], argv[1 ], prefix);
[1] https://lore.kernel.org/git/[email protected]/
Signature · 1 line
Felipe Contreras
Quoted 10 lines · Felipe Contreras
Quoted 8 lines · Derrick Stolee via GitGitGadget
From: Derrick Stolee <[email protected]>
The 'git merge-tree' command handles creating root trees for merges without using the worktree. This is a critical operation in many Git hosts, as they typically store bare repositories.
This builtin does not load the default Git config, which can have several important ramifications.
Quoted 7 lines
For the record, I had already sent a better version of this patch almost 2 years ago [1], not just for
git merge-tree, but other commands as well.The obvious fix was completely ignored by the maintainer.
The reason why it should be
git_xmerge_configand notgit_default_config, is that merge.conflictstyle would not be parsed if you callgit_default_config.
As mentioned by Elijah in a different thread, the merge machinery loads the merge config as needed. I confirmed by creating this test, which I may submit as an independent patch:
test_expect_success 'merge-tree respects merge.conflictstyle' '
test_commit conflict-base && for branch in left right do git checkout -b $branch conflict-base && echo $branch >>conflict-base.t && git add conflict-base.t && git commit -m $branch || return 1 done && test_must_fail git merge-tree left right >out1 && test_must_fail git -c merge.conflictstyle=diff3 merge-tree left right >out2 && tree1=$(head -n 1 out1) && tree2=$(head -n 1 out2) && git cat-file -p $tree1:conflict-base.t >conflict1 && git cat-file -p $tree2:conflict-base.t >conflict2 && ! test_cmp conflict1 conflict2 && ! grep "||||||" conflict1 && grep "||||||" conflict2
'
Thus we do not need to use git_xmerge_config at this point in the process.
Thanks,
-Stolee
Quoted 44 lines · Derrick Stolee
Quoted 10 lines · Felipe Contreras
Quoted 8 lines · Derrick Stolee via GitGitGadget
From: Derrick Stolee <[email protected]>
The 'git merge-tree' command handles creating root trees for merges without using the worktree. This is a critical operation in many Git hosts, as they typically store bare repositories.
This builtin does not load the default Git config, which can have several important ramifications.
Quoted 7 lines
For the record, I had already sent a better version of this patch almost 2 years ago [1], not just for
git merge-tree, but other commands as well.The obvious fix was completely ignored by the maintainer.
The reason why it should be
git_xmerge_configand notgit_default_config, is that merge.conflictstyle would not be parsed if you callgit_default_config.As mentioned by Elijah in a different thread, the merge machinery loads the merge config as needed. I confirmed by creating this test, which I may submit as an independent patch:
test_expect_success'merge-tree respects merge.conflictstyle' 'test_commit conflict-base && for branch in left right do git checkout -b $branch conflict-base && echo $branch >>conflict-base.t && git add conflict-base.t && git commit -m $branch || return 1 done && test_must_fail git merge-tree left right >out1 && test_must_fail git -c merge.conflictstyle=diff3 merge-tree left right >out2 && tree1=$(head -n 1 out1) && tree2=$(head -n 1 out2) && git cat-file -p $tree1:conflict-base.t >conflict1 && git cat-file -p $tree2:conflict-base.t >conflict2 && ! test_cmp conflict1 conflict2 && ! grep "||||||" conflict1 && grep "||||||" conflict2
Quoted 1 line
'
This test is doing a real merge, not a trivial merge.
Try doing a trivial merge instead--which is what most of our testing framework checks--and your test fails:
@@ -14,17 +14,12 @@ test_expect_success 'merge-tree respects merge.conflictstyle' '
git commit -m $branch || return 1
done &&- test_must_fail git merge-tree left right >out1 && - test_must_fail git -c merge.conflictstyle=diff3 merge-tree left right >out2 && + test_expect_code 0 git merge-tree conflict-base left right >out1 && + test_expect_code 0 git -c merge.conflictstyle=diff3 merge-tree conflict-base left right >out2 &&
- tree1=$(head -n 1 out1) &&
- tree2=$(head -n 1 out2) &&
-
- git cat-file -p $tree1:conflict-base.t >conflict1 && - git cat-file -p $tree2:conflict-base.t >conflict2 && - ! test_cmp conflict1 conflict2 && - ! grep "||||||" conflict1 && - grep "||||||" conflict2 + ! test_cmp out1 out2 && + ! grep "||||||" out1 && + grep "||||||" out2
'
Signature · 1 line
Felipe Contreras
Quoted 14 lines · Derrick Stolee
Quoted 13 lines · Felipe Contreras
Quoted 8 lines · Derrick Stolee via GitGitGadget
From: Derrick Stolee <[email protected]>
The 'git merge-tree' command handles creating root trees for merges without using the worktree. This is a critical operation in many Git hosts, as they typically store bare repositories.
This builtin does not load the default Git config, which can have several important ramifications.
For the record, I had already sent a better version of this patch almost 2 years ago [1], not just for
git merge-tree, but other commands as well.
Quoted 8 lines
Quoted 4 lines
The obvious fix was completely ignored by the maintainer.
The reason why it should be
git_xmerge_configand notgit_default_config, is that merge.conflictstyle would not be parsed if you callgit_default_config.As mentioned by Elijah in a different thread, the merge machinery loads the merge config as needed. I confirmed by creating this test, which I may submit as an independent patch:
I wrote my patches before Elijah wrote the real merge implementation, and in his function he does init_merge_options(), which eventually calls git_config(git_xmerge_config, NULL).
But if git_config() is already called, you shouldn't need to add yet another git_config() call.
The problem is that he added init_merge_options() *after* the get_merge_parent() calls, that's why the configuration is ignored.
If we move init_merge_options() to the right place, the problem is fixed:
builtin/merge-tree.c +2 -2
@@ -424,6 +424,8 @@ static int real_merge(struct merge_tree_options *o,struct merge_result result = {0 };int show_messages = o->show_messages; +init_merge_options (&opt, the_repository); + parent1 =get_merge_parent (branch1);if (!parent1) help_unknown_ref(branch1, "merge-tree",
@@ -434,8 +436,6 @@ static int real_merge(struct merge_tree_options *o,help_unknown_ref (branch2,"merge-tree" ,_ ("not something we can merge" )); -init_merge_options (&opt, the_repository); - opt.show_rename_progress =0 ; opt.branch1 = branch1;
I ran your test case, and it passes.
I sent a patch for that here:
https://lore.kernel.org/git/[email protected]/
This is a more proper fix because a) it doesn't add any new line of code, b) it doesn't add a new include, and c) it doesn't call git_config() twice.
Cheers.
Signature · 1 line
Felipe Contreras
Quoted 5 lines · Derrick Stolee via GitGitGadget
The fix is simple: load the default Git config in
cmd_merge_tree(). This may also fix other behaviors that are effected by reading default config. The only possible downside is a little extra computation time spent reading config. The config parsing is placed after basic argument parsing so it does not slow down usage errors.
Presumably merge-tree wants to serve a low-level machinery that gives reliable reproducible result, we may want to keep the configuration variables we read as narrow as practical. The default_config() callback may still be wider than desirable from that point of view, but I guess that is the most reasonable choice?
Thanks.
Quoted 8 lines · Junio C Hamano
Quoted 5 lines · Derrick Stolee via GitGitGadget
The fix is simple: load the default Git config in
cmd_merge_tree(). This may also fix other behaviors that are effected by reading default config. The only possible downside is a little extra computation time spent reading config. The config parsing is placed after basic argument parsing so it does not slow down usage errors.
Quoted 5 lines
Presumably merge-tree wants to serve a low-level machinery that gives reliable reproducible result, we may want to keep the configuration variables we read as narrow as practical. The
default_config()callback may still be wider than desirable from that point of view, but I guess that is the most reasonable choice?
If you want to *intentionally* ignore merge.conflictStyle in git merge-tree then the documentation has to be updated as this is clearly not true:
The performed merge will use the same feature as the "real" linkgit:git-merge[1], including:
* three way content merges of individual files
* rename detection
* proper directory/file conflict handling
* recursive ancestor consolidation (i.e. when there is more than one merge base, creating a virtual merge base by merging the merge bases)
* etc.
If you want to ignore merge.conflictStyle, then git merge-tree would *not* be using the same features as the real git merge, in particular proper conflict halding (the conflict markers will be different).
Signature · 1 line
Felipe Contreras
Quoted 8 lines · Junio C Hamano
Quoted 5 lines · Derrick Stolee via GitGitGadget
The fix is simple: load the default Git config in
cmd_merge_tree(). This may also fix other behaviors that are effected by reading default config. The only possible downside is a little extra computation time spent reading config. The config parsing is placed after basic argument parsing so it does not slow down usage errors.
Quoted 5 lines
Presumably merge-tree wants to serve a low-level machinery that gives reliable reproducible result, we may want to keep the configuration variables we read as narrow as practical. The
default_config()callback may still be wider than desirable from that point of view, but I guess that is the most reasonable choice?
If you want git merge-tree to not call git_xmerge_config, then why did you merge 1f0c3a29da (merge-tree: implement real merges, 2022-06-18) which introduces a call to init_merge_options, which calls merge_recursive_config, which calls git_xmerge_config?
And BTW, the way merge_recursive_config is implemented is completely different to how the rest of the config infraestructure works.
Signature · 1 line
Felipe Contreras
Quoted 14 lines · Derrick Stolee via GitGitGadget
From: Derrick Stolee <[email protected]>
The 'git merge-tree' command handles creating root trees for merges without using the worktree. This is a critical operation in many Git hosts, as they typically store bare repositories.
This builtin does not load the default Git config, which can have several important ramifications.
In particular, one config that is loaded by default is core.useReplaceRefs. This is typically disabled in Git hosts due to the ability to spoof commits in strange ways.
Quoted 5 lines
Since this config is not loaded specifically during merge-tree, users were previously able to use refs/replace/ references to make pull requests that looked valid but introduced malicious content. The resulting merge commit would have the correct commit history, but the malicious content would exist in the root tree of the merge.
Ouch! So sorry for creating this problem.
Quoted 39 lines
The fix is simple: load the default Git config in
cmd_merge_tree(). This may also fix other behaviors that are effected by reading default config. The only possible downside is a little extra computation time spent reading config. The config parsing is placed after basic argument parsing so it does not slow down usage errors.Helped-by Johannes Schindelin <[email protected]>Signed-off-by Derrick Stolee <[email protected]>---
merge-tree: load default git configThis patch was reviewed on the Git security list, but the impact seemed limited to Git forges using merge-ort to create merge commits. The forges represented on the list have deployed versions of this patch and thus are no longer vulnerable.
Thanks, -Stolee
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1530%2Fderrickstolee%2Fstolee%2Frefs-replace-upstream-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1530/derrickstolee/stolee/refs-replace-upstream-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/1530builtin/merge-tree.c | 3 +++ t/t4300-merge-tree.sh | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+)builtin/merge-tree.c +1
-0@@ -17,6 +17,7 @@#include "merge-blobs.h" #include "quote.h" #include "tree.h" +#include "config.h" static int line_termination ='\n' ;
builtin/merge-tree.c int cmd_merge_tree(int argc, const char **argv, const char *prefix) · line 633
Quoted 5 lines
builtin/merge-tree.c +2
-0@@ -628,6 +629,8 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)if (argc != expected_remaining_argc)usage_with_options (merge_tree_usage, mt_options); +git_config (git_default_config,NULL ); +
Always nice when it's a simple fix. :-)
I am curious though...
init_merge_options() in merge-recursive.c (which is also used by merge-ort) calls merge_recursive_config(). merge_recursive_config() does a bunch of config parsing, regardless of whatever config parsing is done beforehand by the caller of init_merge_options(). This makes me wonder if the config which handles replace refs should be included in merge_recursive_config() as well. Doing so would have the added benefit of making sure all the builtins calling the merge logic behave similarly. And if we copy/move the replace-refs-handling config logic, does that replace the fix in this patch, or just supplement it?
To be honest, I've mostly ignored the config side of things while working on the merge machinery, so I didn't even know (or at least remember) the above details until I went digging just now. I don't know if the way init_merge_options()/merge_recursive_config() is how we should do things, or just vestiges of how it's evolved from 15 years ago.
Quoted 20 lines
builtin/merge-tree.c +0
-0/* Do the relevant type of merge */ if (o.mode ==MODE_REAL )return real_merge (&o, merge_base, argv[0 ], argv[1 ], prefix);t/t4300-merge-tree.sh +9
-0@@ -334,4 +334,22 @@ test_expect_success 'turn tree to file' ' test_cmp expect actual ' +test_expect_success 'merge-tree respects core.useReplaceRefs=false' ' + test_commit merge-to && + test_commit valid base && + git reset --hard HEAD^ && + test_commit malicious base && + + test_when_finished "git replace -d $(git rev-parse valid^0)" && + git replace valid^0 malicious^0 && +
t/t4300-merge-tree.sh test_expect_success 'turn tree to file' ' · line 355
Quoted 10 lines
t/t4300-merge-tree.sh +9
-0+ tree=$(git -c core.useReplaceRefs=true merge-tree --write-tree merge-to valid) && + merged=$(git cat-file -p $tree:base) && + test malicious = $merged && + + tree=$(git -c core.useReplaceRefs=false merge-tree --write-tree merge-to valid) && + merged=$(git cat-file -p $tree:base) && + test valid = $merged +' + test_done
Thanks for adding the test case too, as always.
Quoted 3 lines
base-commit:
5597cfdf47db94825213fefe78c4485e6a5702d8--
gitgitgadget
Looks good. I am curious for other's thoughts on whether it may make sense to add parsing of core.useReplaceRefs within merge_recursive_config().
Quoted 83 lines · Elijah Newren
Quoted 14 lines · Derrick Stolee via GitGitGadget
From: Derrick Stolee <[email protected]>
The 'git merge-tree' command handles creating root trees for merges without using the worktree. This is a critical operation in many Git hosts, as they typically store bare repositories.
This builtin does not load the default Git config, which can have several important ramifications.
In particular, one config that is loaded by default is core.useReplaceRefs. This is typically disabled in Git hosts due to the ability to spoof commits in strange ways.
Quoted 5 lines
Since this config is not loaded specifically during merge-tree, users were previously able to use refs/replace/ references to make pull requests that looked valid but introduced malicious content. The resulting merge commit would have the correct commit history, but the malicious content would exist in the root tree of the merge.
Ouch! So sorry for creating this problem.
Quoted 39 lines
The fix is simple: load the default Git config in
cmd_merge_tree(). This may also fix other behaviors that are effected by reading default config. The only possible downside is a little extra computation time spent reading config. The config parsing is placed after basic argument parsing so it does not slow down usage errors.Helped-by Johannes Schindelin <[email protected]>Signed-off-by Derrick Stolee <[email protected]>---
merge-tree: load default git configThis patch was reviewed on the Git security list, but the impact seemed limited to Git forges using merge-ort to create merge commits. The forges represented on the list have deployed versions of this patch and thus are no longer vulnerable.
Thanks, -Stolee
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1530%2Fderrickstolee%2Fstolee%2Frefs-replace-upstream-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1530/derrickstolee/stolee/refs-replace-upstream-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/1530builtin/merge-tree.c | 3 +++ t/t4300-merge-tree.sh | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+)builtin/merge-tree.c +1
-0@@ -17,6 +17,7 @@#include "merge-blobs.h" #include "quote.h" #include "tree.h" +#include "config.h" static int line_termination ='\n' ;builtin/merge-tree.c int cmd_merge_tree(int argc, const char **argv, const char *prefix) · line 633
Quoted 5 lines
builtin/merge-tree.c +2
-0@@ -628,6 +629,8 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)if (argc != expected_remaining_argc)usage_with_options (merge_tree_usage, mt_options); +git_config (git_default_config,NULL ); +Always nice when it's a simple fix. :-)
I am curious though...
init_merge_options()inmerge-recursive.c(which is also used by merge-ort) callsmerge_recursive_config().merge_recursive_config()does a bunch of config parsing, regardless of whatever config parsing is done beforehand by the caller ofinit_merge_options(). This makes me wonder if the config which handles replace refs should be included inmerge_recursive_config()as well. Doing so would have the added benefit of making sure all the builtins calling the merge logic behave similarly. And if we copy/move the replace-refs-handling config logic, does that replace the fix in this patch, or just supplement it?
Quoted 6 lines
To be honest, I've mostly ignored the config side of things while working on the merge machinery, so I didn't even know (or at least remember) the above details until I went digging just now. I don't know if the way init_merge_options()/merge_recursive_config() is how we should do things, or just vestiges of how it's evolved from 15 years ago.
It's obvious this is not the way we should do things as configurations are overriden when they shouldn't be.
Something like this [1] is obviously needed.
[1] https://lore.kernel.org/git/[email protected]/
Signature · 1 line
Felipe Contreras
Quoted 39 lines · Elijah Newren
Quoted 14 lines · Derrick Stolee via GitGitGadget
From: Derrick Stolee <[email protected]>
The 'git merge-tree' command handles creating root trees for merges without using the worktree. This is a critical operation in many Git hosts, as they typically store bare repositories.
This builtin does not load the default Git config, which can have several important ramifications.
In particular, one config that is loaded by default is core.useReplaceRefs. This is typically disabled in Git hosts due to the ability to spoof commits in strange ways.
builtin/merge-tree.c int cmd_merge_tree(int argc, const char **argv, const char *prefix) · line 633
Quoted 2 lines
builtin/merge-tree.c +2
-0+git_config (git_default_config,NULL ); +Always nice when it's a simple fix. :-)
I am curious though...
init_merge_options()inmerge-recursive.c(which is also used by merge-ort) callsmerge_recursive_config().merge_recursive_config()does a bunch of config parsing, regardless of whatever config parsing is done beforehand by the caller ofinit_merge_options(). This makes me wonder if the config which handles replace refs should be included inmerge_recursive_config()as well. Doing so would have the added benefit of making sure all the builtins calling the merge logic behave similarly. And if we copy/move the replace-refs-handling config logic, does that replace the fix in this patch, or just supplement it?To be honest, I've mostly ignored the config side of things while working on the merge machinery, so I didn't even know (or at least remember) the above details until I went digging just now. I don't know if the way init_merge_options()/merge_recursive_config() is how we should do things, or just vestiges of how it's evolved from 15 years ago.
...
Quoted 3 lines
Looks good. I am curious for other's thoughts on whether it may make sense to add parsing of core.useReplaceRefs within
merge_recursive_config().
In terms of a "real" fix to this kind of problem, I'm thinking that we actually need to be sure we've parsed things like core.useReplaceRefs when loading the object database for the first time.
Here, I'm suggesting the simplest fix before we can go about a more rigorous change to prevent this from happening again.
The custom ahead-behind builtin that we have in our fork once also had this same problem, and the fix was exactly like this. The impact was less severe (mostly, things slowed down because the commit-graph was disabled, but also the numbers could be different from expected).
Thanks,
-Stolee
PATCH 1/1 merge-tree: load config correctly
In reply to Derrick Stolee via GitGitGadgetWhen real merges were implemented in 1f0c3a29da (merge-tree: implement real merges, 2022-06-18), init_merge_options was called after doing get_merge_parent, which means core.useReplaceRefs was not parsed at the moment the commit objects were parsed, and thus essentially this option was ignored.
This configuration is typically disabled in git hosts due to the ability to spoof commits in strange ways. Users are able to use refs/replace/ references to make pull requests that look valid but introduce malicious content. The resulting merge has the correct commit history, but the malicious content exists in the root tree of the merge.
To fix this let's simply load the configuration before any call to get_merge_parent().
--- builtin/merge-tree.c | 4 ++-- t/t4300-merge-tree.sh | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-)
builtin/merge-tree.c +2 -2
@@ -424,6 +424,8 @@ static int real_merge(struct merge_tree_options *o,struct merge_result result = {0 };int show_messages = o->show_messages; +init_merge_options (&opt, the_repository); + parent1 =get_merge_parent (branch1);if (!parent1) help_unknown_ref(branch1, "merge-tree",
@@ -434,8 +436,6 @@ static int real_merge(struct merge_tree_options *o,help_unknown_ref (branch2,"merge-tree" ,_ ("not something we can merge" )); -init_merge_options (&opt, the_repository); - opt.show_rename_progress =0 ; opt.branch1 = branch1;
t/t4300-merge-tree.sh +18 -0
@@ -334,4 +334,22 @@ test_expect_success 'turn tree to file' ' test_cmp expect actual ' +test_expect_success 'merge-tree respects core.useReplaceRefs=false' ' + test_commit merge-to && + test_commit valid base && + git reset --hard HEAD^ && + test_commit malicious base && + + test_when_finished "git replace -d $(git rev-parse valid^0)" && + git replace valid^0 malicious^0 && + + tree=$(git -c core.useReplaceRefs=true merge-tree --write-tree merge-to valid) && + merged=$(git cat-file -p $tree:base) && + test malicious = $merged && + + tree=$(git -c core.useReplaceRefs=false merge-tree --write-tree merge-to valid) && + merged=$(git cat-file -p $tree:base) && + test valid = $merged +' + test_done
Signature · 1 line
2.40.0+fc1