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`.
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
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)