curl_quiche: refuse headers with CR, LF or null bytes
No binary (build failed)
—
(log yok)
| @@ -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) { | |