| 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 |
| |