apply: check git diffs for mutually exclusive header lines

A file can either be added, removed, copied, or renamed, but no two of these actions can be done by the same patch. Some of these combinations provoke error messages due to missing file names, and some are only caught by an assertion. Check git patches already as they are parsed and report conflicting lines on sight. Found by Vegard Nossum using AFL. Reported-by: Vegard Nossum <vegard.nossum@oracle.com> Signed-off-by: Rene Scharfe <l.s.r@web.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>

René Scharfe committed Jun 27, 2017 at 19:03 UTC d70e9c5c8c865626b6e69c2bf9fd0e368543617b
2 files changed +33
apply.c
+15
@@ -210,6 +210,7 @@ struct patch {
210 unsigned ws_rule;
211 int lines_added, lines_deleted;
212 int score;
213 + int extension_linenr; /* first line specifying delete/new/rename/copy */
214 unsigned int is_toplevel_relative:1;
215 unsigned int inaccurate_eof:1;
216 unsigned int is_binary:1;
@@ -1329,6 +1330,18 @@ static char *git_header_name(struct apply_state *state,
1330 }
1331 }
1332
1333 +static int check_header_line(struct apply_state *state, struct patch *patch)
1334 +{
1335 + int extensions = (patch->is_delete == 1) + (patch->is_new == 1) +
1336 + (patch->is_rename == 1) + (patch->is_copy == 1);
1337 + if (extensions > 1)
1338 + return error(_("inconsistent header lines %d and %d"),
1339 + patch->extension_linenr, state->linenr);
1340 + if (extensions && !patch->extension_linenr)
1341 + patch->extension_linenr = state->linenr;
1342 + return 0;
1343 +}
1344 +
1345 /* Verify that we recognize the lines following a git header */
1346 static int parse_git_header(struct apply_state *state,
1347 const char *line,
@@ -1395,6 +1408,8 @@ static int parse_git_header(struct apply_state *state,
1408 res = p->fn(state, line + oplen, patch);
1409 if (res < 0)
1410 return -1;
1411 + if (check_header_line(state, patch))
1412 + return -1;
1413 if (res > 0)
1414 return offset;
1415 break;
t/t4136-apply-check.sh
+18
@@ -29,4 +29,22 @@ test_expect_success 'apply exits non-zero with no-op patch' '
29 test_must_fail git apply --check input
30 '
31
32 +test_expect_success 'invalid combination: create and copy' '
33 + test_must_fail git apply --check - <<-\EOF
34 + diff --git a/1 b/2
35 + new file mode 100644
36 + copy from 1
37 + copy to 2
38 + EOF
39 +'
40 +
41 +test_expect_success 'invalid combination: create and rename' '
42 + test_must_fail git apply --check - <<-\EOF
43 + diff --git a/1 b/2
44 + new file mode 100644
45 + rename from 1
46 + rename to 2
47 + EOF
48 +'
49 +
50 test_done