Skip to content

cf-h2-proxy: Curl_peer leaks when ctx_clear runs before Curl_peer_unlink #21602

@MegaManSec

Description

@MegaManSec

I did this

Small leak in cf-h2-proxy.c from the recent bc40e09f63 (2026-05-05, "lib: introduce Curl_peer") — the H2 proxy filter's ctx_clear memsets the context before Curl_peer_unlink(&ctx->dest) gets a chance to drop the refcount, so the peer leaks per failed CONNECT.

lib/cf-h2-proxy.c:198-205:

static void cf_h2_proxy_ctx_free(struct cf_h2_proxy_ctx *ctx)
{
  if(ctx) {
    cf_h2_proxy_ctx_clear(ctx);   /* memsets ctx, including ctx->dest */
    Curl_peer_unlink(&ctx->dest); /* now NULL — no-op */
    curlx_free(ctx);
  }
}

Curl_peer_unlink (lib/peer.c:298) returns immediately when *ppeer is NULL. The H1 sibling has the order right — tunnel_free (lib/cf-h1-proxy.c:182-192) calls Curl_peer_unlink(&ts->dest) first, before any field clears. Same commit fixed H1 correctly and broke H2.

Same shape in cf_h2_proxy_close (lib/cf-h2-proxy.c:1017-1030) — the ctx_clear runs but ctx->dest is never unlinked first. While in there, the H1 sibling at lib/cf-h1-proxy.c:755-767 also explicitly resets cf->connected = FALSE on close; the H2 filter omits that, which is harmless in current call paths (Curl_conn_free immediately follows) but worth aligning for sanity.

Two-line fix:

 static void cf_h2_proxy_ctx_free(struct cf_h2_proxy_ctx *ctx)
 {
   if(ctx) {
-    cf_h2_proxy_ctx_clear(ctx);
     Curl_peer_unlink(&ctx->dest);
+    cf_h2_proxy_ctx_clear(ctx);
     curlx_free(ctx);
   }
 }

 static void cf_h2_proxy_close(...)
 {
   ...
   if(ctx) {
     struct cf_call_data save;
+    cf->connected = FALSE;
     CF_DATA_SAVE(save, cf, data);
+    Curl_peer_unlink(&ctx->dest);
     cf_h2_proxy_ctx_clear(ctx);
     CF_DATA_RESTORE(cf, save);
   }
 }

PoC: a Python H2 proxy that accepts CONNECT, returns :status 200, then closes the inner stream (reports/F21_h2proxy.py). A small client (reports/F21_poc.c) runs three iterations with CURLPROXY_HTTPS2. ASan reports three leaks for three iterations:

==1461494==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 153 byte(s) in 3 object(s) allocated from:
    #0 calloc
    #1 peer_create           lib/peer.c:121
    #2 Curl_peer_from_url    lib/peer.c:445
    ...
SUMMARY: AddressSanitizer: 153 byte(s) leaked in 3 allocation(s).

Patched libcurl: zero leaks across the same run.

I expected the following

no leak

curl/libcurl version

master branch

operating system

all

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions