Merge branch 'rs/apply-validate-input'
authorJunio C Hamano <gitster@pobox.com>
Fri, 30 Jun 2017 20:45:24 +0000 (13:45 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 30 Jun 2017 20:45:24 +0000 (13:45 -0700)
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

1  2 
apply.c
diff --combined apply.c
index b963d7d8fb7ee81300d4383b7bc007fc74af84c5,574ada87c542c8d6490e9d7c7df983fb859265ab..c442b8932823ac0bc104d094a2b372a974ee8217
+++ b/apply.c
@@@ -8,7 -8,6 +8,7 @@@
   */
  
  #include "cache.h"
 +#include "config.h"
  #include "blob.h"
  #include "delta.h"
  #include "diff.h"
@@@ -211,6 -210,7 +211,7 @@@ struct patch 
        unsigned ws_rule;
        int lines_added, lines_deleted;
        int score;
+       int extension_linenr; /* first line specifying delete/new/rename/copy */
        unsigned int is_toplevel_relative:1;
        unsigned int inaccurate_eof:1;
        unsigned int is_binary:1;
@@@ -763,6 -763,17 +764,6 @@@ static char *find_name_traditional(stru
        return find_name_common(state, line, def, p_value, line + len, 0);
  }
  
 -static int count_slashes(const char *cp)
 -{
 -      int cnt = 0;
 -      char ch;
 -
 -      while ((ch = *cp++))
 -              if (ch == '/')
 -                      cnt++;
 -      return cnt;
 -}
 -
  /*
   * Given the string after "--- " or "+++ ", guess the appropriate
   * p_value for the given patch.
@@@ -1001,20 -1012,27 +1002,27 @@@ static int gitdiff_newname(struct apply
                                   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,
                           const char *line,
                           struct patch *patch)
  {
-       patch->old_mode = strtoul(line, NULL, 8);
-       return 0;
+       return parse_mode_line(line, state->linenr, &patch->old_mode);
  }
  
  static int gitdiff_newmode(struct apply_state *state,
                           const char *line,
                           struct patch *patch)
  {
-       patch->new_mode = strtoul(line, NULL, 8);
-       return 0;
+       return parse_mode_line(line, state->linenr, &patch->new_mode);
  }
  
  static int gitdiff_delete(struct apply_state *state,
@@@ -1128,7 -1146,7 +1136,7 @@@ static int gitdiff_index(struct apply_s
        memcpy(patch->new_sha1_prefix, line, len);
        patch->new_sha1_prefix[len] = 0;
        if (*ptr == ' ')
-               patch->old_mode = strtoul(ptr+1, NULL, 8);
+               return gitdiff_oldmode(state, ptr + 1, patch);
        return 0;
  }
  
@@@ -1312,6 -1330,18 +1320,18 @@@ static char *git_header_name(struct app
        }
  }
  
+ 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 */
  static int parse_git_header(struct apply_state *state,
                            const char *line,
                        res = p->fn(state, line + oplen, patch);
                        if (res < 0)
                                return -1;
+                       if (check_header_line(state, patch))
+                               return -1;
                        if (res > 0)
                                return offset;
                        break;
@@@ -1575,7 -1607,8 +1597,8 @@@ static int find_header(struct apply_sta
                                patch->old_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 "
                                             "(line %d)"), state->linenr);
                                return -128;
@@@ -2036,7 -2069,7 +2059,7 @@@ static void prefix_one(struct apply_sta
        char *old_name = *name;
        if (!old_name)
                return;
 -      *name = xstrdup(prefix_filename(state->prefix, state->prefix_length, *name));
 +      *name = prefix_filename(state->prefix, *name);
        free(old_name);
  }
  
@@@ -2177,20 -2210,29 +2200,20 @@@ static int parse_chunk(struct apply_sta
        return offset + hdrsize + patchsize;
  }
  
 -#define swap(a,b) myswap((a),(b),sizeof(a))
 -
 -#define myswap(a, b, size) do {               \
 -      unsigned char mytmp[size];      \
 -      memcpy(mytmp, &a, size);                \
 -      memcpy(&a, &b, size);           \
 -      memcpy(&b, mytmp, size);                \
 -} while (0)
 -
  static void reverse_patches(struct patch *p)
  {
        for (; p; p = p->next) {
                struct fragment *frag = p->fragments;
  
 -              swap(p->new_name, p->old_name);
 -              swap(p->new_mode, p->old_mode);
 -              swap(p->is_new, p->is_delete);
 -              swap(p->lines_added, p->lines_deleted);
 -              swap(p->old_sha1_prefix, p->new_sha1_prefix);
 +              SWAP(p->new_name, p->old_name);
 +              SWAP(p->new_mode, p->old_mode);
 +              SWAP(p->is_new, p->is_delete);
 +              SWAP(p->lines_added, p->lines_deleted);
 +              SWAP(p->old_sha1_prefix, p->new_sha1_prefix);
  
                for (; frag; frag = frag->next) {
 -                      swap(frag->newpos, frag->oldpos);
 -                      swap(frag->newlines, frag->oldlines);
 +                      SWAP(frag->newpos, frag->oldpos);
 +                      SWAP(frag->newlines, frag->oldlines);
                }
        }
  }
@@@ -2257,7 -2299,7 +2280,7 @@@ static int read_old_data(struct stat *s
        case S_IFREG:
                if (strbuf_read_file(buf, path, st->st_size) != st->st_size)
                        return error(_("unable to open or read %s"), path);
 -              convert_to_git(path, buf->buf, buf->len, buf, 0);
 +              convert_to_git(&the_index, path, buf->buf, buf->len, buf, 0);
                return 0;
        default:
                return -1;
@@@ -3695,7 -3737,8 +3718,7 @@@ static int check_preimage(struct apply_
   is_new:
        patch->is_new = 1;
        patch->is_delete = 0;
 -      free(patch->old_name);
 -      patch->old_name = NULL;
 +      FREE_AND_NULL(patch->old_name);
        return 0;
  }
  
@@@ -3730,7 -3773,7 +3753,7 @@@ static int check_to_create(struct apply
                        return 0;
  
                return EXISTS_IN_WORKTREE;
 -      } else if ((errno != ENOENT) && (errno != ENOTDIR)) {
 +      } else if (!is_missing_file_error(errno)) {
                return error_errno("%s", new_name);
        }
        return 0;
@@@ -4080,181 -4123,181 +4103,181 @@@ static int build_fake_ancestor(struct a
        res = write_locked_index(&result, &lock, COMMIT_LOCK);
        discard_index(&result);
  
 -       if (res)
 -               return error(_("could not write temporary index to %s"),
 -                            state->fake_ancestor);
 +      if (res)
 +              return error(_("could not write temporary index to %s"),
 +                           state->fake_ancestor);
  
 -       return 0;
 - }
 +      return 0;
 +}
  
 - static void stat_patch_list(struct apply_state *state, struct patch *patch)
 - {
 -       int files, adds, dels;
 +static void stat_patch_list(struct apply_state *state, struct patch *patch)
 +{
 +      int files, adds, dels;
  
 -       for (files = adds = dels = 0 ; patch ; patch = patch->next) {
 -               files++;
 -               adds += patch->lines_added;
 -               dels += patch->lines_deleted;
 -               show_stats(state, patch);
 -       }
 +      for (files = adds = dels = 0 ; patch ; patch = patch->next) {
 +              files++;
 +              adds += patch->lines_added;
 +              dels += patch->lines_deleted;
 +              show_stats(state, patch);
 +      }
  
 -       print_stat_summary(stdout, files, adds, dels);
 - }
 +      print_stat_summary(stdout, files, adds, dels);
 +}
  
 - static void numstat_patch_list(struct apply_state *state,
 -                              struct patch *patch)
 - {
 -       for ( ; patch; patch = patch->next) {
 -               const char *name;
 -               name = patch->new_name ? patch->new_name : patch->old_name;
 -               if (patch->is_binary)
 -                       printf("-\t-\t");
 -               else
 -                       printf("%d\t%d\t", patch->lines_added, patch->lines_deleted);
 -               write_name_quoted(name, stdout, state->line_termination);
 -       }
 - }
 -
 - static void show_file_mode_name(const char *newdelete, unsigned int mode, const char *name)
 - {
 -       if (mode)
 -               printf(" %s mode %06o %s\n", newdelete, mode, name);
 -       else
 -               printf(" %s %s\n", newdelete, name);
 - }
 -
 - static void show_mode_change(struct patch *p, int show_name)
 - {
 -       if (p->old_mode && p->new_mode && p->old_mode != p->new_mode) {
 -               if (show_name)
 -                       printf(" mode change %06o => %06o %s\n",
 -                              p->old_mode, p->new_mode, p->new_name);
 -               else
 -                       printf(" mode change %06o => %06o\n",
 -                              p->old_mode, p->new_mode);
 -       }
 - }
 -
 - static void show_rename_copy(struct patch *p)
 - {
 -       const char *renamecopy = p->is_rename ? "rename" : "copy";
 -       const char *old, *new;
 -
 -       /* Find common prefix */
 -       old = p->old_name;
 -       new = p->new_name;
 -       while (1) {
 -               const char *slash_old, *slash_new;
 -               slash_old = strchr(old, '/');
 -               slash_new = strchr(new, '/');
 -               if (!slash_old ||
 -                   !slash_new ||
 -                   slash_old - old != slash_new - new ||
 -                   memcmp(old, new, slash_new - new))
 -                       break;
 -               old = slash_old + 1;
 -               new = slash_new + 1;
 -       }
 -       /* p->old_name thru old is the common prefix, and old and new
 -        * through the end of names are renames
 -        */
 -       if (old != p->old_name)
 -               printf(" %s %.*s{%s => %s} (%d%%)\n", renamecopy,
 -                      (int)(old - p->old_name), p->old_name,
 -                      old, new, p->score);
 -       else
 -               printf(" %s %s => %s (%d%%)\n", renamecopy,
 -                      p->old_name, p->new_name, p->score);
 -       show_mode_change(p, 0);
 - }
 -
 - static void summary_patch_list(struct patch *patch)
 - {
 -       struct patch *p;
 -
 -       for (p = patch; p; p = p->next) {
 -               if (p->is_new)
 -                       show_file_mode_name("create", p->new_mode, p->new_name);
 -               else if (p->is_delete)
 -                       show_file_mode_name("delete", p->old_mode, p->old_name);
 -               else {
 -                       if (p->is_rename || p->is_copy)
 -                               show_rename_copy(p);
 -                       else {
 -                               if (p->score) {
 -                                       printf(" rewrite %s (%d%%)\n",
 -                                              p->new_name, p->score);
 -                                       show_mode_change(p, 0);
 -                               }
 -                               else
 -                                       show_mode_change(p, 1);
 -                       }
 -               }
 -       }
 - }
 -
 - static void patch_stats(struct apply_state *state, struct patch *patch)
 - {
 -       int lines = patch->lines_added + patch->lines_deleted;
 -
 -       if (lines > state->max_change)
 -               state->max_change = lines;
 -       if (patch->old_name) {
 -               int len = quote_c_style(patch->old_name, NULL, NULL, 0);
 -               if (!len)
 -                       len = strlen(patch->old_name);
 -               if (len > state->max_len)
 -                       state->max_len = len;
 -       }
 -       if (patch->new_name) {
 -               int len = quote_c_style(patch->new_name, NULL, NULL, 0);
 -               if (!len)
 -                       len = strlen(patch->new_name);
 -               if (len > state->max_len)
 -                       state->max_len = len;
 -       }
 - }
 -
 - static int remove_file(struct apply_state *state, struct patch *patch, int rmdir_empty)
 - {
 -       if (state->update_index) {
 -               if (remove_file_from_cache(patch->old_name) < 0)
 -                       return error(_("unable to remove %s from index"), patch->old_name);
 -       }
 -       if (!state->cached) {
 -               if (!remove_or_warn(patch->old_mode, patch->old_name) && rmdir_empty) {
 -                       remove_path(patch->old_name);
 -               }
 -       }
 -       return 0;
 - }
 -
 - static int add_index_file(struct apply_state *state,
 -                         const char *path,
 -                         unsigned mode,
 -                         void *buf,
 -                         unsigned long size)
 - {
 -       struct stat st;
 -       struct cache_entry *ce;
 -       int namelen = strlen(path);
 -       unsigned ce_size = cache_entry_size(namelen);
 -
 -       if (!state->update_index)
 -               return 0;
 -
 -       ce = xcalloc(1, ce_size);
 -       memcpy(ce->name, path, namelen);
 -       ce->ce_mode = create_ce_mode(mode);
 -       ce->ce_flags = create_ce_flags(0);
 -       ce->ce_namelen = namelen;
 -       if (S_ISGITLINK(mode)) {
 -               const char *s;
 -
 -               if (!skip_prefix(buf, "Subproject commit ", &s) ||
 -                   get_oid_hex(s, &ce->oid)) {
 +static void numstat_patch_list(struct apply_state *state,
 +                             struct patch *patch)
 +{
 +      for ( ; patch; patch = patch->next) {
 +              const char *name;
 +              name = patch->new_name ? patch->new_name : patch->old_name;
 +              if (patch->is_binary)
 +                      printf("-\t-\t");
 +              else
 +                      printf("%d\t%d\t", patch->lines_added, patch->lines_deleted);
 +              write_name_quoted(name, stdout, state->line_termination);
 +      }
 +}
 +
 +static void show_file_mode_name(const char *newdelete, unsigned int mode, const char *name)
 +{
 +      if (mode)
 +              printf(" %s mode %06o %s\n", newdelete, mode, name);
 +      else
 +              printf(" %s %s\n", newdelete, name);
 +}
 +
 +static void show_mode_change(struct patch *p, int show_name)
 +{
 +      if (p->old_mode && p->new_mode && p->old_mode != p->new_mode) {
 +              if (show_name)
 +                      printf(" mode change %06o => %06o %s\n",
 +                             p->old_mode, p->new_mode, p->new_name);
 +              else
 +                      printf(" mode change %06o => %06o\n",
 +                             p->old_mode, p->new_mode);
 +      }
 +}
 +
 +static void show_rename_copy(struct patch *p)
 +{
 +      const char *renamecopy = p->is_rename ? "rename" : "copy";
 +      const char *old, *new;
 +
 +      /* Find common prefix */
 +      old = p->old_name;
 +      new = p->new_name;
 +      while (1) {
 +              const char *slash_old, *slash_new;
 +              slash_old = strchr(old, '/');
 +              slash_new = strchr(new, '/');
 +              if (!slash_old ||
 +                  !slash_new ||
 +                  slash_old - old != slash_new - new ||
 +                  memcmp(old, new, slash_new - new))
 +                      break;
 +              old = slash_old + 1;
 +              new = slash_new + 1;
 +      }
 +      /* p->old_name thru old is the common prefix, and old and new
 +       * through the end of names are renames
 +       */
 +      if (old != p->old_name)
 +              printf(" %s %.*s{%s => %s} (%d%%)\n", renamecopy,
 +                     (int)(old - p->old_name), p->old_name,
 +                     old, new, p->score);
 +      else
 +              printf(" %s %s => %s (%d%%)\n", renamecopy,
 +                     p->old_name, p->new_name, p->score);
 +      show_mode_change(p, 0);
 +}
 +
 +static void summary_patch_list(struct patch *patch)
 +{
 +      struct patch *p;
 +
 +      for (p = patch; p; p = p->next) {
 +              if (p->is_new)
 +                      show_file_mode_name("create", p->new_mode, p->new_name);
 +              else if (p->is_delete)
 +                      show_file_mode_name("delete", p->old_mode, p->old_name);
 +              else {
 +                      if (p->is_rename || p->is_copy)
 +                              show_rename_copy(p);
 +                      else {
 +                              if (p->score) {
 +                                      printf(" rewrite %s (%d%%)\n",
 +                                             p->new_name, p->score);
 +                                      show_mode_change(p, 0);
 +                              }
 +                              else
 +                                      show_mode_change(p, 1);
 +                      }
 +              }
 +      }
 +}
 +
 +static void patch_stats(struct apply_state *state, struct patch *patch)
 +{
 +      int lines = patch->lines_added + patch->lines_deleted;
 +
 +      if (lines > state->max_change)
 +              state->max_change = lines;
 +      if (patch->old_name) {
 +              int len = quote_c_style(patch->old_name, NULL, NULL, 0);
 +              if (!len)
 +                      len = strlen(patch->old_name);
 +              if (len > state->max_len)
 +                      state->max_len = len;
 +      }
 +      if (patch->new_name) {
 +              int len = quote_c_style(patch->new_name, NULL, NULL, 0);
 +              if (!len)
 +                      len = strlen(patch->new_name);
 +              if (len > state->max_len)
 +                      state->max_len = len;
 +      }
 +}
 +
 +static int remove_file(struct apply_state *state, struct patch *patch, int rmdir_empty)
 +{
 +      if (state->update_index) {
 +              if (remove_file_from_cache(patch->old_name) < 0)
 +                      return error(_("unable to remove %s from index"), patch->old_name);
 +      }
 +      if (!state->cached) {
 +              if (!remove_or_warn(patch->old_mode, patch->old_name) && rmdir_empty) {
 +                      remove_path(patch->old_name);
 +              }
 +      }
 +      return 0;
 +}
 +
 +static int add_index_file(struct apply_state *state,
 +                        const char *path,
 +                        unsigned mode,
 +                        void *buf,
 +                        unsigned long size)
 +{
 +      struct stat st;
 +      struct cache_entry *ce;
 +      int namelen = strlen(path);
 +      unsigned ce_size = cache_entry_size(namelen);
 +
 +      if (!state->update_index)
 +              return 0;
 +
 +      ce = xcalloc(1, ce_size);
 +      memcpy(ce->name, path, namelen);
 +      ce->ce_mode = create_ce_mode(mode);
 +      ce->ce_flags = create_ce_flags(0);
 +      ce->ce_namelen = namelen;
 +      if (S_ISGITLINK(mode)) {
 +              const char *s;
 +
 +              if (!skip_prefix(buf, "Subproject commit ", &s) ||
 +                  get_oid_hex(s, &ce->oid)) {
                        free(ce);
 -                      return error(_("corrupt patch for submodule %s"), path);
 +                     return error(_("corrupt patch for submodule %s"), path);
                }
        } else {
                if (!state->cached) {
@@@ -4668,7 -4711,7 +4691,7 @@@ static int apply_patch(struct apply_sta
                                                                 state->index_file,
                                                                 LOCK_DIE_ON_ERROR);
                else
 -                      state->newfd = hold_locked_index(state->lock_file, 1);
 +                      state->newfd = hold_locked_index(state->lock_file, LOCK_DIE_ON_ERROR);
        }
  
        if (state->check_index && read_apply_cache(state) < 0) {
@@@ -4794,7 -4837,6 +4817,7 @@@ int apply_all_patches(struct apply_stat
  
        for (i = 0; i < argc; i++) {
                const char *arg = argv[i];
 +              char *to_free = NULL;
                int fd;
  
                if (!strcmp(arg, "-")) {
                        errs |= res;
                        read_stdin = 0;
                        continue;
 -              } else if (0 < state->prefix_length)
 -                      arg = prefix_filename(state->prefix,
 -                                            state->prefix_length,
 -                                            arg);
 +              } else
 +                      arg = to_free = prefix_filename(state->prefix, arg);
  
                fd = open(arg, O_RDONLY);
                if (fd < 0) {
                        error(_("can't open patch '%s': %s"), arg, strerror(errno));
                        res = -128;
 +                      free(to_free);
                        goto end;
                }
                read_stdin = 0;
                set_default_whitespace_mode(state);
                res = apply_patch(state, fd, arg, options);
                close(fd);
 +              free(to_free);
                if (res < 0)
                        goto end;
                errs |= res;