]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
http2+h3 filters: fix ctx init
authorStefan Eissing <stefan@eissing.org>
Mon, 12 Aug 2024 13:42:41 +0000 (15:42 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Mon, 12 Aug 2024 20:48:08 +0000 (22:48 +0200)
Members of the filter context, like stream hash and buffers, need to be
initialized early and protected by a flag to also avoid double cleanup.

This allow the context to be used safely before a connect() is started
and the other parts of the context are set up.

Closes #14505

lib/http2.c
lib/vquic/curl_msh3.c
lib/vquic/curl_ngtcp2.c
lib/vquic/curl_osslq.c
lib/vquic/curl_quiche.c

index 86f76ab3794774cf8b59529fceb811b84ed941f0..6b4aabca7051cacbe328d260abbf29b02f8259a3 100644 (file)
@@ -142,6 +142,8 @@ struct cf_h2_ctx {
   uint32_t goaway_error;        /* goaway error code from server */
   int32_t remote_max_sid;       /* max id processed by server */
   int32_t local_max_sid;        /* max id processed by us */
+  BIT(initialized);
+  BIT(via_h1_upgrade);
   BIT(conn_closed);
   BIT(rcvd_goaway);
   BIT(sent_goaway);
@@ -154,28 +156,38 @@ struct cf_h2_ctx {
 #define CF_CTX_CALL_DATA(cf)  \
   ((struct cf_h2_ctx *)(cf)->ctx)->call_data
 
-static void cf_h2_ctx_clear(struct cf_h2_ctx *ctx)
+static void h2_stream_hash_free(void *stream);
+
+static void cf_h2_ctx_init(struct cf_h2_ctx *ctx, bool via_h1_upgrade)
 {
-  struct cf_call_data save = ctx->call_data;
+  Curl_bufcp_init(&ctx->stream_bufcp, H2_CHUNK_SIZE, H2_STREAM_POOL_SPARES);
+  Curl_bufq_initp(&ctx->inbufq, &ctx->stream_bufcp, H2_NW_RECV_CHUNKS, 0);
+  Curl_bufq_initp(&ctx->outbufq, &ctx->stream_bufcp, H2_NW_SEND_CHUNKS, 0);
+  Curl_dyn_init(&ctx->scratch, CURL_MAX_HTTP_HEADER);
+  Curl_hash_offt_init(&ctx->streams, 63, h2_stream_hash_free);
+  ctx->remote_max_sid = 2147483647;
+  ctx->via_h1_upgrade = via_h1_upgrade;
+  ctx->initialized = TRUE;
+}
 
-  if(ctx->h2) {
-    nghttp2_session_del(ctx->h2);
+static void cf_h2_ctx_free(struct cf_h2_ctx *ctx)
+{
+  if(ctx && ctx->initialized) {
+    Curl_bufq_free(&ctx->inbufq);
+    Curl_bufq_free(&ctx->outbufq);
+    Curl_bufcp_free(&ctx->stream_bufcp);
+    Curl_dyn_free(&ctx->scratch);
+    Curl_hash_clean(&ctx->streams);
+    Curl_hash_destroy(&ctx->streams);
+    memset(ctx, 0, sizeof(*ctx));
   }
-  Curl_bufq_free(&ctx->inbufq);
-  Curl_bufq_free(&ctx->outbufq);
-  Curl_bufcp_free(&ctx->stream_bufcp);
-  Curl_dyn_free(&ctx->scratch);
-  Curl_hash_clean(&ctx->streams);
-  Curl_hash_destroy(&ctx->streams);
-  memset(ctx, 0, sizeof(*ctx));
-  ctx->call_data = save;
+  free(ctx);
 }
 
-static void cf_h2_ctx_free(struct cf_h2_ctx *ctx)
+static void cf_h2_ctx_close(struct cf_h2_ctx *ctx)
 {
-  if(ctx) {
-    cf_h2_ctx_clear(ctx);
-    free(ctx);
+  if(ctx->h2) {
+    nghttp2_session_del(ctx->h2);
   }
 }
 
@@ -390,7 +402,7 @@ static void http2_data_done(struct Curl_cfilter *cf, struct Curl_easy *data)
   struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data);
 
   DEBUGASSERT(ctx);
-  if(!stream)
+  if(!stream || !ctx->initialized)
     return;
 
   if(ctx->h2) {
@@ -489,12 +501,8 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
 static int error_callback(nghttp2_session *session, const char *msg,
                           size_t len, void *userp);
 
-/*
- * Initialize the cfilter context
- */
-static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf,
-                               struct Curl_easy *data,
-                               bool via_h1_upgrade)
+static CURLcode cf_h2_ctx_open(struct Curl_cfilter *cf,
+                               struct Curl_easy *data)
 {
   struct cf_h2_ctx *ctx = cf->ctx;
   struct h2_stream_ctx *stream;
@@ -503,12 +511,7 @@ static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf,
   nghttp2_session_callbacks *cbs = NULL;
 
   DEBUGASSERT(!ctx->h2);
-  Curl_bufcp_init(&ctx->stream_bufcp, H2_CHUNK_SIZE, H2_STREAM_POOL_SPARES);
-  Curl_bufq_initp(&ctx->inbufq, &ctx->stream_bufcp, H2_NW_RECV_CHUNKS, 0);
-  Curl_bufq_initp(&ctx->outbufq, &ctx->stream_bufcp, H2_NW_SEND_CHUNKS, 0);
-  Curl_dyn_init(&ctx->scratch, CURL_MAX_HTTP_HEADER);
-  Curl_hash_offt_init(&ctx->streams, 63, h2_stream_hash_free);
-  ctx->remote_max_sid = 2147483647;
+  DEBUGASSERT(ctx->initialized);
 
   rc = nghttp2_session_callbacks_new(&cbs);
   if(rc) {
@@ -537,7 +540,7 @@ static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf,
   }
   ctx->max_concurrent_streams = DEFAULT_MAX_CONCURRENT_STREAMS;
 
-  if(via_h1_upgrade) {
+  if(ctx->via_h1_upgrade) {
     /* HTTP/1.1 Upgrade issued. H2 Settings have already been submitted
      * in the H1 request and we upgrade from there. This stream
      * is opened implicitly as #1. */
@@ -603,7 +606,7 @@ static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf,
   /* all set, traffic will be send on connect */
   result = CURLE_OK;
   CURL_TRC_CF(data, cf, "[0] created h2 session%s",
-              via_h1_upgrade? " (via h1 upgrade)" : "");
+              ctx->via_h1_upgrade? " (via h1 upgrade)" : "");
 
 out:
   if(cbs)
@@ -2450,8 +2453,9 @@ static CURLcode cf_h2_connect(struct Curl_cfilter *cf,
   *done = FALSE;
 
   CF_DATA_SAVE(save, cf, data);
+  DEBUGASSERT(ctx->initialized);
   if(!ctx->h2) {
-    result = cf_h2_ctx_init(cf, data, FALSE);
+    result = cf_h2_ctx_open(cf, data);
     if(result)
       goto out;
   }
@@ -2486,7 +2490,7 @@ static void cf_h2_close(struct Curl_cfilter *cf, struct Curl_easy *data)
     struct cf_call_data save;
 
     CF_DATA_SAVE(save, cf, data);
-    cf_h2_ctx_clear(ctx);
+    cf_h2_ctx_close(ctx);
     CF_DATA_RESTORE(cf, save);
     cf->connected = FALSE;
   }
@@ -2735,6 +2739,7 @@ static CURLcode http2_cfilter_add(struct Curl_cfilter **pcf,
   ctx = calloc(1, sizeof(*ctx));
   if(!ctx)
     goto out;
+  cf_h2_ctx_init(ctx, via_h1_upgrade);
 
   result = Curl_cf_create(&cf, &Curl_cft_nghttp2, ctx);
   if(result)
@@ -2742,7 +2747,6 @@ static CURLcode http2_cfilter_add(struct Curl_cfilter **pcf,
 
   ctx = NULL;
   Curl_conn_cf_add(data, conn, sockindex, cf);
-  result = cf_h2_ctx_init(cf, data, via_h1_upgrade);
 
 out:
   if(result)
@@ -2763,6 +2767,7 @@ static CURLcode http2_cfilter_insert_after(struct Curl_cfilter *cf,
   ctx = calloc(1, sizeof(*ctx));
   if(!ctx)
     goto out;
+  cf_h2_ctx_init(ctx, via_h1_upgrade);
 
   result = Curl_cf_create(&cf_h2, &Curl_cft_nghttp2, ctx);
   if(result)
@@ -2770,7 +2775,6 @@ static CURLcode http2_cfilter_insert_after(struct Curl_cfilter *cf,
 
   ctx = NULL;
   Curl_conn_cf_insert_after(cf, cf_h2);
-  result = cf_h2_ctx_init(cf_h2, data, via_h1_upgrade);
 
 out:
   if(result)
index c8466280a20303f457cc4e11d95d0b1efed0e5d5..17e7d402f41e7d258f9b63f53504175a64e78fbf 100644 (file)
@@ -124,11 +124,33 @@ struct cf_msh3_ctx {
   bool handshake_complete;
   bool handshake_succeeded;
   bool connected;
+  BIT(initialized);
   /* Flags written by curl thread */
   BIT(verbose);
   BIT(active);
 };
 
+static void h3_stream_hash_free(void *stream);
+
+static void cf_msh3_ctx_init(struct cf_msh3_ctx *ctx,
+                             const struct Curl_addrinfo *ai)
+{
+  DEBUGASSERT(!ctx->initialized);
+  Curl_hash_offt_init(&ctx->streams, 63, h3_stream_hash_free);
+  Curl_sock_assign_addr(&ctx->addr, ai, TRNSPRT_QUIC);
+  ctx->sock[SP_LOCAL] = CURL_SOCKET_BAD;
+  ctx->sock[SP_REMOTE] = CURL_SOCKET_BAD;
+  ctx->initialized = TRUE;
+}
+
+static void cf_msh3_ctx_free(struct cf_msh3_ctx *ctx)
+{
+  if(ctx && ctx->initialized) {
+    Curl_hash_destroy(&ctx->streams);
+  }
+  free(ctx);
+}
+
 static struct cf_msh3_ctx *h3_get_msh3_ctx(struct Curl_easy *data);
 
 /* How to access `call_data` from a cf_msh3 filter */
@@ -798,7 +820,7 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf,
   CURLcode result;
   bool verify;
 
-  Curl_hash_offt_init(&ctx->streams, 63, h3_stream_hash_free);
+  DEBUGASSERT(ctx->initialized);
   conn_config = Curl_ssl_cf_get_primary_config(cf);
   if(!conn_config)
     return CURLE_FAILED_INIT;
@@ -925,7 +947,6 @@ static void cf_msh3_close(struct Curl_cfilter *cf, struct Curl_easy *data)
       MsH3ApiClose(ctx->api);
       ctx->api = NULL;
     }
-    Curl_hash_destroy(&ctx->streams);
 
     if(ctx->active) {
       /* We share our socket at cf->conn->sock[cf->sockindex] when active.
@@ -964,10 +985,11 @@ static void cf_msh3_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
 
   CF_DATA_SAVE(save, cf, data);
   cf_msh3_close(cf, data);
-  free(cf->ctx);
-  cf->ctx = NULL;
+  if(cf->ctx) {
+    cf_msh3_ctx_free(cf->ctx);
+    cf->ctx = NULL;
+  }
   /* no CF_DATA_RESTORE(cf, save); its gone */
-
 }
 
 static CURLcode cf_msh3_query(struct Curl_cfilter *cf,
@@ -1066,9 +1088,7 @@ CURLcode Curl_cf_msh3_create(struct Curl_cfilter **pcf,
     result = CURLE_OUT_OF_MEMORY;
     goto out;
   }
-  Curl_sock_assign_addr(&ctx->addr, ai, TRNSPRT_QUIC);
-  ctx->sock[SP_LOCAL] = CURL_SOCKET_BAD;
-  ctx->sock[SP_REMOTE] = CURL_SOCKET_BAD;
+  cf_msh3_ctx_init(ctx, ai);
 
   result = Curl_cf_create(&cf, &Curl_cft_http3, ctx);
 
@@ -1076,7 +1096,7 @@ out:
   *pcf = (!result)? cf : NULL;
   if(result) {
     Curl_safefree(cf);
-    Curl_safefree(ctx);
+    cf_msh3_ctx_free(ctx);
   }
 
   return result;
index f672bfcaebb7b5a2092306a88b388a9174eb2d8c..0674e1aa9b2caca864a2f7992f4f03d43a1948da 100644 (file)
@@ -138,6 +138,7 @@ struct cf_ngtcp2_ctx {
   uint64_t used_bidi_streams;        /* bidi streams we have opened */
   uint64_t max_bidi_streams;         /* max bidi streams we can open */
   int qlogfd;
+  BIT(initialized);
   BIT(shutdown_started);             /* queued shutdown packets */
 };
 
@@ -146,6 +147,34 @@ struct cf_ngtcp2_ctx {
 #define CF_CTX_CALL_DATA(cf)  \
   ((struct cf_ngtcp2_ctx *)(cf)->ctx)->call_data
 
+static void h3_stream_hash_free(void *stream);
+
+static void cf_ngtcp2_ctx_init(struct cf_ngtcp2_ctx *ctx)
+{
+  DEBUGASSERT(!ctx->initialized);
+  ctx->qlogfd = -1;
+  ctx->version = NGTCP2_PROTO_VER_MAX;
+  ctx->max_stream_window = H3_STREAM_WINDOW_SIZE;
+  ctx->max_idle_ms = CURL_QUIC_MAX_IDLE_MS;
+  Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE,
+                  H3_STREAM_POOL_SPARES);
+  Curl_dyn_init(&ctx->scratch, CURL_MAX_HTTP_HEADER);
+  Curl_hash_offt_init(&ctx->streams, 63, h3_stream_hash_free);
+  ctx->initialized = TRUE;
+}
+
+static void cf_ngtcp2_ctx_free(struct cf_ngtcp2_ctx *ctx)
+{
+  if(ctx && ctx->initialized) {
+    Curl_bufcp_free(&ctx->stream_bufcp);
+    Curl_dyn_free(&ctx->scratch);
+    Curl_hash_clean(&ctx->streams);
+    Curl_hash_destroy(&ctx->streams);
+    Curl_ssl_peer_cleanup(&ctx->peer);
+  }
+  free(ctx);
+}
+
 struct pkt_io_ctx;
 static CURLcode cf_progress_ingress(struct Curl_cfilter *cf,
                                     struct Curl_easy *data,
@@ -1963,27 +1992,22 @@ static CURLcode cf_ngtcp2_data_event(struct Curl_cfilter *cf,
   return result;
 }
 
-static void cf_ngtcp2_ctx_clear(struct cf_ngtcp2_ctx *ctx)
+static void cf_ngtcp2_ctx_close(struct cf_ngtcp2_ctx *ctx)
 {
   struct cf_call_data save = ctx->call_data;
 
+  if(!ctx->initialized)
+    return;
   if(ctx->qlogfd != -1) {
     close(ctx->qlogfd);
   }
+  ctx->qlogfd = -1;
   Curl_vquic_tls_cleanup(&ctx->tls);
   vquic_ctx_free(&ctx->q);
   if(ctx->h3conn)
     nghttp3_conn_del(ctx->h3conn);
   if(ctx->qconn)
     ngtcp2_conn_del(ctx->qconn);
-  Curl_bufcp_free(&ctx->stream_bufcp);
-  Curl_dyn_free(&ctx->scratch);
-  Curl_hash_clean(&ctx->streams);
-  Curl_hash_destroy(&ctx->streams);
-  Curl_ssl_peer_cleanup(&ctx->peer);
-
-  memset(ctx, 0, sizeof(*ctx));
-  ctx->qlogfd = -1;
   ctx->call_data = save;
 }
 
@@ -2088,7 +2112,7 @@ static void cf_ngtcp2_close(struct Curl_cfilter *cf, struct Curl_easy *data)
   CF_DATA_SAVE(save, cf, data);
   if(ctx && ctx->qconn) {
     cf_ngtcp2_conn_close(cf, data);
-    cf_ngtcp2_ctx_clear(ctx);
+    cf_ngtcp2_ctx_close(ctx);
     CURL_TRC_CF(data, cf, "close");
   }
   cf->connected = FALSE;
@@ -2097,18 +2121,11 @@ static void cf_ngtcp2_close(struct Curl_cfilter *cf, struct Curl_easy *data)
 
 static void cf_ngtcp2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
 {
-  struct cf_ngtcp2_ctx *ctx = cf->ctx;
-  struct cf_call_data save;
-
-  CF_DATA_SAVE(save, cf, data);
   CURL_TRC_CF(data, cf, "destroy");
-  if(ctx) {
-    cf_ngtcp2_ctx_clear(ctx);
-    free(ctx);
+  if(cf->ctx) {
+    cf_ngtcp2_ctx_free(cf->ctx);
+    cf->ctx = NULL;
   }
-  cf->ctx = NULL;
-  /* No CF_DATA_RESTORE(cf, save) possible */
-  (void)save;
 }
 
 #ifdef USE_OPENSSL
@@ -2190,14 +2207,7 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf,
   const struct Curl_sockaddr_ex *sockaddr = NULL;
   int qfd;
 
-  ctx->version = NGTCP2_PROTO_VER_MAX;
-  ctx->max_stream_window = H3_STREAM_WINDOW_SIZE;
-  ctx->max_idle_ms = CURL_QUIC_MAX_IDLE_MS;
-  Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE,
-                  H3_STREAM_POOL_SPARES);
-  Curl_dyn_init(&ctx->scratch, CURL_MAX_HTTP_HEADER);
-  Curl_hash_offt_init(&ctx->streams, 63, h3_stream_hash_free);
-
+  DEBUGASSERT(ctx->initialized);
   result = Curl_ssl_peer_init(&ctx->peer, cf, TRNSPRT_QUIC);
   if(result)
     return result;
@@ -2512,8 +2522,7 @@ CURLcode Curl_cf_ngtcp2_create(struct Curl_cfilter **pcf,
     result = CURLE_OUT_OF_MEMORY;
     goto out;
   }
-  ctx->qlogfd = -1;
-  cf_ngtcp2_ctx_clear(ctx);
+  cf_ngtcp2_ctx_init(ctx);
 
   result = Curl_cf_create(&cf, &Curl_cft_http3, ctx);
   if(result)
@@ -2534,7 +2543,7 @@ out:
     if(udp_cf)
       Curl_conn_cf_discard_sub(cf, udp_cf, data, TRUE);
     Curl_safefree(cf);
-    Curl_safefree(ctx);
+    cf_ngtcp2_ctx_free(ctx);
   }
   return result;
 }
index 46565bee4283fa874244cc399554ba16ee356ab0..31469c8d4315a2466d16370e7e2877a957e6cf34 100644 (file)
@@ -293,6 +293,7 @@ struct cf_osslq_ctx {
   struct Curl_hash streams;          /* hash `data->id` to `h3_stream_ctx` */
   size_t max_stream_window;          /* max flow window for one stream */
   uint64_t max_idle_ms;              /* max idle time for QUIC connection */
+  BIT(initialized);
   BIT(got_first_byte);               /* if first byte was received */
   BIT(x509_store_setup);             /* if x509 store has been set up */
   BIT(protocol_shutdown);            /* QUIC connection is shut down */
@@ -300,19 +301,35 @@ struct cf_osslq_ctx {
   BIT(need_send);                    /* QUIC connection needs to send */
 };
 
-static void cf_osslq_ctx_clear(struct cf_osslq_ctx *ctx)
+static void h3_stream_hash_free(void *stream);
+
+static void cf_osslq_ctx_init(struct cf_osslq_ctx *ctx)
+{
+  DEBUGASSERT(!ctx->initialized);
+  Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE,
+                  H3_STREAM_POOL_SPARES);
+  Curl_hash_offt_init(&ctx->streams, 63, h3_stream_hash_free);
+  ctx->initialized = TRUE;
+}
+
+static void cf_osslq_ctx_free(struct cf_osslq_ctx *ctx)
+{
+  if(ctx && ctx->initialized) {
+    Curl_bufcp_free(&ctx->stream_bufcp);
+    Curl_hash_clean(&ctx->streams);
+    Curl_hash_destroy(&ctx->streams);
+    Curl_ssl_peer_cleanup(&ctx->peer);
+  }
+  free(ctx);
+}
+
+static void cf_osslq_ctx_close(struct cf_osslq_ctx *ctx)
 {
   struct cf_call_data save = ctx->call_data;
 
   cf_osslq_h3conn_cleanup(&ctx->h3);
   Curl_vquic_tls_cleanup(&ctx->tls);
   vquic_ctx_free(&ctx->q);
-  Curl_bufcp_free(&ctx->stream_bufcp);
-  Curl_hash_clean(&ctx->streams);
-  Curl_hash_destroy(&ctx->streams);
-  Curl_ssl_peer_cleanup(&ctx->peer);
-
-  memset(ctx, 0, sizeof(*ctx));
   ctx->call_data = save;
 }
 
@@ -401,7 +418,7 @@ static void cf_osslq_close(struct Curl_cfilter *cf, struct Curl_easy *data)
                       (SSL_SHUTDOWN_FLAG_NO_BLOCK | SSL_SHUTDOWN_FLAG_RAPID),
                       NULL, 0);
     }
-    cf_osslq_ctx_clear(ctx);
+    cf_osslq_ctx_close(ctx);
   }
 
   cf->connected = FALSE;
@@ -417,8 +434,7 @@ static void cf_osslq_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
   CURL_TRC_CF(data, cf, "destroy");
   if(ctx) {
     CURL_TRC_CF(data, cf, "cf_osslq_destroy()");
-    cf_osslq_ctx_clear(ctx);
-    free(ctx);
+    cf_osslq_ctx_free(ctx);
   }
   cf->ctx = NULL;
   /* No CF_DATA_RESTORE(cf, save) possible */
@@ -1148,9 +1164,7 @@ static CURLcode cf_osslq_ctx_start(struct Curl_cfilter *cf,
   BIO *bio = NULL;
   BIO_ADDR *baddr = NULL;
 
-  Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE,
-                  H3_STREAM_POOL_SPARES);
-  Curl_hash_offt_init(&ctx->streams, 63, h3_stream_hash_free);
+  DEBUGASSERT(ctx->initialized);
   result = Curl_ssl_peer_init(&ctx->peer, cf, TRNSPRT_QUIC);
   if(result)
     goto out;
@@ -2330,7 +2344,7 @@ CURLcode Curl_cf_osslq_create(struct Curl_cfilter **pcf,
     result = CURLE_OUT_OF_MEMORY;
     goto out;
   }
-  cf_osslq_ctx_clear(ctx);
+  cf_osslq_ctx_init(ctx);
 
   result = Curl_cf_create(&cf, &Curl_cft_http3, ctx);
   if(result)
@@ -2351,7 +2365,7 @@ out:
     if(udp_cf)
       Curl_conn_cf_discard_sub(cf, udp_cf, data, TRUE);
     Curl_safefree(cf);
-    Curl_safefree(ctx);
+    cf_osslq_ctx_free(ctx);
   }
   return result;
 }
index fafbb03ee64e0c49e18315e41dbcb23d9a2f09a1..79963ca388ffef7c6a5f6b60a50db476f2262c23 100644 (file)
@@ -100,12 +100,15 @@ struct cf_quiche_ctx {
   struct bufc_pool stream_bufcp;     /* chunk pool for streams */
   struct Curl_hash streams;          /* hash `data->id` to `stream_ctx` */
   curl_off_t data_recvd;
+  BIT(initialized);
   BIT(goaway);                       /* got GOAWAY from server */
   BIT(x509_store_setup);             /* if x509 store has been set up */
   BIT(shutdown_started);             /* queued shutdown packets */
 };
 
 #ifdef DEBUG_QUICHE
+/* initialize debug log callback only once */
+static int debug_log_init = 0;
 static void quiche_debug_log(const char *line, void *argp)
 {
   (void)argp;
@@ -113,17 +116,27 @@ static void quiche_debug_log(const char *line, void *argp)
 }
 #endif
 
-static void cf_quiche_ctx_clear(struct cf_quiche_ctx *ctx)
+static void h3_stream_hash_free(void *stream);
+
+static void cf_quiche_ctx_init(struct cf_quiche_ctx *ctx)
 {
-  if(ctx) {
-    if(ctx->h3c)
-      quiche_h3_conn_free(ctx->h3c);
-    if(ctx->h3config)
-      quiche_h3_config_free(ctx->h3config);
-    if(ctx->qconn)
-      quiche_conn_free(ctx->qconn);
-    if(ctx->cfg)
-      quiche_config_free(ctx->cfg);
+  DEBUGASSERT(!ctx->initialized);
+#ifdef DEBUG_QUICHE
+  if(!debug_log_init) {
+    quiche_enable_debug_logging(quiche_debug_log, NULL);
+    debug_log_init = 1;
+  }
+#endif
+  Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE,
+                  H3_STREAM_POOL_SPARES);
+  Curl_hash_offt_init(&ctx->streams, 63, h3_stream_hash_free);
+  ctx->data_recvd = 0;
+  ctx->initialized = TRUE;
+}
+
+static void cf_quiche_ctx_free(struct cf_quiche_ctx *ctx)
+{
+  if(ctx && ctx->initialized) {
     /* quiche just freed it */
     ctx->tls.ossl.ssl = NULL;
     Curl_vquic_tls_cleanup(&ctx->tls);
@@ -132,9 +145,20 @@ static void cf_quiche_ctx_clear(struct cf_quiche_ctx *ctx)
     Curl_bufcp_free(&ctx->stream_bufcp);
     Curl_hash_clean(&ctx->streams);
     Curl_hash_destroy(&ctx->streams);
-
-    memset(ctx, 0, sizeof(*ctx));
   }
+  free(ctx);
+}
+
+static void cf_quiche_ctx_close(struct cf_quiche_ctx *ctx)
+{
+  if(ctx->h3c)
+    quiche_h3_conn_free(ctx->h3c);
+  if(ctx->h3config)
+    quiche_h3_config_free(ctx->h3config);
+  if(ctx->qconn)
+    quiche_conn_free(ctx->qconn);
+  if(ctx->cfg)
+    quiche_config_free(ctx->cfg);
 }
 
 static CURLcode cf_flush_egress(struct Curl_cfilter *cf,
@@ -1240,8 +1264,8 @@ static CURLcode cf_quiche_data_event(struct Curl_cfilter *cf,
   return result;
 }
 
-static CURLcode cf_connect_start(struct Curl_cfilter *cf,
-                                 struct Curl_easy *data)
+static CURLcode cf_quiche_ctx_open(struct Curl_cfilter *cf,
+                                   struct Curl_easy *data)
 {
   struct cf_quiche_ctx *ctx = cf->ctx;
   int rv;
@@ -1249,19 +1273,7 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf,
   const struct Curl_sockaddr_ex *sockaddr;
 
   DEBUGASSERT(ctx->q.sockfd != CURL_SOCKET_BAD);
-
-#ifdef DEBUG_QUICHE
-  /* initialize debug log callback only once */
-  static int debug_log_init = 0;
-  if(!debug_log_init) {
-    quiche_enable_debug_logging(quiche_debug_log, NULL);
-    debug_log_init = 1;
-  }
-#endif
-  Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE,
-                  H3_STREAM_POOL_SPARES);
-  Curl_hash_offt_init(&ctx->streams, 63, h3_stream_hash_free);
-  ctx->data_recvd = 0;
+  DEBUGASSERT(ctx->initialized);
 
   result = vquic_ctx_init(&ctx->q);
   if(result)
@@ -1403,7 +1415,7 @@ static CURLcode cf_quiche_connect(struct Curl_cfilter *cf,
   }
 
   if(!ctx->qconn) {
-    result = cf_connect_start(cf, data);
+    result = cf_quiche_ctx_open(cf, data);
     if(result)
       goto out;
     ctx->started_at = ctx->q.last_op;
@@ -1515,23 +1527,20 @@ out:
 
 static void cf_quiche_close(struct Curl_cfilter *cf, struct Curl_easy *data)
 {
-  struct cf_quiche_ctx *ctx = cf->ctx;
-
-  if(ctx) {
+  if(cf->ctx) {
     bool done;
     (void)cf_quiche_shutdown(cf, data, &done);
-    cf_quiche_ctx_clear(ctx);
+    cf_quiche_ctx_close(cf->ctx);
   }
 }
 
 static void cf_quiche_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
 {
-  struct cf_quiche_ctx *ctx = cf->ctx;
-
   (void)data;
-  cf_quiche_ctx_clear(ctx);
-  free(ctx);
-  cf->ctx = NULL;
+  if(cf->ctx) {
+    cf_quiche_ctx_free(cf->ctx);
+    cf->ctx = NULL;
+  }
 }
 
 static CURLcode cf_quiche_query(struct Curl_cfilter *cf,
@@ -1652,6 +1661,7 @@ CURLcode Curl_cf_quiche_create(struct Curl_cfilter **pcf,
     result = CURLE_OUT_OF_MEMORY;
     goto out;
   }
+  cf_quiche_ctx_init(ctx);
 
   result = Curl_cf_create(&cf, &Curl_cft_http3, ctx);
   if(result)
@@ -1671,7 +1681,7 @@ out:
     if(udp_cf)
       Curl_conn_cf_discard_sub(cf, udp_cf, data, TRUE);
     Curl_safefree(cf);
-    Curl_safefree(ctx);
+    cf_quiche_ctx_free(ctx);
   }
 
   return result;