Commit ae195a71ae898b7d5d61943f24f31d97065a87c6

Edward Thomson 2015-09-29T12:46:41

blame: guard xdiff calls for large files

diff --git a/src/blame.c b/src/blame.c
index a52f940..08a90dc 100644
--- a/src/blame.c
+++ b/src/blame.c
@@ -331,7 +331,7 @@ static int blame_internal(git_blame *blame)
 
 	blame->ent = ent;
 
-	git_blame__like_git(blame, blame->options.flags);
+	error = git_blame__like_git(blame, blame->options.flags);
 
 cleanup:
 	for (ent = blame->ent; ent; ) {
diff --git a/src/blame_git.c b/src/blame_git.c
index f481426..67bae23 100644
--- a/src/blame_git.c
+++ b/src/blame_git.c
@@ -9,6 +9,7 @@
 #include "commit.h"
 #include "blob.h"
 #include "xdiff/xinclude.h"
+#include "diff_xdiff.h"
 
 /*
  * Origin is refcounted and usually we keep the blob contents to be
@@ -351,6 +352,13 @@ static int diff_hunks(mmfile_t file_a, mmfile_t file_b, void *cb_data)
 	ecb.priv = cb_data;
 
 	trim_common_tail(&file_a, &file_b, 0);
+
+	if (file_a.size > GIT_XDIFF_MAX_SIZE ||
+		file_b.size > GIT_XDIFF_MAX_SIZE) {
+		giterr_set(GITERR_INVALID, "file too large to blame");
+		return -1;
+	}
+
 	return xdl_diff(&file_a, &file_b, &xpp, &xecfg, &ecb);
 }
 
@@ -379,7 +387,9 @@ static int pass_blame_to_parent(
 	fill_origin_blob(parent, &file_p);
 	fill_origin_blob(target, &file_o);
 
-	diff_hunks(file_p, file_o, &d);
+	if (diff_hunks(file_p, file_o, &d) < 0)
+		return -1;
+
 	/* The reset (i.e. anything after tlno) are the same as the parent */
 	blame_chunk(blame, d.tlno, d.plno, last_in_target, target, parent);
 
@@ -477,12 +487,13 @@ static void pass_whole_blame(git_blame *blame,
 	}
 }
 
-static void pass_blame(git_blame *blame, git_blame__origin *origin, uint32_t opt)
+static int pass_blame(git_blame *blame, git_blame__origin *origin, uint32_t opt)
 {
 	git_commit *commit = origin->commit;
 	int i, num_parents;
 	git_blame__origin *sg_buf[16];
 	git_blame__origin *porigin, **sg_origin = sg_buf;
+	int ret, error = 0;
 
 	num_parents = git_commit_parentcount(commit);
 	if (!git_oid_cmp(git_commit_id(commit), &blame->options.oldest_commit))
@@ -540,8 +551,13 @@ static void pass_blame(git_blame *blame, git_blame__origin *origin, uint32_t opt
 			origin_incref(porigin);
 			origin->previous = porigin;
 		}
-		if (pass_blame_to_parent(blame, origin, porigin))
+
+		if ((ret = pass_blame_to_parent(blame, origin, porigin)) != 0) {
+			if (ret < 0)
+				error = -1;
+
 			goto finish;
+		}
 	}
 
 	/* TODO: optionally find moves in parents' files */
@@ -554,7 +570,7 @@ finish:
 			origin_decref(sg_origin[i]);
 	if (sg_origin != sg_buf)
 		git__free(sg_origin);
-	return;
+	return error;
 }
 
 /*
@@ -583,7 +599,7 @@ static void coalesce(git_blame *blame)
 	}
 }
 
-void git_blame__like_git(git_blame *blame, uint32_t opt)
+int git_blame__like_git(git_blame *blame, uint32_t opt)
 {
 	while (true) {
 		git_blame__entry *ent;
@@ -594,11 +610,13 @@ void git_blame__like_git(git_blame *blame, uint32_t opt)
 			if (!ent->guilty)
 				suspect = ent->suspect;
 		if (!suspect)
-			return; /* all done */
+			return 0; /* all done */
 
 		/* We'll use this suspect later in the loop, so hold on to it for now. */
 		origin_incref(suspect);
-		pass_blame(blame, suspect, opt);
+
+		if (pass_blame(blame, suspect, opt) < 0)
+			return -1;
 
 		/* Take responsibility for the remaining entries */
 		for (ent = blame->ent; ent; ent = ent->next) {
@@ -613,6 +631,8 @@ void git_blame__like_git(git_blame *blame, uint32_t opt)
 	}
 
 	coalesce(blame);
+
+	return 0;
 }
 
 void git_blame__free_entry(git_blame__entry *ent)
diff --git a/src/blame_git.h b/src/blame_git.h
index 3ec2710..1891b0e 100644
--- a/src/blame_git.h
+++ b/src/blame_git.h
@@ -15,6 +15,6 @@ int git_blame__get_origin(
 		git_commit *commit,
 		const char *path);
 void git_blame__free_entry(git_blame__entry *ent);
-void git_blame__like_git(git_blame *sb, uint32_t flags);
+int git_blame__like_git(git_blame *sb, uint32_t flags);
 
 #endif