Commit 2022b00447a9edaa7caa712431c4888bbb1e0f67

Patrick Steinhardt 2018-02-28T12:06:59

curl: explicitly initialize and cleanup global curl state Our curl-based streams make use of the easy curl interface. This interface automatically initializes and de-initializes the global curl state by calling out to `curl_global_init` and `curl_global_cleanup`. Thus, all global state will be repeatedly re-initialized when creating multiple curl streams in succession. Despite being inefficient, this is not thread-safe due to `curl_global_init` being not thread-safe itself. Thus a multi-threaded programing handling multiple curl streams at the same time is inherently racy. Fix the issue by globally initializing and cleaning up curl's state.

diff --git a/src/global.c b/src/global.c
index 8918308..2f9b45b 100644
--- a/src/global.c
+++ b/src/global.c
@@ -11,6 +11,7 @@
 #include "sysdir.h"
 #include "filter.h"
 #include "merge_driver.h"
+#include "streams/curl.h"
 #include "streams/openssl.h"
 #include "thread-utils.h"
 #include "git2/global.h"
@@ -23,7 +24,7 @@
 
 git_mutex git__mwindow_mutex;
 
-#define MAX_SHUTDOWN_CB 9
+#define MAX_SHUTDOWN_CB 10
 
 static git_global_shutdown_fn git__shutdown_callbacks[MAX_SHUTDOWN_CB];
 static git_atomic git__n_shutdown_callbacks;
@@ -63,7 +64,8 @@ static int init_common(void)
 		(ret = git_filter_global_init()) == 0 &&
 		(ret = git_merge_driver_global_init()) == 0 &&
 		(ret = git_transport_ssh_global_init()) == 0 &&
-		(ret = git_openssl_stream_global_init()) == 0)
+		(ret = git_openssl_stream_global_init()) == 0 &&
+		(ret = git_curl_stream_global_init()) == 0)
 		ret = git_mwindow_global_init();
 
 	GIT_MEMORY_BARRIER;
diff --git a/src/streams/curl.c b/src/streams/curl.c
index 2f06103..ee13be1 100644
--- a/src/streams/curl.c
+++ b/src/streams/curl.c
@@ -14,6 +14,7 @@
 #include "stream.h"
 #include "git2/transport.h"
 #include "buffer.h"
+#include "global.h"
 #include "vector.h"
 #include "proxy.h"
 
@@ -38,6 +39,18 @@ typedef struct {
 	git_cred *proxy_cred;
 } curl_stream;
 
+int git_curl_stream_global_init(void)
+{
+	if (curl_global_init(CURL_GLOBAL_ALL) != 0) {
+		giterr_set(GITERR_NET, "could not initialize curl");
+		return -1;
+	}
+
+	/* `curl_global_cleanup` is provided by libcurl */
+	git__on_shutdown(curl_global_cleanup);
+	return 0;
+}
+
 static int seterr_curl(curl_stream *s)
 {
 	giterr_set(GITERR_NET, "curl error: %s\n", s->curl_error);
@@ -353,6 +366,11 @@ int git_curl_stream_new(git_stream **out, const char *host, const char *port)
 
 #include "stream.h"
 
+int git_curl_stream_global_init(void)
+{
+	return 0;
+}
+
 int git_curl_stream_new(git_stream **out, const char *host, const char *port)
 {
 	GIT_UNUSED(out);
diff --git a/src/streams/curl.h b/src/streams/curl.h
index 548feae..511cd89 100644
--- a/src/streams/curl.h
+++ b/src/streams/curl.h
@@ -11,6 +11,7 @@
 
 #include "git2/sys/stream.h"
 
+extern int git_curl_stream_global_init(void);
 extern int git_curl_stream_new(git_stream **out, const char *host, const char *port);
 
 #endif