debian: apply security fixes from 2.20.2
Apply these as patches instead of using an upstream tarball because
the upstream tarball has not been created yet.
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
diff --git a/debian/changelog b/debian/changelog
index 17e6457..d11fe87 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,28 @@
+git (1:2.11.0-3+deb9u5) stretch-security; urgency=high
+
+ * Apply patches addressing the security issues CVE-2019-1348,
+ CVE-2019-1349, CVE-2019-1352, CVE-2019-1353, and CVE-2019-1387.
+
+ Credit for finding these vulnerabilities goes to Microsoft
+ Security Response Center, in particular to Nicolas Joly. Fixes
+ were provided by Jeff King and Johannes Schindelin with help
+ from Garima Singh.
+
+ * Reject setting "update = !command" in .gitmodules. This makes
+ the behavior better match Git 2.24.1 which made the same change
+ to address the arbitrary code execution issue CVE-2019-19604
+ (which does not affect Git versions before 2.20.0).
+
+ Also reject "update = !command" in fsck. This ensures that if
+ Git is run as a server with "transfer.fsckObjects" enabled,
+ it cannot be used to attack clients vulnerable to
+ CVE-2019-19604.
+
+ Credit for finding this vulnerability goes to Joern
+ Schneeweisz from GitLab.
+
+ -- Jonathan Nieder <jrnieder@gmail.com> Tue, 10 Dec 2019 08:14:58 +0000
+
git (1:2.11.0-3+deb9u4) stretch-security; urgency=high
* Fix CVE-2018-17456, arbitrary code execution via submodule URLs
diff --git a/debian/patches/Disallow-dubiously-nested-submodule-git-directories.diff b/debian/patches/Disallow-dubiously-nested-submodule-git-directories.diff
new file mode 100644
index 0000000..1bf9004
--- /dev/null
+++ b/debian/patches/Disallow-dubiously-nested-submodule-git-directories.diff
@@ -0,0 +1,214 @@
+From 392f99a5d2174e6124f829d034bac6755c33119d Mon Sep 17 00:00:00 2001
+From: Johannes Schindelin <johannes.schindelin@gmx.de>
+Date: Tue, 1 Oct 2019 23:27:18 +0200
+Subject: Disallow dubiously-nested submodule git directories
+
+commit a8dee3ca610f5a1d403634492136c887f83b59d2 upstream.
+
+Currently it is technically possible to let a submodule's git
+directory point right into the git dir of a sibling submodule.
+
+Example: the git directories of two submodules with the names `hippo`
+and `hippo/hooks` would be `.git/modules/hippo/` and
+`.git/modules/hippo/hooks/`, respectively, but the latter is already
+intended to house the former's hooks.
+
+In most cases, this is just confusing, but there is also a (quite
+contrived) attack vector where Git can be fooled into mistaking remote
+content for file contents it wrote itself during a recursive clone.
+
+Let's plug this bug.
+
+To do so, we introduce the new function `validate_submodule_git_dir()`
+which simply verifies that no git dir exists for any leading directories
+of the submodule name (if there are any).
+
+Note: this patch specifically continues to allow sibling modules names
+of the form `core/lib`, `core/doc`, etc, as long as `core` is not a
+submodule name.
+
+This fixes CVE-2019-1387.
+
+[jn: backported to 2.11.y:
+ - port to git-submodule.sh
+ - use explicit chdir to emulate test_commit -C in test]
+
+Reported-by: Nicolas Joly <Nicolas.Joly@microsoft.com>
+Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
+Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
+---
+ builtin/submodule--helper.c | 24 ++++++++++++++++++++++
+ git-submodule.sh | 5 +++++
+ submodule.c | 41 +++++++++++++++++++++++++++++++++++++
+ submodule.h | 5 +++++
+ t/t7415-submodule-names.sh | 23 +++++++++++++++++++++
+ 5 files changed, 98 insertions(+)
+
+diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
+index 9e0f985501..6f33a665df 100644
+--- a/builtin/submodule--helper.c
++++ b/builtin/submodule--helper.c
+@@ -638,6 +638,10 @@ static int module_clone(int argc, const char **argv, const char *prefix)
+ } else
+ path = xstrdup(path);
+
++ if (validate_submodule_git_dir(sm_gitdir, name) < 0)
++ die(_("refusing to create/use '%s' in another submodule's "
++ "git dir"), sm_gitdir);
++
+ if (!file_exists(sm_gitdir)) {
+ if (safe_create_leading_directories_const(sm_gitdir) < 0)
+ die(_("could not create directory '%s'"), sm_gitdir);
+@@ -1111,6 +1115,25 @@ static int check_name(int argc, const char **argv, const char *prefix)
+ return 0;
+ }
+
++/*
++ * Exit non-zero if the proposed submodule repository path is inside
++ * another submodules' git dir.
++ */
++static int validate_git_dir(int argc, const char **argv, const char *prefix)
++{
++ char *sm_gitdir;
++
++ if (argc != 3)
++ usage("git submodule--helper validate-git-dir <path> <name>");
++ sm_gitdir = xstrdup(argv[1]);
++ if (validate_submodule_git_dir(sm_gitdir, argv[2]) < 0) {
++ free(sm_gitdir);
++ return 1;
++ }
++ free(sm_gitdir);
++ return 0;
++}
++
+ struct cmd_struct {
+ const char *cmd;
+ int (*fn)(int, const char **, const char *);
+@@ -1127,6 +1150,7 @@ static struct cmd_struct commands[] = {
+ {"init", module_init},
+ {"remote-branch", resolve_remote_submodule_branch},
+ {"check-name", check_name},
++ {"validate-git-dir", validate_git_dir},
+ };
+
+ int cmd_submodule__helper(int argc, const char **argv, const char *prefix)
+diff --git a/git-submodule.sh b/git-submodule.sh
+index 81495c018f..f4e6b0204d 100755
+--- a/git-submodule.sh
++++ b/git-submodule.sh
+@@ -242,6 +242,11 @@ Use -f if you really want to add it." >&2
+ fi
+
+ else
++ sm_gitdir=".git/modules/$sm_name"
++ if ! git submodule--helper validate-git-dir "$sm_gitdir" "$sm_name"
++ then
++ die "$(eval_gettextln "refusing to create/use '\$sm_gitdir' in another submodule's git dir")"
++ fi
+ if test -d ".git/modules/$sm_name"
+ then
+ if test -z "$force"
+diff --git a/submodule.c b/submodule.c
+index 6f7d883de9..060e67f7e2 100644
+--- a/submodule.c
++++ b/submodule.c
+@@ -1251,6 +1251,47 @@ int parallel_submodules(void)
+ return parallel_jobs;
+ }
+
++int validate_submodule_git_dir(char *git_dir, const char *submodule_name)
++{
++ size_t len = strlen(git_dir), suffix_len = strlen(submodule_name);
++ char *p;
++ int ret = 0;
++
++ if (len <= suffix_len || (p = git_dir + len - suffix_len)[-1] != '/' ||
++ strcmp(p, submodule_name))
++ die("BUG: submodule name '%s' not a suffix of git dir '%s'",
++ submodule_name, git_dir);
++
++ /*
++ * We prevent the contents of sibling submodules' git directories to
++ * clash.
++ *
++ * Example: having a submodule named `hippo` and another one named
++ * `hippo/hooks` would result in the git directories
++ * `.git/modules/hippo/` and `.git/modules/hippo/hooks/`, respectively,
++ * but the latter directory is already designated to contain the hooks
++ * of the former.
++ */
++ for (; *p; p++) {
++ if (is_dir_sep(*p)) {
++ char c = *p;
++
++ *p = '\0';
++ if (is_git_directory(git_dir))
++ ret = -1;
++ *p = c;
++
++ if (ret < 0)
++ return error(_("submodule git dir '%s' is "
++ "inside git dir '%.*s'"),
++ git_dir,
++ (int)(p - git_dir), git_dir);
++ }
++ }
++
++ return 0;
++}
++
+ void prepare_submodule_repo_env(struct argv_array *out)
+ {
+ const char * const *var;
+diff --git a/submodule.h b/submodule.h
+index d9e197a948..4e7d6f12f1 100644
+--- a/submodule.h
++++ b/submodule.h
+@@ -68,6 +68,11 @@ int push_unpushed_submodules(unsigned char new_sha1[20], const char *remotes_nam
+ void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir);
+ int parallel_submodules(void);
+
++/*
++ * Make sure that no submodule's git dir is nested in a sibling submodule's.
++ */
++int validate_submodule_git_dir(char *git_dir, const char *submodule_name);
++
+ /*
+ * Prepare the "env_array" parameter of a "struct child_process" for executing
+ * a submodule by clearing any repo-specific envirionment variables, but
+diff --git a/t/t7415-submodule-names.sh b/t/t7415-submodule-names.sh
+index b3a421d4fd..fd50ae1fb3 100755
+--- a/t/t7415-submodule-names.sh
++++ b/t/t7415-submodule-names.sh
+@@ -183,4 +183,27 @@ test_expect_success MINGW 'prevent git~1 squatting on Windows' '
+ ! grep gitdir squatting-clone/d/a/git~2
+ '
+
++test_expect_success 'git dirs of sibling submodules must not be nested' '
++ git init nested &&
++ (
++ cd nested &&
++ test_commit nested &&
++ cat >.gitmodules <<-EOF &&
++ [submodule "hippo"]
++ url = .
++ path = thing1
++ [submodule "hippo/hooks"]
++ url = .
++ path = thing2
++ EOF
++ git clone . thing1 &&
++ git clone . thing2 &&
++ git add .gitmodules thing1 thing2 &&
++ test_tick &&
++ git commit -m nested
++ ) &&
++ test_must_fail git clone --recurse-submodules nested clone 2>err &&
++ test_i18ngrep "is inside git dir" err
++'
++
+ test_done
+--
+2.24.0.393.g34dc348eaf
+
diff --git a/debian/patches/clone-recurse-submodules-prevent-name-squatting-on-Wi.diff b/debian/patches/clone-recurse-submodules-prevent-name-squatting-on-Wi.diff
new file mode 100644
index 0000000..5b5fa61
--- /dev/null
+++ b/debian/patches/clone-recurse-submodules-prevent-name-squatting-on-Wi.diff
@@ -0,0 +1,238 @@
+From f4e9ab82ab3230776d92b9dd7a007fe66929d21a Mon Sep 17 00:00:00 2001
+From: Johannes Schindelin <johannes.schindelin@gmx.de>
+Date: Thu, 12 Sep 2019 14:20:39 +0200
+Subject: clone --recurse-submodules: prevent name squatting on Windows
+
+commit 0060fd1511b94c918928fa3708f69a3f33895a4a upstream.
+
+In addition to preventing `.git` from being tracked by Git, on Windows
+we also have to prevent `git~1` from being tracked, as the default NTFS
+short name (also known as the "8.3 filename") for the file name `.git`
+is `git~1`, otherwise it would be possible for malicious repositories to
+write directly into the `.git/` directory, e.g. a `post-checkout` hook
+that would then be executed _during_ a recursive clone.
+
+When we implemented appropriate protections in 2b4c6efc821 (read-cache:
+optionally disallow NTFS .git variants, 2014-12-16), we had analyzed
+carefully that the `.git` directory or file would be guaranteed to be
+the first directory entry to be written. Otherwise it would be possible
+e.g. for a file named `..git` to be assigned the short name `git~1` and
+subsequently, the short name generated for `.git` would be `git~2`. Or
+`git~3`. Or even `~9999999` (for a detailed explanation of the lengths
+we have to go to protect `.gitmodules`, see the commit message of
+e7cb0b4455c (is_ntfs_dotgit: match other .git files, 2018-05-11)).
+
+However, by exploiting two issues (that will be addressed in a related
+patch series close by), it is currently possible to clone a submodule
+into a non-empty directory:
+
+- On Windows, file names cannot end in a space or a period (for
+ historical reasons: the period separating the base name from the file
+ extension was not actually written to disk, and the base name/file
+ extension was space-padded to the full 8/3 characters, respectively).
+ Helpfully, when creating a directory under the name, say, `sub.`, that
+ trailing period is trimmed automatically and the actual name on disk
+ is `sub`.
+
+ This means that while Git thinks that the submodule names `sub` and
+ `sub.` are different, they both access `.git/modules/sub/`.
+
+- While the backslash character is a valid file name character on Linux,
+ it is not so on Windows. As Git tries to be cross-platform, it
+ therefore allows backslash characters in the file names stored in tree
+ objects.
+
+ Which means that it is totally possible that a submodule `c` sits next
+ to a file `c\..git`, and on Windows, during recursive clone a file
+ called `..git` will be written into `c/`, of course _before_ the
+ submodule is cloned.
+
+Note that the actual exploit is not quite as simple as having a
+submodule `c` next to a file `c\..git`, as we have to make sure that the
+directory `.git/modules/b` already exists when the submodule is checked
+out, otherwise a different code path is taken in `module_clone()` that
+does _not_ allow a non-empty submodule directory to exist already.
+
+Even if we will address both issues nearby (the next commit will
+disallow backslash characters in tree entries' file names on Windows,
+and another patch will disallow creating directories/files with trailing
+spaces or periods), it is a wise idea to defend in depth against this
+sort of attack vector: when submodules are cloned recursively, we now
+_require_ the directory to be empty, addressing CVE-2019-1349.
+
+Note: the code path we patch is shared with the code path of `git
+submodule update --init`, which must not expect, in general, that the
+directory is empty. Hence we have to introduce the new option
+`--force-init` and hand it all the way down from `git submodule` to the
+actual `git submodule--helper` process that performs the initial clone.
+
+Reported-by: Nicolas Joly <Nicolas.Joly@microsoft.com>
+Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
+Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
+---
+ builtin/clone.c | 2 +-
+ builtin/submodule--helper.c | 13 ++++++++++++-
+ git-submodule.sh | 6 ++++++
+ t/t7415-submodule-names.sh | 31 +++++++++++++++++++++++++++++++
+ 4 files changed, 50 insertions(+), 2 deletions(-)
+
+diff --git a/builtin/clone.c b/builtin/clone.c
+index 6c76a6ed66..2e5543ac50 100644
+--- a/builtin/clone.c
++++ b/builtin/clone.c
+@@ -735,7 +735,7 @@ static int checkout(int submodule_progress)
+
+ if (!err && option_recursive) {
+ struct argv_array args = ARGV_ARRAY_INIT;
+- argv_array_pushl(&args, "submodule", "update", "--init", "--recursive", NULL);
++ argv_array_pushl(&args, "submodule", "update", "--require-init", "--recursive", NULL);
+
+ if (option_shallow_submodules == 1)
+ argv_array_push(&args, "--depth=1");
+diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
+index b6801b6c7a..9e0f985501 100644
+--- a/builtin/submodule--helper.c
++++ b/builtin/submodule--helper.c
+@@ -12,6 +12,7 @@
+ #include "remote.h"
+ #include "refs.h"
+ #include "connect.h"
++#include "dir.h"
+
+ static char *get_default_remote(void)
+ {
+@@ -584,6 +585,7 @@ static int module_clone(int argc, const char **argv, const char *prefix)
+ struct strbuf rel_path = STRBUF_INIT;
+ struct strbuf sb = STRBUF_INIT;
+ struct string_list reference = STRING_LIST_INIT_NODUP;
++ int require_init = 0;
+
+ struct option module_clone_options[] = {
+ OPT_STRING(0, "prefix", &prefix,
+@@ -607,6 +609,8 @@ static int module_clone(int argc, const char **argv, const char *prefix)
+ OPT__QUIET(&quiet, "Suppress output for cloning a submodule"),
+ OPT_BOOL(0, "progress", &progress,
+ N_("force cloning progress")),
++ OPT_BOOL(0, "require-init", &require_init,
++ N_("disallow cloning into non-empty directory")),
+ OPT_END()
+ };
+
+@@ -645,6 +649,8 @@ static int module_clone(int argc, const char **argv, const char *prefix)
+ die(_("clone of '%s' into submodule path '%s' failed"),
+ url, path);
+ } else {
++ if (require_init && !access(path, X_OK) && !is_empty_dir(path))
++ die(_("directory not empty: '%s'"), path);
+ if (safe_create_leading_directories_const(path) < 0)
+ die(_("could not create directory '%s'"), path);
+ strbuf_addf(&sb, "%s/index", sm_gitdir);
+@@ -695,6 +701,7 @@ struct submodule_update_clone {
+ int quiet;
+ int recommend_shallow;
+ struct string_list references;
++ unsigned require_init;
+ const char *depth;
+ const char *recursive_prefix;
+ const char *prefix;
+@@ -710,7 +717,7 @@ struct submodule_update_clone {
+ int failed_clones_nr, failed_clones_alloc;
+ };
+ #define SUBMODULE_UPDATE_CLONE_INIT {0, MODULE_LIST_INIT, 0, \
+- SUBMODULE_UPDATE_STRATEGY_INIT, 0, 0, -1, STRING_LIST_INIT_DUP, \
++ SUBMODULE_UPDATE_STRATEGY_INIT, 0, 0, -1, STRING_LIST_INIT_DUP, 0, \
+ NULL, NULL, NULL, \
+ STRING_LIST_INIT_DUP, 0, NULL, 0, 0}
+
+@@ -820,6 +827,8 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce,
+ argv_array_pushl(&child->args, "--prefix", suc->prefix, NULL);
+ if (suc->recommend_shallow && sub->recommend_shallow == 1)
+ argv_array_push(&child->args, "--depth=1");
++ if (suc->require_init)
++ argv_array_push(&child->args, "--require-init");
+ argv_array_pushl(&child->args, "--path", sub->path, NULL);
+ argv_array_pushl(&child->args, "--name", sub->name, NULL);
+ argv_array_pushl(&child->args, "--url", url, NULL);
+@@ -963,6 +972,8 @@ static int update_clone(int argc, const char **argv, const char *prefix)
+ OPT__QUIET(&suc.quiet, N_("don't print cloning progress")),
+ OPT_BOOL(0, "progress", &suc.progress,
+ N_("force cloning progress")),
++ OPT_BOOL(0, "require-init", &suc.require_init,
++ N_("disallow cloning into non-empty directory")),
+ OPT_END()
+ };
+
+diff --git a/git-submodule.sh b/git-submodule.sh
+index cee4ddc04b..81495c018f 100755
+--- a/git-submodule.sh
++++ b/git-submodule.sh
+@@ -37,6 +37,7 @@ reference=
+ cached=
+ recursive=
+ init=
++require_init=
+ files=
+ remote=
+ nofetch=
+@@ -510,6 +511,10 @@ cmd_update()
+ -i|--init)
+ init=1
+ ;;
++ --require-init)
++ init=1
++ require_init=1
++ ;;
+ --remote)
+ remote=1
+ ;;
+@@ -588,6 +593,7 @@ cmd_update()
+ ${update:+--update "$update"} \
+ ${reference:+"$reference"} \
+ ${depth:+--depth "$depth"} \
++ ${require_init:+--require-init} \
+ ${recommend_shallow:+"$recommend_shallow"} \
+ ${jobs:+$jobs} \
+ "$@" || echo "#unmatched" $?
+diff --git a/t/t7415-submodule-names.sh b/t/t7415-submodule-names.sh
+index 0b2a51ffc8..b3a421d4fd 100755
+--- a/t/t7415-submodule-names.sh
++++ b/t/t7415-submodule-names.sh
+@@ -152,4 +152,35 @@ test_expect_success 'fsck detects symlinked .gitmodules file' '
+ )
+ '
+
++test_expect_success MINGW 'prevent git~1 squatting on Windows' '
++ git init squatting &&
++ (
++ cd squatting &&
++ mkdir a &&
++ touch a/..git &&
++ git add a/..git &&
++ test_tick &&
++ git commit -m initial &&
++
++ modules="$(test_write_lines \
++ "[submodule \"b.\"]" "url = ." "path = c" \
++ "[submodule \"b\"]" "url = ." "path = d\\\\a" |
++ git hash-object -w --stdin)" &&
++ rev="$(git rev-parse --verify HEAD)" &&
++ hash="$(echo x | git hash-object -w --stdin)" &&
++ git update-index --add \
++ --cacheinfo 100644,$modules,.gitmodules \
++ --cacheinfo 160000,$rev,c \
++ --cacheinfo 160000,$rev,d\\a \
++ --cacheinfo 100644,$hash,d./a/x \
++ --cacheinfo 100644,$hash,d./a/..git &&
++ test_tick &&
++ git commit -m "module"
++ ) &&
++ test_must_fail git \
++ clone --recurse-submodules squatting squatting-clone 2>err &&
++ test_i18ngrep "directory not empty" err &&
++ ! grep gitdir squatting-clone/d/a/git~2
++'
++
+ test_done
+--
+2.24.0.393.g34dc348eaf
+
diff --git a/debian/patches/fast-import-delay-creating-leading-directories-for-ex.diff b/debian/patches/fast-import-delay-creating-leading-directories-for-ex.diff
new file mode 100644
index 0000000..4b53be8
--- /dev/null
+++ b/debian/patches/fast-import-delay-creating-leading-directories-for-ex.diff
@@ -0,0 +1,90 @@
+From f6d36ce67e58fad3a35a19c3b3d74914be7fa1c3 Mon Sep 17 00:00:00 2001
+From: Jeff King <peff@peff.net>
+Date: Thu, 29 Aug 2019 13:33:48 -0400
+Subject: fast-import: delay creating leading directories for export-marks
+
+commit 019683025f1b14d7cb671312ab01f7330e9b33e7 upstream.
+
+When we parse the --export-marks option, we don't immediately open the
+file, but we do create any leading directories. This can be especially
+confusing when a command-line option overrides an in-stream one, in
+which case we'd create the leading directory for the in-stream file,
+even though we never actually write the file.
+
+Let's instead create the directories just before opening the file, which
+means we'll create only useful directories. Note that this could change
+the handling of relative paths if we chdir() in between, but we don't
+actually do so; the only permanent chdir is from setup_git_directory()
+which runs before either code path (potentially we should take the
+pre-setup dir into account to avoid surprising the user, but that's an
+orthogonal change).
+
+The test just adapts the existing "override" test to use paths with
+leading directories. This checks both that the correct directory is
+created (which worked before but was not tested), and that the
+overridden one is not (our new fix here).
+
+While we're here, let's also check the error result of
+safe_create_leading_directories(). We'd presumably notice any failure
+immediately after when we try to open the file itself, but we can give a
+more specific error message in this case.
+
+Signed-off-by: Jeff King <peff@peff.net>
+Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
+---
+ fast-import.c | 7 ++++++-
+ t/t9300-fast-import.sh | 13 +++++++++++--
+ 2 files changed, 17 insertions(+), 3 deletions(-)
+
+diff --git a/fast-import.c b/fast-import.c
+index b9144e90bd..c841953cdc 100644
+--- a/fast-import.c
++++ b/fast-import.c
+@@ -1860,6 +1860,12 @@ static void dump_marks(void)
+ if (!export_marks_file || (import_marks_file && !import_marks_file_done))
+ return;
+
++ if (safe_create_leading_directories_const(export_marks_file)) {
++ failure |= error_errno("unable to create leading directories of %s",
++ export_marks_file);
++ return;
++ }
++
+ if (hold_lock_file_for_update(&mark_lock, export_marks_file, 0) < 0) {
+ failure |= error_errno("Unable to write marks file %s",
+ export_marks_file);
+@@ -3259,7 +3265,6 @@ static void option_active_branches(const char *branches)
+ static void option_export_marks(const char *marks)
+ {
+ export_marks_file = make_fast_import_path(marks);
+- safe_create_leading_directories_const(export_marks_file);
+ }
+
+ static void option_cat_blob_fd(const char *fd)
+diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh
+index bf5b937a4c..f6fba4ae16 100755
+--- a/t/t9300-fast-import.sh
++++ b/t/t9300-fast-import.sh
+@@ -2132,8 +2132,17 @@ test_expect_success 'R: export-marks feature results in a marks file being creat
+ '
+
+ test_expect_success 'R: export-marks options can be overridden by commandline options' '
+- git fast-import --export-marks=other.marks <input &&
+- grep :1 other.marks
++ cat >input <<-\EOF &&
++ feature export-marks=feature-sub/git.marks
++ blob
++ mark :1
++ data 3
++ hi
++
++ EOF
++ git fast-import --export-marks=cmdline-sub/other.marks <input &&
++ grep :1 cmdline-sub/other.marks &&
++ test_path_is_missing feature-sub
+ '
+
+ test_expect_success 'R: catch typo in marks file name' '
+--
+2.24.0.393.g34dc348eaf
+
diff --git a/debian/patches/fast-import-disallow-feature-export-marks-by-default.diff b/debian/patches/fast-import-disallow-feature-export-marks-by-default.diff
new file mode 100644
index 0000000..cd2f258
--- /dev/null
+++ b/debian/patches/fast-import-disallow-feature-export-marks-by-default.diff
@@ -0,0 +1,262 @@
+From 4c5ac3beb563991d1f0a3002ebe00255b8846c0d Mon Sep 17 00:00:00 2001
+From: Jeff King <peff@peff.net>
+Date: Thu, 29 Aug 2019 14:37:26 -0400
+Subject: fast-import: disallow "feature export-marks" by default
+
+commit 68061e3470210703cb15594194718d35094afdc0 upstream.
+
+The fast-import stream command "feature export-marks=<path>" lets the
+stream write marks to an arbitrary path. This may be surprising if you
+are running fast-import against an untrusted input (which otherwise
+cannot do anything except update Git objects and refs).
+
+Let's disallow the use of this feature by default, and provide a
+command-line option to re-enable it (you can always just use the
+command-line --export-marks as well, but the in-stream version provides
+an easy way for exporters to control the process).
+
+This is a backwards-incompatible change, since the default is flipping
+to the new, safer behavior. However, since the main users of the
+in-stream versions would be import/export-based remote helpers, and
+since we trust remote helpers already (which are already running
+arbitrary code), we'll pass the new option by default when reading a
+remote helper's stream. This should minimize the impact.
+
+Note that the implementation isn't totally simple, as we have to work
+around the fact that fast-import doesn't parse its command-line options
+until after it has read any "feature" lines from the stream. This is how
+it lets command-line options override in-stream. But in our case, it's
+important to parse the new --allow-unsafe-features first.
+
+There are three options for resolving this:
+
+ 1. Do a separate "early" pass over the options. This is easy for us to
+ do because there are no command-line options that allow the
+ "unstuck" form (so there's no chance of us mistaking an argument
+ for an option), though it does introduce a risk of incorrect
+ parsing later (e.g,. if we convert to parse-options).
+
+ 2. Move the option parsing phase back to the start of the program, but
+ teach the stream-reading code never to override an existing value.
+ This is tricky, because stream "feature" lines override each other
+ (meaning we'd have to start tracking the source for every option).
+
+ 3. Accept that we might parse a "feature export-marks" line that is
+ forbidden, as long we don't _act_ on it until after we've parsed
+ the command line options.
+
+ This would, in fact, work with the current code, but only because
+ the previous patch fixed the export-marks parser to avoid touching
+ the filesystem.
+
+ So while it works, it does carry risk of somebody getting it wrong
+ in the future in a rather subtle and unsafe way.
+
+I've gone with option (1) here as simple, safe, and unlikely to cause
+regressions.
+
+This fixes CVE-2019-1348.
+
+Signed-off-by: Jeff King <peff@peff.net>
+Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
+---
+ Documentation/git-fast-import.txt | 14 ++++++++++++++
+ fast-import.c | 25 +++++++++++++++++++++++++
+ t/t9300-fast-import.sh | 23 +++++++++++++++--------
+ transport-helper.c | 1 +
+ 4 files changed, 55 insertions(+), 8 deletions(-)
+
+diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt
+index 2b762654bf..55b4b203fb 100644
+--- a/Documentation/git-fast-import.txt
++++ b/Documentation/git-fast-import.txt
+@@ -50,6 +50,20 @@ OPTIONS
+ memory used by fast-import during this run. Showing this output
+ is currently the default, but can be disabled with --quiet.
+
++--allow-unsafe-features::
++ Many command-line options can be provided as part of the
++ fast-import stream itself by using the `feature` or `option`
++ commands. However, some of these options are unsafe (e.g.,
++ allowing fast-import to access the filesystem outside of the
++ repository). These options are disabled by default, but can be
++ allowed by providing this option on the command line. This
++ currently impacts only the `feature export-marks` command.
+++
++ Only enable this option if you trust the program generating the
++ fast-import stream! This option is enabled automatically for
++ remote-helpers that use the `import` capability, as they are
++ already trusted to run their own code.
++
+ Options for Frontends
+ ~~~~~~~~~~~~~~~~~~~~~
+
+diff --git a/fast-import.c b/fast-import.c
+index c841953cdc..7ed61a9dad 100644
+--- a/fast-import.c
++++ b/fast-import.c
+@@ -367,6 +367,7 @@ static uintmax_t next_mark;
+ static struct strbuf new_data = STRBUF_INIT;
+ static int seen_data_command;
+ static int require_explicit_termination;
++static int allow_unsafe_features;
+
+ /* Signal handling */
+ static volatile sig_atomic_t checkpoint_requested;
+@@ -3313,6 +3314,8 @@ static int parse_one_option(const char *option)
+ show_stats = 0;
+ } else if (!strcmp(option, "stats")) {
+ show_stats = 1;
++ } else if (!strcmp(option, "allow-unsafe-features")) {
++ ; /* already handled during early option parsing */
+ } else {
+ return 0;
+ }
+@@ -3320,6 +3323,13 @@ static int parse_one_option(const char *option)
+ return 1;
+ }
+
++static void check_unsafe_feature(const char *feature, int from_stream)
++{
++ if (from_stream && !allow_unsafe_features)
++ die(_("feature '%s' forbidden in input without --allow-unsafe-features"),
++ feature);
++}
++
+ static int parse_one_feature(const char *feature, int from_stream)
+ {
+ const char *arg;
+@@ -3331,6 +3341,7 @@ static int parse_one_feature(const char *feature, int from_stream)
+ } else if (skip_prefix(feature, "import-marks-if-exists=", &arg)) {
+ option_import_marks(arg, from_stream, 1);
+ } else if (skip_prefix(feature, "export-marks=", &arg)) {
++ check_unsafe_feature(feature, from_stream);
+ option_export_marks(arg);
+ } else if (!strcmp(feature, "get-mark")) {
+ ; /* Don't die - this feature is supported */
+@@ -3468,6 +3479,20 @@ int cmd_main(int argc, const char **argv)
+ avail_tree_table = xcalloc(avail_tree_table_sz, sizeof(struct avail_tree_content*));
+ marks = pool_calloc(1, sizeof(struct mark_set));
+
++ /*
++ * We don't parse most options until after we've seen the set of
++ * "feature" lines at the start of the stream (which allows the command
++ * line to override stream data). But we must do an early parse of any
++ * command-line options that impact how we interpret the feature lines.
++ */
++ for (i = 1; i < argc; i++) {
++ const char *arg = argv[i];
++ if (*arg != '-' || !strcmp(arg, "--"))
++ break;
++ if (!strcmp(arg, "--allow-unsafe-features"))
++ allow_unsafe_features = 1;
++ }
++
+ global_argc = argc;
+ global_argv = argv;
+
+diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh
+index f6fba4ae16..30b7c8a1f6 100755
+--- a/t/t9300-fast-import.sh
++++ b/t/t9300-fast-import.sh
+@@ -2117,6 +2117,11 @@ test_expect_success 'R: only one import-marks feature allowed per stream' '
+ test_must_fail git fast-import <input
+ '
+
++test_expect_success 'R: export-marks feature forbidden by default' '
++ echo "feature export-marks=git.marks" >input &&
++ test_must_fail git fast-import <input
++'
++
+ test_expect_success 'R: export-marks feature results in a marks file being created' '
+ cat >input <<-EOF &&
+ feature export-marks=git.marks
+@@ -2127,7 +2132,7 @@ test_expect_success 'R: export-marks feature results in a marks file being creat
+
+ EOF
+
+- git fast-import <input &&
++ git fast-import --allow-unsafe-features <input &&
+ grep :1 git.marks
+ '
+
+@@ -2140,7 +2145,8 @@ test_expect_success 'R: export-marks options can be overridden by commandline op
+ hi
+
+ EOF
+- git fast-import --export-marks=cmdline-sub/other.marks <input &&
++ git fast-import --allow-unsafe-features \
++ --export-marks=cmdline-sub/other.marks <input &&
+ grep :1 cmdline-sub/other.marks &&
+ test_path_is_missing feature-sub
+ '
+@@ -2148,7 +2154,7 @@ test_expect_success 'R: export-marks options can be overridden by commandline op
+ test_expect_success 'R: catch typo in marks file name' '
+ test_must_fail git fast-import --import-marks=nonexistent.marks </dev/null &&
+ echo "feature import-marks=nonexistent.marks" |
+- test_must_fail git fast-import
++ test_must_fail git fast-import --allow-unsafe-features
+ '
+
+ test_expect_success 'R: import and output marks can be the same file' '
+@@ -2253,7 +2259,7 @@ test_expect_success 'R: import to output marks works without any content' '
+ feature export-marks=marks.new
+ EOF
+
+- git fast-import <input &&
++ git fast-import --allow-unsafe-features <input &&
+ test_cmp marks.out marks.new
+ '
+
+@@ -2263,7 +2269,7 @@ test_expect_success 'R: import marks prefers commandline marks file over the str
+ feature export-marks=marks.new
+ EOF
+
+- git fast-import --import-marks=marks.out <input &&
++ git fast-import --import-marks=marks.out --allow-unsafe-features <input &&
+ test_cmp marks.out marks.new
+ '
+
+@@ -2276,7 +2282,8 @@ test_expect_success 'R: multiple --import-marks= should be honoured' '
+
+ head -n2 marks.out > one.marks &&
+ tail -n +3 marks.out > two.marks &&
+- git fast-import --import-marks=one.marks --import-marks=two.marks <input &&
++ git fast-import --import-marks=one.marks --import-marks=two.marks \
++ --allow-unsafe-features <input &&
+ test_cmp marks.out combined.marks
+ '
+
+@@ -2289,7 +2296,7 @@ test_expect_success 'R: feature relative-marks should be honoured' '
+
+ mkdir -p .git/info/fast-import/ &&
+ cp marks.new .git/info/fast-import/relative.in &&
+- git fast-import <input &&
++ git fast-import --allow-unsafe-features <input &&
+ test_cmp marks.new .git/info/fast-import/relative.out
+ '
+
+@@ -2301,7 +2308,7 @@ test_expect_success 'R: feature no-relative-marks should be honoured' '
+ feature export-marks=non-relative.out
+ EOF
+
+- git fast-import <input &&
++ git fast-import --allow-unsafe-features <input &&
+ test_cmp marks.new non-relative.out
+ '
+
+diff --git a/transport-helper.c b/transport-helper.c
+index 91aed35ebb..ed107f5f31 100644
+--- a/transport-helper.c
++++ b/transport-helper.c
+@@ -435,6 +435,7 @@ static int get_importer(struct transport *transport, struct child_process *fasti
+ child_process_init(fastimport);
+ fastimport->in = helper->out;
+ argv_array_push(&fastimport->args, "fast-import");
++ argv_array_push(&fastimport->args, "--allow-unsafe-features");
+ argv_array_push(&fastimport->args, debug ? "--stats" : "--quiet");
+
+ if (data->bidi_import) {
+--
+2.24.0.393.g34dc348eaf
+
diff --git a/debian/patches/fast-import-disallow-feature-import-marks-by-default.diff b/debian/patches/fast-import-disallow-feature-import-marks-by-default.diff
new file mode 100644
index 0000000..50d9a85
--- /dev/null
+++ b/debian/patches/fast-import-disallow-feature-import-marks-by-default.diff
@@ -0,0 +1,125 @@
+From edfc470cc2894e9e0771571cb0d7fbf2d8ed995d Mon Sep 17 00:00:00 2001
+From: Jeff King <peff@peff.net>
+Date: Thu, 29 Aug 2019 15:08:42 -0400
+Subject: fast-import: disallow "feature import-marks" by default
+
+commit a52ed76142f6e8d993bb4c50938a408966eb2b7c upstream.
+
+As with export-marks in the previous commit, import-marks can access the
+filesystem. This is significantly less dangerous than export-marks
+because it only involves reading from arbitrary paths, rather than
+writing them. However, it could still be surprising and have security
+implications (e.g., exfiltrating data from a service that accepts
+fast-import streams).
+
+Let's lump it (and its "if-exists" counterpart) in with export-marks,
+and enable the in-stream version only if --allow-unsafe-features is set.
+
+Signed-off-by: Jeff King <peff@peff.net>
+Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
+---
+ Documentation/git-fast-import.txt | 3 ++-
+ fast-import.c | 2 ++
+ t/t9300-fast-import.sh | 22 +++++++++++++++++-----
+ 3 files changed, 21 insertions(+), 6 deletions(-)
+
+diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt
+index 55b4b203fb..221f679cb8 100644
+--- a/Documentation/git-fast-import.txt
++++ b/Documentation/git-fast-import.txt
+@@ -57,7 +57,8 @@ OPTIONS
+ allowing fast-import to access the filesystem outside of the
+ repository). These options are disabled by default, but can be
+ allowed by providing this option on the command line. This
+- currently impacts only the `feature export-marks` command.
++ currently impacts only the `export-marks`, `import-marks`, and
++ `import-marks-if-exists` feature commands.
+ +
+ Only enable this option if you trust the program generating the
+ fast-import stream! This option is enabled automatically for
+diff --git a/fast-import.c b/fast-import.c
+index 7ed61a9dad..61796517ac 100644
+--- a/fast-import.c
++++ b/fast-import.c
+@@ -3337,8 +3337,10 @@ static int parse_one_feature(const char *feature, int from_stream)
+ if (skip_prefix(feature, "date-format=", &arg)) {
+ option_date_format(arg);
+ } else if (skip_prefix(feature, "import-marks=", &arg)) {
++ check_unsafe_feature("import-marks", from_stream);
+ option_import_marks(arg, from_stream, 0);
+ } else if (skip_prefix(feature, "import-marks-if-exists=", &arg)) {
++ check_unsafe_feature("import-marks-if-exists", from_stream);
+ option_import_marks(arg, from_stream, 1);
+ } else if (skip_prefix(feature, "export-marks=", &arg)) {
+ check_unsafe_feature(feature, from_stream);
+diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh
+index 30b7c8a1f6..f8771e2766 100755
+--- a/t/t9300-fast-import.sh
++++ b/t/t9300-fast-import.sh
+@@ -2106,6 +2106,14 @@ test_expect_success 'R: abort on receiving feature after data command' '
+ test_must_fail git fast-import <input
+ '
+
++test_expect_success 'R: import-marks features forbidden by default' '
++ >git.marks &&
++ echo "feature import-marks=git.marks" >input &&
++ test_must_fail git fast-import <input &&
++ echo "feature import-marks-if-exists=git.marks" >input &&
++ test_must_fail git fast-import <input
++'
++
+ test_expect_success 'R: only one import-marks feature allowed per stream' '
+ >git.marks &&
+ >git2.marks &&
+@@ -2114,7 +2122,7 @@ test_expect_success 'R: only one import-marks feature allowed per stream' '
+ feature import-marks=git2.marks
+ EOF
+
+- test_must_fail git fast-import <input
++ test_must_fail git fast-import --allow-unsafe-features <input
+ '
+
+ test_expect_success 'R: export-marks feature forbidden by default' '
+@@ -2210,7 +2218,8 @@ test_expect_success 'R: feature import-marks-if-exists' '
+ rm -f io.marks &&
+ >expect &&
+
+- git fast-import --export-marks=io.marks <<-\EOF &&
++ git fast-import --export-marks=io.marks \
++ --allow-unsafe-features <<-\EOF &&
+ feature import-marks-if-exists=not_io.marks
+ EOF
+ test_cmp expect io.marks &&
+@@ -2221,7 +2230,8 @@ test_expect_success 'R: feature import-marks-if-exists' '
+ echo ":1 $blob" >expect &&
+ echo ":2 $blob" >>expect &&
+
+- git fast-import --export-marks=io.marks <<-\EOF &&
++ git fast-import --export-marks=io.marks \
++ --allow-unsafe-features <<-\EOF &&
+ feature import-marks-if-exists=io.marks
+ blob
+ mark :2
+@@ -2234,7 +2244,8 @@ test_expect_success 'R: feature import-marks-if-exists' '
+ echo ":3 $blob" >>expect &&
+
+ git fast-import --import-marks=io.marks \
+- --export-marks=io.marks <<-\EOF &&
++ --export-marks=io.marks \
++ --allow-unsafe-features <<-\EOF &&
+ feature import-marks-if-exists=not_io.marks
+ blob
+ mark :3
+@@ -2247,7 +2258,8 @@ test_expect_success 'R: feature import-marks-if-exists' '
+ >expect &&
+
+ git fast-import --import-marks-if-exists=not_io.marks \
+- --export-marks=io.marks <<-\EOF &&
++ --export-marks=io.marks \
++ --allow-unsafe-features <<-\EOF &&
+ feature import-marks-if-exists=io.marks
+ EOF
+ test_cmp expect io.marks
+--
+2.24.0.393.g34dc348eaf
+
diff --git a/debian/patches/fast-import-stop-creating-leading-directories-for-imp.diff b/debian/patches/fast-import-stop-creating-leading-directories-for-imp.diff
new file mode 100644
index 0000000..5de88f0
--- /dev/null
+++ b/debian/patches/fast-import-stop-creating-leading-directories-for-imp.diff
@@ -0,0 +1,42 @@
+From 6e50cc232ea775b69ad862245bd64e31447afe3c Mon Sep 17 00:00:00 2001
+From: Jeff King <peff@peff.net>
+Date: Thu, 29 Aug 2019 13:07:04 -0400
+Subject: fast-import: stop creating leading directories for import-marks
+
+commit e075dba3723875f478654068609f69b2a5af8566 upstream.
+
+When asked to import marks from "subdir/file.marks", we create the
+leading directory "subdir" if it doesn't exist. This makes no sense for
+importing marks, where we only ever open the path for reading.
+
+Most of the time this would be a noop, since if the marks file exists,
+then the leading directories exist, too. But if it doesn't (e.g.,
+because --import-marks-if-exists was used), then we'd create the useless
+directory.
+
+This dates back to 580d5f83e7 (fast-import: always create marks_file
+directories, 2010-03-29). Even then it was useless, so it seems to have
+been added in error alongside the --export-marks case (which _is_
+helpful).
+
+Signed-off-by: Jeff King <peff@peff.net>
+Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
+---
+ fast-import.c | 1 -
+ 1 file changed, 1 deletion(-)
+
+diff --git a/fast-import.c b/fast-import.c
+index e8fbc22ddd..b9144e90bd 100644
+--- a/fast-import.c
++++ b/fast-import.c
+@@ -3219,7 +3219,6 @@ static void option_import_marks(const char *marks,
+ }
+
+ import_marks_file = make_fast_import_path(marks);
+- safe_create_leading_directories_const(import_marks_file);
+ import_marks_file_from_stream = from_stream;
+ import_marks_file_ignore_missing = ignore_missing;
+ }
+--
+2.24.0.393.g34dc348eaf
+
diff --git a/debian/patches/fast-import-tighten-parsing-of-boolean-command-line-o.diff b/debian/patches/fast-import-tighten-parsing-of-boolean-command-line-o.diff
new file mode 100644
index 0000000..74f36b4
--- /dev/null
+++ b/debian/patches/fast-import-tighten-parsing-of-boolean-command-line-o.diff
@@ -0,0 +1,41 @@
+From 04b3de36d735275dd6dc2561e83200c14d65f0af Mon Sep 17 00:00:00 2001
+From: Jeff King <peff@peff.net>
+Date: Thu, 29 Aug 2019 11:25:45 -0400
+Subject: fast-import: tighten parsing of boolean command line options
+
+commit 11e934d56e46875b24d8a047d44b45ff243f6715 upstream.
+
+We parse options like "--max-pack-size=" using skip_prefix(), which
+makes sense to get at the bytes after the "=". However, we also parse
+"--quiet" and "--stats" with skip_prefix(), which allows things like
+"--quiet-nonsense" to behave like "--quiet".
+
+This was a mistaken conversion in 0f6927c229 (fast-import: put option
+parsing code in separate functions, 2009-12-04). Let's tighten this to
+an exact match, which was the original intent.
+
+Signed-off-by: Jeff King <peff@peff.net>
+Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
+---
+ fast-import.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/fast-import.c b/fast-import.c
+index cb545d7df5..e8fbc22ddd 100644
+--- a/fast-import.c
++++ b/fast-import.c
+@@ -3305,9 +3305,9 @@ static int parse_one_option(const char *option)
+ option_active_branches(option);
+ } else if (skip_prefix(option, "export-pack-edges=", &option)) {
+ option_export_pack_edges(option);
+- } else if (starts_with(option, "quiet")) {
++ } else if (!strcmp(option, "quiet")) {
+ show_stats = 0;
+- } else if (starts_with(option, "stats")) {
++ } else if (!strcmp(option, "stats")) {
+ show_stats = 1;
+ } else {
+ return 0;
+--
+2.24.0.393.g34dc348eaf
+
diff --git a/debian/patches/fsck-reject-submodule.update-command-in-.gitmodules.diff b/debian/patches/fsck-reject-submodule.update-command-in-.gitmodules.diff
new file mode 100644
index 0000000..cb1d8ca
--- /dev/null
+++ b/debian/patches/fsck-reject-submodule.update-command-in-.gitmodules.diff
@@ -0,0 +1,78 @@
+From 3c3047920680f0c1e91439d0a94162d9b567e9af Mon Sep 17 00:00:00 2001
+From: Jonathan Nieder <jrnieder@gmail.com>
+Date: Thu, 5 Dec 2019 01:30:43 -0800
+Subject: fsck: reject submodule.update = !command in .gitmodules
+
+commit bb92255ebe6bccd76227e023d6d0bc997e318ad0 upstream.
+
+This allows hosting providers to detect whether they are being used
+to attack users using malicious 'update = !command' settings in
+.gitmodules.
+
+Since ac1fbbda2013 (submodule: do not copy unknown update mode from
+.gitmodules, 2013-12-02), in normal cases such settings have been
+treated as 'update = none', so forbidding them should not produce any
+collateral damage to legitimate uses. A quick search does not reveal
+any repositories making use of this construct, either.
+
+Reported-by: Joern Schneeweisz <jschneeweisz@gitlab.com>
+Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
+Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
+Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
+---
+ fsck.c | 6 ++++++
+ t/t7406-submodule-update.sh | 14 ++++++++++++++
+ 2 files changed, 20 insertions(+)
+
+diff --git a/fsck.c b/fsck.c
+index 135fe8c4bc..3a65a3baaf 100644
+--- a/fsck.c
++++ b/fsck.c
+@@ -95,6 +95,7 @@ static int oidhash_contains(struct hashmap *h, const struct object_id *oid)
+ FUNC(GITMODULES_SYMLINK, ERROR) \
+ FUNC(GITMODULES_URL, ERROR) \
+ FUNC(GITMODULES_PATH, ERROR) \
++ FUNC(GITMODULES_UPDATE, ERROR) \
+ /* warnings */ \
+ FUNC(BAD_FILEMODE, WARN) \
+ FUNC(EMPTY_NAME, WARN) \
+@@ -998,6 +999,11 @@ static int fsck_gitmodules_fn(const char *var, const char *value, void *vdata)
+ FSCK_MSG_GITMODULES_PATH,
+ "disallowed submodule path: %s",
+ value);
++ if (!strcmp(key, "update") && value && *value == '!')
++ data->ret |= report(data->options, data->obj,
++ FSCK_MSG_GITMODULES_UPDATE,
++ "disallowed submodule update setting: %s",
++ value);
+ free(name);
+
+ return 0;
+diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh
+index 3781809bd6..a74e7ea477 100755
+--- a/t/t7406-submodule-update.sh
++++ b/t/t7406-submodule-update.sh
+@@ -397,6 +397,20 @@ test_expect_success 'submodule update - command in .gitmodules is ignored' '
+ git -C super submodule update submodule
+ '
+
++test_expect_success 'fsck detects command in .gitmodules' '
++ git init command-in-gitmodules &&
++ (
++ cd command-in-gitmodules &&
++ git submodule add ../submodule submodule &&
++ test_commit adding-submodule &&
++
++ git config -f .gitmodules submodule.submodule.update "!false" &&
++ git add .gitmodules &&
++ test_commit configuring-update &&
++ test_must_fail git fsck
++ )
++'
++
+ cat << EOF >expect
+ Execution of 'false $submodulesha1' failed in submodule path 'submodule'
+ EOF
+--
+2.24.0.393.g34dc348eaf
+
diff --git a/debian/patches/is_ntfs_dotgit-only-verify-the-leading-segment.diff b/debian/patches/is_ntfs_dotgit-only-verify-the-leading-segment.diff
new file mode 100644
index 0000000..25e824c
--- /dev/null
+++ b/debian/patches/is_ntfs_dotgit-only-verify-the-leading-segment.diff
@@ -0,0 +1,144 @@
+From 625a6aef62b0b02bd600ac88bbac68e8543e15d3 Mon Sep 17 00:00:00 2001
+From: Johannes Schindelin <johannes.schindelin@gmx.de>
+Date: Mon, 23 Sep 2019 08:58:11 +0200
+Subject: is_ntfs_dotgit(): only verify the leading segment
+
+commit 288a74bcd28229a00c3632f18cba92dbfdf73ee9 upstream.
+
+The config setting `core.protectNTFS` is specifically designed to work
+not only on Windows, but anywhere, to allow for repositories hosted on,
+say, Linux servers to be protected against NTFS-specific attack vectors.
+
+As a consequence, `is_ntfs_dotgit()` manually splits backslash-separated
+paths (but does not do the same for paths separated by forward slashes),
+under the assumption that the backslash might not be a valid directory
+separator on the _current_ Operating System.
+
+However, the two callers, `verify_path()` and `fsck_tree()`, are
+supposed to feed only individual path segments to the `is_ntfs_dotgit()`
+function.
+
+This causes a lot of duplicate scanning (and very inefficient scanning,
+too, as the inner loop of `is_ntfs_dotgit()` was optimized for
+readability rather than for speed.
+
+Let's simplify the design of `is_ntfs_dotgit()` by putting the burden of
+splitting the paths by backslashes as directory separators on the
+callers of said function.
+
+Consequently, the `verify_path()` function, which already splits the
+path by directory separators, now treats backslashes as directory
+separators _explicitly_ when `core.protectNTFS` is turned on, even on
+platforms where the backslash is _not_ a directory separator.
+
+Note that we have to repeat some code in `verify_path()`: if the
+backslash is not a directory separator on the current Operating System,
+we want to allow file names like `\`, but we _do_ want to disallow paths
+that are clearly intended to cause harm when the repository is cloned on
+Windows.
+
+The `fsck_tree()` function (the other caller of `is_ntfs_dotgit()`) now
+needs to look for backslashes in tree entries' names specifically when
+`core.protectNTFS` is turned on. While it would be tempting to
+completely disallow backslashes in that case (much like `fsck` reports
+names containing forward slashes as "full paths"), this would be
+overzealous: when `core.protectNTFS` is turned on in a non-Windows
+setup, backslashes are perfectly valid characters in file names while we
+_still_ want to disallow tree entries that are clearly designed to
+exploit NTFS-specific behavior.
+
+This simplification will make subsequent changes easier to implement,
+such as turning `core.protectNTFS` on by default (not only on Windows)
+or protecting against attack vectors involving NTFS Alternate Data
+Streams.
+
+Incidentally, this change allows for catching malicious repositories
+that contain tree entries of the form `dir\.gitmodules` already on the
+server side rather than only on the client side (and previously only on
+Windows): in contrast to `is_ntfs_dotgit()`, the
+`is_ntfs_dotgitmodules()` function already expects the caller to split
+the paths by directory separators.
+
+Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
+Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
+---
+ fsck.c | 18 +++++++++++++++++-
+ path.c | 5 +----
+ read-cache.c | 8 ++++++++
+ 3 files changed, 26 insertions(+), 5 deletions(-)
+
+diff --git a/fsck.c b/fsck.c
+index def446a3e3..135fe8c4bc 100644
+--- a/fsck.c
++++ b/fsck.c
+@@ -590,7 +590,7 @@ static int fsck_tree(struct tree *item, struct fsck_options *options)
+
+ while (desc.size) {
+ unsigned mode;
+- const char *name;
++ const char *name, *backslash;
+ const struct object_id *oid;
+
+ oid = tree_entry_extract(&desc, &name, &mode);
+@@ -612,6 +612,22 @@ static int fsck_tree(struct tree *item, struct fsck_options *options)
+ ".gitmodules is a symbolic link");
+ }
+
++ if ((backslash = strchr(name, '\\'))) {
++ while (backslash) {
++ backslash++;
++ has_dotgit |= is_ntfs_dotgit(backslash);
++ if (is_ntfs_dotgitmodules(backslash)) {
++ if (!S_ISLNK(mode))
++ oidhash_insert(&gitmodules_found, oid);
++ else
++ retval += report(options, &item->object,
++ FSCK_MSG_GITMODULES_SYMLINK,
++ ".gitmodules is a symbolic link");
++ }
++ backslash = strchr(backslash, '\\');
++ }
++ }
++
+ if (update_tree_entry_gently(&desc)) {
+ retval += report(options, &item->object, FSCK_MSG_BAD_TREE, "cannot be parsed as a tree");
+ break;
+diff --git a/path.c b/path.c
+index 0444208dfb..5f23d26668 100644
+--- a/path.c
++++ b/path.c
+@@ -1273,10 +1273,7 @@ int is_ntfs_dotgit(const char *name)
+ if (only_spaces_and_periods(name, len, 5) &&
+ !strncasecmp(name, "git~1", 5))
+ return 1;
+- if (name[len] != '\\')
+- return 0;
+- name += len + 1;
+- len = -1;
++ return 0;
+ }
+ }
+
+diff --git a/read-cache.c b/read-cache.c
+index 6dca825751..f0f49dbbb7 100644
+--- a/read-cache.c
++++ b/read-cache.c
+@@ -882,7 +882,15 @@ int verify_path(const char *path, unsigned mode)
+ if ((c == '.' && !verify_dotfile(path, mode)) ||
+ is_dir_sep(c) || c == '\0')
+ return 0;
++ } else if (c == '\\' && protect_ntfs) {
++ if (is_ntfs_dotgit(path))
++ return 0;
++ if (S_ISLNK(mode)) {
++ if (is_ntfs_dotgitmodules(path))
++ return 0;
++ }
+ }
++
+ c = *path++;
+ }
+ }
+--
+2.24.0.393.g34dc348eaf
+
diff --git a/debian/patches/is_ntfs_dotgit-speed-it-up.diff b/debian/patches/is_ntfs_dotgit-speed-it-up.diff
new file mode 100644
index 0000000..c677714
--- /dev/null
+++ b/debian/patches/is_ntfs_dotgit-speed-it-up.diff
@@ -0,0 +1,114 @@
+From 5c2b396f0c1b08fb152981fd1105bd219a82570d Mon Sep 17 00:00:00 2001
+From: Johannes Schindelin <johannes.schindelin@gmx.de>
+Date: Fri, 6 Sep 2019 21:09:35 +0200
+Subject: is_ntfs_dotgit(): speed it up
+
+commit 3a85dc7d534fc2d410ddc0c771c963b20d1b4857 upstream.
+
+Previously, this function was written without focusing on speed,
+intending to make reviewing the code as easy as possible, to avoid any
+bugs in this critical code.
+
+Turns out: we can do much better on both accounts. With this patch, we
+make it as fast as this developer can make it go:
+
+- We avoid the call to `is_dir_sep()` and make all the character
+ comparisons explicit.
+
+- We avoid the cost of calling `strncasecmp()` and unroll the test for
+ `.git` and `git~1`, not even using `tolower()` because it is faster to
+ compare against two constant values.
+
+- We look for `.git` and `.git~1` first thing, and return early if not
+ found.
+
+- We also avoid calling a separate function for detecting chains of
+ spaces and periods.
+
+Each of these improvements has a noticeable impact on the speed of
+`is_ntfs_dotgit()`.
+
+Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
+Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
+---
+ path.c | 55 ++++++++++++++++++++++++++++++-------------------------
+ 1 file changed, 30 insertions(+), 25 deletions(-)
+
+diff --git a/path.c b/path.c
+index 93e321bbd0..83006824b3 100644
+--- a/path.c
++++ b/path.c
+@@ -1219,20 +1219,6 @@ int daemon_avoid_alias(const char *p)
+ }
+ }
+
+-static int only_spaces_and_periods(const char *path, size_t len, size_t skip)
+-{
+- if (len < skip)
+- return 0;
+- len -= skip;
+- path += skip;
+- while (len-- > 0) {
+- char c = *(path++);
+- if (c != ' ' && c != '.')
+- return 0;
+- }
+- return 1;
+-}
+-
+ /*
+ * On NTFS, we need to be careful to disallow certain synonyms of the `.git/`
+ * directory:
+@@ -1272,19 +1258,38 @@ static int only_spaces_and_periods(const char *path, size_t len, size_t skip)
+ */
+ int is_ntfs_dotgit(const char *name)
+ {
+- size_t len;
++ char c;
+
+- for (len = 0; ; len++)
+- if (!name[len] || name[len] == '\\' || is_dir_sep(name[len]) ||
+- name[len] == ':') {
+- if (only_spaces_and_periods(name, len, 4) &&
+- !strncasecmp(name, ".git", 4))
+- return 1;
+- if (only_spaces_and_periods(name, len, 5) &&
+- !strncasecmp(name, "git~1", 5))
+- return 1;
++ /*
++ * Note that when we don't find `.git` or `git~1` we end up with `name`
++ * advanced partway through the string. That's okay, though, as we
++ * return immediately in those cases, without looking at `name` any
++ * further.
++ */
++ c = *(name++);
++ if (c == '.') {
++ /* .git */
++ if (((c = *(name++)) != 'g' && c != 'G') ||
++ ((c = *(name++)) != 'i' && c != 'I') ||
++ ((c = *(name++)) != 't' && c != 'T'))
+ return 0;
+- }
++ } else if (c == 'g' || c == 'G') {
++ /* git ~1 */
++ if (((c = *(name++)) != 'i' && c != 'I') ||
++ ((c = *(name++)) != 't' && c != 'T') ||
++ *(name++) != '~' ||
++ *(name++) != '1')
++ return 0;
++ } else
++ return 0;
++
++ for (;;) {
++ c = *(name++);
++ if (!c || c == '\\' || c == '/' || c == ':')
++ return 1;
++ if (c != '.' && c != ' ')
++ return 0;
++ }
+ }
+
+ static int is_ntfs_dot_generic(const char *name,
+--
+2.24.0.393.g34dc348eaf
+
diff --git a/debian/patches/path-also-guard-.gitmodules-against-NTFS-Alternate-Da.diff b/debian/patches/path-also-guard-.gitmodules-against-NTFS-Alternate-Da.diff
new file mode 100644
index 0000000..936441b
--- /dev/null
+++ b/debian/patches/path-also-guard-.gitmodules-against-NTFS-Alternate-Da.diff
@@ -0,0 +1,64 @@
+From 7be9f6f27a64c2041fd9bbeaa61d6193c70c1614 Mon Sep 17 00:00:00 2001
+From: Johannes Schindelin <johannes.schindelin@gmx.de>
+Date: Wed, 28 Aug 2019 12:22:17 +0200
+Subject: path: also guard `.gitmodules` against NTFS Alternate Data Streams
+
+commit 91bd46588e6959e6903e275f78b10bd07830d547 upstream.
+
+We just safe-guarded `.git` against NTFS Alternate Data Stream-related
+attack vectors, and now it is time to do the same for `.gitmodules`.
+
+Note: In the added regression test, we refrain from verifying all kinds
+of variations between short names and NTFS Alternate Data Streams: as
+the new code disallows _all_ Alternate Data Streams of `.gitmodules`, it
+is enough to test one in order to know that all of them are guarded
+against.
+
+Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
+Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
+---
+ path.c | 2 +-
+ t/t0060-path-utils.sh | 7 ++++++-
+ 2 files changed, 7 insertions(+), 2 deletions(-)
+
+diff --git a/path.c b/path.c
+index 4ab11c8d45..93e321bbd0 100644
+--- a/path.c
++++ b/path.c
+@@ -1300,7 +1300,7 @@ static int is_ntfs_dot_generic(const char *name,
+ only_spaces_and_periods:
+ for (;;) {
+ char c = name[i++];
+- if (!c)
++ if (!c || c == ':')
+ return 1;
+ if (c != ' ' && c != '.')
+ return 0;
+diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh
+index f2e58b4604..7298439947 100755
+--- a/t/t0060-path-utils.sh
++++ b/t/t0060-path-utils.sh
+@@ -406,6 +406,9 @@ test_expect_success 'match .gitmodules' '
+ ~1000000 \
+ ~9999999 \
+ \
++ .gitmodules:\$DATA \
++ "gitmod~4 . :\$DATA" \
++ \
+ --not \
+ ".gitmodules x" \
+ ".gitmodules .x" \
+@@ -430,7 +433,9 @@ test_expect_success 'match .gitmodules' '
+ \
+ GI7EB~1 \
+ GI7EB~01 \
+- GI7EB~1X
++ GI7EB~1X \
++ \
++ .gitmodules,:\$DATA
+ '
+
+ test_done
+--
+2.24.0.393.g34dc348eaf
+
diff --git a/debian/patches/path-safeguard-.git-against-NTFS-Alternate-Streams-Ac.diff b/debian/patches/path-safeguard-.git-against-NTFS-Alternate-Streams-Ac.diff
new file mode 100644
index 0000000..1442c78
--- /dev/null
+++ b/debian/patches/path-safeguard-.git-against-NTFS-Alternate-Streams-Ac.diff
@@ -0,0 +1,96 @@
+From 3853b2d546c43d5bf4be6342e359b42f81ac2999 Mon Sep 17 00:00:00 2001
+From: Johannes Schindelin <johannes.schindelin@gmx.de>
+Date: Wed, 28 Aug 2019 12:22:17 +0200
+Subject: path: safeguard `.git` against NTFS Alternate Streams Accesses
+
+commit 7c3745fc6185495d5765628b4dfe1bd2c25a2981 upstream.
+
+Probably inspired by HFS' resource streams, NTFS supports "Alternate
+Data Streams": by appending `:<stream-name>` to the file name,
+information in addition to the file contents can be written and read,
+information that is copied together with the file (unless copied to a
+non-NTFS location).
+
+These Alternate Data Streams are typically used for things like marking
+an executable as having just been downloaded from the internet (and
+hence not necessarily being trustworthy).
+
+In addition to a stream name, a stream type can be appended, like so:
+`:<stream-name>:<stream-type>`. Unless specified, the default stream
+type is `$DATA` for files and `$INDEX_ALLOCATION` for directories. In
+other words, `.git::$INDEX_ALLOCATION` is a valid way to reference the
+`.git` directory!
+
+In our work in Git v2.2.1 to protect Git on NTFS drives under
+`core.protectNTFS`, we focused exclusively on NTFS short names, unaware
+of the fact that NTFS Alternate Data Streams offer a similar attack
+vector.
+
+Let's fix this.
+
+Seeing as it is better to be safe than sorry, we simply disallow paths
+referring to *any* NTFS Alternate Data Stream of `.git`, not just
+`::$INDEX_ALLOCATION`. This also simplifies the implementation.
+
+This closes CVE-2019-1352.
+
+Further reading about NTFS Alternate Data Streams:
+https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/c54dec26-1551-4d3a-a0ea-4fa40f848eb3
+
+Reported-by: Nicolas Joly <Nicolas.Joly@microsoft.com>
+Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
+Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
+---
+ path.c | 12 +++++++++++-
+ t/t1014-read-tree-confusing.sh | 1 +
+ 2 files changed, 12 insertions(+), 1 deletion(-)
+
+diff --git a/path.c b/path.c
+index 5f23d26668..4ab11c8d45 100644
+--- a/path.c
++++ b/path.c
+@@ -1252,10 +1252,19 @@ static int only_spaces_and_periods(const char *path, size_t len, size_t skip)
+ * `.git` is the first item in a directory, therefore it will be associated
+ * with the short name `git~1` (unless short names are disabled).
+ *
++ * - For yet other historical reasons, NTFS supports so-called "Alternate Data
++ * Streams", i.e. metadata associated with a given file, referred to via
++ * `<filename>:<stream-name>:<stream-type>`. There exists a default stream
++ * type for directories, allowing `.git/` to be accessed via
++ * `.git::$INDEX_ALLOCATION/`.
++ *
+ * When this function returns 1, it indicates that the specified file/directory
+ * name refers to a `.git` file or directory, or to any of these synonyms, and
+ * Git should therefore not track it.
+ *
++ * For performance reasons, _all_ Alternate Data Streams of `.git/` are
++ * forbidden, not just `::$INDEX_ALLOCATION`.
++ *
+ * This function is intended to be used by `git fsck` even on platforms where
+ * the backslash is a regular filename character, therefore it needs to handle
+ * backlash characters in the provided `name` specially: they are interpreted
+@@ -1266,7 +1275,8 @@ int is_ntfs_dotgit(const char *name)
+ size_t len;
+
+ for (len = 0; ; len++)
+- if (!name[len] || name[len] == '\\' || is_dir_sep(name[len])) {
++ if (!name[len] || name[len] == '\\' || is_dir_sep(name[len]) ||
++ name[len] == ':') {
+ if (only_spaces_and_periods(name, len, 4) &&
+ !strncasecmp(name, ".git", 4))
+ return 1;
+diff --git a/t/t1014-read-tree-confusing.sh b/t/t1014-read-tree-confusing.sh
+index 2f5a25d503..da3376b3bb 100755
+--- a/t/t1014-read-tree-confusing.sh
++++ b/t/t1014-read-tree-confusing.sh
+@@ -49,6 +49,7 @@ git~1
+ .git.SPACE .git.{space}
+ .\\\\.GIT\\\\foobar backslashes
+ .git\\\\foobar backslashes2
++.git...:alternate-stream
+ EOF
+
+ test_expect_success 'utf-8 paths allowed with core.protectHFS off' '
+--
+2.24.0.393.g34dc348eaf
+
diff --git a/debian/patches/path.c-document-the-purpose-of-is_ntfs_dotgit.diff b/debian/patches/path.c-document-the-purpose-of-is_ntfs_dotgit.diff
new file mode 100644
index 0000000..fc71282
--- /dev/null
+++ b/debian/patches/path.c-document-the-purpose-of-is_ntfs_dotgit.diff
@@ -0,0 +1,59 @@
+From 53b0b78673e950d66f7f21fac8e83fa324f9d83e Mon Sep 17 00:00:00 2001
+From: Johannes Schindelin <johannes.schindelin@gmx.de>
+Date: Mon, 16 Sep 2019 20:44:31 +0200
+Subject: path.c: document the purpose of `is_ntfs_dotgit()`
+
+commit 525e7fba7854c23ee3530d0bf88d75f106f14c95 upstream.
+
+Previously, this function was completely undocumented. It is worth,
+though, to explain what is going on, as it is not really obvious at all.
+
+Suggested-by: Garima Singh <garima.singh@microsoft.com>
+Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
+Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
+---
+ path.c | 28 ++++++++++++++++++++++++++++
+ 1 file changed, 28 insertions(+)
+
+diff --git a/path.c b/path.c
+index 98c35e0e9c..0444208dfb 100644
+--- a/path.c
++++ b/path.c
+@@ -1233,6 +1233,34 @@ static int only_spaces_and_periods(const char *path, size_t len, size_t skip)
+ return 1;
+ }
+
++/*
++ * On NTFS, we need to be careful to disallow certain synonyms of the `.git/`
++ * directory:
++ *
++ * - For historical reasons, file names that end in spaces or periods are
++ * automatically trimmed. Therefore, `.git . . ./` is a valid way to refer
++ * to `.git/`.
++ *
++ * - For other historical reasons, file names that do not conform to the 8.3
++ * format (up to eight characters for the basename, three for the file
++ * extension, certain characters not allowed such as `+`, etc) are associated
++ * with a so-called "short name", at least on the `C:` drive by default.
++ * Which means that `git~1/` is a valid way to refer to `.git/`.
++ *
++ * Note: Technically, `.git/` could receive the short name `git~2` if the
++ * short name `git~1` were already used. In Git, however, we guarantee that
++ * `.git` is the first item in a directory, therefore it will be associated
++ * with the short name `git~1` (unless short names are disabled).
++ *
++ * When this function returns 1, it indicates that the specified file/directory
++ * name refers to a `.git` file or directory, or to any of these synonyms, and
++ * Git should therefore not track it.
++ *
++ * This function is intended to be used by `git fsck` even on platforms where
++ * the backslash is a regular filename character, therefore it needs to handle
++ * backlash characters in the provided `name` specially: they are interpreted
++ * as directory separators.
++ */
+ int is_ntfs_dotgit(const char *name)
+ {
+ size_t len;
+--
+2.24.0.393.g34dc348eaf
+
diff --git a/debian/patches/protect_ntfs-turn-on-NTFS-protection-by-default.diff b/debian/patches/protect_ntfs-turn-on-NTFS-protection-by-default.diff
new file mode 100644
index 0000000..61bd94b
--- /dev/null
+++ b/debian/patches/protect_ntfs-turn-on-NTFS-protection-by-default.diff
@@ -0,0 +1,144 @@
+From 084a768d9ac36e05d3f79211723d383ac8601a05 Mon Sep 17 00:00:00 2001
+From: Johannes Schindelin <johannes.schindelin@gmx.de>
+Date: Mon, 9 Sep 2019 21:04:41 +0200
+Subject: protect_ntfs: turn on NTFS protection by default
+
+commit 9102f958ee5254b10c0be72672aa3305bf4f4704 upstream.
+
+Back in the DOS days, in the FAT file system, file names always
+consisted of a base name of length 8 plus a file extension of length 3.
+Shorter file names were simply padded with spaces to the full 8.3
+format.
+
+Later, the FAT file system was taught to support _also_ longer names,
+with an 8.3 "short name" as primary file name. While at it, the same
+facility allowed formerly illegal file names, such as `.git` (empty base
+names were not allowed), which would have the "short name" `git~1`
+associated with it.
+
+For backwards-compatibility, NTFS supports alternative 8.3 short
+filenames, too, even if starting with Windows Vista, they are only
+generated on the system drive by default.
+
+We addressed the problem that the `.git/` directory can _also_ be
+accessed via `git~1/` (when short names are enabled) in 2b4c6efc821
+(read-cache: optionally disallow NTFS .git variants, 2014-12-16), i.e.
+since Git v1.9.5, by introducing the config setting `core.protectNTFS`
+and enabling it by default on Windows.
+
+In the meantime, Windows 10 introduced the "Windows Subsystem for Linux"
+(short: WSL), i.e. a way to run Linux applications/distributions in a
+thinly-isolated subsystem on Windows (giving rise to many a "2016 is the
+Year of Linux on the Desktop" jokes). WSL is getting increasingly
+popular, also due to the painless way Linux application can operate
+directly ("natively") on files on Windows' file system: the Windows
+drives are mounted automatically (e.g. `C:` as `/mnt/c/`).
+
+Taken together, this means that we now have to enable the safe-guards of
+Git v1.9.5 also in WSL: it is possible to access a `.git` directory
+inside `/mnt/c/` via the 8.3 name `git~1` (unless short name generation
+was disabled manually). Since regular Linux distributions run in WSL,
+this means we have to enable `core.protectNTFS` at least on Linux, too.
+
+To enable Services for Macintosh in Windows NT to store so-called
+resource forks, NTFS introduced "Alternate Data Streams". Essentially,
+these constitute additional metadata that are connected to (and copied
+with) their associated files, and they are accessed via pseudo file
+names of the form `filename:<stream-name>:<stream-type>`.
+
+In a recent patch, we extended `core.protectNTFS` to also protect
+against accesses via NTFS Alternate Data Streams, e.g. to prevent
+contents of the `.git/` directory to be "tracked" via yet another
+alternative file name.
+
+While it is not possible (at least by default) to access files via NTFS
+Alternate Data Streams from within WSL, the defaults on macOS when
+mounting network shares via SMB _do_ allow accessing files and
+directories in that way. Therefore, we need to enable `core.protectNTFS`
+on macOS by default, too, and really, on any Operating System that can
+mount network shares via SMB/CIFS.
+
+A couple of approaches were considered for fixing this:
+
+1. We could perform a dynamic NTFS check similar to the `core.symlinks`
+ check in `init`/`clone`: instead of trying to create a symbolic link
+ in the `.git/` directory, we could create a test file and try to
+ access `.git/config` via 8.3 name and/or Alternate Data Stream.
+
+2. We could simply "flip the switch" on `core.protectNTFS`, to make it
+ "on by default".
+
+The obvious downside of 1. is that it won't protect worktrees that were
+clone with a vulnerable Git version already. We considered patching code
+paths that check out files to check whether we're running on an NTFS
+system dynamically and persist the result in the repository-local config
+setting `core.protectNTFS`, but in the end decided that this solution
+would be too fragile, and too involved.
+
+The obvious downside of 2. is that everybody will have to "suffer" the
+performance penalty incurred from calling `is_ntfs_dotgit()` on every
+path, even in setups where.
+
+After the recent work to accelerate `is_ntfs_dotgit()` in most cases,
+it looks as if the time spent on validating ten million random
+file names increases only negligibly (less than 20ms, well within the
+standard deviation of ~50ms). Therefore the benefits outweigh the cost.
+
+Another downside of this is that paths that might have been acceptable
+previously now will be forbidden. Realistically, though, this is an
+improvement because public Git hosters already would reject any `git
+push` that contains such file names.
+
+Note: There might be a similar problem mounting HFS+ on Linux. However,
+this scenario has been considered unlikely and in light of the cost (in
+the aforementioned benchmark, `core.protectHFS = true` increased the
+time from ~440ms to ~610ms), it was decided _not_ to touch the default
+of `core.protectHFS`.
+
+This change addresses CVE-2019-1353.
+
+Reported-by: Nicolas Joly <Nicolas.Joly@microsoft.com>
+Helped-by: Garima Singh <garima.singh@microsoft.com>
+Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
+Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
+---
+ config.mak.uname | 2 --
+ environment.c | 2 +-
+ 2 files changed, 1 insertion(+), 3 deletions(-)
+
+diff --git a/config.mak.uname b/config.mak.uname
+index 2831a68c3c..1a7e7cf62f 100644
+--- a/config.mak.uname
++++ b/config.mak.uname
+@@ -383,7 +383,6 @@ ifeq ($(uname_S),Windows)
+ EXTLIBS = user32.lib advapi32.lib shell32.lib wininet.lib ws2_32.lib invalidcontinue.obj
+ PTHREAD_LIBS =
+ lib =
+- BASIC_CFLAGS += -DPROTECT_NTFS_DEFAULT=1
+ ifndef DEBUG
+ BASIC_CFLAGS += -GL -Os -MD
+ BASIC_LDFLAGS += -LTCG
+@@ -524,7 +523,6 @@ ifneq (,$(findstring MINGW,$(uname_S)))
+ COMPAT_OBJS += compat/mingw.o compat/winansi.o \
+ compat/win32/pthread.o compat/win32/syslog.o \
+ compat/win32/dirent.o
+- BASIC_CFLAGS += -DPROTECT_NTFS_DEFAULT=1
+ EXTLIBS += -lws2_32
+ GITLIBS += git.res
+ PTHREAD_LIBS =
+diff --git a/environment.c b/environment.c
+index 0935ec696e..62ba70176b 100644
+--- a/environment.c
++++ b/environment.c
+@@ -71,7 +71,7 @@ enum hide_dotfiles_type hide_dotfiles = HIDE_DOTFILES_DOTGITONLY;
+ int protect_hfs = PROTECT_HFS_DEFAULT;
+
+ #ifndef PROTECT_NTFS_DEFAULT
+-#define PROTECT_NTFS_DEFAULT 0
++#define PROTECT_NTFS_DEFAULT 1
+ #endif
+ int protect_ntfs = PROTECT_NTFS_DEFAULT;
+
+--
+2.24.0.393.g34dc348eaf
+
diff --git a/debian/patches/series b/debian/patches/series
index 693edf4..05f0975 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -42,3 +42,22 @@
fsck-detect-submodule-urls-starting-with-dash.diff
fsck-detect-submodule-paths-starting-with-dash.diff
cvsimport-apply-shell-quoting-regex-globally.diff
+t9300-drop-some-useless-uses-of-cat.diff
+t9300-create-marks-files-for-double-import-marks-test.diff
+fast-import-tighten-parsing-of-boolean-command-line-o.diff
+fast-import-stop-creating-leading-directories-for-imp.diff
+fast-import-delay-creating-leading-directories-for-ex.diff
+fast-import-disallow-feature-export-marks-by-default.diff
+fast-import-disallow-feature-import-marks-by-default.diff
+clone-recurse-submodules-prevent-name-squatting-on-Wi.diff
+path.c-document-the-purpose-of-is_ntfs_dotgit.diff
+is_ntfs_dotgit-only-verify-the-leading-segment.diff
+path-safeguard-.git-against-NTFS-Alternate-Streams-Ac.diff
+path-also-guard-.gitmodules-against-NTFS-Alternate-Da.diff
+is_ntfs_dotgit-speed-it-up.diff
+protect_ntfs-turn-on-NTFS-protection-by-default.diff
+Disallow-dubiously-nested-submodule-git-directories.diff
+unpack-trees-let-merged_entry-pass-through-do_add_ent.diff
+t7406-submodule.-name-.update-command-must-not-be-run.diff
+submodule-reject-submodule.update-command-in-.gitmodu.diff
+fsck-reject-submodule.update-command-in-.gitmodules.diff
diff --git a/debian/patches/submodule-reject-submodule.update-command-in-.gitmodu.diff b/debian/patches/submodule-reject-submodule.update-command-in-.gitmodu.diff
new file mode 100644
index 0000000..920238e
--- /dev/null
+++ b/debian/patches/submodule-reject-submodule.update-command-in-.gitmodu.diff
@@ -0,0 +1,137 @@
+From 301307952288e563850ae8216a36d689837463e5 Mon Sep 17 00:00:00 2001
+From: Jonathan Nieder <jrnieder@gmail.com>
+Date: Thu, 5 Dec 2019 01:28:28 -0800
+Subject: submodule: reject submodule.update = !command in .gitmodules
+
+commit e904deb89d9a9669a76a426182506a084d3f6308 upstream.
+
+Since ac1fbbda2013 (submodule: do not copy unknown update mode from
+.gitmodules, 2013-12-02), Git has been careful to avoid copying
+
+ [submodule "foo"]
+ update = !run an arbitrary scary command
+
+from .gitmodules to a repository's local config, copying in the
+setting 'update = none' instead. The gitmodules(5) manpage documents
+the intention:
+
+ The !command form is intentionally ignored here for security
+ reasons
+
+Unfortunately, starting with v2.20.0-rc0 (which integrated ee69b2a9
+(submodule--helper: introduce new update-module-mode helper,
+2018-08-13, first released in v2.20.0-rc0)), there are scenarios where
+we *don't* ignore it: if the config store contains no
+submodule.foo.update setting, the submodule-config API falls back to
+reading .gitmodules and the repository-supplied !command gets run
+after all.
+
+This was part of a general change over time in submodule support to
+read more directly from .gitmodules, since unlike .git/config it
+allows a project to change values between branches and over time
+(while still allowing .git/config to override things). But it was
+never intended to apply to this kind of dangerous configuration.
+
+The behavior change was not advertised in ee69b2a9's commit message
+and was missed in review.
+
+Let's take the opportunity to make the protection more robust, even in
+Git versions that are technically not affected: instead of quietly
+converting 'update = !command' to 'update = none', noisily treat it as
+an error. Allowing the setting but treating it as meaning something
+else was just confusing; users are better served by seeing the error
+sooner. Forbidding the construct makes the semantics simpler and
+means we can check for it in fsck (in a separate patch).
+
+As a result, the submodule-config API cannot read this value from
+.gitmodules under any circumstance, and we can declare with confidence
+
+ For security reasons, the '!command' form is not accepted
+ here.
+
+[jn: backported to 2.11.y:
+ - allow submodule-config to read !command after all, since
+ v2.15.0-rc0~120^2~10 (submodule--helper: don't overlay config in
+ update-clone, 2017-08-03) hasn't happened yet
+ - move the code to error out when "git submodule init" encounters
+ !command in .gitmodules to module_init()
+ - cmd_update in git-submodule.sh is responsible for reading the
+ update strategy since v2.20.0-rc0~247^2 (submodule--helper:
+ introduce update-module-mode, 2018-08-13) hasn't happened yet.
+ cmd_update checks config directly and does not read .gitmodules,
+ so we don't have to change it. Accordingly, t7406 also does
+ not need to change its expectation from ignoring to rejecting
+ commands from .gitmodules.]
+
+Reported-by: Joern Schneeweisz <jschneeweisz@gitlab.com>
+Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
+Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
+Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
+---
+ Documentation/gitmodules.txt | 5 ++---
+ builtin/submodule--helper.c | 4 +---
+ t/t7406-submodule-update.sh | 10 ++++++----
+ 3 files changed, 9 insertions(+), 10 deletions(-)
+
+diff --git a/Documentation/gitmodules.txt b/Documentation/gitmodules.txt
+index 8f7c50f330..0c10fb01c1 100644
+--- a/Documentation/gitmodules.txt
++++ b/Documentation/gitmodules.txt
+@@ -44,9 +44,8 @@ submodule.<name>.update::
+ submodule init` to initialize the configuration variable of
+ the same name. Allowed values here are 'checkout', 'rebase',
+ 'merge' or 'none'. See description of 'update' command in
+- linkgit:git-submodule[1] for their meaning. Note that the
+- '!command' form is intentionally ignored here for security
+- reasons.
++ linkgit:git-submodule[1] for their meaning. For security
++ reasons, the '!command' form is not accepted here.
+
+ submodule.<name>.branch::
+ A remote branch name for tracking updates in the upstream submodule.
+diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
+index 6f33a665df..68c84a10a4 100644
+--- a/builtin/submodule--helper.c
++++ b/builtin/submodule--helper.c
+@@ -381,9 +381,7 @@ static void init_submodule(const char *path, const char *prefix, int quiet)
+ if (git_config_get_string(sb.buf, &upd) &&
+ sub->update_strategy.type != SM_UPDATE_UNSPECIFIED) {
+ if (sub->update_strategy.type == SM_UPDATE_COMMAND) {
+- fprintf(stderr, _("warning: command update mode suggested for submodule '%s'\n"),
+- sub->name);
+- upd = xstrdup("none");
++ die(_("invalid value for %s"), sb.buf);
+ } else
+ upd = xstrdup(submodule_strategy_to_string(&sub->update_strategy));
+
+diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh
+index b957b97370..3781809bd6 100755
+--- a/t/t7406-submodule-update.sh
++++ b/t/t7406-submodule-update.sh
+@@ -453,6 +453,9 @@ test_expect_success 'recursive submodule update - command in .git/config catches
+ '
+
+ test_expect_success 'submodule init does not copy command into .git/config' '
++ test_when_finished "git -C super update-index --force-remove submodule1" &&
++ test_when_finished git config -f super/.gitmodules \
++ --remove-section submodule.submodule1 &&
+ (cd super &&
+ H=$(git ls-files -s submodule | cut -d" " -f2) &&
+ mkdir submodule1 &&
+@@ -460,10 +463,9 @@ test_expect_success 'submodule init does not copy command into .git/config' '
+ git config -f .gitmodules submodule.submodule1.path submodule1 &&
+ git config -f .gitmodules submodule.submodule1.url ../submodule &&
+ git config -f .gitmodules submodule.submodule1.update !false &&
+- git submodule init submodule1 &&
+- echo "none" >expect &&
+- git config submodule.submodule1.update >actual &&
+- test_cmp expect actual
++ test_must_fail git submodule init submodule1 &&
++ test_expect_code 1 git config submodule.submodule1.update >actual &&
++ test_must_be_empty actual
+ )
+ '
+
+--
+2.24.0.393.g34dc348eaf
+
diff --git a/debian/patches/t7406-submodule.-name-.update-command-must-not-be-run.diff b/debian/patches/t7406-submodule.-name-.update-command-must-not-be-run.diff
new file mode 100644
index 0000000..645e5da
--- /dev/null
+++ b/debian/patches/t7406-submodule.-name-.update-command-must-not-be-run.diff
@@ -0,0 +1,46 @@
+From 1742900c4cce993203d088f16a751073547ec350 Mon Sep 17 00:00:00 2001
+From: Stefan Beller <sbeller@google.com>
+Date: Tue, 26 Sep 2017 12:54:13 -0700
+Subject: t7406: submodule.<name>.update command must not be run from
+ .gitmodules
+
+commit 83a17fa83b24ed713e2c2647bf89dae171971b73 upstream.
+
+submodule.<name>.update can be assigned an arbitrary command via setting
+it to "!command". When this command is found in the regular config, Git
+ought to just run that command instead of other update mechanisms.
+
+However if that command is just found in the .gitmodules file, it is
+potentially untrusted, which is why we do not run it. Add a test
+confirming the behavior.
+
+Suggested-by: Jonathan Nieder <jrnieder@gmail.com>
+Signed-off-by: Stefan Beller <sbeller@google.com>
+Signed-off-by: Junio C Hamano <gitster@pobox.com>
+Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
+---
+ t/t7406-submodule-update.sh | 8 ++++++++
+ 1 file changed, 8 insertions(+)
+
+diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh
+index 64f322c4cc..b957b97370 100755
+--- a/t/t7406-submodule-update.sh
++++ b/t/t7406-submodule-update.sh
+@@ -389,6 +389,14 @@ test_expect_success 'submodule update - command in .git/config' '
+ )
+ '
+
++test_expect_success 'submodule update - command in .gitmodules is ignored' '
++ test_when_finished "git -C super reset --hard HEAD^" &&
++ git -C super config -f .gitmodules submodule.submodule.update "!false" &&
++ git -C super commit -a -m "add command to .gitmodules file" &&
++ git -C super/submodule reset --hard $submodulesha1^ &&
++ git -C super submodule update submodule
++'
++
+ cat << EOF >expect
+ Execution of 'false $submodulesha1' failed in submodule path 'submodule'
+ EOF
+--
+2.24.0.393.g34dc348eaf
+
diff --git a/debian/patches/t9300-create-marks-files-for-double-import-marks-test.diff b/debian/patches/t9300-create-marks-files-for-double-import-marks-test.diff
new file mode 100644
index 0000000..f006d1a
--- /dev/null
+++ b/debian/patches/t9300-create-marks-files-for-double-import-marks-test.diff
@@ -0,0 +1,36 @@
+From 926a9fda9c5e1633837396fefa070fd7d99d4538 Mon Sep 17 00:00:00 2001
+From: Jeff King <peff@peff.net>
+Date: Thu, 29 Aug 2019 13:43:23 -0400
+Subject: t9300: create marks files for double-import-marks test
+
+commit 816f806786e12435163c591942a204c5a3bdd795 upstream.
+
+Our tests confirm that providing two "import-marks" options in a
+fast-import stream is an error. However, the invoked command would fail
+even without covering this case, because the marks files themselves do
+not actually exist. Let's create the files to make sure we fail for the
+right reason (we actually do, because the option parsing happens before
+we open anything, but this future-proofs our test).
+
+Signed-off-by: Jeff King <peff@peff.net>
+Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
+---
+ t/t9300-fast-import.sh | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh
+index 9ac3ca187d..bf5b937a4c 100755
+--- a/t/t9300-fast-import.sh
++++ b/t/t9300-fast-import.sh
+@@ -2107,6 +2107,8 @@ test_expect_success 'R: abort on receiving feature after data command' '
+ '
+
+ test_expect_success 'R: only one import-marks feature allowed per stream' '
++ >git.marks &&
++ >git2.marks &&
+ cat >input <<-EOF &&
+ feature import-marks=git.marks
+ feature import-marks=git2.marks
+--
+2.24.0.393.g34dc348eaf
+
diff --git a/debian/patches/t9300-drop-some-useless-uses-of-cat.diff b/debian/patches/t9300-drop-some-useless-uses-of-cat.diff
new file mode 100644
index 0000000..5c6861b
--- /dev/null
+++ b/debian/patches/t9300-drop-some-useless-uses-of-cat.diff
@@ -0,0 +1,64 @@
+From b1d7002fab247bd5e25dfd0a7303eefadc9a1cc6 Mon Sep 17 00:00:00 2001
+From: Jeff King <peff@peff.net>
+Date: Thu, 29 Aug 2019 11:19:18 -0400
+Subject: t9300: drop some useless uses of cat
+
+commit f94804c1f2626831c6bdf8cc269a571324e3f2f2 upstream.
+
+These waste a process, and make the line longer than it needs to be.
+
+Signed-off-by: Jeff King <peff@peff.net>
+Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
+---
+ t/t9300-fast-import.sh | 10 +++++-----
+ 1 file changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh
+index 2e0ba3ebd8..9ac3ca187d 100755
+--- a/t/t9300-fast-import.sh
++++ b/t/t9300-fast-import.sh
+@@ -2125,12 +2125,12 @@ test_expect_success 'R: export-marks feature results in a marks file being creat
+
+ EOF
+
+- cat input | git fast-import &&
++ git fast-import <input &&
+ grep :1 git.marks
+ '
+
+ test_expect_success 'R: export-marks options can be overridden by commandline options' '
+- cat input | git fast-import --export-marks=other.marks &&
++ git fast-import --export-marks=other.marks <input &&
+ grep :1 other.marks
+ '
+
+@@ -2242,7 +2242,7 @@ test_expect_success 'R: import to output marks works without any content' '
+ feature export-marks=marks.new
+ EOF
+
+- cat input | git fast-import &&
++ git fast-import <input &&
+ test_cmp marks.out marks.new
+ '
+
+@@ -2252,7 +2252,7 @@ test_expect_success 'R: import marks prefers commandline marks file over the str
+ feature export-marks=marks.new
+ EOF
+
+- cat input | git fast-import --import-marks=marks.out &&
++ git fast-import --import-marks=marks.out <input &&
+ test_cmp marks.out marks.new
+ '
+
+@@ -2560,7 +2560,7 @@ test_expect_success 'R: quiet option results in no stats being output' '
+
+ EOF
+
+- cat input | git fast-import 2> output &&
++ git fast-import 2>output <input &&
+ test_must_be_empty output
+ '
+
+--
+2.24.0.393.g34dc348eaf
+
diff --git a/debian/patches/unpack-trees-let-merged_entry-pass-through-do_add_ent.diff b/debian/patches/unpack-trees-let-merged_entry-pass-through-do_add_ent.diff
new file mode 100644
index 0000000..4b80214
--- /dev/null
+++ b/debian/patches/unpack-trees-let-merged_entry-pass-through-do_add_ent.diff
@@ -0,0 +1,44 @@
+From 752250c75bc989d259fb2e6cb29ee5f034777d78 Mon Sep 17 00:00:00 2001
+From: Johannes Schindelin <johannes.schindelin@gmx.de>
+Date: Mon, 9 Sep 2019 13:56:15 +0200
+Subject: unpack-trees: let merged_entry() pass through do_add_entry()'s errors
+
+commit cc756edda63769cf6d7acc99e6ad3a9cbb5dc3ec upstream.
+
+A `git clone` will end with exit code 0 when `merged_entry()` returns a
+positive value during a call of `unpack_trees()` to `traverse_trees()`.
+The reason is that `unpack_trees()` will interpret a positive value not
+to be an error.
+
+The problem is, however, that `add_index_entry()` (which is called by
+`merged_entry()` can report an error, and we really should fail the
+entire clone in such a case.
+
+Let's fix this problem, in preparation for a Windows-specific patch
+disallowing `mkdir()` with directory names that contain a trailing space
+(which is illegal on NTFS): we want `git clone` to abort when a path
+cannot be checked out due to that condition.
+
+Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
+Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
+---
+ unpack-trees.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/unpack-trees.c b/unpack-trees.c
+index ea6bdd20e0..008fe03811 100644
+--- a/unpack-trees.c
++++ b/unpack-trees.c
+@@ -1626,7 +1626,8 @@ static int merged_entry(const struct cache_entry *ce,
+ invalidate_ce_path(old, o);
+ }
+
+- do_add_entry(o, merge, update, CE_STAGEMASK);
++ if (do_add_entry(o, merge, update, CE_STAGEMASK) < 0)
++ return -1;
+ return 1;
+ }
+
+--
+2.24.0.393.g34dc348eaf
+