Compare commits
10 Commits
a7a2f6c238
...
f76993c5a4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f76993c5a4 | ||
|
|
82c376fb9b | ||
|
|
32f1379e74 | ||
|
|
11064c8bff | ||
|
|
9cdb4bec4c | ||
|
|
ac09e19f00 | ||
|
|
7ffd11bad4 | ||
|
|
ea9184ed90 | ||
|
|
2084bc5915 | ||
|
|
77c39c2253 |
164
backport-CVE-2024-32002-submodules-submodule-paths-m.patch
Normal file
164
backport-CVE-2024-32002-submodules-submodule-paths-m.patch
Normal file
@ -0,0 +1,164 @@
|
||||
From 6393e6afd414ab9ebeffe069726440d397cae268 Mon Sep 17 00:00:00 2001
|
||||
From: Johannes Schindelin <johannes.schindelin@gmx.de>
|
||||
Date: Fri, 22 Mar 2024 11:19:22 +0100
|
||||
Subject: [PATCH] backport CVE-2024-32002 submodules: submodule paths must not
|
||||
contain symlinks
|
||||
|
||||
mainline inclusion
|
||||
from v2.43.4
|
||||
commit 97065761333fd62db1912d81b489db938d8c991d
|
||||
category: bugfix
|
||||
bugzilla: https://nvd.nist.gov/vuln/detail/CVE-2024-32002
|
||||
CVE: CVE-2024-32002
|
||||
|
||||
When creating a submodule path, we must be careful not to follow
|
||||
symbolic links. Otherwise we may follow a symbolic link pointing to
|
||||
a gitdir (which are valid symbolic links!) e.g. while cloning.
|
||||
|
||||
On case-insensitive filesystems, however, we blindly replace a directory
|
||||
that has been created as part of the `clone` operation with a symlink
|
||||
when the path to the latter differs only in case from the former's path.
|
||||
|
||||
Let's simply avoid this situation by expecting not ever having to
|
||||
overwrite any existing file/directory/symlink upon cloning. That way, we
|
||||
won't even replace a directory that we just created.
|
||||
|
||||
This addresses CVE-2024-32002.
|
||||
confliects:
|
||||
t/t7406-submodule-update.sh
|
||||
Reported-by: Filip Hejsek <filip.hejsek@gmail.com>
|
||||
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
|
||||
Signed-off-by: qiaojijun <qiaojijun@kylinos.cn>
|
||||
---
|
||||
builtin/submodule--helper.c | 35 +++++++++++++++++++++++++++
|
||||
t/t7406-submodule-update.sh | 48 +++++++++++++++++++++++++++++++++++++
|
||||
2 files changed, 83 insertions(+)
|
||||
|
||||
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
|
||||
index cce4645..c46d420 100644
|
||||
--- a/builtin/submodule--helper.c
|
||||
+++ b/builtin/submodule--helper.c
|
||||
@@ -1663,12 +1663,35 @@ static char *clone_submodule_sm_gitdir(const char *name)
|
||||
return sm_gitdir;
|
||||
}
|
||||
|
||||
+static int dir_contains_only_dotgit(const char *path)
|
||||
+{
|
||||
+ DIR *dir = opendir(path);
|
||||
+ struct dirent *e;
|
||||
+ int ret = 1;
|
||||
+
|
||||
+ if (!dir)
|
||||
+ return 0;
|
||||
+
|
||||
+ e = readdir_skip_dot_and_dotdot(dir);
|
||||
+ if (!e)
|
||||
+ ret = 0;
|
||||
+ else if (strcmp(DEFAULT_GIT_DIR_ENVIRONMENT, e->d_name) ||
|
||||
+ (e = readdir_skip_dot_and_dotdot(dir))) {
|
||||
+ error("unexpected item '%s' in '%s'", e->d_name, path);
|
||||
+ ret = 0;
|
||||
+ }
|
||||
+
|
||||
+ closedir(dir);
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
static int clone_submodule(const struct module_clone_data *clone_data,
|
||||
struct string_list *reference)
|
||||
{
|
||||
char *p;
|
||||
char *sm_gitdir = clone_submodule_sm_gitdir(clone_data->name);
|
||||
char *sm_alternate = NULL, *error_strategy = NULL;
|
||||
+ struct stat st;
|
||||
struct child_process cp = CHILD_PROCESS_INIT;
|
||||
const char *clone_data_path = clone_data->path;
|
||||
char *to_free = NULL;
|
||||
@@ -1682,6 +1705,10 @@ static int clone_submodule(const struct module_clone_data *clone_data,
|
||||
"git dir"), sm_gitdir);
|
||||
|
||||
if (!file_exists(sm_gitdir)) {
|
||||
+ if (clone_data->require_init && !stat(clone_data_path, &st) &&
|
||||
+ !is_empty_dir(clone_data_path))
|
||||
+ die(_("directory not empty: '%s'"), clone_data_path);
|
||||
+
|
||||
if (safe_create_leading_directories_const(sm_gitdir) < 0)
|
||||
die(_("could not create directory '%s'"), sm_gitdir);
|
||||
|
||||
@@ -1726,6 +1753,14 @@ static int clone_submodule(const struct module_clone_data *clone_data,
|
||||
if(run_command(&cp))
|
||||
die(_("clone of '%s' into submodule path '%s' failed"),
|
||||
clone_data->url, clone_data_path);
|
||||
+
|
||||
+ if (clone_data->require_init && !stat(clone_data_path, &st) &&
|
||||
+ !dir_contains_only_dotgit(clone_data_path)) {
|
||||
+ char *dot_git = xstrfmt("%s/.git", clone_data_path);
|
||||
+ unlink(dot_git);
|
||||
+ free(dot_git);
|
||||
+ die(_("directory not empty: '%s'"), clone_data_path);
|
||||
+ }
|
||||
} else {
|
||||
char *path;
|
||||
|
||||
diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh
|
||||
index 8491b8c..1f98b01 100755
|
||||
--- a/t/t7406-submodule-update.sh
|
||||
+++ b/t/t7406-submodule-update.sh
|
||||
@@ -1179,6 +1179,54 @@ test_expect_success 'submodule update --recursive skip submodules with strategy=
|
||||
test_cmp expect.err actual.err
|
||||
'
|
||||
|
||||
+test_expect_success CASE_INSENSITIVE_FS,SYMLINKS \
|
||||
+ 'submodule paths must not follow symlinks' '
|
||||
+
|
||||
+ # This is only needed because we want to run this in a self-contained
|
||||
+ # test without having to spin up an HTTP server; However, it would not
|
||||
+ # be needed in a real-world scenario where the submodule is simply
|
||||
+ # hosted on a public site.
|
||||
+ test_config_global protocol.file.allow always &&
|
||||
+
|
||||
+ # Make sure that Git tries to use symlinks on Windows
|
||||
+ test_config_global core.symlinks true &&
|
||||
+
|
||||
+ tell_tale_path="$PWD/tell.tale" &&
|
||||
+ git init hook &&
|
||||
+ (
|
||||
+ cd hook &&
|
||||
+ mkdir -p y/hooks &&
|
||||
+ write_script y/hooks/post-checkout <<-EOF &&
|
||||
+ echo HOOK-RUN >&2
|
||||
+ echo hook-run >"$tell_tale_path"
|
||||
+ EOF
|
||||
+ git add y/hooks/post-checkout &&
|
||||
+ test_tick &&
|
||||
+ git commit -m post-checkout
|
||||
+ ) &&
|
||||
+
|
||||
+ hook_repo_path="$(pwd)/hook" &&
|
||||
+ git init captain &&
|
||||
+ (
|
||||
+ cd captain &&
|
||||
+ git submodule add --name x/y "$hook_repo_path" A/modules/x &&
|
||||
+ test_tick &&
|
||||
+ git commit -m add-submodule &&
|
||||
+
|
||||
+ printf .git >dotgit.txt &&
|
||||
+ git hash-object -w --stdin <dotgit.txt >dot-git.hash &&
|
||||
+ printf "120000 %s 0\ta\n" "$(cat dot-git.hash)" >index.info &&
|
||||
+ git update-index --index-info <index.info &&
|
||||
+ test_tick &&
|
||||
+ git commit -m add-symlink
|
||||
+ ) &&
|
||||
+
|
||||
+ test_path_is_missing "$tell_tale_path" &&
|
||||
+ test_must_fail git clone --recursive captain hooked 2>err &&
|
||||
+ grep "directory not empty" err &&
|
||||
+ test_path_is_missing "$tell_tale_path"
|
||||
+'
|
||||
+
|
||||
add_submodule_commit_and_validate () {
|
||||
HASH=$(git rev-parse HEAD) &&
|
||||
git update-index --add --cacheinfo 160000,$HASH,sub &&
|
||||
--
|
||||
2.20.1
|
||||
|
||||
@ -0,0 +1,153 @@
|
||||
From f4aa8c8bb11dae6e769cd930565173808cbb69c8 Mon Sep 17 00:00:00 2001
|
||||
From: Johannes Schindelin <johannes.schindelin@gmx.de>
|
||||
Date: Wed, 10 Apr 2024 14:39:37 +0200
|
||||
Subject: [PATCH] fetch/clone: detect dubious ownership of local repositories
|
||||
|
||||
When cloning from somebody else's repositories, it is possible that,
|
||||
say, the `upload-pack` command is overridden in the repository that is
|
||||
about to be cloned, which would then be run in the user's context who
|
||||
started the clone.
|
||||
|
||||
To remind the user that this is a potentially unsafe operation, let's
|
||||
extend the ownership checks we have already established for regular
|
||||
gitdir discovery to extend also to local repositories that are about to
|
||||
be cloned.
|
||||
|
||||
This protection extends also to file:// URLs.
|
||||
|
||||
The fixes in this commit address CVE-2024-32004.
|
||||
|
||||
Note: This commit does not touch the `fetch`/`clone` code directly, but
|
||||
instead the function used implicitly by both: `enter_repo()`. This
|
||||
function is also used by `git receive-pack` (i.e. pushes), by `git
|
||||
upload-archive`, by `git daemon` and by `git http-backend`. In setups
|
||||
that want to serve repositories owned by different users than the
|
||||
account running the service, this will require `safe.*` settings to be
|
||||
configured accordingly.
|
||||
|
||||
Also note: there are tiny time windows where a time-of-check-time-of-use
|
||||
("TOCTOU") race is possible. The real solution to those would be to work
|
||||
with `fstat()` and `openat()`. However, the latter function is not
|
||||
available on Windows (and would have to be emulated with rather
|
||||
expensive low-level `NtCreateFile()` calls), and the changes would be
|
||||
quite extensive, for my taste too extensive for the little gain given
|
||||
that embargoed releases need to pay extra attention to avoid introducing
|
||||
inadvertent bugs.
|
||||
|
||||
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
|
||||
---
|
||||
setup.h | 12 ++++++++++++
|
||||
path.c | 2 ++
|
||||
setup.c | 21 +++++++++++++++++++++
|
||||
t/t0411-clone-from-partial.sh | 6 +++---
|
||||
4 files changed, 38 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/setup.h b/setup.h
|
||||
index fcf49706a..a46a3e4b6 100644
|
||||
--- a/setup.h
|
||||
+++ b/setup.h
|
||||
@@ -41,6 +41,18 @@ const char *read_gitfile_gently(const char *path, int *return_error_code);
|
||||
const char *resolve_gitdir_gently(const char *suspect, int *return_error_code);
|
||||
#define resolve_gitdir(path) resolve_gitdir_gently((path), NULL)
|
||||
|
||||
+/*
|
||||
+ * Check if a repository is safe and die if it is not, by verifying the
|
||||
+ * ownership of the worktree (if any), the git directory, and the gitfile (if
|
||||
+ * any).
|
||||
+ *
|
||||
+ * Exemptions for known-safe repositories can be added via `safe.directory`
|
||||
+ * config settings; for non-bare repositories, their worktree needs to be
|
||||
+ * added, for bare ones their git directory.
|
||||
+ */
|
||||
+void die_upon_dubious_ownership(const char *gitfile, const char *worktree,
|
||||
+ const char *gitdir);
|
||||
+
|
||||
void setup_work_tree(void);
|
||||
|
||||
/*
|
||||
diff --git a/path.c b/path.c
|
||||
index 492e17ad1..d61f70e87 100644
|
||||
--- a/path.c
|
||||
+++ b/path.c
|
||||
@@ -840,6 +840,7 @@ const char *enter_repo(const char *path, int strict)
|
||||
if (!suffix[i])
|
||||
return NULL;
|
||||
gitfile = read_gitfile(used_path.buf);
|
||||
+ die_upon_dubious_ownership(gitfile, NULL, used_path.buf);
|
||||
if (gitfile) {
|
||||
strbuf_reset(&used_path);
|
||||
strbuf_addstr(&used_path, gitfile);
|
||||
@@ -850,6 +851,7 @@ const char *enter_repo(const char *path, int strict)
|
||||
}
|
||||
else {
|
||||
const char *gitfile = read_gitfile(path);
|
||||
+ die_upon_dubious_ownership(gitfile, NULL, path);
|
||||
if (gitfile)
|
||||
path = gitfile;
|
||||
if (chdir(path))
|
||||
diff --git a/setup.c b/setup.c
|
||||
index cefd5f63c..9d401ae4c 100644
|
||||
--- a/setup.c
|
||||
+++ b/setup.c
|
||||
@@ -1165,6 +1165,27 @@ static int ensure_valid_ownership(const char *gitfile,
|
||||
return data.is_safe;
|
||||
}
|
||||
|
||||
+void die_upon_dubious_ownership(const char *gitfile, const char *worktree,
|
||||
+ const char *gitdir)
|
||||
+{
|
||||
+ struct strbuf report = STRBUF_INIT, quoted = STRBUF_INIT;
|
||||
+ const char *path;
|
||||
+
|
||||
+ if (ensure_valid_ownership(gitfile, worktree, gitdir, &report))
|
||||
+ return;
|
||||
+
|
||||
+ strbuf_complete(&report, '\n');
|
||||
+ path = gitfile ? gitfile : gitdir;
|
||||
+ sq_quote_buf_pretty("ed, path);
|
||||
+
|
||||
+ die(_("detected dubious ownership in repository at '%s'\n"
|
||||
+ "%s"
|
||||
+ "To add an exception for this directory, call:\n"
|
||||
+ "\n"
|
||||
+ "\tgit config --global --add safe.directory %s"),
|
||||
+ path, report.buf, quoted.buf);
|
||||
+}
|
||||
+
|
||||
static int allowed_bare_repo_cb(const char *key, const char *value,
|
||||
const struct config_context *ctx UNUSED,
|
||||
void *d)
|
||||
diff --git a/t/t0411-clone-from-partial.sh b/t/t0411-clone-from-partial.sh
|
||||
index fb72a0a9f..eb3360dbc 100755
|
||||
--- a/t/t0411-clone-from-partial.sh
|
||||
+++ b/t/t0411-clone-from-partial.sh
|
||||
@@ -23,7 +23,7 @@ test_expect_success 'create evil repo' '
|
||||
>evil/.git/shallow
|
||||
'
|
||||
|
||||
-test_expect_failure 'local clone must not fetch from promisor remote and execute script' '
|
||||
+test_expect_success 'local clone must not fetch from promisor remote and execute script' '
|
||||
rm -f script-executed &&
|
||||
test_must_fail git clone \
|
||||
--upload-pack="GIT_TEST_ASSUME_DIFFERENT_OWNER=true git-upload-pack" \
|
||||
@@ -32,7 +32,7 @@ test_expect_failure 'local clone must not fetch from promisor remote and execute
|
||||
test_path_is_missing script-executed
|
||||
'
|
||||
|
||||
-test_expect_failure 'clone from file://... must not fetch from promisor remote and execute script' '
|
||||
+test_expect_success 'clone from file://... must not fetch from promisor remote and execute script' '
|
||||
rm -f script-executed &&
|
||||
test_must_fail git clone \
|
||||
--upload-pack="GIT_TEST_ASSUME_DIFFERENT_OWNER=true git-upload-pack" \
|
||||
@@ -41,7 +41,7 @@ test_expect_failure 'clone from file://... must not fetch from promisor remote a
|
||||
test_path_is_missing script-executed
|
||||
'
|
||||
|
||||
-test_expect_failure 'fetch from file://... must not fetch from promisor remote and execute script' '
|
||||
+test_expect_success 'fetch from file://... must not fetch from promisor remote and execute script' '
|
||||
rm -f script-executed &&
|
||||
test_must_fail git fetch \
|
||||
--upload-pack="GIT_TEST_ASSUME_DIFFERENT_OWNER=true git-upload-pack" \
|
||||
--
|
||||
2.33.0
|
||||
|
||||
@ -0,0 +1,90 @@
|
||||
From 5c5a4a1c05932378d259b1fdd9526cab971656a2 Mon Sep 17 00:00:00 2001
|
||||
From: Filip Hejsek <filip.hejsek@gmail.com>
|
||||
Date: Sun, 28 Jan 2024 04:29:33 +0100
|
||||
Subject: [PATCH] t0411: add tests for cloning from partial repo
|
||||
|
||||
Cloning from a partial repository must not fetch missing objects into
|
||||
the partial repository, because that can lead to arbitrary code
|
||||
execution.
|
||||
|
||||
Add a couple of test cases, pretending to the `upload-pack` command (and
|
||||
to that command only) that it is working on a repository owned by
|
||||
someone else.
|
||||
|
||||
Helped-by: Jeff King <peff@peff.net>
|
||||
Signed-off-by: Filip Hejsek <filip.hejsek@gmail.com>
|
||||
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
|
||||
---
|
||||
t/t0411-clone-from-partial.sh | 60 +++++++++++++++++++++++++++++++++++
|
||||
1 file changed, 60 insertions(+)
|
||||
create mode 100755 t/t0411-clone-from-partial.sh
|
||||
|
||||
diff --git a/t/t0411-clone-from-partial.sh b/t/t0411-clone-from-partial.sh
|
||||
new file mode 100755
|
||||
index 000000000..fb72a0a9f
|
||||
--- /dev/null
|
||||
+++ b/t/t0411-clone-from-partial.sh
|
||||
@@ -0,0 +1,60 @@
|
||||
+#!/bin/sh
|
||||
+
|
||||
+test_description='check that local clone does not fetch from promisor remotes'
|
||||
+
|
||||
+. ./test-lib.sh
|
||||
+
|
||||
+test_expect_success 'create evil repo' '
|
||||
+ git init tmp &&
|
||||
+ test_commit -C tmp a &&
|
||||
+ git -C tmp config uploadpack.allowfilter 1 &&
|
||||
+ git clone --filter=blob:none --no-local --no-checkout tmp evil &&
|
||||
+ rm -rf tmp &&
|
||||
+
|
||||
+ git -C evil config remote.origin.uploadpack \"\$TRASH_DIRECTORY/fake-upload-pack\" &&
|
||||
+ write_script fake-upload-pack <<-\EOF &&
|
||||
+ echo >&2 "fake-upload-pack running"
|
||||
+ >"$TRASH_DIRECTORY/script-executed"
|
||||
+ exit 1
|
||||
+ EOF
|
||||
+ export TRASH_DIRECTORY &&
|
||||
+
|
||||
+ # empty shallow file disables local clone optimization
|
||||
+ >evil/.git/shallow
|
||||
+'
|
||||
+
|
||||
+test_expect_failure 'local clone must not fetch from promisor remote and execute script' '
|
||||
+ rm -f script-executed &&
|
||||
+ test_must_fail git clone \
|
||||
+ --upload-pack="GIT_TEST_ASSUME_DIFFERENT_OWNER=true git-upload-pack" \
|
||||
+ evil clone1 2>err &&
|
||||
+ ! grep "fake-upload-pack running" err &&
|
||||
+ test_path_is_missing script-executed
|
||||
+'
|
||||
+
|
||||
+test_expect_failure 'clone from file://... must not fetch from promisor remote and execute script' '
|
||||
+ rm -f script-executed &&
|
||||
+ test_must_fail git clone \
|
||||
+ --upload-pack="GIT_TEST_ASSUME_DIFFERENT_OWNER=true git-upload-pack" \
|
||||
+ "file://$(pwd)/evil" clone2 2>err &&
|
||||
+ ! grep "fake-upload-pack running" err &&
|
||||
+ test_path_is_missing script-executed
|
||||
+'
|
||||
+
|
||||
+test_expect_failure 'fetch from file://... must not fetch from promisor remote and execute script' '
|
||||
+ rm -f script-executed &&
|
||||
+ test_must_fail git fetch \
|
||||
+ --upload-pack="GIT_TEST_ASSUME_DIFFERENT_OWNER=true git-upload-pack" \
|
||||
+ "file://$(pwd)/evil" 2>err &&
|
||||
+ ! grep "fake-upload-pack running" err &&
|
||||
+ test_path_is_missing script-executed
|
||||
+'
|
||||
+
|
||||
+test_expect_success 'pack-objects should fetch from promisor remote and execute script' '
|
||||
+ rm -f script-executed &&
|
||||
+ echo "HEAD" | test_must_fail git -C evil pack-objects --revs --stdout >/dev/null 2>err &&
|
||||
+ grep "fake-upload-pack running" err &&
|
||||
+ test_path_is_file script-executed
|
||||
+'
|
||||
+
|
||||
+test_done
|
||||
--
|
||||
2.33.0
|
||||
|
||||
@ -0,0 +1,109 @@
|
||||
From 1204e1a824c34071019fe106348eaa6d88f9528d Mon Sep 17 00:00:00 2001
|
||||
From: Patrick Steinhardt <ps@pks.im>
|
||||
Date: Mon, 15 Apr 2024 13:30:41 +0200
|
||||
Subject: [PATCH] builtin/clone: refuse local clones of unsafe repositories
|
||||
|
||||
When performing a local clone of a repository we end up either copying
|
||||
or hardlinking the source repository into the target repository. This is
|
||||
significantly more performant than if we were to use git-upload-pack(1)
|
||||
and git-fetch-pack(1) to create the new repository and preserves both
|
||||
disk space and compute time.
|
||||
|
||||
Unfortunately though, performing such a local clone of a repository that
|
||||
is not owned by the current user is inherently unsafe:
|
||||
|
||||
- It is possible that source files get swapped out underneath us while
|
||||
we are copying or hardlinking them. While we do perform some checks
|
||||
here to assert that we hardlinked the expected file, they cannot
|
||||
reliably thwart time-of-check-time-of-use (TOCTOU) style races. It
|
||||
is thus possible for an adversary to make us copy or hardlink
|
||||
unexpected files into the target directory.
|
||||
|
||||
Ideally, we would address this by starting to use openat(3P),
|
||||
fstatat(3P) and friends. Due to platform compatibility with Windows
|
||||
we cannot easily do that though. Furthermore, the scope of these
|
||||
fixes would likely be quite broad and thus not fit for an embargoed
|
||||
security release.
|
||||
|
||||
- Even if we handled TOCTOU-style races perfectly, hardlinking files
|
||||
owned by a different user into the target repository is not a good
|
||||
idea in general. It is possible for an adversary to rewrite those
|
||||
files to contain whatever data they want even after the clone has
|
||||
completed.
|
||||
|
||||
Address these issues by completely refusing local clones of a repository
|
||||
that is not owned by the current user. This reuses our existing infra we
|
||||
have in place via `ensure_valid_ownership()` and thus allows a user to
|
||||
override the safety guard by adding the source repository path to the
|
||||
"safe.directory" configuration.
|
||||
|
||||
This addresses CVE-2024-32020.
|
||||
|
||||
Signed-off-by: Patrick Steinhardt <ps@pks.im>
|
||||
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
|
||||
---
|
||||
builtin/clone.c | 14 ++++++++++++++
|
||||
t/t0033-safe-directory.sh | 24 ++++++++++++++++++++++++
|
||||
2 files changed, 38 insertions(+)
|
||||
|
||||
diff --git a/builtin/clone.c b/builtin/clone.c
|
||||
index 4b80fa087..9ec500d42 100644
|
||||
--- a/builtin/clone.c
|
||||
+++ b/builtin/clone.c
|
||||
@@ -321,6 +321,20 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
|
||||
struct dir_iterator *iter;
|
||||
int iter_status;
|
||||
|
||||
+ /*
|
||||
+ * Refuse copying directories by default which aren't owned by us. The
|
||||
+ * code that performs either the copying or hardlinking is not prepared
|
||||
+ * to handle various edge cases where an adversary may for example
|
||||
+ * racily swap out files for symlinks. This can cause us to
|
||||
+ * inadvertently use the wrong source file.
|
||||
+ *
|
||||
+ * Furthermore, even if we were prepared to handle such races safely,
|
||||
+ * creating hardlinks across user boundaries is an inherently unsafe
|
||||
+ * operation as the hardlinked files can be rewritten at will by the
|
||||
+ * potentially-untrusted user. We thus refuse to do so by default.
|
||||
+ */
|
||||
+ die_upon_dubious_ownership(NULL, NULL, src_repo);
|
||||
+
|
||||
mkdir_if_missing(dest->buf, 0777);
|
||||
|
||||
iter = dir_iterator_begin(src->buf, DIR_ITERATOR_PEDANTIC);
|
||||
diff --git a/t/t0033-safe-directory.sh b/t/t0033-safe-directory.sh
|
||||
index dc3496897..11c3e8f28 100755
|
||||
--- a/t/t0033-safe-directory.sh
|
||||
+++ b/t/t0033-safe-directory.sh
|
||||
@@ -80,4 +80,28 @@ test_expect_success 'safe.directory in included file' '
|
||||
git status
|
||||
'
|
||||
|
||||
+test_expect_success 'local clone of unowned repo refused in unsafe directory' '
|
||||
+ test_when_finished "rm -rf source" &&
|
||||
+ git init source &&
|
||||
+ (
|
||||
+ sane_unset GIT_TEST_ASSUME_DIFFERENT_OWNER &&
|
||||
+ test_commit -C source initial
|
||||
+ ) &&
|
||||
+ test_must_fail git clone --local source target &&
|
||||
+ test_path_is_missing target
|
||||
+'
|
||||
+
|
||||
+test_expect_success 'local clone of unowned repo accepted in safe directory' '
|
||||
+ test_when_finished "rm -rf source" &&
|
||||
+ git init source &&
|
||||
+ (
|
||||
+ sane_unset GIT_TEST_ASSUME_DIFFERENT_OWNER &&
|
||||
+ test_commit -C source initial
|
||||
+ ) &&
|
||||
+ test_must_fail git clone --local source target &&
|
||||
+ git config --global --add safe.directory "$(pwd)/source/.git" &&
|
||||
+ git clone --local source target &&
|
||||
+ test_path_is_dir target
|
||||
+'
|
||||
+
|
||||
test_done
|
||||
--
|
||||
2.33.0
|
||||
|
||||
@ -0,0 +1,60 @@
|
||||
From d1bb66a546b4bb46005d17ba711caaad26f26c1e Mon Sep 17 00:00:00 2001
|
||||
From: Patrick Steinhardt <ps@pks.im>
|
||||
Date: Mon, 15 Apr 2024 13:30:31 +0200
|
||||
Subject: [PATCH] builtin/clone: abort when hardlinked source and target file
|
||||
differ
|
||||
|
||||
When performing local clones with hardlinks we refuse to copy source
|
||||
files which are symlinks as a mitigation for CVE-2022-39253. This check
|
||||
can be raced by an adversary though by changing the file to a symlink
|
||||
after we have checked it.
|
||||
|
||||
Fix the issue by checking whether the hardlinked destination file
|
||||
matches the source file and abort in case it doesn't.
|
||||
|
||||
This addresses CVE-2024-32021.
|
||||
|
||||
Reported-by: Apple Product Security <product-security@apple.com>
|
||||
Suggested-by: Linus Torvalds <torvalds@linuxfoundation.org>
|
||||
Signed-off-by: Patrick Steinhardt <ps@pks.im>
|
||||
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
|
||||
---
|
||||
builtin/clone.c | 21 ++++++++++++++++++++-
|
||||
1 file changed, 20 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/builtin/clone.c b/builtin/clone.c
|
||||
index 073e6323d..4b80fa087 100644
|
||||
--- a/builtin/clone.c
|
||||
+++ b/builtin/clone.c
|
||||
@@ -357,8 +357,27 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
|
||||
if (unlink(dest->buf) && errno != ENOENT)
|
||||
die_errno(_("failed to unlink '%s'"), dest->buf);
|
||||
if (!option_no_hardlinks) {
|
||||
- if (!link(src->buf, dest->buf))
|
||||
+ if (!link(src->buf, dest->buf)) {
|
||||
+ struct stat st;
|
||||
+
|
||||
+ /*
|
||||
+ * Sanity-check whether the created hardlink
|
||||
+ * actually links to the expected file now. This
|
||||
+ * catches time-of-check-time-of-use bugs in
|
||||
+ * case the source file was meanwhile swapped.
|
||||
+ */
|
||||
+ if (lstat(dest->buf, &st))
|
||||
+ die(_("hardlink cannot be checked at '%s'"), dest->buf);
|
||||
+ if (st.st_mode != iter->st.st_mode ||
|
||||
+ st.st_ino != iter->st.st_ino ||
|
||||
+ st.st_dev != iter->st.st_dev ||
|
||||
+ st.st_size != iter->st.st_size ||
|
||||
+ st.st_uid != iter->st.st_uid ||
|
||||
+ st.st_gid != iter->st.st_gid)
|
||||
+ die(_("hardlink different from source at '%s'"), dest->buf);
|
||||
+
|
||||
continue;
|
||||
+ }
|
||||
if (option_local > 0)
|
||||
die_errno(_("failed to create link '%s'"), dest->buf);
|
||||
option_no_hardlinks = 1;
|
||||
--
|
||||
2.33.0
|
||||
|
||||
@ -0,0 +1,84 @@
|
||||
From 150e6b0aedf57d224c3c49038c306477fa159886 Mon Sep 17 00:00:00 2001
|
||||
From: Patrick Steinhardt <ps@pks.im>
|
||||
Date: Mon, 15 Apr 2024 13:30:26 +0200
|
||||
Subject: [PATCH] builtin/clone: stop resolving symlinks when copying files
|
||||
|
||||
When a user performs a local clone without `--no-local`, then we end up
|
||||
copying the source repository into the target repository directly. To
|
||||
optimize this even further, we try to hardlink files into place instead
|
||||
of copying data over, which helps both disk usage and speed.
|
||||
|
||||
There is an important edge case in this context though, namely when we
|
||||
try to hardlink symlinks from the source repository into the target
|
||||
repository. Depending on both platform and filesystem the resulting
|
||||
behaviour here can be different:
|
||||
|
||||
- On macOS and NetBSD, calling link(3P) with a symlink target creates
|
||||
a hardlink to the file pointed to by the symlink.
|
||||
|
||||
- On Linux, calling link(3P) instead creates a hardlink to the symlink
|
||||
itself.
|
||||
|
||||
To unify this behaviour, 36596fd2df (clone: better handle symlinked
|
||||
files at .git/objects/, 2019-07-10) introduced logic to resolve symlinks
|
||||
before we try to link(3P) files. Consequently, the new behaviour was to
|
||||
always create a hard link to the target of the symlink on all platforms.
|
||||
|
||||
Eventually though, we figured out that following symlinks like this can
|
||||
cause havoc when performing a local clone of a malicious repository,
|
||||
which resulted in CVE-2022-39253. This issue was fixed via 6f054f9fb3
|
||||
(builtin/clone.c: disallow `--local` clones with symlinks, 2022-07-28),
|
||||
by refusing symlinks in the source repository.
|
||||
|
||||
But even though we now shouldn't ever link symlinks anymore, the code
|
||||
that resolves symlinks still exists. In the best case the code does not
|
||||
end up doing anything because there are no symlinks anymore. In the
|
||||
worst case though this can be abused by an adversary that rewrites the
|
||||
source file after it has been checked not to be a symlink such that it
|
||||
actually is a symlink when we call link(3P). Thus, it is still possible
|
||||
to recreate CVE-2022-39253 due to this time-of-check-time-of-use bug.
|
||||
|
||||
Remove the call to `realpath()`. This doesn't yet address the actual
|
||||
vulnerability, which will be handled in a subsequent commit.
|
||||
|
||||
Reported-by: Apple Product Security <product-security@apple.com>
|
||||
Signed-off-by: Patrick Steinhardt <ps@pks.im>
|
||||
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
|
||||
---
|
||||
builtin/clone.c | 6 +-----
|
||||
1 file changed, 1 insertion(+), 5 deletions(-)
|
||||
|
||||
diff --git a/builtin/clone.c b/builtin/clone.c
|
||||
index 3c2ae31a5..073e6323d 100644
|
||||
--- a/builtin/clone.c
|
||||
+++ b/builtin/clone.c
|
||||
@@ -320,7 +320,6 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
|
||||
int src_len, dest_len;
|
||||
struct dir_iterator *iter;
|
||||
int iter_status;
|
||||
- struct strbuf realpath = STRBUF_INIT;
|
||||
|
||||
mkdir_if_missing(dest->buf, 0777);
|
||||
|
||||
@@ -358,8 +357,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
|
||||
if (unlink(dest->buf) && errno != ENOENT)
|
||||
die_errno(_("failed to unlink '%s'"), dest->buf);
|
||||
if (!option_no_hardlinks) {
|
||||
- strbuf_realpath(&realpath, src->buf, 1);
|
||||
- if (!link(realpath.buf, dest->buf))
|
||||
+ if (!link(src->buf, dest->buf))
|
||||
continue;
|
||||
if (option_local > 0)
|
||||
die_errno(_("failed to create link '%s'"), dest->buf);
|
||||
@@ -373,8 +371,6 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
|
||||
strbuf_setlen(src, src_len);
|
||||
die(_("failed to iterate over '%s'"), src->buf);
|
||||
}
|
||||
-
|
||||
- strbuf_release(&realpath);
|
||||
}
|
||||
|
||||
static void clone_local(const char *src_repo, const char *dest_repo)
|
||||
--
|
||||
2.33.0
|
||||
|
||||
@ -0,0 +1,201 @@
|
||||
From 7b70e9efb18c2cc3f219af399bd384c5801ba1d7 Mon Sep 17 00:00:00 2001
|
||||
From: Jeff King <peff@peff.net>
|
||||
Date: Tue, 16 Apr 2024 04:35:33 -0400
|
||||
Subject: [PATCH] upload-pack: disable lazy-fetching by default
|
||||
|
||||
The upload-pack command tries to avoid trusting the repository in which
|
||||
it's run (e.g., by not running any hooks and not using any config that
|
||||
contains arbitrary commands). But if the server side of a fetch or a
|
||||
clone is a partial clone, then either upload-pack or its child
|
||||
pack-objects may run a lazy "git fetch" under the hood. And it is very
|
||||
easy to convince fetch to run arbitrary commands.
|
||||
|
||||
The "server" side can be a local repository owned by someone else, who
|
||||
would be able to configure commands that are run during a clone with the
|
||||
current user's permissions. This issue has been designated
|
||||
CVE-2024-32004.
|
||||
|
||||
The fix in this commit's parent helps in this scenario, as well as in
|
||||
related scenarios using SSH to clone, where the untrusted .git directory
|
||||
is owned by a different user id. But if you received one as a zip file,
|
||||
on a USB stick, etc, it may be owned by your user but still untrusted.
|
||||
|
||||
This has been designated CVE-2024-32465.
|
||||
|
||||
To mitigate the issue more completely, let's disable lazy fetching
|
||||
entirely during `upload-pack`. While fetching from a partial repository
|
||||
should be relatively rare, it is certainly not an unreasonable workflow.
|
||||
And thus we need to provide an escape hatch.
|
||||
|
||||
This commit works by respecting a GIT_NO_LAZY_FETCH environment variable
|
||||
(to skip the lazy-fetch), and setting it in upload-pack, but only when
|
||||
the user has not already done so (which gives us the escape hatch).
|
||||
|
||||
The name of the variable is specifically chosen to match what has
|
||||
already been added in 'master' via e6d5479e7a (git: extend
|
||||
--no-lazy-fetch to work across subprocesses, 2024-02-27). Since we're
|
||||
building this fix as a backport for older versions, we could cherry-pick
|
||||
that patch and its earlier steps. However, we don't really need the
|
||||
niceties (like a "--no-lazy-fetch" option) that it offers. By using the
|
||||
same name, everything should just work when the two are eventually
|
||||
merged, but here are a few notes:
|
||||
|
||||
- the blocking of the fetch in e6d5479e7a is incomplete! It sets
|
||||
fetch_if_missing to 0 when we setup the repository variable, but
|
||||
that isn't enough. pack-objects in particular will call
|
||||
prefetch_to_pack() even if that variable is 0. This patch by
|
||||
contrast checks the environment variable at the lowest level before
|
||||
we call the lazy fetch, where we can be sure to catch all code
|
||||
paths.
|
||||
|
||||
Possibly the setting of fetch_if_missing from e6d5479e7a can be
|
||||
reverted, but it may be useful to have. For example, some code may
|
||||
want to use that flag to change behavior before it gets to the point
|
||||
of trying to start the fetch. At any rate, that's all outside the
|
||||
scope of this patch.
|
||||
|
||||
- there's documentation for GIT_NO_LAZY_FETCH in e6d5479e7a. We can
|
||||
live without that here, because for the most part the user shouldn't
|
||||
need to set it themselves. The exception is if they do want to
|
||||
override upload-pack's default, and that requires a separate
|
||||
documentation section (which is added here)
|
||||
|
||||
- it would be nice to use the NO_LAZY_FETCH_ENVIRONMENT macro added by
|
||||
e6d5479e7a, but those definitions have moved from cache.h to
|
||||
environment.h between 2.39.3 and master. I just used the raw string
|
||||
literals, and we can replace them with the macro once this topic is
|
||||
merged to master.
|
||||
|
||||
At least with respect to CVE-2024-32004, this does render this commit's
|
||||
parent commit somewhat redundant. However, it is worth retaining that
|
||||
commit as defense in depth, and because it may help other issues (e.g.,
|
||||
symlink/hardlink TOCTOU races, where zip files are not really an
|
||||
interesting attack vector).
|
||||
|
||||
The tests in t0411 still pass, but now we have _two_ mechanisms ensuring
|
||||
that the evil command is not run. Let's beef up the existing ones to
|
||||
check that they failed for the expected reason, that we refused to run
|
||||
upload-pack at all with an alternate user id. And add two new ones for
|
||||
the same-user case that both the restriction and its escape hatch.
|
||||
|
||||
Signed-off-by: Jeff King <peff@peff.net>
|
||||
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
|
||||
---
|
||||
Documentation/git-upload-pack.txt | 16 ++++++++++++++++
|
||||
builtin/upload-pack.c | 2 ++
|
||||
promisor-remote.c | 10 ++++++++++
|
||||
t/t0411-clone-from-partial.sh | 18 ++++++++++++++++++
|
||||
4 files changed, 46 insertions(+)
|
||||
|
||||
diff --git a/Documentation/git-upload-pack.txt b/Documentation/git-upload-pack.txt
|
||||
index b656b4756..fc4c62d7b 100644
|
||||
--- a/Documentation/git-upload-pack.txt
|
||||
+++ b/Documentation/git-upload-pack.txt
|
||||
@@ -55,6 +55,22 @@ ENVIRONMENT
|
||||
admins may need to configure some transports to allow this
|
||||
variable to be passed. See the discussion in linkgit:git[1].
|
||||
|
||||
+`GIT_NO_LAZY_FETCH`::
|
||||
+ When cloning or fetching from a partial repository (i.e., one
|
||||
+ itself cloned with `--filter`), the server-side `upload-pack`
|
||||
+ may need to fetch extra objects from its upstream in order to
|
||||
+ complete the request. By default, `upload-pack` will refuse to
|
||||
+ perform such a lazy fetch, because `git fetch` may run arbitrary
|
||||
+ commands specified in configuration and hooks of the source
|
||||
+ repository (and `upload-pack` tries to be safe to run even in
|
||||
+ untrusted `.git` directories).
|
||||
++
|
||||
+This is implemented by having `upload-pack` internally set the
|
||||
+`GIT_NO_LAZY_FETCH` variable to `1`. If you want to override it
|
||||
+(because you are fetching from a partial clone, and you are sure
|
||||
+you trust it), you can explicitly set `GIT_NO_LAZY_FETCH` to
|
||||
+`0`.
|
||||
+
|
||||
SEE ALSO
|
||||
--------
|
||||
linkgit:gitnamespaces[7]
|
||||
diff --git a/builtin/upload-pack.c b/builtin/upload-pack.c
|
||||
index 25b69da2b..f446ff04f 100644
|
||||
--- a/builtin/upload-pack.c
|
||||
+++ b/builtin/upload-pack.c
|
||||
@@ -35,6 +35,8 @@ int cmd_upload_pack(int argc, const char **argv, const char *prefix)
|
||||
|
||||
packet_trace_identity("upload-pack");
|
||||
disable_replace_refs();
|
||||
+ /* TODO: This should use NO_LAZY_FETCH_ENVIRONMENT */
|
||||
+ xsetenv("GIT_NO_LAZY_FETCH", "1", 0);
|
||||
|
||||
argc = parse_options(argc, argv, prefix, options, upload_pack_usage, 0);
|
||||
|
||||
diff --git a/promisor-remote.c b/promisor-remote.c
|
||||
index faa761294..550a38f75 100644
|
||||
--- a/promisor-remote.c
|
||||
+++ b/promisor-remote.c
|
||||
@@ -20,6 +20,16 @@ static int fetch_objects(struct repository *repo,
|
||||
int i;
|
||||
FILE *child_in;
|
||||
|
||||
+ /* TODO: This should use NO_LAZY_FETCH_ENVIRONMENT */
|
||||
+ if (git_env_bool("GIT_NO_LAZY_FETCH", 0)) {
|
||||
+ static int warning_shown;
|
||||
+ if (!warning_shown) {
|
||||
+ warning_shown = 1;
|
||||
+ warning(_("lazy fetching disabled; some objects may not be available"));
|
||||
+ }
|
||||
+ return -1;
|
||||
+ }
|
||||
+
|
||||
child.git_cmd = 1;
|
||||
child.in = -1;
|
||||
if (repo != the_repository)
|
||||
diff --git a/t/t0411-clone-from-partial.sh b/t/t0411-clone-from-partial.sh
|
||||
index eb3360dbc..b3d6ddc4b 100755
|
||||
--- a/t/t0411-clone-from-partial.sh
|
||||
+++ b/t/t0411-clone-from-partial.sh
|
||||
@@ -28,6 +28,7 @@ test_expect_success 'local clone must not fetch from promisor remote and execute
|
||||
test_must_fail git clone \
|
||||
--upload-pack="GIT_TEST_ASSUME_DIFFERENT_OWNER=true git-upload-pack" \
|
||||
evil clone1 2>err &&
|
||||
+ grep "detected dubious ownership" err &&
|
||||
! grep "fake-upload-pack running" err &&
|
||||
test_path_is_missing script-executed
|
||||
'
|
||||
@@ -37,6 +38,7 @@ test_expect_success 'clone from file://... must not fetch from promisor remote a
|
||||
test_must_fail git clone \
|
||||
--upload-pack="GIT_TEST_ASSUME_DIFFERENT_OWNER=true git-upload-pack" \
|
||||
"file://$(pwd)/evil" clone2 2>err &&
|
||||
+ grep "detected dubious ownership" err &&
|
||||
! grep "fake-upload-pack running" err &&
|
||||
test_path_is_missing script-executed
|
||||
'
|
||||
@@ -46,6 +48,7 @@ test_expect_success 'fetch from file://... must not fetch from promisor remote a
|
||||
test_must_fail git fetch \
|
||||
--upload-pack="GIT_TEST_ASSUME_DIFFERENT_OWNER=true git-upload-pack" \
|
||||
"file://$(pwd)/evil" 2>err &&
|
||||
+ grep "detected dubious ownership" err &&
|
||||
! grep "fake-upload-pack running" err &&
|
||||
test_path_is_missing script-executed
|
||||
'
|
||||
@@ -57,4 +60,19 @@ test_expect_success 'pack-objects should fetch from promisor remote and execute
|
||||
test_path_is_file script-executed
|
||||
'
|
||||
|
||||
+test_expect_success 'clone from promisor remote does not lazy-fetch by default' '
|
||||
+ rm -f script-executed &&
|
||||
+ test_must_fail git clone evil no-lazy 2>err &&
|
||||
+ grep "lazy fetching disabled" err &&
|
||||
+ test_path_is_missing script-executed
|
||||
+'
|
||||
+
|
||||
+test_expect_success 'promisor lazy-fetching can be re-enabled' '
|
||||
+ rm -f script-executed &&
|
||||
+ test_must_fail env GIT_NO_LAZY_FETCH=0 \
|
||||
+ git clone evil lazy-ok 2>err &&
|
||||
+ grep "fake-upload-pack running" err &&
|
||||
+ test_path_is_file script-executed
|
||||
+'
|
||||
+
|
||||
test_done
|
||||
--
|
||||
2.33.0
|
||||
|
||||
@ -0,0 +1,316 @@
|
||||
From 7725b8100ffbbff2750ee4d61a0fcc1f53a086e8 Mon Sep 17 00:00:00 2001
|
||||
From: Johannes Schindelin <johannes.schindelin@gmx.de>
|
||||
Date: Wed, 30 Oct 2024 13:26:10 +0100
|
||||
Subject: [PATCH] credential: sanitize the user prompt
|
||||
|
||||
When asking the user interactively for credentials, we want to avoid
|
||||
misleading them e.g. via control sequences that pretend that the URL
|
||||
targets a trusted host when it does not.
|
||||
|
||||
While Git learned, over the course of the preceding commits, to disallow
|
||||
URLs containing URL-encoded control characters by default, credential
|
||||
helpers are still allowed to specify values very freely (apart from Line
|
||||
Feed and NUL characters, anything is allowed), and this would allow,
|
||||
say, a username containing control characters to be specified that would
|
||||
then be displayed in the interactive terminal prompt asking the user for
|
||||
the password, potentially sending those control characters directly to
|
||||
the terminal. This is undesirable because control characters can be used
|
||||
to mislead users to divulge secret information to untrusted sites.
|
||||
|
||||
To prevent such an attack vector, let's add a `git_prompt()` that forces
|
||||
the displayed text to be sanitized, i.e. displaying question marks
|
||||
instead of control characters.
|
||||
|
||||
Note: While this commit's diff changes a lot of `user@host` strings to
|
||||
`user%40host`, which may look suspicious on the surface, there is a good
|
||||
reason for that: this string specifies a user name, not a
|
||||
<username>@<hostname> combination! In the context of t5541, the actual
|
||||
combination looks like this: `user%40@127.0.0.1:5541`. Therefore, these
|
||||
string replacements document a net improvement introduced by this
|
||||
commit, as `user@host@127.0.0.1` could have left readers wondering where
|
||||
the user name ends and where the host name begins.
|
||||
|
||||
Hinted-at-by: Jeff King <peff@peff.net>
|
||||
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
|
||||
---
|
||||
Documentation/config/credential.txt | 6 ++++++
|
||||
credential.c | 7 ++++++-
|
||||
credential.h | 4 +++-
|
||||
t/t0300-credentials.sh | 20 ++++++++++++++++++++
|
||||
t/t5541-http-push-smart.sh | 6 +++---
|
||||
t/t5550-http-fetch-dumb.sh | 14 +++++++-------
|
||||
t/t5551-http-fetch-smart.sh | 16 ++++++++--------
|
||||
7 files changed, 53 insertions(+), 20 deletions(-)
|
||||
|
||||
diff --git a/Documentation/config/credential.txt b/Documentation/config/credential.txt
|
||||
index 512f3187..fd8113d6 100644
|
||||
--- a/Documentation/config/credential.txt
|
||||
+++ b/Documentation/config/credential.txt
|
||||
@@ -14,6 +14,12 @@ credential.useHttpPath::
|
||||
or https URL to be important. Defaults to false. See
|
||||
linkgit:gitcredentials[7] for more information.
|
||||
|
||||
+credential.sanitizePrompt::
|
||||
+ By default, user names and hosts that are shown as part of the
|
||||
+ password prompt are not allowed to contain control characters (they
|
||||
+ will be URL-encoded by default). Configure this setting to `false` to
|
||||
+ override that behavior.
|
||||
+
|
||||
credential.username::
|
||||
If no username is set for a network authentication, use this username
|
||||
by default. See credential.<context>.* below, and
|
||||
diff --git a/credential.c b/credential.c
|
||||
index 572f1785..1392a54d 100644
|
||||
--- a/credential.c
|
||||
+++ b/credential.c
|
||||
@@ -67,6 +67,8 @@ static int credential_config_callback(const char *var, const char *value,
|
||||
}
|
||||
else if (!strcmp(key, "usehttppath"))
|
||||
c->use_http_path = git_config_bool(var, value);
|
||||
+ else if (!strcmp(key, "sanitizeprompt"))
|
||||
+ c->sanitize_prompt = git_config_bool(var, value);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -179,7 +181,10 @@ static char *credential_ask_one(const char *what, struct credential *c,
|
||||
struct strbuf prompt = STRBUF_INIT;
|
||||
char *r;
|
||||
|
||||
- credential_describe(c, &desc);
|
||||
+ if (c->sanitize_prompt)
|
||||
+ credential_format(c, &desc);
|
||||
+ else
|
||||
+ credential_describe(c, &desc);
|
||||
if (desc.len)
|
||||
strbuf_addf(&prompt, "%s for '%s': ", what, desc.buf);
|
||||
else
|
||||
diff --git a/credential.h b/credential.h
|
||||
index 935b28a7..0364d436 100644
|
||||
--- a/credential.h
|
||||
+++ b/credential.h
|
||||
@@ -119,7 +119,8 @@ struct credential {
|
||||
configured:1,
|
||||
quit:1,
|
||||
use_http_path:1,
|
||||
- username_from_proto:1;
|
||||
+ username_from_proto:1,
|
||||
+ sanitize_prompt:1;
|
||||
|
||||
char *username;
|
||||
char *password;
|
||||
@@ -132,6 +133,7 @@ struct credential {
|
||||
.helpers = STRING_LIST_INIT_DUP, \
|
||||
.password_expiry_utc = TIME_MAX, \
|
||||
.wwwauth_headers = STRVEC_INIT, \
|
||||
+ .sanitize_prompt = 1, \
|
||||
}
|
||||
|
||||
/* Initialize a credential structure, setting all fields to empty. */
|
||||
diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh
|
||||
index cb91be14..b62c70c1 100755
|
||||
--- a/t/t0300-credentials.sh
|
||||
+++ b/t/t0300-credentials.sh
|
||||
@@ -45,6 +45,10 @@ test_expect_success 'setup helper scripts' '
|
||||
test -z "$pexpiry" || echo password_expiry_utc=$pexpiry
|
||||
EOF
|
||||
|
||||
+ write_script git-credential-cntrl-in-username <<-\EOF &&
|
||||
+ printf "username=\\007latrix Lestrange\\n"
|
||||
+ EOF
|
||||
+
|
||||
PATH="$PWD:$PATH"
|
||||
'
|
||||
|
||||
@@ -825,4 +829,20 @@ test_expect_success 'credential config with partial URLs' '
|
||||
test_grep "skipping credential lookup for key" stderr
|
||||
'
|
||||
|
||||
+BEL="$(printf '\007')"
|
||||
+
|
||||
+test_expect_success 'interactive prompt is sanitized' '
|
||||
+ check fill cntrl-in-username <<-EOF
|
||||
+ protocol=https
|
||||
+ host=example.org
|
||||
+ --
|
||||
+ protocol=https
|
||||
+ host=example.org
|
||||
+ username=${BEL}latrix Lestrange
|
||||
+ password=askpass-password
|
||||
+ --
|
||||
+ askpass: Password for ${SQ}https://%07latrix%20Lestrange@example.org${SQ}:
|
||||
+ EOF
|
||||
+'
|
||||
+
|
||||
test_done
|
||||
diff --git a/t/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh
|
||||
index d0211cd8..2cd2e1a0 100755
|
||||
--- a/t/t5541-http-push-smart.sh
|
||||
+++ b/t/t5541-http-push-smart.sh
|
||||
@@ -351,7 +351,7 @@ test_expect_success 'push over smart http with auth' '
|
||||
git push "$HTTPD_URL"/auth/smart/test_repo.git &&
|
||||
git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git" \
|
||||
log -1 --format=%s >actual &&
|
||||
- expect_askpass both user@host &&
|
||||
+ expect_askpass both user%40host &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
@@ -363,7 +363,7 @@ test_expect_success 'push to auth-only-for-push repo' '
|
||||
git push "$HTTPD_URL"/auth-push/smart/test_repo.git &&
|
||||
git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git" \
|
||||
log -1 --format=%s >actual &&
|
||||
- expect_askpass both user@host &&
|
||||
+ expect_askpass both user%40host &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
@@ -393,7 +393,7 @@ test_expect_success 'push into half-auth-complete requires password' '
|
||||
git push "$HTTPD_URL/half-auth-complete/smart/half-auth.git" &&
|
||||
git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/half-auth.git" \
|
||||
log -1 --format=%s >actual &&
|
||||
- expect_askpass both user@host &&
|
||||
+ expect_askpass both user%40host &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
diff --git a/t/t5550-http-fetch-dumb.sh b/t/t5550-http-fetch-dumb.sh
|
||||
index 8f182a3c..5d0e3946 100755
|
||||
--- a/t/t5550-http-fetch-dumb.sh
|
||||
+++ b/t/t5550-http-fetch-dumb.sh
|
||||
@@ -90,13 +90,13 @@ test_expect_success 'http auth can use user/pass in URL' '
|
||||
test_expect_success 'http auth can use just user in URL' '
|
||||
set_askpass wrong pass@host &&
|
||||
git clone "$HTTPD_URL_USER/auth/dumb/repo.git" clone-auth-pass &&
|
||||
- expect_askpass pass user@host
|
||||
+ expect_askpass pass user%40host
|
||||
'
|
||||
|
||||
test_expect_success 'http auth can request both user and pass' '
|
||||
set_askpass user@host pass@host &&
|
||||
git clone "$HTTPD_URL/auth/dumb/repo.git" clone-auth-both &&
|
||||
- expect_askpass both user@host
|
||||
+ expect_askpass both user%40host
|
||||
'
|
||||
|
||||
test_expect_success 'http auth respects credential helper config' '
|
||||
@@ -114,14 +114,14 @@ test_expect_success 'http auth can get username from config' '
|
||||
test_config_global "credential.$HTTPD_URL.username" user@host &&
|
||||
set_askpass wrong pass@host &&
|
||||
git clone "$HTTPD_URL/auth/dumb/repo.git" clone-auth-user &&
|
||||
- expect_askpass pass user@host
|
||||
+ expect_askpass pass user%40host
|
||||
'
|
||||
|
||||
test_expect_success 'configured username does not override URL' '
|
||||
test_config_global "credential.$HTTPD_URL.username" wrong &&
|
||||
set_askpass wrong pass@host &&
|
||||
git clone "$HTTPD_URL_USER/auth/dumb/repo.git" clone-auth-user2 &&
|
||||
- expect_askpass pass user@host
|
||||
+ expect_askpass pass user%40host
|
||||
'
|
||||
|
||||
test_expect_success 'set up repo with http submodules' '
|
||||
@@ -142,7 +142,7 @@ test_expect_success 'cmdline credential config passes to submodule via clone' '
|
||||
set_askpass wrong pass@host &&
|
||||
git -c "credential.$HTTPD_URL.username=user@host" \
|
||||
clone --recursive super super-clone &&
|
||||
- expect_askpass pass user@host
|
||||
+ expect_askpass pass user%40host
|
||||
'
|
||||
|
||||
test_expect_success 'cmdline credential config passes submodule via fetch' '
|
||||
@@ -153,7 +153,7 @@ test_expect_success 'cmdline credential config passes submodule via fetch' '
|
||||
git -C super-clone \
|
||||
-c "credential.$HTTPD_URL.username=user@host" \
|
||||
fetch --recurse-submodules &&
|
||||
- expect_askpass pass user@host
|
||||
+ expect_askpass pass user%40host
|
||||
'
|
||||
|
||||
test_expect_success 'cmdline credential config passes submodule update' '
|
||||
@@ -170,7 +170,7 @@ test_expect_success 'cmdline credential config passes submodule update' '
|
||||
git -C super-clone \
|
||||
-c "credential.$HTTPD_URL.username=user@host" \
|
||||
submodule update &&
|
||||
- expect_askpass pass user@host
|
||||
+ expect_askpass pass user%40host
|
||||
'
|
||||
|
||||
test_expect_success 'fetch changes via http' '
|
||||
diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh
|
||||
index 0908534f..8a27768d 100755
|
||||
--- a/t/t5551-http-fetch-smart.sh
|
||||
+++ b/t/t5551-http-fetch-smart.sh
|
||||
@@ -181,7 +181,7 @@ test_expect_success 'clone from password-protected repository' '
|
||||
echo two >expect &&
|
||||
set_askpass user@host pass@host &&
|
||||
git clone --bare "$HTTPD_URL/auth/smart/repo.git" smart-auth &&
|
||||
- expect_askpass both user@host &&
|
||||
+ expect_askpass both user%40host &&
|
||||
git --git-dir=smart-auth log -1 --format=%s >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
@@ -199,7 +199,7 @@ test_expect_success 'clone from auth-only-for-objects repository' '
|
||||
echo two >expect &&
|
||||
set_askpass user@host pass@host &&
|
||||
git clone --bare "$HTTPD_URL/auth-fetch/smart/repo.git" half-auth &&
|
||||
- expect_askpass both user@host &&
|
||||
+ expect_askpass both user%40host &&
|
||||
git --git-dir=half-auth log -1 --format=%s >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
@@ -224,14 +224,14 @@ test_expect_success 'redirects send auth to new location' '
|
||||
set_askpass user@host pass@host &&
|
||||
git -c credential.useHttpPath=true \
|
||||
clone $HTTPD_URL/smart-redir-auth/repo.git repo-redir-auth &&
|
||||
- expect_askpass both user@host auth/smart/repo.git
|
||||
+ expect_askpass both user%40host auth/smart/repo.git
|
||||
'
|
||||
|
||||
test_expect_success 'GIT_TRACE_CURL redacts auth details' '
|
||||
rm -rf redact-auth trace &&
|
||||
set_askpass user@host pass@host &&
|
||||
GIT_TRACE_CURL="$(pwd)/trace" git clone --bare "$HTTPD_URL/auth/smart/repo.git" redact-auth &&
|
||||
- expect_askpass both user@host &&
|
||||
+ expect_askpass both user%40host &&
|
||||
|
||||
# Ensure that there is no "Basic" followed by a base64 string, but that
|
||||
# the auth details are redacted
|
||||
@@ -243,7 +243,7 @@ test_expect_success 'GIT_CURL_VERBOSE redacts auth details' '
|
||||
rm -rf redact-auth trace &&
|
||||
set_askpass user@host pass@host &&
|
||||
GIT_CURL_VERBOSE=1 git clone --bare "$HTTPD_URL/auth/smart/repo.git" redact-auth 2>trace &&
|
||||
- expect_askpass both user@host &&
|
||||
+ expect_askpass both user%40host &&
|
||||
|
||||
# Ensure that there is no "Basic" followed by a base64 string, but that
|
||||
# the auth details are redacted
|
||||
@@ -256,7 +256,7 @@ test_expect_success 'GIT_TRACE_CURL does not redact auth details if GIT_TRACE_RE
|
||||
set_askpass user@host pass@host &&
|
||||
GIT_TRACE_REDACT=0 GIT_TRACE_CURL="$(pwd)/trace" \
|
||||
git clone --bare "$HTTPD_URL/auth/smart/repo.git" redact-auth &&
|
||||
- expect_askpass both user@host &&
|
||||
+ expect_askpass both user%40host &&
|
||||
|
||||
grep -i "Authorization: Basic [0-9a-zA-Z+/]" trace
|
||||
'
|
||||
@@ -568,7 +568,7 @@ test_expect_success 'http auth remembers successful credentials' '
|
||||
# the first request prompts the user...
|
||||
set_askpass user@host pass@host &&
|
||||
git ls-remote "$HTTPD_URL/auth/smart/repo.git" >/dev/null &&
|
||||
- expect_askpass both user@host &&
|
||||
+ expect_askpass both user%40host &&
|
||||
|
||||
# ...and the second one uses the stored value rather than
|
||||
# prompting the user.
|
||||
@@ -599,7 +599,7 @@ test_expect_success 'http auth forgets bogus credentials' '
|
||||
# us to prompt the user again.
|
||||
set_askpass user@host pass@host &&
|
||||
git ls-remote "$HTTPD_URL/auth/smart/repo.git" >/dev/null &&
|
||||
- expect_askpass both user@host
|
||||
+ expect_askpass both user%40host
|
||||
'
|
||||
|
||||
test_expect_success 'client falls back from v2 to v0 to match server' '
|
||||
--
|
||||
2.23.0
|
||||
@ -0,0 +1,98 @@
|
||||
From c903985bf7e772e2d08275c1a95c8a55ab011577 Mon Sep 17 00:00:00 2001
|
||||
From: Johannes Schindelin <johannes.schindelin@gmx.de>
|
||||
Date: Thu, 7 Nov 2024 08:57:52 +0100
|
||||
Subject: [PATCH] credential_format(): also encode <host>[:<port>]
|
||||
|
||||
An upcoming change wants to sanitize the credential password prompt
|
||||
where a URL is displayed that may potentially come from a `.gitmodules`
|
||||
file. To this end, the `credential_format()` function is employed.
|
||||
|
||||
To sanitize the host name (and optional port) part of the URL, we need a
|
||||
new mode of the `strbuf_add_percentencode()` function because the
|
||||
current mode is both too strict and too lenient: too strict because it
|
||||
encodes `:`, `[` and `]` (which should be left unencoded in
|
||||
`<host>:<port>` and in IPv6 addresses), and too lenient because it does
|
||||
not encode invalid host name characters `/`, `_` and `~`.
|
||||
|
||||
So let's introduce and use a new mode specifically to encode the host
|
||||
name and optional port part of a URI, leaving alpha-numerical
|
||||
characters, periods, colons and brackets alone and encoding all others.
|
||||
|
||||
This only leads to a change of behavior for URLs that contain invalid
|
||||
host names.
|
||||
|
||||
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
|
||||
---
|
||||
credential.c | 3 ++-
|
||||
strbuf.c | 4 +++-
|
||||
strbuf.h | 1 +
|
||||
t/t0300-credentials.sh | 13 +++++++++++++
|
||||
4 files changed, 19 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/credential.c b/credential.c
|
||||
index f3201134..572f1785 100644
|
||||
--- a/credential.c
|
||||
+++ b/credential.c
|
||||
@@ -164,7 +164,8 @@ static void credential_format(struct credential *c, struct strbuf *out)
|
||||
strbuf_addch(out, '@');
|
||||
}
|
||||
if (c->host)
|
||||
- strbuf_addstr(out, c->host);
|
||||
+ strbuf_add_percentencode(out, c->host,
|
||||
+ STRBUF_ENCODE_HOST_AND_PORT);
|
||||
if (c->path) {
|
||||
strbuf_addch(out, '/');
|
||||
strbuf_add_percentencode(out, c->path, 0);
|
||||
diff --git a/strbuf.c b/strbuf.c
|
||||
index c383f41a..756b96c5 100644
|
||||
--- a/strbuf.c
|
||||
+++ b/strbuf.c
|
||||
@@ -492,7 +492,9 @@ void strbuf_add_percentencode(struct strbuf *dst, const char *src, int flags)
|
||||
unsigned char ch = src[i];
|
||||
if (ch <= 0x1F || ch >= 0x7F ||
|
||||
(ch == '/' && (flags & STRBUF_ENCODE_SLASH)) ||
|
||||
- strchr(URL_UNSAFE_CHARS, ch))
|
||||
+ ((flags & STRBUF_ENCODE_HOST_AND_PORT) ?
|
||||
+ !isalnum(ch) && !strchr("-.:[]", ch) :
|
||||
+ !!strchr(URL_UNSAFE_CHARS, ch)))
|
||||
strbuf_addf(dst, "%%%02X", (unsigned char)ch);
|
||||
else
|
||||
strbuf_addch(dst, ch);
|
||||
diff --git a/strbuf.h b/strbuf.h
|
||||
index f6dbb968..f9f8bb03 100644
|
||||
--- a/strbuf.h
|
||||
+++ b/strbuf.h
|
||||
@@ -380,6 +380,7 @@ size_t strbuf_expand_dict_cb(struct strbuf *sb,
|
||||
void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src);
|
||||
|
||||
#define STRBUF_ENCODE_SLASH 1
|
||||
+#define STRBUF_ENCODE_HOST_AND_PORT 2
|
||||
|
||||
/**
|
||||
* Append the contents of a string to a strbuf, percent-encoding any characters
|
||||
diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh
|
||||
index c66d91e8..cb91be14 100755
|
||||
--- a/t/t0300-credentials.sh
|
||||
+++ b/t/t0300-credentials.sh
|
||||
@@ -514,6 +514,19 @@ test_expect_success 'match percent-encoded values in username' '
|
||||
EOF
|
||||
'
|
||||
|
||||
+test_expect_success 'match percent-encoded values in hostname' '
|
||||
+ test_config "credential.https://a%20b%20c/.helper" "$HELPER" &&
|
||||
+ check fill <<-\EOF
|
||||
+ url=https://a b c/
|
||||
+ --
|
||||
+ protocol=https
|
||||
+ host=a b c
|
||||
+ username=foo
|
||||
+ password=bar
|
||||
+ --
|
||||
+ EOF
|
||||
+'
|
||||
+
|
||||
test_expect_success 'fetch with multiple path components' '
|
||||
test_unconfig credential.helper &&
|
||||
test_config credential.https://example.com/foo/repo.git.helper "verbatim foo bar" &&
|
||||
--
|
||||
2.23.0
|
||||
388
backport-CVE-2024-52005.patch
Normal file
388
backport-CVE-2024-52005.patch
Normal file
@ -0,0 +1,388 @@
|
||||
From 5b257412e25ad29410c389300324886aa59e1f83 Mon Sep 17 00:00:00 2001
|
||||
From: Johannes Schindelin <johannes.schindelin@gmx.de>
|
||||
Date: Wed, 6 Nov 2024 20:34:50 +0100
|
||||
Subject: [PATCH 1/3] sideband: mask control characters
|
||||
|
||||
The output of `git clone` is a vital component for understanding what
|
||||
has happened when things go wrong. However, these logs are partially
|
||||
under the control of the remote server (via the "sideband", which
|
||||
typically contains what the remote `git pack-objects` process sends to
|
||||
`stderr`), and is currently not sanitized by Git.
|
||||
|
||||
This makes Git susceptible to ANSI escape sequence injection (see
|
||||
CWE-150, https://cwe.mitre.org/data/definitions/150.html), which allows
|
||||
attackers to corrupt terminal state, to hide information, and even to
|
||||
insert characters into the input buffer (i.e. as if the user had typed
|
||||
those characters).
|
||||
|
||||
To plug this vulnerability, disallow any control character in the
|
||||
sideband, replacing them instead with the common `^<letter/symbol>`
|
||||
(e.g. `^[` for `\x1b`, `^A` for `\x01`).
|
||||
|
||||
There is likely a need for more fine-grained controls instead of using a
|
||||
"heavy hammer" like this, which will be introduced subsequently.
|
||||
|
||||
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
|
||||
---
|
||||
sideband.c | 17 +++++++++++++++--
|
||||
t/t5409-colorize-remote-messages.sh | 12 ++++++++++++
|
||||
2 files changed, 27 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/sideband.c b/sideband.c
|
||||
index 85bddfdcd4f57a..9384cb02d56a04 100644
|
||||
--- a/sideband.c
|
||||
+++ b/sideband.c
|
||||
@@ -61,6 +61,19 @@ void list_config_color_sideband_slots(struct string_list *list, const char *pref
|
||||
list_config_item(list, prefix, keywords[i].keyword);
|
||||
}
|
||||
|
||||
+static void strbuf_add_sanitized(struct strbuf *dest, const char *src, int n)
|
||||
+{
|
||||
+ strbuf_grow(dest, n);
|
||||
+ for (; n && *src; src++, n--) {
|
||||
+ if (!iscntrl(*src) || *src == '\t' || *src == '\n')
|
||||
+ strbuf_addch(dest, *src);
|
||||
+ else {
|
||||
+ strbuf_addch(dest, '^');
|
||||
+ strbuf_addch(dest, 0x40 + *src);
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
/*
|
||||
* Optionally highlight one keyword in remote output if it appears at the start
|
||||
* of the line. This should be called for a single line only, which is
|
||||
@@ -73,7 +86,7 @@ static void maybe_colorize_sideband(struct strbuf *dest, const char *src, int n)
|
||||
int i;
|
||||
|
||||
if (!want_color_stderr(use_sideband_colors())) {
|
||||
- strbuf_add(dest, src, n);
|
||||
+ strbuf_add_sanitized(dest, src, n);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -106,7 +119,7 @@ static void maybe_colorize_sideband(struct strbuf *dest, const char *src, int n)
|
||||
}
|
||||
}
|
||||
|
||||
- strbuf_add(dest, src, n);
|
||||
+ strbuf_add_sanitized(dest, src, n);
|
||||
}
|
||||
|
||||
|
||||
diff --git a/t/t5409-colorize-remote-messages.sh b/t/t5409-colorize-remote-messages.sh
|
||||
index fa5de4500a4f50..6a6e0d15b21050 100755
|
||||
--- a/t/t5409-colorize-remote-messages.sh
|
||||
+++ b/t/t5409-colorize-remote-messages.sh
|
||||
@@ -98,4 +98,16 @@ test_expect_success 'fallback to color.ui' '
|
||||
grep "<BOLD;RED>error<RESET>: error" decoded
|
||||
'
|
||||
|
||||
+test_expect_success 'disallow (color) control sequences in sideband' '
|
||||
+ write_script .git/color-me-surprised <<-\EOF &&
|
||||
+ printf "error: Have you \\033[31mread\\033[m this?\\n" >&2
|
||||
+ exec "$@"
|
||||
+ EOF
|
||||
+ test_config_global uploadPack.packObjectshook ./color-me-surprised &&
|
||||
+ test_commit need-at-least-one-commit &&
|
||||
+ git clone --no-local . throw-away 2>stderr &&
|
||||
+ test_decode_color <stderr >decoded &&
|
||||
+ test_i18ngrep ! RED decoded
|
||||
+'
|
||||
+
|
||||
test_done
|
||||
|
||||
From a8c289b0a531d25336a96eaa5e3584414ed4c6c4 Mon Sep 17 00:00:00 2001
|
||||
From: Johannes Schindelin <johannes.schindelin@gmx.de>
|
||||
Date: Wed, 6 Nov 2024 21:07:51 +0100
|
||||
Subject: [PATCH 2/3] sideband: introduce an "escape hatch" to allow control
|
||||
characters
|
||||
|
||||
The preceding commit fixed the vulnerability whereas sideband messages
|
||||
(that are under the control of the remote server) could contain ANSI
|
||||
escape sequences that would be sent to the terminal verbatim.
|
||||
|
||||
However, this fix may not be desirable under all circumstances, e.g.
|
||||
when remote servers deliberately add coloring to their messages to
|
||||
increase their urgency.
|
||||
|
||||
To help with those use cases, give users a way to opt-out of the
|
||||
protections: `sideband.allowControlCharacters`.
|
||||
|
||||
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
|
||||
---
|
||||
Documentation/config.txt | 2 ++
|
||||
Documentation/config/sideband.txt | 5 +++++
|
||||
sideband.c | 10 ++++++++++
|
||||
t/t5409-colorize-remote-messages.sh | 8 +++++++-
|
||||
4 files changed, 24 insertions(+), 1 deletion(-)
|
||||
create mode 100644 Documentation/config/sideband.txt
|
||||
|
||||
diff --git a/Documentation/config.txt b/Documentation/config.txt
|
||||
index 0e93aef86264db..abdbfba9bd756a 100644
|
||||
--- a/Documentation/config.txt
|
||||
+++ b/Documentation/config.txt
|
||||
@@ -511,6 +511,8 @@ include::config/sequencer.txt[]
|
||||
|
||||
include::config/showbranch.txt[]
|
||||
|
||||
+include::config/sideband.txt[]
|
||||
+
|
||||
include::config/sparse.txt[]
|
||||
|
||||
include::config/splitindex.txt[]
|
||||
diff --git a/Documentation/config/sideband.txt b/Documentation/config/sideband.txt
|
||||
new file mode 100644
|
||||
index 00000000000000..3fb5045cd79581
|
||||
--- /dev/null
|
||||
+++ b/Documentation/config/sideband.txt
|
||||
@@ -0,0 +1,5 @@
|
||||
+sideband.allowControlCharacters::
|
||||
+ By default, control characters that are delivered via the sideband
|
||||
+ are masked, to prevent potentially unwanted ANSI escape sequences
|
||||
+ from being sent to the terminal. Use this config setting to override
|
||||
+ this behavior.
|
||||
diff --git a/sideband.c b/sideband.c
|
||||
index 9384cb02d56a04..8ebf1f0743e6b6 100644
|
||||
--- a/sideband.c
|
||||
+++ b/sideband.c
|
||||
@@ -20,6 +20,8 @@ static struct keyword_entry keywords[] = {
|
||||
{ "error", GIT_COLOR_BOLD_RED },
|
||||
};
|
||||
|
||||
+static int allow_control_characters;
|
||||
+
|
||||
/* Returns a color setting (GIT_COLOR_NEVER, etc). */
|
||||
static int use_sideband_colors(void)
|
||||
{
|
||||
@@ -33,6 +35,9 @@ static int use_sideband_colors(void)
|
||||
if (use_sideband_colors_cached >= 0)
|
||||
return use_sideband_colors_cached;
|
||||
|
||||
+ git_config_get_bool("sideband.allowcontrolcharacters",
|
||||
+ &allow_control_characters);
|
||||
+
|
||||
if (!git_config_get_string(key, &value)) {
|
||||
use_sideband_colors_cached = git_config_colorbool(key, value);
|
||||
} else if (!git_config_get_string("color.ui", &value)) {
|
||||
@@ -63,6 +68,11 @@ void list_config_color_sideband_slots(struct string_list *list, const char *pref
|
||||
|
||||
static void strbuf_add_sanitized(struct strbuf *dest, const char *src, int n)
|
||||
{
|
||||
+ if (allow_control_characters) {
|
||||
+ strbuf_add(dest, src, n);
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
strbuf_grow(dest, n);
|
||||
for (; n && *src; src++, n--) {
|
||||
if (!iscntrl(*src) || *src == '\t' || *src == '\n')
|
||||
diff --git a/t/t5409-colorize-remote-messages.sh b/t/t5409-colorize-remote-messages.sh
|
||||
index 6a6e0d15b21050..1cd0640f200009 100755
|
||||
--- a/t/t5409-colorize-remote-messages.sh
|
||||
+++ b/t/t5409-colorize-remote-messages.sh
|
||||
@@ -105,9 +105,15 @@ test_expect_success 'disallow (color) control sequences in sideband' '
|
||||
EOF
|
||||
test_config_global uploadPack.packObjectshook ./color-me-surprised &&
|
||||
test_commit need-at-least-one-commit &&
|
||||
+
|
||||
git clone --no-local . throw-away 2>stderr &&
|
||||
test_decode_color <stderr >decoded &&
|
||||
- test_i18ngrep ! RED decoded
|
||||
+ test_i18ngrep ! RED decoded &&
|
||||
+
|
||||
+ rm -rf throw-away &&
|
||||
+ git -c sideband.allowControlCharacters clone --no-local . throw-away 2>stderr &&
|
||||
+ test_decode_color <stderr >decoded &&
|
||||
+ test_i18ngrep RED decoded
|
||||
'
|
||||
|
||||
test_done
|
||||
|
||||
From c7049c2a7f47c99a67fd869f1ee89d7aa1a328d2 Mon Sep 17 00:00:00 2001
|
||||
From: Johannes Schindelin <johannes.schindelin@gmx.de>
|
||||
Date: Mon, 18 Nov 2024 21:42:57 +0100
|
||||
Subject: [PATCH 3/3] sideband: do allow ANSI color sequences by default
|
||||
|
||||
The preceding two commits introduced special handling of the sideband
|
||||
channel to neutralize ANSI escape sequences before sending the payload
|
||||
to the terminal, and `sideband.allowControlCharacters` to override that
|
||||
behavior.
|
||||
|
||||
However, some `pre-receive` hooks that are actively used in practice
|
||||
want to color their messages and therefore rely on the fact that Git
|
||||
passes them through to the terminal.
|
||||
|
||||
In contrast to other ANSI escape sequences, it is highly unlikely that
|
||||
coloring sequences can be essential tools in attack vectors that mislead
|
||||
Git users e.g. by hiding crucial information.
|
||||
|
||||
Therefore we can have both: Continue to allow ANSI coloring sequences to
|
||||
be passed to the terminal, and neutralize all other ANSI escape
|
||||
sequences.
|
||||
|
||||
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
|
||||
---
|
||||
Documentation/config/sideband.txt | 17 ++++++--
|
||||
sideband.c | 61 ++++++++++++++++++++++++++---
|
||||
t/t5409-colorize-remote-messages.sh | 16 +++++++-
|
||||
3 files changed, 84 insertions(+), 10 deletions(-)
|
||||
|
||||
diff --git a/Documentation/config/sideband.txt b/Documentation/config/sideband.txt
|
||||
index 3fb5045cd79581..f347fd6b33004a 100644
|
||||
--- a/Documentation/config/sideband.txt
|
||||
+++ b/Documentation/config/sideband.txt
|
||||
@@ -1,5 +1,16 @@
|
||||
sideband.allowControlCharacters::
|
||||
By default, control characters that are delivered via the sideband
|
||||
- are masked, to prevent potentially unwanted ANSI escape sequences
|
||||
- from being sent to the terminal. Use this config setting to override
|
||||
- this behavior.
|
||||
+ are masked, except ANSI color sequences. This prevents potentially
|
||||
+ unwanted ANSI escape sequences from being sent to the terminal. Use
|
||||
+ this config setting to override this behavior:
|
||||
++
|
||||
+--
|
||||
+ color::
|
||||
+ Allow ANSI color sequences, line feeds and horizontal tabs,
|
||||
+ but mask all other control characters. This is the default.
|
||||
+ false::
|
||||
+ Mask all control characters other than line feeds and
|
||||
+ horizontal tabs.
|
||||
+ true::
|
||||
+ Allow all control characters to be sent to the terminal.
|
||||
+--
|
||||
diff --git a/sideband.c b/sideband.c
|
||||
index 8ebf1f0743e6b6..afd62aa008154b 100644
|
||||
--- a/sideband.c
|
||||
+++ b/sideband.c
|
||||
@@ -20,7 +20,11 @@ static struct keyword_entry keywords[] = {
|
||||
{ "error", GIT_COLOR_BOLD_RED },
|
||||
};
|
||||
|
||||
-static int allow_control_characters;
|
||||
+static enum {
|
||||
+ ALLOW_NO_CONTROL_CHARACTERS = 0,
|
||||
+ ALLOW_ALL_CONTROL_CHARACTERS = 1,
|
||||
+ ALLOW_ANSI_COLOR_SEQUENCES = 2
|
||||
+} allow_control_characters = ALLOW_ANSI_COLOR_SEQUENCES;
|
||||
|
||||
/* Returns a color setting (GIT_COLOR_NEVER, etc). */
|
||||
static int use_sideband_colors(void)
|
||||
@@ -35,8 +39,24 @@ static int use_sideband_colors(void)
|
||||
if (use_sideband_colors_cached >= 0)
|
||||
return use_sideband_colors_cached;
|
||||
|
||||
- git_config_get_bool("sideband.allowcontrolcharacters",
|
||||
- &allow_control_characters);
|
||||
+ switch (git_config_get_maybe_bool("sideband.allowcontrolcharacters", &i)) {
|
||||
+ case 0: /* Boolean value */
|
||||
+ allow_control_characters = i ? ALLOW_ALL_CONTROL_CHARACTERS :
|
||||
+ ALLOW_NO_CONTROL_CHARACTERS;
|
||||
+ break;
|
||||
+ case -1: /* non-Boolean value */
|
||||
+ if (git_config_get_string("sideband.allowcontrolcharacters",
|
||||
+ &value))
|
||||
+ ; /* huh? `get_maybe_bool()` returned -1 */
|
||||
+ else if (!strcmp(value, "color"))
|
||||
+ allow_control_characters = ALLOW_ANSI_COLOR_SEQUENCES;
|
||||
+ else
|
||||
+ warning(_("unrecognized value for `sideband."
|
||||
+ "allowControlCharacters`: '%s'"), value);
|
||||
+ break;
|
||||
+ default:
|
||||
+ break; /* not configured */
|
||||
+ }
|
||||
|
||||
if (!git_config_get_string(key, &value)) {
|
||||
use_sideband_colors_cached = git_config_colorbool(key, value);
|
||||
@@ -66,9 +86,37 @@ void list_config_color_sideband_slots(struct string_list *list, const char *pref
|
||||
list_config_item(list, prefix, keywords[i].keyword);
|
||||
}
|
||||
|
||||
+static int handle_ansi_color_sequence(struct strbuf *dest, const char *src, int n)
|
||||
+{
|
||||
+ int i;
|
||||
+
|
||||
+ /*
|
||||
+ * Valid ANSI color sequences are of the form
|
||||
+ *
|
||||
+ * ESC [ [<n> [; <n>]*] m
|
||||
+ */
|
||||
+
|
||||
+ if (allow_control_characters != ALLOW_ANSI_COLOR_SEQUENCES ||
|
||||
+ n < 3 || src[0] != '\x1b' || src[1] != '[')
|
||||
+ return 0;
|
||||
+
|
||||
+ for (i = 2; i < n; i++) {
|
||||
+ if (src[i] == 'm') {
|
||||
+ strbuf_add(dest, src, i + 1);
|
||||
+ return i;
|
||||
+ }
|
||||
+ if (!isdigit(src[i]) && src[i] != ';')
|
||||
+ break;
|
||||
+ }
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
+
|
||||
static void strbuf_add_sanitized(struct strbuf *dest, const char *src, int n)
|
||||
{
|
||||
- if (allow_control_characters) {
|
||||
+ int i;
|
||||
+
|
||||
+ if (allow_control_characters == ALLOW_ALL_CONTROL_CHARACTERS) {
|
||||
strbuf_add(dest, src, n);
|
||||
return;
|
||||
}
|
||||
@@ -77,7 +125,10 @@ static void strbuf_add_sanitized(struct strbuf *dest, const char *src, int n)
|
||||
for (; n && *src; src++, n--) {
|
||||
if (!iscntrl(*src) || *src == '\t' || *src == '\n')
|
||||
strbuf_addch(dest, *src);
|
||||
- else {
|
||||
+ else if ((i = handle_ansi_color_sequence(dest, src, n))) {
|
||||
+ src += i;
|
||||
+ n -= i;
|
||||
+ } else {
|
||||
strbuf_addch(dest, '^');
|
||||
strbuf_addch(dest, 0x40 + *src);
|
||||
}
|
||||
diff --git a/t/t5409-colorize-remote-messages.sh b/t/t5409-colorize-remote-messages.sh
|
||||
index 1cd0640f200009..43296ea51c5db1 100755
|
||||
--- a/t/t5409-colorize-remote-messages.sh
|
||||
+++ b/t/t5409-colorize-remote-messages.sh
|
||||
@@ -100,7 +100,7 @@ test_expect_success 'fallback to color.ui' '
|
||||
|
||||
test_expect_success 'disallow (color) control sequences in sideband' '
|
||||
write_script .git/color-me-surprised <<-\EOF &&
|
||||
- printf "error: Have you \\033[31mread\\033[m this?\\n" >&2
|
||||
+ printf "error: Have you \\033[31mread\\033[m this?\\a\\n" >&2
|
||||
exec "$@"
|
||||
EOF
|
||||
test_config_global uploadPack.packObjectshook ./color-me-surprised &&
|
||||
@@ -108,12 +108,24 @@ test_expect_success 'disallow (color) control sequences in sideband' '
|
||||
|
||||
git clone --no-local . throw-away 2>stderr &&
|
||||
test_decode_color <stderr >decoded &&
|
||||
+ test_i18ngrep RED decoded &&
|
||||
+ test_i18ngrep "\\^G" stderr &&
|
||||
+ tr -dc "\\007" <stderr >actual &&
|
||||
+ test_must_be_empty actual &&
|
||||
+
|
||||
+ rm -rf throw-away &&
|
||||
+ git -c sideband.allowControlCharacters=false \
|
||||
+ clone --no-local . throw-away 2>stderr &&
|
||||
+ test_decode_color <stderr >decoded &&
|
||||
test_i18ngrep ! RED decoded &&
|
||||
+ test_i18ngrep "\\^G" stderr &&
|
||||
|
||||
rm -rf throw-away &&
|
||||
git -c sideband.allowControlCharacters clone --no-local . throw-away 2>stderr &&
|
||||
test_decode_color <stderr >decoded &&
|
||||
- test_i18ngrep RED decoded
|
||||
+ test_i18ngrep RED decoded &&
|
||||
+ tr -dc "\\007" <stderr >actual &&
|
||||
+ test_file_not_empty actual
|
||||
'
|
||||
|
||||
test_done
|
||||
@ -0,0 +1,171 @@
|
||||
From b01b9b81d36759cdcd07305e78765199e1bc2060 Mon Sep 17 00:00:00 2001
|
||||
From: Johannes Schindelin <johannes.schindelin@gmx.de>
|
||||
Date: Mon, 4 Nov 2024 14:48:22 +0100
|
||||
Subject: [PATCH] credential: disallow Carriage Returns in the protocol by
|
||||
default
|
||||
|
||||
While Git has documented that the credential protocol is line-based,
|
||||
with newlines as terminators, the exact shape of a newline has not been
|
||||
documented.
|
||||
|
||||
From Git's perspective, which is firmly rooted in the Linux ecosystem,
|
||||
it is clear that "a newline" means a Line Feed character.
|
||||
|
||||
However, even Git's credential protocol respects Windows line endings
|
||||
(a Carriage Return character followed by a Line Feed character, "CR/LF")
|
||||
by virtue of using `strbuf_getline()`.
|
||||
|
||||
There is a third category of line endings that has been used originally
|
||||
by MacOS, and that is respected by the default line readers of .NET and
|
||||
node.js: bare Carriage Returns.
|
||||
|
||||
Git cannot handle those, and what is worse: Git's remedy against
|
||||
CVE-2020-5260 does not catch when credential helpers are used that
|
||||
interpret bare Carriage Returns as newlines.
|
||||
|
||||
Git Credential Manager addressed this as CVE-2024-50338, but other
|
||||
credential helpers may still be vulnerable. So let's not only disallow
|
||||
Line Feed characters as part of the values in the credential protocol,
|
||||
but also disallow Carriage Return characters.
|
||||
|
||||
In the unlikely event that a credential helper relies on Carriage
|
||||
Returns in the protocol, introduce an escape hatch via the
|
||||
`credential.protectProtocol` config setting.
|
||||
|
||||
This addresses CVE-2024-52006.
|
||||
|
||||
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
|
||||
---
|
||||
Documentation/config/credential.txt | 5 +++++
|
||||
credential.c | 21 ++++++++++++++-------
|
||||
credential.h | 4 +++-
|
||||
t/t0300-credentials.sh | 16 ++++++++++++++++
|
||||
4 files changed, 38 insertions(+), 8 deletions(-)
|
||||
|
||||
diff --git a/Documentation/config/credential.txt b/Documentation/config/credential.txt
|
||||
index fd8113d6..9cadca7f 100644
|
||||
--- a/Documentation/config/credential.txt
|
||||
+++ b/Documentation/config/credential.txt
|
||||
@@ -20,6 +20,11 @@ credential.sanitizePrompt::
|
||||
will be URL-encoded by default). Configure this setting to `false` to
|
||||
override that behavior.
|
||||
|
||||
+credential.protectProtocol::
|
||||
+ By default, Carriage Return characters are not allowed in the protocol
|
||||
+ that is used when Git talks to a credential helper. This setting allows
|
||||
+ users to override this default.
|
||||
+
|
||||
credential.username::
|
||||
If no username is set for a network authentication, use this username
|
||||
by default. See credential.<context>.* below, and
|
||||
diff --git a/credential.c b/credential.c
|
||||
index 1392a54d..b76a7309 100644
|
||||
--- a/credential.c
|
||||
+++ b/credential.c
|
||||
@@ -69,6 +69,8 @@ static int credential_config_callback(const char *var, const char *value,
|
||||
c->use_http_path = git_config_bool(var, value);
|
||||
else if (!strcmp(key, "sanitizeprompt"))
|
||||
c->sanitize_prompt = git_config_bool(var, value);
|
||||
+ else if (!strcmp(key, "protectprotocol"))
|
||||
+ c->protect_protocol = git_config_bool(var, value);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -262,7 +264,8 @@ int credential_read(struct credential *c, FILE *fp)
|
||||
return 0;
|
||||
}
|
||||
|
||||
-static void credential_write_item(FILE *fp, const char *key, const char *value,
|
||||
+static void credential_write_item(const struct credential *c,
|
||||
+ FILE *fp, const char *key, const char *value,
|
||||
int required)
|
||||
{
|
||||
if (!value && required)
|
||||
@@ -271,24 +274,28 @@ static void credential_write_item(FILE *fp, const char *key, const char *value,
|
||||
return;
|
||||
if (strchr(value, '\n'))
|
||||
die("credential value for %s contains newline", key);
|
||||
+ if (c->protect_protocol && strchr(value, '\r'))
|
||||
+ die("credential value for %s contains carriage return\n"
|
||||
+ "If this is intended, set `credential.protectProtocol=false`",
|
||||
+ key);
|
||||
fprintf(fp, "%s=%s\n", key, value);
|
||||
}
|
||||
|
||||
void credential_write(const struct credential *c, FILE *fp)
|
||||
{
|
||||
- credential_write_item(fp, "protocol", c->protocol, 1);
|
||||
- credential_write_item(fp, "host", c->host, 1);
|
||||
- credential_write_item(fp, "path", c->path, 0);
|
||||
- credential_write_item(fp, "username", c->username, 0);
|
||||
- credential_write_item(fp, "password", c->password, 0);
|
||||
- credential_write_item(fp, "oauth_refresh_token", c->oauth_refresh_token, 0);
|
||||
+ credential_write_item(c, fp, "protocol", c->protocol, 1);
|
||||
+ credential_write_item(c, fp, "host", c->host, 1);
|
||||
+ credential_write_item(c, fp, "path", c->path, 0);
|
||||
+ credential_write_item(c, fp, "username", c->username, 0);
|
||||
+ credential_write_item(c, fp, "password", c->password, 0);
|
||||
+ credential_write_item(c, fp, "oauth_refresh_token", c->oauth_refresh_token, 0);
|
||||
if (c->password_expiry_utc != TIME_MAX) {
|
||||
char *s = xstrfmt("%"PRItime, c->password_expiry_utc);
|
||||
- credential_write_item(fp, "password_expiry_utc", s, 0);
|
||||
+ credential_write_item(c, fp, "password_expiry_utc", s, 0);
|
||||
free(s);
|
||||
}
|
||||
for (size_t i = 0; i < c->wwwauth_headers.nr; i++)
|
||||
- credential_write_item(fp, "wwwauth[]", c->wwwauth_headers.v[i], 0);
|
||||
+ credential_write_item(c, fp, "wwwauth[]", c->wwwauth_headers.v[i], 0);
|
||||
}
|
||||
|
||||
static int run_credential_helper(struct credential *c,
|
||||
diff --git a/credential.h b/credential.h
|
||||
index 0364d436..2c0b39a9 100644
|
||||
--- a/credential.h
|
||||
+++ b/credential.h
|
||||
@@ -120,7 +120,8 @@ struct credential {
|
||||
quit:1,
|
||||
use_http_path:1,
|
||||
username_from_proto:1,
|
||||
- sanitize_prompt:1;
|
||||
+ sanitize_prompt:1,
|
||||
+ protect_protocol:1;
|
||||
|
||||
char *username;
|
||||
char *password;
|
||||
@@ -134,6 +135,7 @@ struct credential {
|
||||
.password_expiry_utc = TIME_MAX, \
|
||||
.wwwauth_headers = STRVEC_INIT, \
|
||||
.sanitize_prompt = 1, \
|
||||
+ .protect_protocol = 1, \
|
||||
}
|
||||
|
||||
/* Initialize a credential structure, setting all fields to empty. */
|
||||
diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh
|
||||
index b62c70c1..168ae765 100755
|
||||
--- a/t/t0300-credentials.sh
|
||||
+++ b/t/t0300-credentials.sh
|
||||
@@ -720,6 +720,22 @@ test_expect_success 'url parser rejects embedded newlines' '
|
||||
test_cmp expect stderr
|
||||
'
|
||||
|
||||
+test_expect_success 'url parser rejects embedded carriage returns' '
|
||||
+ test_config credential.helper "!true" &&
|
||||
+ test_must_fail git credential fill 2>stderr <<-\EOF &&
|
||||
+ url=https://example%0d.com/
|
||||
+ EOF
|
||||
+ cat >expect <<-\EOF &&
|
||||
+ fatal: credential value for host contains carriage return
|
||||
+ If this is intended, set `credential.protectProtocol=false`
|
||||
+ EOF
|
||||
+ test_cmp expect stderr &&
|
||||
+ GIT_ASKPASS=true \
|
||||
+ git -c credential.protectProtocol=false credential fill <<-\EOF
|
||||
+ url=https://example%0d.com/
|
||||
+ EOF
|
||||
+'
|
||||
+
|
||||
test_expect_success 'host-less URLs are parsed as empty host' '
|
||||
check fill "verbatim foo bar" <<-\EOF
|
||||
url=cert:///path/to/cert.pem
|
||||
--
|
||||
2.23.0
|
||||
116
backport-send-email-avoid-duplicate-specification-warnings.patch
Normal file
116
backport-send-email-avoid-duplicate-specification-warnings.patch
Normal file
@ -0,0 +1,116 @@
|
||||
From 6ff658cc78f36baa74c0f25314b0043a8f4b4fc6 Mon Sep 17 00:00:00 2001
|
||||
From: Todd Zullinger <tmz@pobox.com>
|
||||
Date: Thu, 16 Nov 2023 14:30:11 -0500
|
||||
Subject: [PATCH] send-email: avoid duplicate specification warnings
|
||||
|
||||
A warning is issued for options which are specified more than once
|
||||
beginning with perl-Getopt-Long >= 2.55. In addition to causing users
|
||||
to see warnings, this results in test failures which compare the output.
|
||||
An example, from t9001-send-email.37:
|
||||
|
||||
| +++ diff -u expect actual
|
||||
| --- expect 2023-11-14 10:38:23.854346488 +0000
|
||||
| +++ actual 2023-11-14 10:38:23.848346466 +0000
|
||||
| @@ -1,2 +1,7 @@
|
||||
| +Duplicate specification "no-chain-reply-to" for option "no-chain-reply-to"
|
||||
| +Duplicate specification "to-cover|to-cover!" for option "to-cover"
|
||||
| +Duplicate specification "cc-cover|cc-cover!" for option "cc-cover"
|
||||
| +Duplicate specification "no-thread" for option "no-thread"
|
||||
| +Duplicate specification "no-to-cover" for option "no-to-cover"
|
||||
| fatal: longline.patch:35 is longer than 998 characters
|
||||
| warning: no patches were sent
|
||||
| error: last command exited with $?=1
|
||||
| not ok 37 - reject long lines
|
||||
|
||||
Remove the duplicate option specs. These are primarily the explicit
|
||||
'--no-' prefix opts which were added in f471494303 (git-send-email.perl:
|
||||
support no- prefix with older GetOptions, 2015-01-30). This was done
|
||||
specifically to support perl-5.8.0 which includes Getopt::Long 2.32[1].
|
||||
|
||||
Getopt::Long 2.33 added support for the '--no-' prefix natively by
|
||||
appending '!' to the option specification string, which was included in
|
||||
perl-5.8.1 and is not present in perl-5.8.0. The previous commit bumped
|
||||
the minimum supported Perl version to 5.8.1 so we no longer need to
|
||||
provide the '--no-' variants for negatable options manually.
|
||||
|
||||
Teach `--git-completion-helper` to output the '--no-' options. They are
|
||||
not included in the options hash and would otherwise be lost.
|
||||
|
||||
Signed-off-by: Todd Zullinger <tmz@pobox.com>
|
||||
Signed-off-by: Junio C Hamano <gitster@pobox.com>
|
||||
---
|
||||
git-send-email.perl | 19 ++++++-------------
|
||||
1 file changed, 6 insertions(+), 13 deletions(-)
|
||||
|
||||
diff --git a/git-send-email.perl b/git-send-email.perl
|
||||
index 041db702d4..60afafb375 100755
|
||||
--- a/git-send-email.perl
|
||||
+++ b/git-send-email.perl
|
||||
@@ -119,13 +119,16 @@ sub completion_helper {
|
||||
|
||||
foreach my $key (keys %$original_opts) {
|
||||
unless (exists $not_for_completion{$key}) {
|
||||
- $key =~ s/!$//;
|
||||
+ my $negatable = ($key =~ s/!$//);
|
||||
|
||||
if ($key =~ /[:=][si]$/) {
|
||||
$key =~ s/[:=][si]$//;
|
||||
push (@send_email_opts, "--$_=") foreach (split (/\|/, $key));
|
||||
} else {
|
||||
push (@send_email_opts, "--$_") foreach (split (/\|/, $key));
|
||||
+ if ($negatable) {
|
||||
+ push (@send_email_opts, "--no-$_") foreach (split (/\|/, $key));
|
||||
+ }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -491,7 +494,6 @@ sub config_regexp {
|
||||
"bcc=s" => \@getopt_bcc,
|
||||
"no-bcc" => \$no_bcc,
|
||||
"chain-reply-to!" => \$chain_reply_to,
|
||||
- "no-chain-reply-to" => sub {$chain_reply_to = 0},
|
||||
"sendmail-cmd=s" => \$sendmail_cmd,
|
||||
"smtp-server=s" => \$smtp_server,
|
||||
"smtp-server-option=s" => \@smtp_server_options,
|
||||
@@ -506,36 +508,27 @@ sub config_regexp {
|
||||
"smtp-auth=s" => \$smtp_auth,
|
||||
"no-smtp-auth" => sub {$smtp_auth = 'none'},
|
||||
"annotate!" => \$annotate,
|
||||
- "no-annotate" => sub {$annotate = 0},
|
||||
"compose" => \$compose,
|
||||
"quiet" => \$quiet,
|
||||
"cc-cmd=s" => \$cc_cmd,
|
||||
"header-cmd=s" => \$header_cmd,
|
||||
"no-header-cmd" => \$no_header_cmd,
|
||||
"suppress-from!" => \$suppress_from,
|
||||
- "no-suppress-from" => sub {$suppress_from = 0},
|
||||
"suppress-cc=s" => \@suppress_cc,
|
||||
"signed-off-cc|signed-off-by-cc!" => \$signed_off_by_cc,
|
||||
- "no-signed-off-cc|no-signed-off-by-cc" => sub {$signed_off_by_cc = 0},
|
||||
- "cc-cover|cc-cover!" => \$cover_cc,
|
||||
- "no-cc-cover" => sub {$cover_cc = 0},
|
||||
- "to-cover|to-cover!" => \$cover_to,
|
||||
- "no-to-cover" => sub {$cover_to = 0},
|
||||
+ "cc-cover!" => \$cover_cc,
|
||||
+ "to-cover!" => \$cover_to,
|
||||
"confirm=s" => \$confirm,
|
||||
"dry-run" => \$dry_run,
|
||||
"envelope-sender=s" => \$envelope_sender,
|
||||
"thread!" => \$thread,
|
||||
- "no-thread" => sub {$thread = 0},
|
||||
"validate!" => \$validate,
|
||||
- "no-validate" => sub {$validate = 0},
|
||||
"transfer-encoding=s" => \$target_xfer_encoding,
|
||||
"format-patch!" => \$format_patch,
|
||||
- "no-format-patch" => sub {$format_patch = 0},
|
||||
"8bit-encoding=s" => \$auto_8bit_encoding,
|
||||
"compose-encoding=s" => \$compose_encoding,
|
||||
"force" => \$force,
|
||||
"xmailer!" => \$use_xmailer,
|
||||
- "no-xmailer" => sub {$use_xmailer = 0},
|
||||
"batch-size=i" => \$batch_size,
|
||||
"relogin-delay=i" => \$relogin_delay,
|
||||
"git-completion-helper" => \$git_completion_helper,
|
||||
--
|
||||
2.33.0
|
||||
|
||||
45
git.spec
45
git.spec
@ -1,7 +1,7 @@
|
||||
%global gitexecdir %{_libexecdir}/git-core
|
||||
Name: git
|
||||
Version: 2.43.0
|
||||
Release: 1
|
||||
Release: 6
|
||||
Summary: A popular and widely used Version Control System
|
||||
License: GPLv2+ or LGPLv2.1
|
||||
URL: https://git-scm.com/
|
||||
@ -12,6 +12,19 @@ Source100: git-gui.desktop
|
||||
Source101: git@.service.in
|
||||
Source102: git.socket
|
||||
|
||||
Patch0: backport-send-email-avoid-duplicate-specification-warnings.patch
|
||||
Patch1: backport-CVE-2024-32002-submodules-submodule-paths-m.patch
|
||||
Patch2: backport-CVE-2024-32021-builtin-clone-stop-resolving-symlinks-when-copying-f.patch
|
||||
Patch3: backport-CVE-2024-32021-builtin-clone-abort-when-hardlinked-source-and-targe.patch
|
||||
Patch4: backport-CVE-2024-32004-t0411-add-tests-for-cloning-from-partial-repo.patch
|
||||
Patch5: backport-CVE-2024-32004-fetch-clone-detect-dubious-ownership-of-local-reposi.patch
|
||||
Patch6: backport-CVE-2024-32020-builtin-clone-refuse-local-clones-of-unsafe-reposito.patch
|
||||
Patch7: backport-CVE-2024-32465-upload-pack-disable-lazy-fetching-by-default.patch
|
||||
Patch8: backport-CVE-2024-50349-credential_format-also-encode-host-port.patch
|
||||
Patch9: backport-CVE-2024-50349-credential-sanitize-the-user-prompt.patch
|
||||
Patch10: backport-CVE-2024-52006-credential-disallow-Carriage-Returns-in-the-protocol.patch
|
||||
Patch11: backport-CVE-2024-52005.patch
|
||||
|
||||
BuildRequires: gcc gettext
|
||||
BuildRequires: openssl-devel libcurl-devel expat-devel systemd asciidoc xmlto glib2-devel libsecret-devel pcre2-devel desktop-file-utils
|
||||
BuildRequires: python3-devel perl-generators perl-interpreter perl-Error perl(Test::More) perl-MailTools perl(Test)
|
||||
@ -295,6 +308,36 @@ make %{?_smp_mflags} test
|
||||
%{_mandir}/man7/git*.7.*
|
||||
|
||||
%changelog
|
||||
* Fri Jan 17 2025 fuanan <fuanan3@h-partners.com> - 2.43.0-6
|
||||
- Type:CVE
|
||||
- ID:CVE-2024-52005
|
||||
- SUG:NA
|
||||
- DESC:Fix CVE-2024-52005
|
||||
|
||||
* Wed Jan 15 2025 fuanan <fuanan3@h-partners.com> - 2.43.0-5
|
||||
- Type:CVE
|
||||
- ID:CVE-2024-50349 CVE-2024-52006
|
||||
- SUG:NA
|
||||
- DESC:Fix CVE-2024-50349 CVE-2024-52006
|
||||
|
||||
* Fri May 17 2024 fuanan <fuanan3@h-partners.com> - 2.43.0-4
|
||||
- Type:CVE
|
||||
- ID:CVE-2024-32021 CVE-2024-32004 CVE-2024-32020 CVE-2024-32465
|
||||
- SUG:NA
|
||||
- DESC:Fix CVE-2024-32021 CVE-2024-32004 CVE-2024-32020 CVE-2024-32465
|
||||
|
||||
* Wed May 15 2024 qiaojijun <qiaojijun@kylinos.cn> - 2.43.0-3
|
||||
- Type:CVE
|
||||
- ID:CVE-2024-32002
|
||||
- SUG:NA
|
||||
- DESC:Fix CVE-2024-32002
|
||||
|
||||
* Mon Apr 08 2024 fuanan <fuanan3@h-partners.com> - 2.43.0-2
|
||||
- Type:bugfix
|
||||
- ID:NA
|
||||
- SUG:NA
|
||||
- DESC:Fix t9001-send-email.sh test error
|
||||
|
||||
* Fri Dec 15 2023 fuanan <fuanan3@h-partners.com> - 2.43.0-1
|
||||
- Type:enhancement
|
||||
- ID:NA
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user