sq_dequote: fix extra consumption of source string

This fixes a (probably harmless) parsing problem in
sq_dequote_step(), in which we parse some bogus input
incorrectly rather than complaining that it's bogus.

Our shell-dequoting function is very strict: it can unquote
everything generated by sq_quote(), but not arbitrary
strings. In particular, it only allows characters outside of
the single-quoted string if they are immediately backslashed
and then the single-quoted string is resumed. So:

  'foo'\''bar'

is OK. But these are not:

  'foo'\'bar
  'foo'\'
  'foo'\'\''bar'

even though they are all valid shell. The parser has a funny
corner case here. When we see a backslashed character, we
keep incrementing the "src" pointer as we parse it. For a
single sq_dequote() call, that's OK; our next step is to
bail with an error, and we don't care where "src" points.

But if we're parsing multiple strings with sq_dequote_to_argv(),
then our next step is to see if the string is followed by
whitespace. Because we erroneously incremented the "src"
pointer, we don't barf on the bogus backslash that we
skipped. Instead, we may find whitespace that immediately
follows it, and continue as if all is well (skipping the
backslashed character completely!).

In practice, this shouldn't be a big deal. The input is
bogus, and our sq_quote() would never generate this bogus
input. In all but one callers, we are parsing input created
by an earlier call to sq_quote(). That final case is "git
shell", which parses shell-quoting generated by the client.
And in that case we use the singular sq_quote(), which has
always behaved correctly.

One might also wonder if you could provoke a read past the
end of the string. But the answer is no; we still parse
character by character, and would never advance past a NUL.

This patch implements the minimal fix, along with
documenting the restriction (which confused at least me
while reading the code). We should possibly consider
being more liberal in accepting valid shell-quoted words. I
suspect the code may actually be simpler, and it would be
more friendly to anybody generating or editing input by
hand. But I wanted to fix just the immediate bug in this
patch.

We don't have a direct way to unit-test the sq_dequote()
functions, but we can do this by feeding input to
GIT_CONFIG_PARAMETERS (which is not normally a user-facing
interface, but serves here as it expects to see sq_quote()
input from "git -c"). I've included both a bogus example,
and a related "good" one to confirm that we still parse it
correctly.

Noticed-by: Michael Haggerty <mhagger@alum.mit.edu>
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Jeff King 2018-02-13 18:41:49 -05:00 коммит произвёл Junio C Hamano
Родитель fc849d8d6b
Коммит ddbbf8eb25
2 изменённых файлов: 32 добавлений и 3 удалений

12
quote.c
Просмотреть файл

@ -94,9 +94,15 @@ static char *sq_dequote_step(char *arg, char **next)
*next = NULL;
return arg;
case '\\':
c = *++src;
if (need_bs_quote(c) && *++src == '\'') {
*dst++ = c;
/*
* Allow backslashed characters outside of
* single-quotes only if they need escaping,
* and only if we resume the single-quoted part
* afterward.
*/
if (need_bs_quote(src[1]) && src[2] == '\'') {
*dst++ = src[1];
src += 2;
continue;
}
/* Fallthrough */

Просмотреть файл

@ -1176,6 +1176,29 @@ test_expect_success 'git -c is not confused by empty environment' '
GIT_CONFIG_PARAMETERS="" git -c x.one=1 config --list
'
sq="'"
test_expect_success 'detect bogus GIT_CONFIG_PARAMETERS' '
cat >expect <<-\EOF &&
env.one one
env.two two
EOF
GIT_CONFIG_PARAMETERS="${sq}env.one=one${sq} ${sq}env.two=two${sq}" \
git config --get-regexp "env.*" >actual &&
test_cmp expect actual &&
cat >expect <<-EOF &&
env.one one${sq}
env.two two
EOF
GIT_CONFIG_PARAMETERS="${sq}env.one=one${sq}\\$sq$sq$sq ${sq}env.two=two${sq}" \
git config --get-regexp "env.*" >actual &&
test_cmp expect actual &&
test_must_fail env \
GIT_CONFIG_PARAMETERS="${sq}env.one=one${sq}\\$sq ${sq}env.two=two${sq}" \
git config --get-regexp "env.*"
'
test_expect_success 'git config --edit works' '
git config -f tmp test.value no &&
echo test.value=yes >expect &&