blame: guard xdiff calls for large files
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
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