Commit 5cf17e0f269387f8345201aef482a10c96490d95

Patrick Steinhardt 2019-10-03T09:39:42

commit_list: store in/out-degrees as uint16_t The commit list's in- and out-degrees are currently stored as `unsigned short`. When assigning it the value of `git_array_size`, which returns an `size_t`, this generates a warning on some Win32 platforms due to loosing precision. We could just cast the returned value of `git_array_size`, which would work fine for 99.99% of all cases as commits typically have less than 2^16 parents. For crafted commits though we might end up with a wrong value, and thus we should definitely check whether the array size actually fits into the field. To ease the check, let's convert the fields to store the degrees as `uint16_t`. We shouldn't rely on such unspecific types anyway, as it may lead to different behaviour across platforms. Furthermore, this commit introduces a new `git__is_uint16` function to check whether it actually fits -- if not, we return an error.

diff --git a/src/commit_list.c b/src/commit_list.c
index 2096554..a58a0a7 100644
--- a/src/commit_list.c
+++ b/src/commit_list.c
@@ -114,8 +114,14 @@ static int commit_quick_parse(
 		return error;
 	}
 
+	if (!git__is_uint16(git_array_size(commit->parent_ids))) {
+		git__free(commit);
+		git_error_set(GIT_ERROR_INVALID, "commit has more than 2^16 parents");
+		return -1;
+	}
+
 	node->time = commit->committer->when.time;
-	node->out_degree = git_array_size(commit->parent_ids);
+	node->out_degree = (uint16_t) git_array_size(commit->parent_ids);
 	node->parents = alloc_parents(walk, node, node->out_degree);
 	GIT_ERROR_CHECK_ALLOC(node->parents);
 
diff --git a/src/commit_list.h b/src/commit_list.h
index a7551a2..6a65f8a 100644
--- a/src/commit_list.h
+++ b/src/commit_list.h
@@ -33,8 +33,8 @@ typedef struct git_commit_list_node {
 			 added:1,
 			 flags : FLAG_BITS;
 
-	unsigned short in_degree;
-	unsigned short out_degree;
+	uint16_t in_degree;
+	uint16_t out_degree;
 
 	struct git_commit_list_node **parents;
 } git_commit_list_node;
diff --git a/src/integer.h b/src/integer.h
index 10b1097..4738e9e 100644
--- a/src/integer.h
+++ b/src/integer.h
@@ -21,6 +21,13 @@ GIT_INLINE(int) git__is_ssizet(size_t p)
 	return p == (size_t)r;
 }
 
+/** @return true if p fits into the range of a uint16_t */
+GIT_INLINE(int) git__is_uint16(size_t p)
+{
+	uint16_t r = (uint16_t)p;
+	return p == (size_t)r;
+}
+
 /** @return true if p fits into the range of a uint32_t */
 GIT_INLINE(int) git__is_uint32(size_t p)
 {