Skip to content

Commit 71ad7fe

Browse files
peffttaylorr
authored andcommitted
shell: limit size of interactive commands
When git-shell is run in interactive mode (which must be enabled by creating $HOME/git-shell-commands), it reads commands from stdin, one per line, and executes them. We read the commands with git_read_line_interactively(), which uses a strbuf under the hood. That means we'll accept an input of arbitrary size (limited only by how much heap we can allocate). That creates two problems: - the rest of the code is not prepared to handle large inputs. The most serious issue here is that split_cmdline() uses "int" for most of its types, which can lead to integer overflow and out-of-bounds array reads and writes. But even with that fixed, we assume that we can feed the command name to snprintf() (via xstrfmt()), which is stuck for historical reasons using "int", and causes it to fail (and even trigger a BUG() call). - since the point of git-shell is to take input from untrusted or semi-trusted clients, it's a mild denial-of-service. We'll allocate as many bytes as the client sends us (actually twice as many, since we immediately duplicate the buffer). We can fix both by just limiting the amount of per-command input we're willing to receive. We should also fix split_cmdline(), of course, which is an accident waiting to happen, but that can come on top. Most calls to split_cmdline(), including the other one in git-shell, are OK because they are reading from an OS-provided argv, which is limited in practice. This patch should eliminate the immediate vulnerabilities. I picked 4MB as an arbitrary limit. It's big enough that nobody should ever run into it in practice (since the point is to run the commands via exec, we're subject to OS limits which are typically much lower). But it's small enough that allocating it isn't that big a deal. The code is mostly just swapping out fgets() for the strbuf call, but we have to add a few niceties like flushing and trimming line endings. We could simplify things further by putting the buffer on the stack, but 4MB is probably a bit much there. Note that we'll _always_ allocate 4MB, which for normal, non-malicious requests is more than we would before this patch. But on the other hand, other git programs are happy to use 96MB for a delta cache. And since we'd never touch most of those pages, on a lazy-allocating OS like Linux they won't even get allocated to actual RAM. The ideal would be a version of strbuf_getline() that accepted a maximum value. But for a minimal vulnerability fix, let's keep things localized and simple. We can always refactor further on top. The included test fails in an obvious way with ASan or UBSan (which notice the integer overflow and out-of-bounds reads). Without them, it fails in a less obvious way: we may segfault, or we may try to xstrfmt() a long string, leading to a BUG(). Either way, it fails reliably before this patch, and passes with it. Note that we don't need an EXPENSIVE prereq on it. It does take 10-15s to fail before this patch, but with the new limit, we fail almost immediately (and the perl process generating 2GB of data exits via SIGPIPE). Signed-off-by: Jeff King <[email protected]> Signed-off-by: Taylor Blau <[email protected]>
1 parent 32696a4 commit 71ad7fe

File tree

2 files changed

+36
-4
lines changed

2 files changed

+36
-4
lines changed

shell.c

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ static void cd_to_homedir(void)
4747
die("could not chdir to user's home directory");
4848
}
4949

50+
#define MAX_INTERACTIVE_COMMAND (4*1024*1024)
51+
5052
static void run_shell(void)
5153
{
5254
int done = 0;
@@ -67,22 +69,46 @@ static void run_shell(void)
6769
run_command_v_opt(help_argv, RUN_SILENT_EXEC_FAILURE);
6870

6971
do {
70-
struct strbuf line = STRBUF_INIT;
7172
const char *prog;
7273
char *full_cmd;
7374
char *rawargs;
75+
size_t len;
7476
char *split_args;
7577
const char **argv;
7678
int code;
7779
int count;
7880

7981
fprintf(stderr, "git> ");
80-
if (git_read_line_interactively(&line) == EOF) {
82+
83+
/*
84+
* Avoid using a strbuf or git_read_line_interactively() here.
85+
* We don't want to allocate arbitrary amounts of memory on
86+
* behalf of a possibly untrusted client, and we're subject to
87+
* OS limits on command length anyway.
88+
*/
89+
fflush(stdout);
90+
rawargs = xmalloc(MAX_INTERACTIVE_COMMAND);
91+
if (!fgets(rawargs, MAX_INTERACTIVE_COMMAND, stdin)) {
8192
fprintf(stderr, "\n");
82-
strbuf_release(&line);
93+
free(rawargs);
8394
break;
8495
}
85-
rawargs = strbuf_detach(&line, NULL);
96+
len = strlen(rawargs);
97+
98+
/*
99+
* If we truncated due to our input buffer size, reject the
100+
* command. That's better than running bogus input, and
101+
* there's a good chance it's just malicious garbage anyway.
102+
*/
103+
if (len >= MAX_INTERACTIVE_COMMAND - 1)
104+
die("invalid command format: input too long");
105+
106+
if (len > 0 && rawargs[len - 1] == '\n') {
107+
if (--len > 0 && rawargs[len - 1] == '\r')
108+
--len;
109+
rawargs[len] = '\0';
110+
}
111+
86112
split_args = xstrdup(rawargs);
87113
count = split_cmdline(split_args, &argv);
88114
if (count < 0) {

t/t9850-shell.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,10 @@ test_expect_success 'shell allows interactive command' '
2828
test_cmp expect actual
2929
'
3030

31+
test_expect_success 'shell complains of overlong commands' '
32+
perl -e "print \"a\" x 2**12 for (0..2**19)" |
33+
test_must_fail git shell 2>err &&
34+
grep "too long" err
35+
'
36+
3137
test_done

0 commit comments

Comments
 (0)