diff options
Diffstat (limited to 'apply.c')
| -rw-r--r-- | apply.c | 187 |
1 files changed, 139 insertions, 48 deletions
@@ -7,21 +7,34 @@ * */ -#include "cache.h" +#include "git-compat-util.h" +#include "abspath.h" +#include "base85.h" #include "config.h" -#include "object-store.h" -#include "blob.h" +#include "object-store-ll.h" #include "delta.h" #include "diff.h" #include "dir.h" +#include "environment.h" +#include "gettext.h" +#include "hex.h" #include "xdiff-interface.h" -#include "ll-merge.h" +#include "merge-ll.h" #include "lockfile.h" +#include "name-hash.h" +#include "object-name.h" +#include "object-file.h" #include "parse-options.h" +#include "path.h" #include "quote.h" +#include "read-cache.h" #include "rerere.h" #include "apply.h" #include "entry.h" +#include "setup.h" +#include "symlinks.h" +#include "wildmatch.h" +#include "ws.h" struct gitdiff_data { struct strbuf *root; @@ -64,7 +77,8 @@ static int parse_whitespace_option(struct apply_state *state, const char *option return 0; } /* - * Please update $__git_whitespacelist in git-completion.bash + * Please update $__git_whitespacelist in git-completion.bash, + * Documentation/git-apply.txt, and Documentation/git-am.txt * when you add new options. */ return error(_("unrecognized whitespace option '%s'"), option); @@ -125,7 +139,7 @@ void clear_apply_state(struct apply_state *state) /* &state->fn_table is cleared at the end of apply_patch() */ } -static void mute_routine(const char *msg, va_list params) +static void mute_routine(const char *msg UNUSED, va_list params UNUSED) { /* do nothing */ } @@ -386,11 +400,22 @@ static void say_patch_name(FILE *output, const char *fmt, struct patch *patch) #define SLOP (16) +/* + * apply.c isn't equipped to handle arbitrarily large patches, because + * it intermingles `unsigned long` with `int` for the type used to store + * buffer lengths. + * + * Only process patches that are just shy of 1 GiB large in order to + * avoid any truncation or overflow issues. + */ +#define MAX_APPLY_SIZE (1024UL * 1024 * 1023) + static int read_patch_file(struct strbuf *sb, int fd) { if (strbuf_read(sb, fd, 0) < 0) - return error_errno("git apply: failed to read"); - + return error_errno(_("failed to read patch")); + else if (sb->len >= MAX_APPLY_SIZE) + return error(_("patch too large")); /* * Make sure that we have some slop in the buffer * so that we can do speculative "memcmp" etc, and @@ -892,9 +917,9 @@ static int parse_traditional_patch(struct apply_state *state, return 0; } -static int gitdiff_hdrend(struct gitdiff_data *state, - const char *line, - struct patch *patch) +static int gitdiff_hdrend(struct gitdiff_data *state UNUSED, + const char *line UNUSED, + struct patch *patch UNUSED) { return 1; } @@ -1044,7 +1069,7 @@ static int gitdiff_renamedst(struct gitdiff_data *state, return 0; } -static int gitdiff_similarity(struct gitdiff_data *state, +static int gitdiff_similarity(struct gitdiff_data *state UNUSED, const char *line, struct patch *patch) { @@ -1054,7 +1079,7 @@ static int gitdiff_similarity(struct gitdiff_data *state, return 0; } -static int gitdiff_dissimilarity(struct gitdiff_data *state, +static int gitdiff_dissimilarity(struct gitdiff_data *state UNUSED, const char *line, struct patch *patch) { @@ -1104,9 +1129,9 @@ static int gitdiff_index(struct gitdiff_data *state, * This is normal for a diff that doesn't change anything: we'll fall through * into the next diff. Tell the parser to break out. */ -static int gitdiff_unrecognized(struct gitdiff_data *state, - const char *line, - struct patch *patch) +static int gitdiff_unrecognized(struct gitdiff_data *state UNUSED, + const char *line UNUSED, + struct patch *patch UNUSED) { return 1; } @@ -1267,8 +1292,15 @@ static char *git_header_name(int p_value, return NULL; /* no postimage name */ second = skip_tree_prefix(p_value, name + len + 1, line_len - (len + 1)); + /* + * If we are at the SP at the end of a directory, + * skip_tree_prefix() may return NULL as that makes + * it appears as if we have an absolute path. + * Keep going to find another SP. + */ if (!second) - return NULL; + continue; + /* * Does len bytes starting at "name" and "second" * (that are separated by one HT or SP we just @@ -2195,7 +2227,8 @@ static void reverse_patches(struct patch *p) struct fragment *frag = p->fragments; SWAP(p->new_name, p->old_name); - SWAP(p->new_mode, p->old_mode); + if (p->new_mode) + SWAP(p->new_mode, p->old_mode); SWAP(p->is_new, p->is_delete); SWAP(p->lines_added, p->lines_deleted); SWAP(p->old_oid_prefix, p->new_oid_prefix); @@ -2903,7 +2936,7 @@ static int apply_one_fragment(struct apply_state *state, break; case ' ': if (plen && (ws_rule & WS_BLANK_AT_EOF) && - ws_blank_line(patch + 1, plen, ws_rule)) + ws_blank_line(patch + 1, plen)) is_blank_context = 1; /* fallthrough */ case '-': @@ -2932,7 +2965,7 @@ static int apply_one_fragment(struct apply_state *state, (first == '+' ? 0 : LINE_COMMON)); if (first == '+' && (ws_rule & WS_BLANK_AT_EOF) && - ws_blank_line(patch + 1, plen, ws_rule)) + ws_blank_line(patch + 1, plen)) added_blank_line = 1; break; case '@': case '\\': @@ -3191,7 +3224,8 @@ static int apply_binary(struct apply_state *state, unsigned long size; char *result; - result = read_object_file(&oid, &type, &size); + result = repo_read_object_file(the_repository, &oid, &type, + &size); if (!result) return error(_("the necessary postimage %s for " "'%s' cannot be read"), @@ -3254,7 +3288,8 @@ static int read_blob_object(struct strbuf *buf, const struct object_id *oid, uns unsigned long sz; char *result; - result = read_object_file(oid, &type, &sz); + result = repo_read_object_file(the_repository, oid, &type, + &sz); if (!result) return -1; /* XXX read_sha1_file NUL-terminates */ @@ -3482,7 +3517,8 @@ static int resolve_to(struct image *image, const struct object_id *result_id) clear_image(image); - image->buf = read_object_file(result_id, &type, &size); + image->buf = repo_read_object_file(the_repository, result_id, &type, + &size); if (!image->buf || type != OBJ_BLOB) die("unable to read blob object %s", oid_to_hex(result_id)); image->len = size; @@ -3600,7 +3636,7 @@ static int try_threeway(struct apply_state *state, /* Preimage the patch was prepared for */ if (patch->is_new) write_object_file("", 0, OBJ_BLOB, &pre_oid); - else if (get_oid(patch->old_oid_prefix, &pre_oid) || + else if (repo_get_oid(the_repository, patch->old_oid_prefix, &pre_oid) || read_blob_object(&buf, &pre_oid, patch->old_mode)) return error(_("repository lacks the necessary blob to perform 3-way merge.")); @@ -3676,8 +3712,10 @@ static int apply_data(struct apply_state *state, struct patch *patch, fprintf(stderr, _("Falling back to direct application...\n")); /* Note: with --reject, apply_fragments() returns 0 */ - if (patch->direct_to_threeway || apply_fragments(state, &image, patch) < 0) + if (patch->direct_to_threeway || apply_fragments(state, &image, patch) < 0) { + clear_image(&image); return -1; + } } patch->result = image.buf; patch->resultsize = image.len; @@ -3750,8 +3788,17 @@ static int check_preimage(struct apply_state *state, return error_errno("%s", old_name); } - if (!state->cached && !previous) - st_mode = ce_mode_from_stat(*ce, st->st_mode); + if (!state->cached && !previous) { + if (*ce && !(*ce)->ce_mode) + BUG("ce_mode == 0 for path '%s'", old_name); + + if (trust_executable_bit) + st_mode = ce_mode_from_stat(*ce, st->st_mode); + else if (*ce) + st_mode = (*ce)->ce_mode; + else + st_mode = patch->old_mode; + } if (patch->is_new < 0) patch->is_new = 0; @@ -4095,7 +4142,7 @@ static int preimage_oid_in_gitlink_patch(struct patch *p, struct object_id *oid) static int build_fake_ancestor(struct apply_state *state, struct patch *list) { struct patch *patch; - struct index_state result = { NULL }; + struct index_state result = INDEX_STATE_INIT(state->repo); struct lock_file lock = LOCK_INIT; int res; @@ -4117,7 +4164,7 @@ static int build_fake_ancestor(struct apply_state *state, struct patch *list) else return error(_("sha1 information is lacking or " "useless for submodule %s"), name); - } else if (!get_oid_blob(patch->old_oid_prefix, &oid)) { + } else if (!repo_get_oid_blob(the_repository, patch->old_oid_prefix, &oid)) { ; /* ok */ } else if (!patch->lines_added && !patch->lines_deleted) { /* mode-only change: update the current */ @@ -4403,11 +4450,39 @@ static int create_one_file(struct apply_state *state, const char *buf, unsigned long size) { + char *newpath = NULL; int res; if (state->cached) return 0; + /* + * We already try to detect whether files are beyond a symlink in our + * up-front checks. But in the case where symlinks are created by any + * of the intermediate hunks it can happen that our up-front checks + * didn't yet see the symlink, but at the point of arriving here there + * in fact is one. We thus repeat the check for symlinks here. + * + * Note that this does not make the up-front check obsolete as the + * failure mode is different: + * + * - The up-front checks cause us to abort before we have written + * anything into the working directory. So when we exit this way the + * working directory remains clean. + * + * - The checks here happen in the middle of the action where we have + * already started to apply the patch. The end result will be a dirty + * working directory. + * + * Ideally, we should update the up-front checks to catch what would + * happen when we apply the patch before we damage the working tree. + * We have all the information necessary to do so. But for now, as a + * part of embargoed security work, having this check would serve as a + * reasonable first step. + */ + if (path_is_beyond_symlink(state, path)) + return error(_("affected file '%s' is beyond a symbolic link"), path); + res = try_create_file(state, path, mode, buf, size); if (res < 0) return -1; @@ -4437,24 +4512,26 @@ static int create_one_file(struct apply_state *state, unsigned int nr = getpid(); for (;;) { - char newpath[PATH_MAX]; - mksnpath(newpath, sizeof(newpath), "%s~%u", path, nr); + newpath = mkpathdup("%s~%u", path, nr); res = try_create_file(state, newpath, mode, buf, size); if (res < 0) - return -1; + goto out; if (!res) { if (!rename(newpath, path)) - return 0; + goto out; unlink_or_warn(newpath); break; } if (errno != EEXIST) break; ++nr; + FREE_AND_NULL(newpath); } } - return error_errno(_("unable to write file '%s' mode %o"), - path, mode); + res = error_errno(_("unable to write file '%s' mode %o"), path, mode); +out: + free(newpath); + return res; } static int add_conflicted_stages_file(struct apply_state *state, @@ -4537,9 +4614,9 @@ static int write_out_one_result(struct apply_state *state, static int write_out_one_reject(struct apply_state *state, struct patch *patch) { FILE *rej; - char namebuf[PATH_MAX]; + char *namebuf; struct fragment *frag; - int cnt = 0; + int fd, cnt = 0; struct strbuf sb = STRBUF_INIT; for (cnt = 0, frag = patch->fragments; frag; frag = frag->next) { @@ -4570,18 +4647,30 @@ static int write_out_one_reject(struct apply_state *state, struct patch *patch) say_patch_name(stderr, sb.buf, patch); strbuf_release(&sb); - cnt = strlen(patch->new_name); - if (ARRAY_SIZE(namebuf) <= cnt + 5) { - cnt = ARRAY_SIZE(namebuf) - 5; - warning(_("truncating .rej filename to %.*s.rej"), - cnt - 1, patch->new_name); - } - memcpy(namebuf, patch->new_name, cnt); - memcpy(namebuf + cnt, ".rej", 5); + namebuf = xstrfmt("%s.rej", patch->new_name); - rej = fopen(namebuf, "w"); - if (!rej) - return error_errno(_("cannot open %s"), namebuf); + fd = open(namebuf, O_CREAT | O_EXCL | O_WRONLY, 0666); + if (fd < 0) { + if (errno != EEXIST) { + error_errno(_("cannot open %s"), namebuf); + goto error; + } + if (unlink(namebuf)) { + error_errno(_("cannot unlink '%s'"), namebuf); + goto error; + } + fd = open(namebuf, O_CREAT | O_EXCL | O_WRONLY, 0666); + if (fd < 0) { + error_errno(_("cannot open %s"), namebuf); + goto error; + } + } + rej = fdopen(fd, "w"); + if (!rej) { + error_errno(_("cannot open %s"), namebuf); + close(fd); + goto error; + } /* Normal git tools never deal with .rej, so do not pretend * this is a git patch by saying --git or giving extended @@ -4605,6 +4694,8 @@ static int write_out_one_reject(struct apply_state *state, struct patch *patch) fputc('\n', rej); } fclose(rej); +error: + free(namebuf); return -1; } |
