From: Daniel Stenberg Date: Sat, 27 Dec 2025 09:19:08 +0000 (+0100) Subject: curl_quiche: refuse headers with CR, LF or null bytes X-Git-Tag: curl-8_18_0~81 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6842d4ec4d1be8d54447c4b096dc402d42dd0287;p=thirdparty%2Fcurl.git curl_quiche: refuse headers with CR, LF or null bytes Also renamed the struct field to 'h1hdr' from 'scratch' to better say what its purpose is. Closes #20101 --- diff --git a/lib/vquic/curl_quiche.c b/lib/vquic/curl_quiche.c index 5c431110a5..e4140e776d 100644 --- a/lib/vquic/curl_quiche.c +++ b/lib/vquic/curl_quiche.c @@ -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) {