Origin: https://github.com/git/git/commit/32696a4cbe90929ae79ea442f5102c513ce3dfaa Origin: https://github.com/git/git/commit/71ad7fe1bcec2a115bd0ab187240348358aa7f21 Origin: https://github.com/git/git/commit/0ca6ead81edd4fb1984b69aae87c1189e3025530 Reviewed-by: Aron Xu Last-Updated: 2023-01-26 diff --git a/alias.c b/alias.c index c471538..00abde0 100644 --- a/alias.c +++ b/alias.c @@ -46,14 +46,16 @@ void list_aliases(struct string_list *list) #define SPLIT_CMDLINE_BAD_ENDING 1 #define SPLIT_CMDLINE_UNCLOSED_QUOTE 2 +#define SPLIT_CMDLINE_ARGC_OVERFLOW 3 static const char *split_cmdline_errors[] = { N_("cmdline ends with \\"), - N_("unclosed quote") + N_("unclosed quote"), + N_("too many arguments"), }; int split_cmdline(char *cmdline, const char ***argv) { - int src, dst, count = 0, size = 16; + size_t src, dst, count = 0, size = 16; char quoted = 0; ALLOC_ARRAY(*argv, size); @@ -96,6 +98,11 @@ int split_cmdline(char *cmdline, const char ***argv) return -SPLIT_CMDLINE_UNCLOSED_QUOTE; } + if (count >= INT_MAX) { + FREE_AND_NULL(*argv); + return -SPLIT_CMDLINE_ARGC_OVERFLOW; + } + ALLOC_GROW(*argv, count + 1, size); (*argv)[count] = NULL; diff --git a/shell.c b/shell.c index cef7ffd..02cfd96 100644 --- a/shell.c +++ b/shell.c @@ -47,6 +47,8 @@ static void cd_to_homedir(void) die("could not chdir to user's home directory"); } +#define MAX_INTERACTIVE_COMMAND (4*1024*1024) + static void run_shell(void) { int done = 0; @@ -67,22 +69,46 @@ static void run_shell(void) run_command_v_opt(help_argv, RUN_SILENT_EXEC_FAILURE); do { - struct strbuf line = STRBUF_INIT; const char *prog; char *full_cmd; char *rawargs; + size_t len; char *split_args; const char **argv; int code; int count; fprintf(stderr, "git> "); - if (git_read_line_interactively(&line) == EOF) { + + /* + * Avoid using a strbuf or git_read_line_interactively() here. + * We don't want to allocate arbitrary amounts of memory on + * behalf of a possibly untrusted client, and we're subject to + * OS limits on command length anyway. + */ + fflush(stdout); + rawargs = xmalloc(MAX_INTERACTIVE_COMMAND); + if (!fgets(rawargs, MAX_INTERACTIVE_COMMAND, stdin)) { fprintf(stderr, "\n"); - strbuf_release(&line); + free(rawargs); break; } - rawargs = strbuf_detach(&line, NULL); + len = strlen(rawargs); + + /* + * If we truncated due to our input buffer size, reject the + * command. That's better than running bogus input, and + * there's a good chance it's just malicious garbage anyway. + */ + if (len >= MAX_INTERACTIVE_COMMAND - 1) + die("invalid command format: input too long"); + + if (len > 0 && rawargs[len - 1] == '\n') { + if (--len > 0 && rawargs[len - 1] == '\r') + --len; + rawargs[len] = '\0'; + } + split_args = xstrdup(rawargs); count = split_cmdline(split_args, &argv); if (count < 0) { diff --git a/t/t9850-shell.sh b/t/t9850-shell.sh new file mode 100755 index 0000000..cfc71c3 --- /dev/null +++ b/t/t9850-shell.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +test_description='git shell tests' +. ./test-lib.sh + +test_expect_success 'shell allows upload-pack' ' + printf 0000 >input && + git upload-pack . expect && + git shell -c "git-upload-pack $SQ.$SQ" actual && + test_cmp expect actual +' + +test_expect_success 'shell forbids other commands' ' + test_must_fail git shell -c "git config foo.bar baz" +' + +test_expect_success 'shell forbids interactive use by default' ' + test_must_fail git shell +' + +test_expect_success 'shell allows interactive command' ' + mkdir git-shell-commands && + write_script git-shell-commands/ping <<-\EOF && + echo pong + EOF + echo pong >expect && + echo ping | git shell >actual && + test_cmp expect actual +' + +test_expect_success 'shell complains of overlong commands' ' + perl -e "print \"a\" x 2**12 for (0..2**19)" | + test_must_fail git shell 2>err && + grep "too long" err +' + +test_done