Commit 3101a3e5b8235285440e0eb62924266f2fc1892e

Vicent Marti 2011-06-23T02:28:29

refs: Do not overflow when normalizing refnames

diff --git a/src/refs.c b/src/refs.c
index 1b2154b..ac13736 100644
--- a/src/refs.c
+++ b/src/refs.c
@@ -87,7 +87,7 @@ static int reference_available(git_repository *repo, const char *ref, const char
 
 /* name normalization */
 static int check_valid_ref_char(char ch);
-static int normalize_name(char *buffer_out, const char *name, int is_oid_ref);
+static int normalize_name(char *buffer_out, size_t out_size, const char *name, int is_oid_ref);
 
 /*****************************************
  * Internal methods - Constructor/destructor
@@ -112,7 +112,7 @@ static int reference_create(
 	const char *name,
 	git_rtype type)
 {
-	char normalized[MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH];
+	char normalized[GIT_REFNAME_MAX];
 	int error = GIT_SUCCESS, size;
 	git_reference *reference = NULL;
 
@@ -134,7 +134,7 @@ static int reference_create(
 	reference->owner = repo;
 	reference->type = type;
 
-	error = normalize_name(normalized, name, (type & GIT_REF_OID));
+	error = normalize_name(normalized, sizeof(normalized), name, (type & GIT_REF_OID));
 	if (error < GIT_SUCCESS)
 		goto cleanup;
 
@@ -458,7 +458,7 @@ static int packed_parse_oid(
 
 	int error = GIT_SUCCESS;
 	int refname_len;
-	char refname[MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH];
+	char refname[GIT_REFNAME_MAX];
 	git_oid id;
 
 	refname_begin = (buffer + GIT_OID_HEXSZ + 1);
@@ -926,7 +926,7 @@ cleanup:
 
 static int reference_create_symbolic(git_reference **ref_out, git_repository *repo, const char *name, const char *target, int force)
 {
-	char normalized[MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH];
+	char normalized[GIT_REFNAME_MAX];
 	int error = GIT_SUCCESS, updated = 0;
 	git_reference *ref = NULL, *old_ref = NULL;
 
@@ -950,7 +950,7 @@ static int reference_create_symbolic(git_reference **ref_out, git_repository *re
 	}
 
 	/* The target can aither be the name of an object id reference or the name of another symbolic reference */
-	error = normalize_name(normalized, target, 0);
+	error = normalize_name(normalized, sizeof(normalized), target, 0);
 	if (error < GIT_SUCCESS)
 		goto cleanup;
 
@@ -1092,13 +1092,13 @@ static int reference_rename(git_reference *ref, const char *new_name, int force)
 {
 	int error;
 	char *old_name;
-	char old_path[GIT_PATH_MAX], new_path[GIT_PATH_MAX], normalized_name[MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH];
+	char old_path[GIT_PATH_MAX], new_path[GIT_PATH_MAX], normalized_name[GIT_REFNAME_MAX];
 	git_reference *looked_up_ref, *old_ref = NULL;
 
 	assert(ref);
 
 	/* Ensure the name is valid */
-	error = normalize_name(normalized_name, new_name, ref->type & GIT_REF_OID);
+	error = normalize_name(normalized_name, sizeof(normalized_name), new_name, ref->type & GIT_REF_OID);
 	if (error < GIT_SUCCESS)
 		return git__rethrow(error, "Failed to rename reference");
 
@@ -1216,13 +1216,13 @@ rename_loose_to_old_name:
 int git_reference_lookup(git_reference **ref_out, git_repository *repo, const char *name)
 {
 	int error;
-	char normalized_name[GIT_PATH_MAX];
+	char normalized_name[GIT_REFNAME_MAX];
 
 	assert(ref_out && repo && name);
 
 	*ref_out = NULL;
 
-	error = normalize_name(normalized_name, name, 0);
+	error = normalize_name(normalized_name, sizeof(normalized_name), name, 0);
 	if (error < GIT_SUCCESS)
 		return git__rethrow(error, "Failed to lookup reference");
 
@@ -1688,7 +1688,7 @@ static int check_valid_ref_char(char ch)
 	}
 }
 
-static int normalize_name(char *buffer_out, const char *name, int is_oid_ref)
+static int normalize_name(char *buffer_out, size_t out_size, const char *name, int is_oid_ref)
 {
 	const char *name_end, *buffer_out_start;
 	char *current;
@@ -1700,6 +1700,9 @@ static int normalize_name(char *buffer_out, const char *name, int is_oid_ref)
 	current = (char *)name;
 	name_end = name + strlen(name);
 
+	/* Terminating null byte */
+	out_size--;
+
 	/* A refname can not be empty */
 	if (name_end == name)
 		return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name is empty");
@@ -1708,7 +1711,7 @@ static int normalize_name(char *buffer_out, const char *name, int is_oid_ref)
 	if (*(name_end - 1) == '.' || *(name_end - 1) == '/')
 		return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name ends with dot or slash");
 
-	while (current < name_end) {
+	while (current < name_end && out_size) {
 		if (check_valid_ref_char(*current))
 			return git__throw(GIT_EINVALIDREFNAME, "Failed to normalize name. Reference name contains invalid characters");
 
@@ -1734,8 +1737,12 @@ static int normalize_name(char *buffer_out, const char *name, int is_oid_ref)
 			contains_a_slash = 1;
 
 		*buffer_out++ = *current++;
+		out_size--;
 	}
 
+	if (!out_size)
+		return git__throw(GIT_EINVALIDREFNAME, "Reference name is too long");
+
 	/* Object id refname have to contain at least one slash, except
 	 * for HEAD in a detached state or MERGE_HEAD if we're in the
 	 * middle of a merge */
@@ -1759,14 +1766,14 @@ static int normalize_name(char *buffer_out, const char *name, int is_oid_ref)
 	return GIT_SUCCESS;
 }
 
-int git_reference__normalize_name(char *buffer_out, const char *name)
+int git_reference__normalize_name(char *buffer_out, size_t out_size, const char *name)
 {
-	return normalize_name(buffer_out, name, 0);
+	return normalize_name(buffer_out, out_size, name, 0);
 }
 
-int git_reference__normalize_name_oid(char *buffer_out, const char *name)
+int git_reference__normalize_name_oid(char *buffer_out, size_t out_size, const char *name)
 {
-	return normalize_name(buffer_out, name, 1);
+	return normalize_name(buffer_out, out_size, name, 1);
 }
 
 
diff --git a/src/refs.h b/src/refs.h
index b8f3e2f..a0159b0 100644
--- a/src/refs.h
+++ b/src/refs.h
@@ -14,12 +14,13 @@
 #define GIT_SYMREF "ref: "
 #define GIT_PACKEDREFS_FILE "packed-refs"
 #define GIT_PACKEDREFS_HEADER "# pack-refs with: peeled "
-#define MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH 100
 
 #define GIT_HEAD_FILE "HEAD"
 #define GIT_MERGE_HEAD_FILE "MERGE_HEAD"
 #define GIT_REFS_HEADS_MASTER_FILE GIT_REFS_HEADS_DIR "master"
 
+#define GIT_REFNAME_MAX 1024
+
 struct git_reference {
 	git_repository *owner;
 	char *name;
@@ -37,7 +38,7 @@ typedef struct {
 void git_repository__refcache_free(git_refcache *refs);
 int git_repository__refcache_init(git_refcache *refs);
 
-int git_reference__normalize_name(char *buffer_out, const char *name);
-int git_reference__normalize_name_oid(char *buffer_out, const char *name);
+int git_reference__normalize_name(char *buffer_out, size_t out_size, const char *name);
+int git_reference__normalize_name_oid(char *buffer_out, size_t out_size, const char *name);
 
 #endif
diff --git a/src/tag.c b/src/tag.c
index 4a0710f..c3924a1 100644
--- a/src/tag.c
+++ b/src/tag.c
@@ -237,7 +237,7 @@ static int tag_create(
 	char *tagger_str;
 	git_reference *new_ref;
 
-	char ref_name[MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH];
+	char ref_name[GIT_REFNAME_MAX];
 
 	int type_str_len, tag_name_len, tagger_str_len, message_len;
 	int error, should_update_ref = 0;
@@ -310,7 +310,7 @@ int git_tag_create_frombuffer(git_oid *oid, git_repository *repo, const char *bu
 	git_odb_stream *stream;
 	
 	git_reference *new_ref;
-	char ref_name[MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH];
+	char ref_name[GIT_REFNAME_MAX];
 	
 	assert(oid && buffer);
 	
@@ -324,14 +324,12 @@ int git_tag_create_frombuffer(git_oid *oid, git_repository *repo, const char *bu
 	if ((error = tag_valid_in_odb(&new_ref, ref_name, &tag.target, tag.type, repo, tag.tag_name)) < GIT_SUCCESS)
 		return git__rethrow(error, "Failed to create tag");
 	
-	if(new_ref != NULL) {
+	if (new_ref != NULL) {
 		git_oid_cpy(oid, git_reference_oid(new_ref));
 		return git__throw(GIT_EEXISTS, "Tag already exists");
 	}
 	
-	
 	/* write the buffer */
-	
 	if ((error = git_odb_open_wstream(&stream, repo->db, strlen(buffer), GIT_OBJ_TAG)) < GIT_SUCCESS)
 		return git__rethrow(error, "Failed to create tag");
 	
@@ -419,7 +417,7 @@ int git_tag_delete(git_repository *repo, const char *tag_name)
 {
 	int error;
 	git_reference *tag_ref;
-	char ref_name[MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH];
+	char ref_name[GIT_REFNAME_MAX];
 
 	error = retreive_tag_reference(&tag_ref, ref_name, repo, tag_name);
 	if (error < GIT_SUCCESS)
diff --git a/tests/t10-refs.c b/tests/t10-refs.c
index 054991c..5efe804 100644
--- a/tests/t10-refs.c
+++ b/tests/t10-refs.c
@@ -34,7 +34,7 @@ BEGIN_TEST(readtag0, "lookup a loose tag reference")
 	git_repository *repo;
 	git_reference *reference;
 	git_object *object;
-	char ref_name_from_tag_name[MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH];
+	char ref_name_from_tag_name[GIT_REFNAME_MAX];
 
 	must_pass(git_repository_open(&repo, REPOSITORY_FOLDER));
 
@@ -718,12 +718,12 @@ END_TEST
 static int ensure_refname_normalized(int is_oid_ref, const char *input_refname, const char *expected_refname)
 {
 	int error = GIT_SUCCESS;
-	char buffer_out[GIT_PATH_MAX];
+	char buffer_out[GIT_REFNAME_MAX];
 
 	if (is_oid_ref)
-		error = git_reference__normalize_name_oid(buffer_out, input_refname);
+		error = git_reference__normalize_name_oid(buffer_out, sizeof(buffer_out), input_refname);
 	else
-		error = git_reference__normalize_name(buffer_out, input_refname);
+		error = git_reference__normalize_name(buffer_out, sizeof(buffer_out), input_refname);
 
 	if (error < GIT_SUCCESS)
 		return error;
@@ -804,7 +804,7 @@ BEGIN_TEST(normalize2, "tests borrowed from JGit")
 /* NoAsciiControlCharacters */
 	{
 		char c;
-		char buffer[MAX_GITDIR_TREE_STRUCTURE_PATH_LENGTH];
+		char buffer[GIT_REFNAME_MAX];
 		for (c = '\1'; c < ' '; c++) {
 			strncpy(buffer, "refs/heads/mast", 15);
 			strncpy(buffer + 15, (const char *)&c, 1);