git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH 14/28] http: fix leak of http_object_request struct

From
Jeff King <peff@peff.net>
Date
Sep 24, 2024, 22:01 UTC
Message-ID
<20240924220109.GN1143820@coredump.intra.peff.net>
In-Reply-To
<20240924214930.GA1143523@coredump.intra.peff.net>

The new_http_object_request() function allocates a struct on the heap, along with some fields inside the struct. But the matching function to clean it up, release_http_object_request(), only frees the interior fields without freeing the struct itself, causing a leak.

The related http_pack_request new/release pair gets this right, and at first glance we should be able to do the same thing and just add a single free() call. But there's a catch.

These http_object_request structs are typically embedded in the object_request struct of http-walker.c. And when we clean up that parent struct, it sanity-checks the embedded struct to make sure we are not leaking descriptors. Which means a use-after-free if we simply free() the embedded struct.

I have no idea how valuable that sanity-check is, or whether it can simply be deleted. This all goes back to 5424bc557f (http*: add helper methods for fetching objects (loose), 2009-06-06). But the obvious way to make it all work is to be sure we set the pointer to NULL after freeing it (and our freeing process closes the descriptor, so we know there is no leak).

To make sure we do that consistently, we'll switch the pointer we take in release_http_object_request() to a pointer-to-pointer, and we'll set it to NULL ourselves. And then the compiler can help us find each caller which needs to be updated.

Most cases will just pass "&obj_req->req", which will obviously do the right thing. In a few cases, like http-push's finish_request(), we are working with a copy of the pointer, so we don't NULL the original. But it's OK because the next step is to free the struct containing the original pointer anyway.

This lets us mark t5551 as leak-free. Ironically this is the "smart" http test, and the leak here only affects dumb http. But there's a single dumb-http invocation in there. The full dumb tests are in t5550, which still has some more leaks.

This also makes t5559 leak-free, as it's just an HTTP/2 variant of t5551. But we don't need to mark it as such, since it inherits the flag from t5551.

Signed-off-by: Jeff King <peff@peff.net>
---
 http-push.c                 |  4 ++--
 http-walker.c               |  8 ++++----
 http.c                      | 11 ++++++++---
 http.h                      |  4 ++--
 t/t5551-http-fetch-smart.sh |  1 +
 5 files changed, 17 insertions(+), 11 deletions(-)
diff --git a/http-push.c b/http-push.c
index 7315a694aa..7196ffa525 100644
--- a/http-push.c
+++ b/http-push.c
@@ -275,7 +275,7 @@ static void start_fetch_loose(struct transfer_request *request)
 	if (!start_active_slot(slot)) {
 		fprintf(stderr, "Unable to start GET request\n");
 		repo->can_update_info_refs = 0;
-		release_http_object_request(obj_req);
+		release_http_object_request(&obj_req);
 		release_request(request);
 	}
 }
@@ -580,7 +580,7 @@ static void finish_request(struct transfer_request *request)
 
 		/* Try fetching packed if necessary */
 		if (request->obj->flags & LOCAL) {
-			release_http_object_request(obj_req);
+			release_http_object_request(&obj_req);
 			release_request(request);
 		} else
 			start_fetch_packed(request);
diff --git a/http-walker.c b/http-walker.c
index e417a7f51c..9c1e5c37e6 100644
--- a/http-walker.c
+++ b/http-walker.c
@@ -74,7 +74,7 @@ static void start_object_request(struct object_request *obj_req)
 	obj_req->state = ACTIVE;
 	if (!start_active_slot(slot)) {
 		obj_req->state = ABORTED;
-		release_http_object_request(req);
+		release_http_object_request(&req);
 		return;
 	}
 }
@@ -110,7 +110,7 @@ static void process_object_response(void *callback_data)
 		if (obj_req->repo->next) {
 			obj_req->repo =
 				obj_req->repo->next;
-			release_http_object_request(obj_req->req);
+			release_http_object_request(&obj_req->req);
 			start_object_request(obj_req);
 			return;
 		}
@@ -495,7 +495,7 @@ static int fetch_object(struct walker *walker, unsigned char *hash)
 
 	if (repo_has_object_file(the_repository, &obj_req->oid)) {
 		if (obj_req->req)
-			abort_http_object_request(obj_req->req);
+			abort_http_object_request(&obj_req->req);
 		abort_object_request(obj_req);
 		return 0;
 	}
@@ -543,7 +543,7 @@ static int fetch_object(struct walker *walker, unsigned char *hash)
 		strbuf_release(&buf);
 	}
 
-	release_http_object_request(req);
+	release_http_object_request(&obj_req->req);
 	release_object_request(obj_req);
 	return ret;
 }
diff --git a/http.c b/http.c
index cc136408c0..d0242ffb50 100644
--- a/http.c
+++ b/http.c
@@ -2816,15 +2816,17 @@ int finish_http_object_request(struct http_object_request *freq)
 	return freq->rename;
 }
 
-void abort_http_object_request(struct http_object_request *freq)
+void abort_http_object_request(struct http_object_request **freq_p)
 {
+	struct http_object_request *freq = *freq_p;
 	unlink_or_warn(freq->tmpfile.buf);
 
-	release_http_object_request(freq);
+	release_http_object_request(freq_p);
 }
 
-void release_http_object_request(struct http_object_request *freq)
+void release_http_object_request(struct http_object_request **freq_p)
 {
+	struct http_object_request *freq = *freq_p;
 	if (freq->localfile != -1) {
 		close(freq->localfile);
 		freq->localfile = -1;
@@ -2838,4 +2840,7 @@ void release_http_object_request(struct http_object_request *freq)
 	}
 	curl_slist_free_all(freq->headers);
 	strbuf_release(&freq->tmpfile);
+
+	free(freq);
+	*freq_p = NULL;
 }
diff --git a/http.h b/http.h
index a516ca4a9a..46e334c2c2 100644
--- a/http.h
+++ b/http.h
@@ -240,8 +240,8 @@ struct http_object_request *new_http_object_request(
 	const char *base_url, const struct object_id *oid);
 void process_http_object_request(struct http_object_request *freq);
 int finish_http_object_request(struct http_object_request *freq);
-void abort_http_object_request(struct http_object_request *freq);
-void release_http_object_request(struct http_object_request *freq);
+void abort_http_object_request(struct http_object_request **freq);
+void release_http_object_request(struct http_object_request **freq);
 
 /*
  * Instead of using environment variables to determine if curl tracing happens,
diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh
index 7b5ab0eae1..e36dfde17e 100755
--- a/t/t5551-http-fetch-smart.sh
+++ b/t/t5551-http-fetch-smart.sh
@@ -5,6 +5,7 @@ test_description="test smart fetching over http via http-backend ($HTTP_PROTO)"
 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/lib-httpd.sh
 test "$HTTP_PROTO" = "HTTP/2" && enable_http2
-- 
2.46.2.1011.gf1f9323e02
Previous: Jeff KingNext: Patrick Steinhardt
Message 26 of 49 in “leak fixes for http fetch/push”
  1. 0/28 leak fixes for http fetch/pushJeff King, Sep 24, 2024
  2. 01/28 http-fetch: clear leaking git-index-pack(1) argumentsJeff King, Sep 24, 2024
  3. 02/28 shallow: fix leak when unregistering last shallow rootJeff King, Sep 24, 2024
  4. 03/28 fetch-pack: fix leaking sought refsJeff King, Sep 24, 2024
  5. René ScharfeSep 25, 2024
  6. Patrick SteinhardtSep 26, 2024
  7. 04/28 connect: clear child process before freeing in diagnostic modeJeff King, Sep 24, 2024
  8. Patrick SteinhardtSep 26, 2024
  9. 05/28 fetch-pack: free object filter before exitingJeff King, Sep 24, 2024
  10. Patrick SteinhardtSep 26, 2024
  11. 06/28 fetch-pack, send-pack: clean up shallow oid arrayJeff King, Sep 24, 2024
  12. Patrick SteinhardtSep 26, 2024
  13. Jeff KingSep 27, 2024
  14. 07/28 commit: avoid leaking already-saved bufferJeff King, Sep 24, 2024
  15. Patrick SteinhardtSep 26, 2024
  16. 08/28 send-pack: free cas options before exitJeff King, Sep 24, 2024
  17. Patrick SteinhardtSep 26, 2024
  18. Jeff KingSep 27, 2024
  19. 09/28 transport-helper: fix strbuf leak in push_refs_with_push()Jeff King, Sep 24, 2024
  20. Patrick SteinhardtSep 26, 2024
  21. Jeff KingSep 27, 2024
  22. 10/28 fetch: free "raw" string when shrinking refspecJeff King, Sep 24, 2024
  23. 11/28 fetch-pack: clear pack lockfiles listJeff King, Sep 24, 2024
  24. 12/28 transport-helper: fix leak of dummy refs_listJeff King, Sep 24, 2024
  25. 13/28 http: fix leak when redacting cookies from curl traceJeff King, Sep 24, 2024
  26. 14/28 http: fix leak of http_object_request structJeff King, Sep 24, 2024
  27. Patrick SteinhardtSep 26, 2024
  28. Jeff KingSep 27, 2024
  29. 15/28 http: call git_inflate_end() when releasing http_object_requestJeff King, Sep 24, 2024
  30. Patrick SteinhardtSep 26, 2024
  31. Jeff KingSep 27, 2024
  32. 16/28 http: stop leaking buffer in http_get_info_packs()Jeff King, Sep 24, 2024
  33. 17/28 remote-curl: free HEAD ref with free_one_ref()Jeff King, Sep 24, 2024
  34. 18/28 http-walker: free fake packed_git listJeff King, Sep 24, 2024
  35. 19/28 http-push: clear refspecs before exitingJeff King, Sep 24, 2024
  36. 20/28 http-push: free repo->url stringJeff King, Sep 24, 2024
  37. Patrick SteinhardtSep 26, 2024
  38. Jeff KingSep 27, 2024
  39. 21/28 http-push: free curl header listsJeff King, Sep 24, 2024
  40. Patrick SteinhardtSep 26, 2024
  41. 22/28 http-push: free transfer_request dest fieldJeff King, Sep 24, 2024
  42. 23/28 http-push: free transfer_request strbufJeff King, Sep 24, 2024
  43. 24/28 http-push: free remote_ls_ctx.dentry_nameJeff King, Sep 24, 2024
  44. 25/28 http-push: free xml_ctx.cdata after useJeff King, Sep 24, 2024
  45. 26/28 http-push: clean up objects listJeff King, Sep 24, 2024
  46. 27/28 http-push: clean up loose request when falling back to packedJeff King, Sep 24, 2024
  47. 28/28 http-push: clean up local_refs at exitJeff King, Sep 24, 2024
  48. Patrick SteinhardtSep 26, 2024
  49. Jeff KingSep 27, 2024

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.