diff --git a/config.c b/config.c index e0bb29b53d..2b84fb215c 100644 --- a/config.c +++ b/config.c @@ -3066,6 +3066,28 @@ int repo_config_set_multivar_in_file_gently(struct repository *r, if (!config_filename) config_filename = filename_buf = repo_git_path(r, "config"); + /* + * Do not allow updating a read-only configuration file; it is + * annoying and alarming that "chmod a-w $HOME/.gitconfig" does not + * prevent the global configuration file from getting clobbered by + * mistake with the usual "create a new lockfile, copy the original + * while setting a new value, propagate the permission bits to the + * lockfile, and then rename it to replace the original" dance. + */ + if (!access(config_filename, W_OK)) { + ; /* happy: we can write to it */ + } else if (errno == ENOENT) { + ; /* happy: we are creating this file anew */ + } else { + /* + * most likely EACCES but other errors also indicate + * that we cannot write to it anyway. + */ + error_errno(_("file %s unwritable"), config_filename); + ret = CONFIG_NO_WRITE; + goto out_free; + } + /* * The lock serves a purpose in addition to locking: the new * contents of .git/config will be written into it. diff --git a/t/t1300-config.sh b/t/t1300-config.sh index e3f8064889..876d376cc7 100755 --- a/t/t1300-config.sh +++ b/t/t1300-config.sh @@ -3019,4 +3019,34 @@ test_expect_success 'writing config retries until lock is released' ' test "$(git config retried.key)" = value ' +test_expect_success POSIXPERM 'refuse to "overwrite" a read-only file' ' + gconf=custom-global-config && + test_when_finished "chmod +w $gconf" && + ( + GIT_CONFIG_GLOBAL=$gconf && + export GIT_CONFIG_GLOBAL && + git config set --global some.variable some-value && + git config list --global >expect && + + chmod a-w $gconf && + # "get" from read-only file should just work fine. + git config list --global >actual && + + # "set" to read-only file should be refused. + test_must_fail git config set --global some.variable other-value && + git config list --global >actual && + test_cmp expect actual && + + # traditional "config var val" syntax to set + test_must_fail git config --global some.variable third-value && + git config list --global >actual && + test_cmp expect actual && + + # "unset" to read-only file should be refused. + test_must_fail git config unset --global some.variable && + git config list --global >actual && + test_cmp expect actual + ) +' + test_done