1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
|
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 <aron@debian.org>
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 . <input >expect &&
+ git shell -c "git-upload-pack $SQ.$SQ" <input >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
|