Merge branch 'rs/apply-validate-input' into maint

Tighten error checks for invalid "git apply" input.

* rs/apply-validate-input:
  apply: check git diffs for mutually exclusive header lines
  apply: check git diffs for invalid file modes
  apply: check git diffs for missing old filenames
This commit is contained in:
Junio C Hamano 2017-07-10 13:59:01 -07:00
commit 33c3c2d368
4 changed files with 86 additions and 7 deletions

35
apply.c
View File

@ -210,6 +210,7 @@ struct patch {
unsigned ws_rule; unsigned ws_rule;
int lines_added, lines_deleted; int lines_added, lines_deleted;
int score; int score;
int extension_linenr; /* first line specifying delete/new/rename/copy */
unsigned int is_toplevel_relative:1; unsigned int is_toplevel_relative:1;
unsigned int inaccurate_eof:1; unsigned int inaccurate_eof:1;
unsigned int is_binary:1; unsigned int is_binary:1;
@ -1011,20 +1012,27 @@ static int gitdiff_newname(struct apply_state *state,
DIFF_NEW_NAME); DIFF_NEW_NAME);
} }
static int parse_mode_line(const char *line, int linenr, unsigned int *mode)
{
char *end;
*mode = strtoul(line, &end, 8);
if (end == line || !isspace(*end))
return error(_("invalid mode on line %d: %s"), linenr, line);
return 0;
}
static int gitdiff_oldmode(struct apply_state *state, static int gitdiff_oldmode(struct apply_state *state,
const char *line, const char *line,
struct patch *patch) struct patch *patch)
{ {
patch->old_mode = strtoul(line, NULL, 8); return parse_mode_line(line, state->linenr, &patch->old_mode);
return 0;
} }
static int gitdiff_newmode(struct apply_state *state, static int gitdiff_newmode(struct apply_state *state,
const char *line, const char *line,
struct patch *patch) struct patch *patch)
{ {
patch->new_mode = strtoul(line, NULL, 8); return parse_mode_line(line, state->linenr, &patch->new_mode);
return 0;
} }
static int gitdiff_delete(struct apply_state *state, static int gitdiff_delete(struct apply_state *state,
@ -1138,7 +1146,7 @@ static int gitdiff_index(struct apply_state *state,
memcpy(patch->new_sha1_prefix, line, len); memcpy(patch->new_sha1_prefix, line, len);
patch->new_sha1_prefix[len] = 0; patch->new_sha1_prefix[len] = 0;
if (*ptr == ' ') if (*ptr == ' ')
patch->old_mode = strtoul(ptr+1, NULL, 8); return gitdiff_oldmode(state, ptr + 1, patch);
return 0; return 0;
} }
@ -1322,6 +1330,18 @@ static char *git_header_name(struct apply_state *state,
} }
} }
static int check_header_line(struct apply_state *state, struct patch *patch)
{
int extensions = (patch->is_delete == 1) + (patch->is_new == 1) +
(patch->is_rename == 1) + (patch->is_copy == 1);
if (extensions > 1)
return error(_("inconsistent header lines %d and %d"),
patch->extension_linenr, state->linenr);
if (extensions && !patch->extension_linenr)
patch->extension_linenr = state->linenr;
return 0;
}
/* Verify that we recognize the lines following a git header */ /* Verify that we recognize the lines following a git header */
static int parse_git_header(struct apply_state *state, static int parse_git_header(struct apply_state *state,
const char *line, const char *line,
@ -1388,6 +1408,8 @@ static int parse_git_header(struct apply_state *state,
res = p->fn(state, line + oplen, patch); res = p->fn(state, line + oplen, patch);
if (res < 0) if (res < 0)
return -1; return -1;
if (check_header_line(state, patch))
return -1;
if (res > 0) if (res > 0)
return offset; return offset;
break; break;
@ -1585,7 +1607,8 @@ static int find_header(struct apply_state *state,
patch->old_name = xstrdup(patch->def_name); patch->old_name = xstrdup(patch->def_name);
patch->new_name = xstrdup(patch->def_name); patch->new_name = xstrdup(patch->def_name);
} }
if (!patch->is_delete && !patch->new_name) { if ((!patch->new_name && !patch->is_delete) ||
(!patch->old_name && !patch->is_new)) {
error(_("git diff header lacks filename information " error(_("git diff header lacks filename information "
"(line %d)"), state->linenr); "(line %d)"), state->linenr);
return -128; return -128;

View File

@ -13,7 +13,9 @@ test_expect_success setup '
echo modified >file && echo modified >file &&
git diff --stat -p >patch-0.txt && git diff --stat -p >patch-0.txt &&
chmod +x file && chmod +x file &&
git diff --stat -p >patch-1.txt git diff --stat -p >patch-1.txt &&
sed "s/^\(new mode \).*/\1/" <patch-1.txt >patch-empty-mode.txt &&
sed "s/^\(new mode \).*/\1garbage/" <patch-1.txt >patch-bogus-mode.txt
' '
test_expect_success FILEMODE 'same mode (no index)' ' test_expect_success FILEMODE 'same mode (no index)' '
@ -59,4 +61,16 @@ test_expect_success FILEMODE 'mode update (index only)' '
git ls-files -s file | grep "^100755" git ls-files -s file | grep "^100755"
' '
test_expect_success FILEMODE 'empty mode is rejected' '
git reset --hard &&
test_must_fail git apply patch-empty-mode.txt 2>err &&
test_i18ngrep "invalid mode" err
'
test_expect_success FILEMODE 'bogus mode is rejected' '
git reset --hard &&
test_must_fail git apply patch-bogus-mode.txt 2>err &&
test_i18ngrep "invalid mode" err
'
test_done test_done

View File

@ -35,4 +35,28 @@ test_expect_success 'apply diff with inconsistent filenames in headers' '
test_i18ngrep "inconsistent old filename" err test_i18ngrep "inconsistent old filename" err
' '
test_expect_success 'apply diff with new filename missing from headers' '
cat >missing_new_filename.diff <<-\EOF &&
diff --git a/f b/f
index 0000000..d00491f
--- a/f
@@ -0,0 +1 @@
+1
EOF
test_must_fail git apply missing_new_filename.diff 2>err &&
test_i18ngrep "lacks filename information" err
'
test_expect_success 'apply diff with old filename missing from headers' '
cat >missing_old_filename.diff <<-\EOF &&
diff --git a/f b/f
index d00491f..0000000
+++ b/f
@@ -1 +0,0 @@
-1
EOF
test_must_fail git apply missing_old_filename.diff 2>err &&
test_i18ngrep "lacks filename information" err
'
test_done test_done

View File

@ -29,4 +29,22 @@ test_expect_success 'apply exits non-zero with no-op patch' '
test_must_fail git apply --check input test_must_fail git apply --check input
' '
test_expect_success 'invalid combination: create and copy' '
test_must_fail git apply --check - <<-\EOF
diff --git a/1 b/2
new file mode 100644
copy from 1
copy to 2
EOF
'
test_expect_success 'invalid combination: create and rename' '
test_must_fail git apply --check - <<-\EOF
diff --git a/1 b/2
new file mode 100644
rename from 1
rename to 2
EOF
'
test_done test_done