Commit 0933fdc5f5db346ff0fe9e1614bf2a54e96acba9

Patrick Steinhardt 2018-05-04T13:40:54

global: adjust init count under lock Our global initialization functions `git_libgit2_init()` and `git_libgit2_shutdown()` both adjust a global init counter to determine whether we are the first respectively last user of libgit2. On Unix-systems do not do so under lock, though, which opens the possibility of a race between these two functions: Thread 1 Thread 2 git__n_inits = 0; git_libgit2_init(); git_atomic_inc(&git__n_inits); /* git__n_inits == 1 */ git_libgit2_shutdown(); if (git_atomic_dec(&git__n_inits) != 0) /* git__n_inits == 0, no early exit here */ pthread_mutex_lock(&_init_mutex); shutdown_common(); pthread_mutex_unlock(&_init_mutex); pthread_mutex_lock(&_init_mutex); init_once(); pthread_mutex_unlock(&_init_mutex); So we can end up in a situation where we try to shutdown shared data structures before they have been initialized. Fix the race by always locking `_init_mutex` before incrementing or decrementing `git__n_inits`.

diff --git a/src/global.c b/src/global.c
index 8918308..29c1118 100644
--- a/src/global.c
+++ b/src/global.c
@@ -272,10 +272,10 @@ int git_libgit2_init(void)
 {
 	int ret, err;
 
-	ret = git_atomic_inc(&git__n_inits);
-
 	if ((err = pthread_mutex_lock(&_init_mutex)) != 0)
 		return err;
+
+	ret = git_atomic_inc(&git__n_inits);
 	err = pthread_once(&_once_init, init_once);
 	err |= pthread_mutex_unlock(&_init_mutex);
 
@@ -289,13 +289,13 @@ int git_libgit2_shutdown(void)
 {
 	void *ptr = NULL;
 	pthread_once_t new_once = PTHREAD_ONCE_INIT;
-	int ret;
+	int error, ret;
 
-	if ((ret = git_atomic_dec(&git__n_inits)) != 0)
-		return ret;
+	if ((error = pthread_mutex_lock(&_init_mutex)) != 0)
+		return error;
 
-	if ((ret = pthread_mutex_lock(&_init_mutex)) != 0)
-		return ret;
+	if ((ret = git_atomic_dec(&git__n_inits)) != 0)
+		goto out;
 
 	/* Shut down any subsystems that have global state */
 	shutdown_common();
@@ -310,10 +310,11 @@ int git_libgit2_shutdown(void)
 	git_mutex_free(&git__mwindow_mutex);
 	_once_init = new_once;
 
-	if ((ret = pthread_mutex_unlock(&_init_mutex)) != 0)
-		return ret;
+out:
+	if ((error = pthread_mutex_unlock(&_init_mutex)) != 0)
+		return error;
 
-	return 0;
+	return ret;
 }
 
 git_global_st *git__global_state(void)