diff mbox series

[10/22] send-pack: fix leaking push cert nonce

Message ID 138a5ded35a43d3aeaa5058ba316a45b7b50b9ef.1724656120.git.ps@pks.im (mailing list archive)
State Superseded
Headers show
Series Memory leak fixes (pt.6) | expand

Commit Message

Patrick Steinhardt Aug. 26, 2024, 7:22 a.m. UTC
When retrieving the push cert nonce from the server, we first store the
constant returned by `server_feature_value()` and then, if the nonce is
valid, we duplicate the nonce memory to extend its lifetime. We never
free the latter and thus cause a memory leak.

Fix this by storing the limited-lifetime nonce into a scope-local
variable such that the long-lived, allocated nonce can be easily freed
without having to cast away its constness.

This leak was exposed by t5534, but fixing it is not sufficient to make
the whole test suite leak free.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 send-pack.c | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

Comments

Junio C Hamano Sept. 4, 2024, 10:08 p.m. UTC | #1
Patrick Steinhardt <ps@pks.im> writes:

> When retrieving the push cert nonce from the server, we first store the
> constant returned by `server_feature_value()` and then, if the nonce is
> valid, we duplicate the nonce memory to extend its lifetime. We never
> free the latter and thus cause a memory leak.

"to extend its lifetime" -> "to a NUL-terminated string, so that we
can pass it to generate_push_cert()".

What is going on in this code path is this.

The other side may send a nonce in the server capability.  We die if
that nonce is bogus.  Otherwise we make a xmemdupz() copy because we
need to pass the nonce to generate_push_cert() that expects nonce to
be a NUL terminated string (the original server capability is a long
concatenation of capabilities and we learn the <ptr, len> for the
nonce).  The function cryptographically signs the ref update request
we have, together with the nonce we got from the server, so that the
other side can validate that it is signed by us, and the nonce serves
as a protection against replay attacks.

> diff --git a/send-pack.c b/send-pack.c
> index b224ef9fc5e..c37f6ab3c07 100644
> --- a/send-pack.c
> +++ b/send-pack.c
> @@ -501,7 +501,7 @@ int send_pack(struct send_pack_args *args,
>  	unsigned cmds_sent = 0;
>  	int ret;
>  	struct async demux;
> -	const char *push_cert_nonce = NULL;
> +	char *push_cert_nonce = NULL;
>  	struct packet_reader reader;
>  	int use_bitmaps;

This is a change necessary to avoid having to cast the parameter to
free().

> @@ -550,10 +550,11 @@ int send_pack(struct send_pack_args *args,
>  
>  	if (args->push_cert != SEND_PACK_PUSH_CERT_NEVER) {
>  		size_t len;
> -		push_cert_nonce = server_feature_value("push-cert", &len);
> -		if (push_cert_nonce) {
> -			reject_invalid_nonce(push_cert_nonce, len);
> -			push_cert_nonce = xmemdupz(push_cert_nonce, len);
> +		const char *nonce = server_feature_value("push-cert", &len);
> +
> +		if (nonce) {
> +			reject_invalid_nonce(nonce, len);
> +			push_cert_nonce = xmemdupz(nonce, len);

And this hunk become needed because push_cert_nonce cannot receive
the return value from server_feature_value() without stripping
constness.

>  		} else if (args->push_cert == SEND_PACK_PUSH_CERT_ALWAYS) {
>  			die(_("the receiving end does not support --signed push"));
>  		} else if (args->push_cert == SEND_PACK_PUSH_CERT_IF_ASKED) {
> @@ -771,5 +772,6 @@ int send_pack(struct send_pack_args *args,
>  	oid_array_clear(&commons);
>  	strbuf_release(&req_buf);
>  	strbuf_release(&cap_buf);
> +	free(push_cert_nonce);

And this is my fault to forget freeing.  Thanks for spotting and fixing.

>  	return ret;
>  }
diff mbox series

Patch

diff --git a/send-pack.c b/send-pack.c
index b224ef9fc5e..c37f6ab3c07 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -501,7 +501,7 @@  int send_pack(struct send_pack_args *args,
 	unsigned cmds_sent = 0;
 	int ret;
 	struct async demux;
-	const char *push_cert_nonce = NULL;
+	char *push_cert_nonce = NULL;
 	struct packet_reader reader;
 	int use_bitmaps;
 
@@ -550,10 +550,11 @@  int send_pack(struct send_pack_args *args,
 
 	if (args->push_cert != SEND_PACK_PUSH_CERT_NEVER) {
 		size_t len;
-		push_cert_nonce = server_feature_value("push-cert", &len);
-		if (push_cert_nonce) {
-			reject_invalid_nonce(push_cert_nonce, len);
-			push_cert_nonce = xmemdupz(push_cert_nonce, len);
+		const char *nonce = server_feature_value("push-cert", &len);
+
+		if (nonce) {
+			reject_invalid_nonce(nonce, len);
+			push_cert_nonce = xmemdupz(nonce, len);
 		} else if (args->push_cert == SEND_PACK_PUSH_CERT_ALWAYS) {
 			die(_("the receiving end does not support --signed push"));
 		} else if (args->push_cert == SEND_PACK_PUSH_CERT_IF_ASKED) {
@@ -771,5 +772,6 @@  int send_pack(struct send_pack_args *args,
 	oid_array_clear(&commons);
 	strbuf_release(&req_buf);
 	strbuf_release(&cap_buf);
+	free(push_cert_nonce);
 	return ret;
 }