Bug
https_make_request (thirdparty/vschannel/vschannel.c), the core of vschannel's one-shot HTTP/1.1 client (vschannel_h1_do/vschannel_h1_on_open, used both when HTTP/2 is disabled and as the ALPN-negotiation-failed fallback), reads the response until the peer closes the connection — it never parses Content-Length (or chunked encoding) to know the response is already complete:
// Read data from server until done.
while(TRUE){
if(0 == cbIoBuffer || scRet == SEC_E_INCOMPLETE_MESSAGE) {
cbData = recv(tls_ctx->socket, pbIoBuffer + cbIoBuffer, cbIoBufferLength - cbIoBuffer, 0);
...
}
...
}
This works fine against a server that closes the connection immediately after sending its response (or sends Connection: close and actually closes), but HTTP/1.1 keep-alive is the default — a compliant server is free to leave the connection open waiting for a pipelined next request. Against such a server, the client blocks in recv() until the peer's own idle-read timeout eventually forces it to give up and close the socket — there is no bound on the client's own side at all.
How this was found
While adding HTTP/2 connection pooling for the Windows SChannel backend (#27702), a new automated test exercised vschannel_h1_on_open (the ALPN-negotiation-failed fallback) against a realistic loopback HTTP/1.1 server that keeps the connection open after responding (ordinary keep-alive behavior). The request took ~41 seconds to complete instead of being near-instant, tracking almost exactly to the test server's own configured idle-read timeout before it gave up and closed the connection.
This had never been caught before because every existing caller of this one-shot path either talks to a real-world server that closes promptly, or is gated behind -d network (opt-in, manual). No prior automated test exercised it against a keep-alive-capable peer.
Impact
Any https:// request on native Windows (SChannel backend) that falls into the one-shot HTTP/1.1 path — either because enable_http2 is false, or because the origin doesn't select h2 via ALPN — against a server that keeps the connection open after responding will stall for as long as that server's own idle timeout takes to fire, rather than completing promptly.
Suggested fix
https_make_request's read loop needs to track response completion itself: parse the response headers as they arrive for Content-Length (or detect chunked Transfer-Encoding and parse chunk boundaries), and stop reading once the declared body is fully received, rather than relying solely on the peer closing the connection. This is a non-trivial change to the C read loop (needs incremental HTTP header parsing at the byte level) and is scoped separately from #27702, which only adds pooling on top of the existing (buggy) one-shot client.
Bug
https_make_request(thirdparty/vschannel/vschannel.c), the core of vschannel's one-shot HTTP/1.1 client (vschannel_h1_do/vschannel_h1_on_open, used both when HTTP/2 is disabled and as the ALPN-negotiation-failed fallback), reads the response until the peer closes the connection — it never parsesContent-Length(or chunked encoding) to know the response is already complete:This works fine against a server that closes the connection immediately after sending its response (or sends
Connection: closeand actually closes), but HTTP/1.1 keep-alive is the default — a compliant server is free to leave the connection open waiting for a pipelined next request. Against such a server, the client blocks inrecv()until the peer's own idle-read timeout eventually forces it to give up and close the socket — there is no bound on the client's own side at all.How this was found
While adding HTTP/2 connection pooling for the Windows SChannel backend (#27702), a new automated test exercised
vschannel_h1_on_open(the ALPN-negotiation-failed fallback) against a realistic loopback HTTP/1.1 server that keeps the connection open after responding (ordinary keep-alive behavior). The request took ~41 seconds to complete instead of being near-instant, tracking almost exactly to the test server's own configured idle-read timeout before it gave up and closed the connection.This had never been caught before because every existing caller of this one-shot path either talks to a real-world server that closes promptly, or is gated behind
-d network(opt-in, manual). No prior automated test exercised it against a keep-alive-capable peer.Impact
Any
https://request on native Windows (SChannel backend) that falls into the one-shot HTTP/1.1 path — either becauseenable_http2isfalse, or because the origin doesn't selecth2via ALPN — against a server that keeps the connection open after responding will stall for as long as that server's own idle timeout takes to fire, rather than completing promptly.Suggested fix
https_make_request's read loop needs to track response completion itself: parse the response headers as they arrive forContent-Length(or detect chunkedTransfer-Encodingand parse chunk boundaries), and stop reading once the declared body is fully received, rather than relying solely on the peer closing the connection. This is a non-trivial change to the C read loop (needs incremental HTTP header parsing at the byte level) and is scoped separately from #27702, which only adds pooling on top of the existing (buggy) one-shot client.