summaryrefslogtreecommitdiff
path: root/apply.c
diff options
context:
space:
mode:
Diffstat (limited to 'apply.c')
-rw-r--r--apply.c187
1 files changed, 139 insertions, 48 deletions
diff --git a/apply.c b/apply.c
index 2b7cd930ef..901b67e625 100644
--- a/apply.c
+++ b/apply.c
@@ -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;
}