]> git.feebdaed.xyz Git - 0xmirror/curl.git/commitdiff
curl_quiche: refuse headers with CR, LF or null bytes master
authorDaniel Stenberg <daniel@haxx.se>
Sat, 27 Dec 2025 09:19:08 +0000 (10:19 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Sat, 27 Dec 2025 15:27:11 +0000 (16:27 +0100)
Also renamed the struct field to 'h1hdr' from 'scratch' to better say
what its purpose is.

Closes #20101

lib/vquic/curl_quiche.c

index 5c431110a5f08513476c24cfdb3a1044f0f9712c..e4140e776d5ec48443ab76367ca9d296149cc0f1 100644 (file)
@@ -88,7 +88,7 @@ struct cf_quiche_ctx {
   struct curltime started_at;        /* time the current attempt started */
   struct curltime handshake_at;      /* time connect handshake finished */
   struct uint_hash streams;          /* hash `data->mid` to `stream_ctx` */
-  struct dynbuf scratch;             /* temp buffer for header construction */
+  struct dynbuf h1hdr;               /* temp buffer for header construction */
   struct bufq writebuf;              /* temp buffer for writing bodies */
   curl_off_t data_recvd;
   BIT(initialized);
@@ -118,7 +118,7 @@ static void cf_quiche_ctx_init(struct cf_quiche_ctx *ctx)
     debug_log_init = 1;
   }
 #endif
-  curlx_dyn_init(&ctx->scratch, CURL_MAX_HTTP_HEADER);
+  curlx_dyn_init(&ctx->h1hdr, CURL_MAX_HTTP_HEADER);
   Curl_uint32_hash_init(&ctx->streams, 63, h3_stream_hash_free);
   Curl_bufq_init2(&ctx->writebuf, H3_STREAM_CHUNK_SIZE, H3_STREAM_RECV_CHUNKS,
                   BUFQ_OPT_SOFT_LIMIT);
@@ -135,7 +135,7 @@ static void cf_quiche_ctx_free(struct cf_quiche_ctx *ctx)
     Curl_ssl_peer_cleanup(&ctx->peer);
     vquic_ctx_free(&ctx->q);
     Curl_uint32_hash_destroy(&ctx->streams);
-    curlx_dyn_free(&ctx->scratch);
+    curlx_dyn_free(&ctx->h1hdr);
     Curl_bufq_free(&ctx->writebuf);
   }
   curlx_free(ctx);
@@ -351,6 +351,19 @@ struct cb_ctx {
   struct h3_stream_ctx *stream;
 };
 
+static bool is_valid_h3_header(const uint8_t *hdr, size_t hlen)
+{
+  while(hlen--) {
+    switch(*hdr++) {
+    case '\n':
+    case '\r':
+    case '\0':
+      return FALSE;
+    }
+  }
+  return TRUE;
+}
+
 static int cb_each_header(uint8_t *name, size_t name_len,
                           uint8_t *value, size_t value_len,
                           void *argp)
@@ -360,46 +373,50 @@ static int cb_each_header(uint8_t *name, size_t name_len,
   struct Curl_easy *data = x->data;
   struct h3_stream_ctx *stream = x->stream;
   struct cf_quiche_ctx *ctx = cf->ctx;
-  CURLcode result;
+  CURLcode result = CURLE_OK;
 
   if(!stream || stream->xfer_result)
     return 1; /* abort iteration */
 
-  if((name_len == 7) && !strncmp(HTTP_PSEUDO_STATUS, (char *)name, 7)) {
-    curlx_dyn_reset(&ctx->scratch);
+  if((name_len == 7) && !strncmp(HTTP_PSEUDO_STATUS, (char *)name, 7) &&
+     is_valid_h3_header(value, value_len)) {
+    curlx_dyn_reset(&ctx->h1hdr);
     result = Curl_http_decode_status(&stream->status_code,
                                      (const char *)value, value_len);
     if(!result)
-      result = curlx_dyn_addn(&ctx->scratch, STRCONST("HTTP/3 "));
+      result = curlx_dyn_addn(&ctx->h1hdr, STRCONST("HTTP/3 "));
     if(!result)
-      result = curlx_dyn_addn(&ctx->scratch,
-                              (const char *)value, value_len);
+      result = curlx_dyn_addn(&ctx->h1hdr, (const char *)value, value_len);
     if(!result)
-      result = curlx_dyn_addn(&ctx->scratch, STRCONST(" \r\n"));
+      result = curlx_dyn_addn(&ctx->h1hdr, STRCONST(" \r\n"));
     if(!result)
-      cf_quiche_write_hd(cf, data, stream, curlx_dyn_ptr(&ctx->scratch),
-                         curlx_dyn_len(&ctx->scratch), FALSE);
+      cf_quiche_write_hd(cf, data, stream, curlx_dyn_ptr(&ctx->h1hdr),
+                         curlx_dyn_len(&ctx->h1hdr), FALSE);
     CURL_TRC_CF(data, cf, "[%" PRId64 "] status: %s",
-                stream->id, curlx_dyn_ptr(&ctx->scratch));
+                stream->id, curlx_dyn_ptr(&ctx->h1hdr));
   }
   else {
-    /* store as an HTTP1-style header */
-    CURL_TRC_CF(data, cf, "[%" PRId64 "] header: %.*s: %.*s",
-                stream->id, (int)name_len, name,
-                (int)value_len, value);
-    curlx_dyn_reset(&ctx->scratch);
-    result = curlx_dyn_addn(&ctx->scratch,
-                            (const char *)name, name_len);
-    if(!result)
-      result = curlx_dyn_addn(&ctx->scratch, STRCONST(": "));
-    if(!result)
-      result = curlx_dyn_addn(&ctx->scratch,
-                              (const char *)value, value_len);
-    if(!result)
-      result = curlx_dyn_addn(&ctx->scratch, STRCONST("\r\n"));
-    if(!result)
-      cf_quiche_write_hd(cf, data, stream, curlx_dyn_ptr(&ctx->scratch),
-                         curlx_dyn_len(&ctx->scratch), FALSE);
+    if(is_valid_h3_header(value, value_len) &&
+       is_valid_h3_header(name, name_len)) {
+      /* store as an HTTP1-style header */
+      CURL_TRC_CF(data, cf, "[%" PRId64 "] header: %.*s: %.*s",
+                  stream->id, (int)name_len, name,
+                  (int)value_len, value);
+      curlx_dyn_reset(&ctx->h1hdr);
+      result = curlx_dyn_addn(&ctx->h1hdr, (const char *)name, name_len);
+      if(!result)
+        result = curlx_dyn_addn(&ctx->h1hdr, STRCONST(": "));
+      if(!result)
+        result = curlx_dyn_addn(&ctx->h1hdr, (const char *)value, value_len);
+      if(!result)
+        result = curlx_dyn_addn(&ctx->h1hdr, STRCONST("\r\n"));
+      if(!result)
+        cf_quiche_write_hd(cf, data, stream, curlx_dyn_ptr(&ctx->h1hdr),
+                           curlx_dyn_len(&ctx->h1hdr), FALSE);
+    }
+    else
+      CURL_TRC_CF(x->data, x->cf, "[%" PRIu64 "] ignore %zu bytes bad header",
+                  stream->id, value_len + name_len);
   }
 
   if(result) {