-
-
Notifications
You must be signed in to change notification settings - Fork 6.5k
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
CURLE_RECV_ERROR schannel: SEC_E_DECRYPT_FAILURE #9431
Comments
/cc @gvollant @DHowett @wyattoday any clues? |
Is this reproducible with the curl tool (curl.exe)? How did you build libcurl? Please give us minimal self contained example code that can be used to reproduce. FWIW this was not reproducible for me using the curl tool in Windows 7, 8 or 10 (21H2). |
This is custom libcurl build. I re-check my flags. Maybe there was some new |
Here is a repro.
|
That's reproducible for me in Windows 11 and it happens regardless of HTTP version setting |
It looks like a TLS 1.3 issue, when I set /cc @wyattoday |
I agree that this is an issue with how the internals changes in SChannel when using TLS 1.3. The actual code I wrote runs on Windows 10, 1809 (a.k.a. Windows 10 build 17763) and newer regardless of whether TLS 1.3 is used or not. The only difference is the presence of the So, this internal SChannel code path difference depending on whether it takes the TLS 1.2 route or the TLS 1.3 route. I assume this is the exact same issue reported here: #9451 . Namely a bug in the ALPN code because SChannel altered the way it handled some internal code. My guess is that if they (@egorpugin) disabled HTTP2 while still using TLS 1.3 it would work (similar to the other issue). I don't currently have time to dig into either one of these issues, but I might have some time end of next week if no one picks it up in the meantime. |
I have been doing a bit of investigation into this problem. I am easily able to reproduce the problem on my Windows 11 machine. This is still reproducible after #9756 is merged, so it is not the same problem as reported in #9451. The problem persists regardless of HTTP version used, and whether or not ALPN is used. Here is the command line I used in my testing:
My full output from this command is here, I will go through this in a bit more detail below.
I have tracked this down to a problem in "step 2" of the TLS connection. It appears that the initial traffic from the server to libcurl ( In step 1, libcurl calls
We then move to step 2, where we read data from the server and pass it to another call to
This is where things get weird. The call to
Since the return code was
After this libcurl calls the
Notice that the 19 bytes from the initial
If I change the
So it appears that the second call to
|
Thanks for the excellent analysis.
I'd agree... InitializeSecurityContext (Schannel) return value SEC_E_OK says: "The security context was successfully initialized. There is no need for another InitializeSecurityContext (Schannel) call. If the function returns an output token, that is, if the SECBUFFER_TOKEN in pOutput is of nonzero length, that token must be sent to the server."
We already have logic that was added a decade ago in 72a5813 for a large handshake problem with Windows CE. Currently that logic looks like this: Lines 1538 to 1567 in 93d0928
Perhaps that logic is too restrictive? We would need to read more data to complete the handshake, so doread would need to be set true, correct? But the comments there worry me. How can we be expected to tell if the extra data is handshake data or app data at that point? |
I've found this page regarding SECBUFFER_EXTRA. It says: When the input buffers contains too much information, Schannel does not treat this as an error. The function processes as much of the input as it can, and returns the status code for that processing activity. In addition, Schannel indicates the presence of unprocessed information in the input buffers by returning an output buffer of type SECBUFFER_EXTRA. Testing the output buffers for this type of buffer is the only way to detect this situation. The cbBuffer field of the extra buffer structure indicates how many bytes of input were not processed. |
My potential fix for this problem is available here: Note that that branch includes the commit in #9807, so it is really just the final two commits which are of interest to this issue. I have some reservations about the fix however. As @jay mentioned in his previous comment, we cannot really know whether the "extra" data reported by the This is unlikely with https, because the server would not know which data to serve without first receiving a request. I imagine this could be possible in other protocols (ftps maybe?). |
@andrey-popov are we doing this wrong or is it a bug in schannel? |
A mistag? 🙂 |
I think @jay meant to ping @Andrei-Popov to see if this is a bug in the internals of SChannel. |
I've discovered a potential workaround for this issue. When I turn off certificate revocation checking (via Due to the way that TLS 1.3 works, it is not really possible to examine the relevant parts of the |
@JDepooter though we cannot decrypt Schannel client TLS sessions we can decrypt OpenSSL client TLS sessions so let's take a look at that:
If Note that server's Back to Schannel. If I disable the prerecv buffer decryption does not fail. I wonder if you see similar results? diff --git a/lib/curl_setup.h b/lib/curl_setup.h
index 59efe4d..3adcfbc 100644
--- a/lib/curl_setup.h
+++ b/lib/curl_setup.h
@@ -780,6 +780,7 @@ endings either CRLF or LF so 't' is appropriate.
* wrappers for WinSock sockets. https://github.com/curl/curl/issues/657
* Define DONT_USE_RECV_BEFORE_SEND_WORKAROUND to force disable workaround.
*/
+#define DONT_USE_RECV_BEFORE_SEND_WORKAROUND
#if !defined(DONT_USE_RECV_BEFORE_SEND_WORKAROUND)
# if defined(WIN32) || defined(__CYGWIN__)
# define USE_RECV_BEFORE_SEND_WORKAROUND --- fail.txt 2022-11-12 04:06:27.722117500 -0500
+++ success.txt 2022-11-12 04:06:32.284378400 -0500
@@ -1,7 +1,19 @@
* schannel: client wants to read 102400 bytes
* schannel: encdata_buffer resized 103424
* schannel: encrypted data buffer: offset 19 length 103424
-* schannel: encrypted data got 716
-* schannel: encrypted data buffer: offset 735 length 103424
-* schannel: failed to read data from server: SEC_E_DECRYPT_FAILURE (0x80090330) - The specified data could not be decrypted.
-* schannel: schannel_recv cleanup
+* schannel: encrypted data got 913
+* schannel: encrypted data buffer: offset 932 length 103424
+* schannel: decrypted data length: 0
+* schannel: decrypted data added: 0
+* schannel: decrypted cached: offset 0 length 102400
+* schannel: encrypted data length: 932
+* schannel: encrypted cached: offset 932 length 103424
+* schannel: remote party requests renegotiation
+* schannel: renegotiating SSL/TLS connection
+* schannel: SSL/TLS connection with raw.githubusercontent.com port 443 (step 2/3)
+* schannel: encrypted data buffer: offset 932 length 103424
+* schannel: encrypted data length: 717
+* schannel: SSL/TLS handshake complete
+* schannel: SSL/TLS connection with raw.githubusercontent.com port 443 (step 3/3)
+* Found Session ID in cache for host HTTPS://raw.githubusercontent.com:443
+* schannel: SSL/TLS connection renegotiated What prerecv is for: Lines 154 to 158 in cd95ee9
Interestingly, Curl_read_plain does not fetch prerecv data but Curl_recv_plain (which looks functionally the same) does. Guess which one schannel_step2 and schannel_recv calls... Lines 2200 to 2204 in cd95ee9
So here's a theory, possibly some part of the |
Please try #9904 |
Prior to this change Curl_read_plain would attempt to read the socket directly. On Windows that's a problem because recv data may be cached by libcurl and that data is only drained using Curl_recv_plain. Rather than rewrite Curl_read_plain to handle cached recv data, I changed it to wrap Curl_recv_plain, in much the same way that Curl_write_plain already wraps Curl_send_plain. Curl_read_plain -> Curl_recv_plain Curl_write_plain -> Curl_send_plain This fixes a bug in the schannel backend where decryption of arbitrary TLS records fails because cached recv data is never drained. We send data (TLS records formed by Schannel) using Curl_write_plain, which calls Curl_send_plain, and that may do a recv-before-send ("pre-receive") to cache received data. The code calls Curl_read_plain to read data (TLS records from the server), which prior to this change did not call Curl_recv_plain and therefore cached recv data wasn't retrieved, resulting in malformed TLS records and decryption failure (SEC_E_DECRYPT_FAILURE). Ref: curl#9431 (comment) Fixes curl#9431 Closes #xxxx
Awesome, this works for me. I had a feeling that pre-recv buffer was involved some how. |
Reopened by 18383fb |
Prior to this change Curl_read_plain would attempt to read the socket directly. On Windows that's a problem because recv data may be cached by libcurl and that data is only drained using Curl_recv_plain. Rather than rewrite Curl_read_plain to handle cached recv data, I changed it to wrap Curl_recv_plain, in much the same way that Curl_write_plain already wraps Curl_send_plain. Curl_read_plain -> Curl_recv_plain Curl_write_plain -> Curl_send_plain This fixes a bug in the schannel backend where decryption of arbitrary TLS records fails because cached recv data is never drained. We send data (TLS records formed by Schannel) using Curl_write_plain, which calls Curl_send_plain, and that may do a recv-before-send ("pre-receive") to cache received data. The code calls Curl_read_plain to read data (TLS records from the server), which prior to this change did not call Curl_recv_plain and therefore cached recv data wasn't retrieved, resulting in malformed TLS records and decryption failure (SEC_E_DECRYPT_FAILURE). The bug has only been observed during Schannel TLS 1.3 handshakes. Refer to the issue and PR for more information. Ref: curl#9431 (comment) Assisted-by: Joel Depooter Reported-by: Egor Pugin Fixes curl#9431 Closes curl#9904
…ading files. (curl/curl#9431) git-svn-id: https://svn.r-project.org/R-dev-web/trunk@5448 c52295ea-58df-0310-926a-d16021944841
Prior to this change a workaround for Windows to recv before every send was enabled by default. The way it works is a recv is called before every send and saves the received data, in case send fails because in Windows apparently that can wipe out the socket's internal received data buffer. This feature has led to several bugs because the way libcurl operates it waits on a socket to read or to write, and may not at all times check for buffered receive data. Two recent significant bugs this workaround caused: - Broken Schannel TLS 1.3 connections (curl#9431) - HTTP/2 arbitrary hangs (curl#10253) The actual code remains though it is disabled by default. Though future changes to connection filter buffering could improve the situation IMO it's just not tenable to manage this workaround. Ref: curl#657 Ref: curl#668 Ref: curl#720 Ref: curl#9431 Ref: curl#10253 Closes curl#10409
Prior to this change a workaround for Windows to recv before every send was enabled by default. The way it works is a recv is called before every send and saves the received data, in case send fails because in Windows apparently that can wipe out the socket's internal received data buffer. This feature has led to several bugs because the way libcurl operates it waits on a socket to read or to write, and may not at all times check for buffered receive data. Two recent significant bugs this workaround caused: - Broken Schannel TLS 1.3 connections (#9431) - HTTP/2 arbitrary hangs (#10253) The actual code remains though it is disabled by default. Though future changes to connection filter buffering could improve the situation IMO it's just not tenable to manage this workaround. Ref: #657 Ref: #668 Ref: #720 Ref: #9431 Ref: #10253 Closes #10409
Prior to this change a workaround for Windows to recv before every send was enabled by default. The way it works is a recv is called before every send and saves the received data, in case send fails because in Windows apparently that can wipe out the socket's internal received data buffer. This feature has led to several bugs because the way libcurl operates it waits on a socket to read or to write, and may not at all times check for buffered receive data. Two recent significant bugs this workaround caused: - Broken Schannel TLS 1.3 connections (curl#9431) - HTTP/2 arbitrary hangs (curl#10253) The actual code remains though it is disabled by default. Though future changes to connection filter buffering could improve the situation IMO it's just not tenable to manage this workaround. Ref: curl#657 Ref: curl#668 Ref: curl#720 Ref: curl#9431 Ref: curl#10253 Closes curl#10409
I did this
libcurl
GET https://raw.githubusercontent.com/SoftwareNetwork/database/master//db.version
I expected the following
Everything worked ok for a long time.
curl/libcurl version
7.85.0
operating system
win11, native tls (schannel)
Output
Same for http1.1.
curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
The text was updated successfully, but these errors were encountered: