New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Windows nghttp2 + schannel + tls 1.3 error: Received HTTP/0.9 when not allowed #9451
Comments
if you add If the first fixes the problem, then 8beff43 certainly sounds like the culprit... /cc @wyattoday @gvollant |
We don't use HTTP2 or HTTP3 (and thus no ALPN either) in our products, and thus this got 0 internal testing from our side of things. I relied on CI tests to stress that functionality. I haven't reproduced what @ehuss has seen (I don't have the time, currently). But I will if no one else picks up this thread. Based on a quick glance of where things could go wrong (and where I would start poking around) is in My guess is that when this original code was written and tested everything worked fine without the loop, and now that the black-box internals of SChannel have been modified to add TLS 1.3, the loop is now necessary. It's just my first guess based on a similar finding (SChannel requesting more data at an "unexpected time" when it previously didn't) when adding TLS 1.3 support in the first place. Also, can someone from @microsoft chime in? @Andrei-Popov or someone else working on networking and/or SChannel code internally at MS? Microsoft uses curl with SChannel in Visual Studio (via Git) and in Windows itself. If you don't provide money or developers, then at least a peek inside the SChannel would be nice so this can be written in a robust way. |
The initial handshake data is sent and then it moves on to step 2, where it receives whatever the initial reply is and continues to call InitializeSecurityContext until the handshake ie "security context" is complete. This is the same for all vtls, step 2 at least finishes the handshake and there may or may not be a step 3 for post-handshake logic. Sometimes that is in step2... For example openssl handles the ALPN result at the end of step2 and schannel handles it in step3. I would think the schannel way is more correct, but I wonder what happens if it's moved to the end of step2. Lines 1702 to 1738 in 93d0928
I've started to set up a Windows 11 VM from ISO, and so far I've followed these directions and it at least boots. I will work on configuring it soon. If anyone is running Win 11 in VM anything I should know? |
Confirmed that adding either flag makes the problem go away. |
What is the verbose output |
|
By the time schannel_recv is renegotiating the connection, libcurl has already decided on a protocol and it is too late for the server to select a protocol via ALPN. Ref: curl#9451 Closes #xxxx
That is interesting. I wonder whether or not schannel sends ALPN during renegotiation. In step1 we create the security context handle with the ALPN extension and then for further calls with the same handle to InitializeSecurityContext (like in step2) we do not set the extension. AFAICT this is correct @JDepooter The code flow is basically (but not exactly) this new tls transfer -> common -> step1 -> step2 -> step3 -> do transfer schannel_send/recv/etc -> renegotiation in recv (SEC_I_RENEGOTIATE received -> reset connection state to step2 -> common -> step2 -> step3 -> common returns when handshake complete -> recv attempts to decrypt any data and then returns) -> do transfer schannel_send/recv/etc and so on schannel_recv renegotiate logic: Lines 2284 to 2305 in 93d0928
Regardless of where the alpn result logic is handled (for schannel it's step3) conn->alpn isn't changed when there is no ALPN result: Lines 1702 to 1738 in 93d0928
So in other words if ALPN returns h2 the first time (the initial handshake) but no result after that (!=SecApplicationProtocolNegotiationStatus_Success) then conn->alpn should stay CURL_HTTP_VERSION_2. I don't think any of this explains what is causing the bug, maybe it's a total red herring, but it's still interesting. I've filed #9463 to ban server ALPN selection during recv renegotiation. |
I've been poking around. I'm not at all familiar with curl's internals or schannel, so I'm not sure I can provide much insight. Here's the debug output when it fails to get the initial HTTP/2 header: CLICK ME
src\Debug\curl -v -O https://github.com/curl/curl/releases/download/curl-7_85_0/curl-7.85.0.zip * STATE: INIT => CONNECT handle 0x2156f290428; line 1881 (connection #-5000) * Added connection 0. The cache now contains 1 members * STATE: CONNECT => RESOLVING handle 0x2156f290428; line 1927 (connection #0) % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* family0 == v4, family1 == v6 * Trying 192.30.255.112:443... * STATE: RESOLVING => CONNECTING handle 0x2156f290428; line 2011 (connection #0) * Connected to github.com (192.30.255.112) port 443 (#0) * STATE: CONNECTING => PROTOCONNECT handle 0x2156f290428; line 2076 (connection #0) * schannel: SSL/TLS connection with github.com port 443 (step 1/3) * Didn't find Session ID in cache for host HTTPS://github.com:443 * schannel: checking server certificate revocation * schannel: disabled automatic use of client certificate * ALPN: offers h2 * ALPN: offers http/1.1 * schannel: sending initial handshake data: sending 292 bytes. * schannel: sent initial handshake data: sent 292 bytes * schannel: SSL/TLS connection with github.com port 443 (step 2/3) * schannel: failed to receive handshake, need more data * STATE: PROTOCONNECT => PROTOCONNECTING handle 0x2156f290428; line 2094 (connection #0) * schannel: SSL/TLS connection with github.com port 443 (step 2/3) * schannel: encrypted data got 2814 * schannel: encrypted data buffer: offset 2814 length 4096 * schannel: sending next handshake data: sending 64 bytes. * schannel: SSL/TLS handshake complete * schannel: SSL/TLS connection with github.com port 443 (step 3/3) * ALPN: server accepted h2 * Didn't find Session ID in cache for host HTTPS://github.com:443 * Added Session ID to cache for HTTPS://github.com:443 [server] * schannel: stored credential handle in session cache * STATE: PROTOCONNECTING => DO handle 0x2156f290428; line 2113 (connection #0) 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Using HTTP2, server supports multiplexing * Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0 * h2h3 [:method: GET] * h2h3 [:path: /curl/curl/releases/download/curl-7_85_0/curl-7.85.0.zip] * h2h3 [:scheme: https] * h2h3 [:authority: github.com] * h2h3 [user-agent: curl/7.86.0-DEV] * h2h3 [accept: */*] * Using Stream ID: 1 (easy handle 0x2156f290428) > GET /curl/curl/releases/download/curl-7_85_0/curl-7.85.0.zip HTTP/2 > Host: github.com > user-agent: curl/7.86.0-DEV > accept: */* > * STATE: DO => DID handle 0x2156f290428; line 2193 (connection #0) * multi changed, check CONNECT_PEND queue * STATE: DID => PERFORMING handle 0x2156f290428; line 2312 (connection #0) * schannel: client wants to read 32768 bytes * schannel: encdata_buffer resized 33792 * schannel: encrypted data buffer: offset 0 length 33792 * schannel: encrypted data got 216 * schannel: encrypted data buffer: offset 216 length 33792 * schannel: decrypted data length: 0 * schannel: decrypted data added: 0 * schannel: decrypted cached: offset 0 length 32768 * schannel: encrypted data length: 216 * schannel: encrypted cached: offset 216 length 33792 * schannel: remote party requests renegotiation * schannel: renegotiating SSL/TLS connection * schannel: SSL/TLS connection with github.com port 443 (step 2/3) * schannel: encrypted data buffer: offset 216 length 33792 * schannel: encrypted data length: 137 * schannel: SSL/TLS handshake complete * schannel: SSL/TLS connection with github.com port 443 (step 3/3) * ALPN: server did not agree on a protocol. Uses default. * Found Session ID in cache for host HTTPS://github.com:443 * schannel: SSL/TLS connection renegotiated * schannel: decrypted data length: 0 * schannel: decrypted data added: 0 * schannel: decrypted cached: offset 0 length 32768 * schannel: encrypted data length: 137 * schannel: encrypted cached: offset 137 length 33792 * schannel: remote party requests renegotiation * schannel: renegotiating SSL/TLS connection * schannel: SSL/TLS connection with github.com port 443 (step 2/3) * schannel: encrypted data buffer: offset 137 length 33792 * schannel: encrypted data length: 58 * schannel: SSL/TLS handshake complete * schannel: SSL/TLS connection with github.com port 443 (step 3/3) * ALPN: server did not agree on a protocol. Uses default. * Found Session ID in cache for host HTTPS://github.com:443 * schannel: SSL/TLS connection renegotiated * schannel: decrypted data length: 36 * schannel: decrypted data added: 36 * schannel: decrypted cached: offset 36 length 32768 * schannel: encrypted data buffer: offset 0 length 33792 * schannel: decrypted data buffer: offset 36 length 32768 * schannel: schannel_recv cleanup * schannel: decrypted data returned 36 * schannel: decrypted data buffer: offset 0 length 32768 * schannel: client wants to read 102400 bytes * schannel: encdata_buffer resized 103424 * schannel: encrypted data buffer: offset 0 length 103424 * schannel: encrypted data got 2783 * schannel: encrypted data buffer: offset 2783 length 103424 * schannel: decrypted data length: 1370 * schannel: decrypted data added: 1370 * schannel: decrypted cached: offset 1370 length 102400 * schannel: encrypted data length: 1391 * schannel: encrypted cached: offset 1391 length 103424 * schannel: decrypted data length: 1369 * schannel: decrypted data added: 1369 * schannel: decrypted cached: offset 2739 length 102400 * schannel: encrypted data buffer: offset 0 length 103424 * schannel: decrypted data buffer: offset 2739 length 102400 * schannel: schannel_recv cleanup * schannel: decrypted data returned 2739 * schannel: decrypted data buffer: offset 0 length 102400 * first header: 2 * Received HTTP/0.9 when not allowed * multi_done: status: 1 prem: 1 done: 0 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 * Connection #0 to host github.com left intact * Expire cleared (transfer 0x2156f290428) curl: (1) Received HTTP/0.9 when not allowed The first two bytes of Here's what it looks like when it is able to get past reading the headers: CLICK ME
\Debug\curl -v -O https://github.com/curl/curl/releases/download/curl-7_85_0/curl-7.85.0.zip * STATE: INIT => CONNECT handle 0x26127bf1e78; line 1881 (connection #-5000) * Added connection 0. The cache now contains 1 members * STATE: CONNECT => RESOLVING handle 0x26127bf1e78; line 1927 (connection #0) % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* family0 == v4, family1 == v6 * Trying 192.30.255.112:443... * STATE: RESOLVING => CONNECTING handle 0x26127bf1e78; line 2011 (connection #0) * Connected to github.com (192.30.255.112) port 443 (#0) * STATE: CONNECTING => PROTOCONNECT handle 0x26127bf1e78; line 2076 (connection #0) * schannel: SSL/TLS connection with github.com port 443 (step 1/3) * Didn't find Session ID in cache for host HTTPS://github.com:443 * schannel: checking server certificate revocation * schannel: disabled automatic use of client certificate * ALPN: offers h2 * ALPN: offers http/1.1 * schannel: sending initial handshake data: sending 292 bytes. * schannel: sent initial handshake data: sent 292 bytes * schannel: SSL/TLS connection with github.com port 443 (step 2/3) * schannel: failed to receive handshake, need more data * STATE: PROTOCONNECT => PROTOCONNECTING handle 0x26127bf1e78; line 2094 (connection #0) * schannel: SSL/TLS connection with github.com port 443 (step 2/3) * schannel: encrypted data got 2814 * schannel: encrypted data buffer: offset 2814 length 4096 * schannel: sending next handshake data: sending 64 bytes. * schannel: SSL/TLS handshake complete * schannel: SSL/TLS connection with github.com port 443 (step 3/3) * ALPN: server accepted h2 * Didn't find Session ID in cache for host HTTPS://github.com:443 * Added Session ID to cache for HTTPS://github.com:443 [server] * schannel: stored credential handle in session cache * STATE: PROTOCONNECTING => DO handle 0x26127bf1e78; line 2113 (connection #0) * Using HTTP2, server supports multiplexing * Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0 * h2h3 [:method: GET] * h2h3 [:path: /curl/curl/releases/download/curl-7_85_0/curl-7.85.0.zip] * h2h3 [:scheme: https] * h2h3 [:authority: github.com] * h2h3 [user-agent: curl/7.86.0-DEV] * h2h3 [accept: */*] * Using Stream ID: 1 (easy handle 0x26127bf1e78) > GET /curl/curl/releases/download/curl-7_85_0/curl-7.85.0.zip HTTP/2 > Host: github.com > user-agent: curl/7.86.0-DEV > accept: */* > * STATE: DO => DID handle 0x26127bf1e78; line 2193 (connection #0) * multi changed, check CONNECT_PEND queue * STATE: DID => PERFORMING handle 0x26127bf1e78; line 2312 (connection #0) * schannel: client wants to read 32768 bytes * schannel: encdata_buffer resized 33792 * schannel: encrypted data buffer: offset 0 length 33792 * schannel: encrypted data got 2999 * schannel: encrypted data buffer: offset 2999 length 33792 * schannel: decrypted data length: 0 * schannel: decrypted data added: 0 * schannel: decrypted cached: offset 0 length 32768 * schannel: encrypted data length: 2999 * schannel: encrypted cached: offset 2999 length 33792 * schannel: remote party requests renegotiation * schannel: renegotiating SSL/TLS connection * schannel: SSL/TLS connection with github.com port 443 (step 2/3) * schannel: encrypted data buffer: offset 2999 length 33792 * schannel: encrypted data length: 2920 * schannel: SSL/TLS handshake complete * schannel: SSL/TLS connection with github.com port 443 (step 3/3) * ALPN: server did not agree on a protocol. Uses default. * Found Session ID in cache for host HTTPS://github.com:443 * schannel: SSL/TLS connection renegotiated * schannel: decrypted data length: 0 * schannel: decrypted data added: 0 * schannel: decrypted cached: offset 0 length 32768 * schannel: encrypted data length: 2920 * schannel: encrypted cached: offset 2920 length 33792 * schannel: remote party requests renegotiation * schannel: renegotiating SSL/TLS connection * schannel: SSL/TLS connection with github.com port 443 (step 2/3) * schannel: encrypted data buffer: offset 2920 length 33792 * schannel: encrypted data length: 2841 * schannel: SSL/TLS handshake complete * schannel: SSL/TLS connection with github.com port 443 (step 3/3) * ALPN: server did not agree on a protocol. Uses default. * Found Session ID in cache for host HTTPS://github.com:443 * schannel: SSL/TLS connection renegotiated * schannel: decrypted data length: 36 * schannel: decrypted data added: 36 * schannel: decrypted cached: offset 36 length 32768 * schannel: encrypted data length: 2783 * schannel: encrypted cached: offset 2783 length 33792 * schannel: decrypted data length: 1370 * schannel: decrypted data added: 1370 * schannel: decrypted cached: offset 1406 length 32768 * schannel: encrypted data length: 1391 * schannel: encrypted cached: offset 1391 length 33792 * schannel: decrypted data length: 1369 * schannel: decrypted data added: 1369 * schannel: decrypted cached: offset 2775 length 32768 * schannel: encrypted data buffer: offset 0 length 33792 * schannel: decrypted data buffer: offset 2775 length 32768 * schannel: schannel_recv cleanup * schannel: decrypted data returned 2775 * schannel: decrypted data buffer: offset 0 length 32768 * first header: 13 HTTP/2 302 * HTTP/2 found, allow multiplexing < HTTP/2 302 < server: GitHub.com < date: Fri, 09 Sep 2022 22:04:12 GMT < content-type: text/html; charset=utf-8 < vary: X-PJAX, X-PJAX-Container, Turbo-Visit, Turbo-Frame, Accept-Encoding, Accept, X-Requested-With < permissions-policy: interest-cohort=() < location: https://objects.githubusercontent.com/github-production-release-asset-2e65be/569041/abdf82e3-624c-4307-8d41-a9bf109bbac5?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20220909%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20220909T220412Z&X-Amz-Expires=300&X-Amz-Signature=2cd5f85c79435cd09820025f48c192b776f03175834bc246f611cabd94814bc8&X-Amz-SignedHeaders=host&actor_id=0&key_id=0&repo_id=569041&response-content-disposition=attachment%3B%20filename%3Dcurl-7.85.0.zip&response-content-type=application%2Foctet-stream < cache-control: no-cache < strict-transport-security: max-age=31536000; includeSubdomains; preload < x-frame-options: deny < x-content-type-options: nosniff < x-xss-protection: 0 < referrer-policy: no-referrer-when-downgrade < expect-ct: max-age=2592000, report-uri="https://api.github.com/_private/browser/errors" < content-security-policy: default-src 'none'; base-uri 'self'; block-all-mixed-content; child-src github.com/assets-cdn/worker/ gist.github.com/assets-cdn/worker/; connect-src 'self' uploads.github.com objects-origin.githubusercontent.com www.githubstatus.com collector.github.com raw.githubusercontent.com api.github.com github-cloud.s3.amazonaws.com github-production-repository-file-5c1aeb.s3.amazonaws.com github-production-upload-manifest-file-7fdce7.s3.amazonaws.com github-production-user-asset-6210df.s3.amazonaws.com cdn.optimizely.com logx.optimizely.com/v1/events *.actions.githubusercontent.com wss://*.actions.githubusercontent.com online.visualstudio.com/api/v1/locations github-production-repository-image-32fea6.s3.amazonaws.com github-production-release-asset-2e65be.s3.amazonaws.com insights.github.com wss://alive.github.com; font-src github.githubassets.com; form-action 'self' github.com gist.github.com objects-origin.githubusercontent.com; frame-ancestors 'none'; frame-src render.githubusercontent.com viewscreen.githubusercontent.com notebooks.githubusercontent.com; img-src 'self' data: github.githubassets.com identicons.github.com github-cloud.s3.amazonaws.com secured-user-images.githubusercontent.com/ github-production-user-asset-6210df.s3.amazonaws.com customer-stories-feed.github.com spotlights-feed.github.com *.githubusercontent.com; manifest-src 'self'; media-src github.com user-images.githubusercontent.com/ secured-user-images.githubusercontent.com/; script-src github.githubassets.com; style-src 'unsafe-inline' github.githubassets.com; worker-src github.com/assets-cdn/worker/ gist.github.com/assets-cdn/worker/ < content-length: 0 < x-github-request-id: 1406:5742:2C83305:2EC590C:631BB8D9 < * schannel: client wants to read 102400 bytes * schannel: encdata_buffer resized 103424 * schannel: encrypted data buffer: offset 0 length 103424 * schannel: Curl_read_plain returned CURLE_AGAIN * schannel: encrypted data buffer: offset 0 length 103424 * schannel: encrypted data buffer: offset 0 length 103424 * schannel: decrypted data buffer: offset 0 length 32768 * schannel: schannel_recv cleanup 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Curl_readwrite: forcibly told to drain data * schannel: client wants to read 102400 bytes * schannel: encrypted data buffer: offset 0 length 103424 * schannel: Curl_read_plain returned CURLE_AGAIN * schannel: encrypted data buffer: offset 0 length 103424 * schannel: encrypted data buffer: offset 0 length 103424 * schannel: decrypted data buffer: offset 0 length 32768 * schannel: schannel_recv cleanup * Curl_readwrite: forcibly told to drain data * schannel: client wants to read 102400 bytes * schannel: encrypted data buffer: offset 0 length 103424 * schannel: Curl_read_plain returned CURLE_AGAIN * schannel: encrypted data buffer: offset 0 length 103424 * schannel: encrypted data buffer: offset 0 length 103424 * schannel: decrypted data buffer: offset 0 length 32768 * schannel: schannel_recv cleanup 0 0 0 0 0 0 0 0 --:--:-- 0:00:01 --:--:-- 0^C However, it gets stuck while transferring the body repeatedly saying |
By the time schannel_recv is renegotiating the connection, libcurl has already decided on a protocol and it is too late for the server to select a protocol via ALPN. Ref: curl#9451 Closes #xxxx
By the time schannel_recv is renegotiating the connection, libcurl has already decided on a protocol and it is too late for the server to select a protocol via ALPN. Ref: curl#9451 Closes #xxxx
By the time schannel_recv is renegotiating the connection, libcurl has already decided on a protocol and it is too late for the server to select a protocol via ALPN. Ref: curl#9451 Closes #xxxx
I have run into this problem myself, and did a bit of digging. I have a proposed fix in #9756 . Essentially this is what happens:
The fix is to not reset a connections Thanks to @jay for introducing the |
By the time schannel_recv is renegotiating the connection, libcurl has already decided on a protocol and it is too late for the server to select a protocol via ALPN except for the originally selected protocol. Ref: curl/curl#9451 Closes curl/curl#9463
I did this
Compiling curl with nghttp2 1.49 and schannel support with MSVC is causing TLS 1.3 connections to fail with the error
Unsupported protocol
(CURLE_UNSUPPORTED_PROTOCOL) with the detailsReceived HTTP/0.9 when not allowed
. This doesn't happen 100% of the time, but it almost always happens for me.This only happens on Windows 11 or Windows Server 2022. Windows 10 does not seem affected.
Bisecting, this started with #8419.
Roughly I did:
-DCURL_USE_SCHANNEL=ON
-DUSE_NGHTTP2=ON
-DNGHTTP2_INCLUDE_DIR=...
-DNGHTTP2_LIBRARY=...
curl.exe -O https://github.com/curl/curl/releases/download/curl-7_85_0/curl-7.85.0.zip
(may need to run a few times)The exact URL doesn't seem too important, as long as the server supports 1.3. It seems to fail more readily with larger files (very small files like https://example.com/ don't fail quite as often).
It also happens with really trivial programs like
simple.c
from the examples directory. Lowering the max TLS version below 1.3 makes the problem go away.I expected the following
Should not fail.
curl/libcurl version
Latest tested version is 5390c9d. Seems to be happening with any version since #8419.
operating system
Windows 11 22000.856
The text was updated successfully, but these errors were encountered: