Subject: [PATCH v2 0/1] remote-curl: unbreak http.extraHeader with custom allocators

From: "Johannes Schindelin via GitGitGadget" <[email-masked]>
Date: Wed, 06 Nov 2019 10:04:54 +0000

This is one of those bugs that can cost entire days of work.

The symptom of this bug is that git -c http.extraheader push ... will fail
frequently in Git for Windows v2.24.0, but not always. When it fails,
though, it will cause a segmentation fault in git remote-https while calling 
http_cleanup() and leave no visual indication of that problem, the only
indication of a problem will be the exit code of the caller (in this
instance, git push will fail with exit code 1).

In my tests during the pre-release period, I pushed many a time, it probably
failed a lot, in the way indicated above, and due to the absence of any
error message, I failed to realize that there was a problem in the first
place.

Fun side note: this bug haunted me for the best part of this past Monday,
when I tried to get Git for Windows v2.24.0 out the door. Large parts of Git
for Windows' release management are scripted, and that script failed,
claiming to have been unsuccessful in pushing the tag v2.24.0.windows.1,
just after printing a message to the extent that the tag is already up to
date (except in the first attempt, when it reported to have been successful
in pushing the tag). My attempts to fix the release script were doomed to
fail because the root cause was not a bug in the script, but the bug fixed
in this patch.

Changes since v1:

 * The patch was completely redone: instead of moving the call to 
   curl_global_init() (which would have broken support for http.sslbackend),
   this iteration instead replaces the usage of cURL's slist in the config
   handling by using Git's own string_list.

Johannes Schindelin (1):
  remote-curl: unbreak http.extraHeader with custom allocators

 http.c | 18 ++++++++----------
 1 file changed, 8 insertions(+), 10 deletions(-)

base-commit: 93b980e58f5624ee4e3b2dc0d0babaa97ef66d19
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-453%2Fdscho%2Ffix-curl-xmalloc-regression-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-453/dscho/fix-curl-xmalloc-regression-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/453

Range-diff vs v1:

 1:  d47a2aa594 ! 1:  3168ba2c9e remote-curl: unbreak http.extraHeader with custom allocators
     @@ -15,8 +15,18 @@
          presence of custom allocators, cURL would attempt to use the wrong
          allocator to release the memory.
      
     -    Let's fix this by moving the initialization _before_ the
     -    `http_options()` function is called.
     +    A naïve attempt at fixing this would move the call to
     +    `curl_global_init()` _before_ the config is parsed (i.e. before that
     +    call to `slist_append()`).
     +
     +    However, that does work, as we _also_ parse the config setting
     +    `http.sslbackend` and if found, call `curl_global_sslset()` which *must*
     +    be called before `curl_global_init()`, for details see:
     +    https://curl.haxx.se/libcurl/c/curl_global_sslset.html
     +
     +    So let's instead make the config parsing entirely independent from
     +    cURL's data structures. Incidentally, this deletes two more lines than
     +    it introduces, which is nice.
      
          Signed-off-by: Johannes Schindelin <[email-masked]>
      
     @@ -24,22 +34,50 @@
       --- a/http.c
       +++ b/http.c
      @@
     - 	char *normalized_url;
     - 	struct urlmatch_config config = { STRING_LIST_INIT_DUP };
     - 
     -+	if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK)
     -+		die("curl_global_init failed");
     -+
     - 	config.section = "http";
     - 	config.key = NULL;
     - 	config.collect_fn = http_options;
     + 
     + static struct curl_slist *pragma_header;
     + static struct curl_slist *no_pragma_header;
     +-static struct curl_slist *extra_http_headers;
     ++static struct string_list extra_http_headers = STRING_LIST_INIT_DUP;
     + 
     + static struct active_request_slot *active_queue_head;
     + 
      @@
     + 		if (!value) {
     + 			return config_error_nonbool(var);
     + 		} else if (!*value) {
     +-			curl_slist_free_all(extra_http_headers);
     +-			extra_http_headers = NULL;
     ++			string_list_clear(&extra_http_headers, 0);
     + 		} else {
     +-			extra_http_headers =
     +-				curl_slist_append(extra_http_headers, value);
     ++			string_list_append(&extra_http_headers, value);
     + 		}
     + 		return 0;
       	}
     +@@
       #endif
     + 	curl_global_cleanup();
     + 
     +-	curl_slist_free_all(extra_http_headers);
     +-	extra_http_headers = NULL;
     ++	string_list_clear(&extra_http_headers, 0);
     + 
     + 	curl_slist_free_all(pragma_header);
     + 	pragma_header = NULL;
     +@@
     + 
     + struct curl_slist *http_copy_default_headers(void)
     + {
     +-	struct curl_slist *headers = NULL, *h;
     ++	struct curl_slist *headers = NULL;
     ++	const struct string_list_item *item;
       
     --	if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK)
     --		die("curl_global_init failed");
     --
     - 	http_proactive_auth = proactive_auth;
     +-	for (h = extra_http_headers; h; h = h->next)
     +-		headers = curl_slist_append(headers, h->data);
     ++	for_each_string_list_item(item, &extra_http_headers)
     ++		headers = curl_slist_append(headers, item->string);
       
     - 	if (remote && remote->http_proxy)
     + 	return headers;
     + }

-- 
gitgitgadget