Commit 275f103d4c515d40c73cc17ae7880f1091414393

Patrick Steinhardt 2018-01-12T08:59:40

odb: reject reading and writing null OIDs The null OID (hash with all zeroes) indicates a missing object in upstream git and is thus not a valid object ID. Add defensive measurements to avoid writing such a hash to the object database in the very unlikely case where some data results in the null OID. Furthermore, add shortcuts when reading the null OID from the ODB to avoid ever returning an object when a faulty repository may contain the null OID.

diff --git a/src/odb.c b/src/odb.c
index 7da391b..c2b17fa 100644
--- a/src/odb.c
+++ b/src/odb.c
@@ -53,6 +53,7 @@ static git_cache *odb_cache(git_odb *odb)
 
 static int odb_otype_fast(git_otype *type_p, git_odb *db, const git_oid *id);
 static int load_alternates(git_odb *odb, const char *objects_dir, int alternate_depth);
+static int error_null_oid(int error, const char *message);
 
 static git_otype odb_hardcoded_type(const git_oid *id)
 {
@@ -735,6 +736,9 @@ int git_odb_exists(git_odb *db, const git_oid *id)
 
 	assert(db && id);
 
+	if (git_oid_iszero(id))
+		return 0;
+
 	if ((object = git_cache_get_raw(odb_cache(db), id)) != NULL) {
 		git_odb_object_free(object);
 		return 1;
@@ -958,6 +962,11 @@ int git_odb__read_header_or_object(
 
 	assert(db && id && out && len_p && type_p);
 
+	*out = NULL;
+
+	if (git_oid_iszero(id))
+		return error_null_oid(GIT_ENOTFOUND, "cannot read object");
+
 	if ((object = git_cache_get_raw(odb_cache(db), id)) != NULL) {
 		*len_p = object->cached.size;
 		*type_p = object->cached.type;
@@ -965,7 +974,6 @@ int git_odb__read_header_or_object(
 		return 0;
 	}
 
-	*out = NULL;
 	error = odb_read_header_1(len_p, type_p, db, id, false);
 
 	if (error == GIT_ENOTFOUND && !git_odb_refresh(db))
@@ -1057,6 +1065,9 @@ int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id)
 
 	assert(out && db && id);
 
+	if (git_oid_iszero(id))
+		return error_null_oid(GIT_ENOTFOUND, "cannot read object");
+
 	*out = git_cache_get_raw(odb_cache(db), id);
 	if (*out != NULL)
 		return 0;
@@ -1078,6 +1089,9 @@ static int odb_otype_fast(git_otype *type_p, git_odb *db, const git_oid *id)
 	size_t _unused;
 	int error;
 
+	if (git_oid_iszero(id))
+		return error_null_oid(GIT_ENOTFOUND, "cannot get object type");
+
 	if ((object = git_cache_get_raw(odb_cache(db), id)) != NULL) {
 		*type_p = object->cached.type;
 		return 0;
@@ -1231,6 +1245,10 @@ int git_odb_write(
 	assert(oid && db);
 
 	git_odb_hash(oid, data, len, type);
+
+	if (git_oid_iszero(oid))
+		return error_null_oid(GIT_EINVALID, "cannot write object");
+
 	if (git_odb__freshen(db, oid))
 		return 0;
 
@@ -1484,6 +1502,12 @@ int git_odb__error_notfound(
 	return GIT_ENOTFOUND;
 }
 
+static int error_null_oid(int error, const char *message)
+{
+	giterr_set(GITERR_ODB, "odb: %s: null OID cannot exist", message);
+	return error;
+}
+
 int git_odb__error_ambiguous(const char *message)
 {
 	giterr_set(GITERR_ODB, "ambiguous SHA1 prefix - %s", message);
diff --git a/tests/odb/backend/simple.c b/tests/odb/backend/simple.c
index c0fcd40..f4d29cc 100644
--- a/tests/odb/backend/simple.c
+++ b/tests/odb/backend/simple.c
@@ -230,3 +230,21 @@ void test_odb_backend_simple__exists_with_highly_ambiguous_prefix(void)
 	cl_git_pass(git_odb_exists_prefix(&found, _odb, &_oid, 40));
 	cl_assert(git_oid_equal(&found, &_oid));
 }
+
+void test_odb_backend_simple__null_oid_is_ignored(void)
+{
+	const fake_object objs[] = {
+		{ "0000000000000000000000000000000000000000", "null oid content" },
+		{ NULL, NULL }
+	};
+	git_oid null_oid = {{0}};
+	git_odb_object *obj;
+
+	setup_backend(objs);
+
+	cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 0));
+	cl_assert(!git_odb_exists(_odb, &null_oid));
+
+	cl_git_fail_with(GIT_ENOTFOUND, git_odb_read(&obj, _odb, &null_oid));
+	cl_assert(giterr_last() && strstr(giterr_last()->message, "null OID"));
+}