-
-
Notifications
You must be signed in to change notification settings - Fork 11k
Ignore EPIPE when sending NewSessionTickets in TLSv1.3 #6944
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
Conversation
|
Set to WIP because I'd like feedback on the approach. Also I've not done any kind of test yet. |
ssl/statem/statem_srvr.c
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should use get_last_sys_error() instead of errno
|
It should probably also handle at least ECONNRESET. I'm not at all convinced this is a good idea. |
|
I think we should fix the problem that this is trying to fix, I'm just not sure that this approach is the correct one. |
I'm not married to this approach if there are alternative suggestions? |
Just in case, a better solution might be to issue new tickets right after the server's Finished message, as suggested by RFC 8446: This is how BoringSSL handles it, and it seems to produce less clutter from application point of view. |
|
Small correction: we currently only do that in 0-RTT. It wasn't for this issue but because otherwise the server sees a read-triggered write and applications are often confused by "unusual" I/O patterns. (Read-triggered write is less avoidable on the client, but 0-RTT is sufficiently complex on the client that asking them to handle I/O in general is minor by comparison.) I don't think we'd thought about this particular issue much. Half-RTT tickets might paper over it a bit but you could still be sad depending on send buffer sizes and the like. |
I'm not sure that really fixes the problem (it just hides it). I'm also worried about creating sessions before the client has been authenticated. So I'm not keen on this approach. |
If a client sends data to a server and then immediately closes without waiting to read the NewSessionTickets then the server can receive EPIPE when trying to write the tickets and never gets the opportunity to read the data that was sent. Therefore we ignore EPIPE when writing out the tickets in TLSv1.3 Fixes openssl#6904
I'm not clear on when a write will return ECONNRESET and when it will return EPIPE. A quick search on google didn't particularly help. Are you able to explain? |
0cf5bb2 to
620ab0c
Compare
|
I took this out of WIP, and updated the use of |
Thanks for the link. So reading the answer in that link it seems to me that the behaviour is quite platform and socket type dependent. But, at least on Linux for TCP sockets, we could get ECONNRESET in a scenario where we are writing two tickets. The first ticket writes successfully as far as OpenSSL is concerned, but the data has not been written out by the time we get a close from the client. When we then attempt to write the second ticket we would get ECONNRESET. So, I think you are right - we do need to handle ECONNRESET too. |
|
Fixup commit pushed adding a check for ECONNRESET. |
ssl/statem/statem_srvr.c
Outdated
| #if defined(EPIPE) && defined(ECONNRESET) | ||
| if (SSL_get_error(s, 0) == SSL_ERROR_SYSCALL | ||
| && (get_last_sys_error() == EPIPE | ||
| || get_last_sys_error() == ECONNRESET)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you will find that this is too late to be calling get_last_sys_error in that the underlying functions hit state that is reset in the calls in code paths from the read/write through until here. You need to look at get_last_socket_error() in its usage and reference the error values that are saved at the point of the IO call. That has to be checked.
There is also the function BIO_sock_non_fatal_error (poorly named) where it has the set of things for non-blocking IO - and it would make sense to me to follow the same style and have a function we pass the error to which makes the determination so we can add/remove items.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you will find that this is too late to be calling get_last_sys_error in that the underlying functions hit state that is reset in the calls in code paths from the read/write through until here.
I don't think so. We don't try and write anything to the underlying socket until we call statem_flush(). Up until that point we just buffer anything we attempt to write. The statem_flush() causes the buffering BIO to write out all of its data to the underlying socket - and then we immediately test the value of get_last_sys_error(). So there really shouldn't be any code paths that reset this state.
You need to look at get_last_socket_error() in its usage and reference the error values that are saved at the point of the IO call
The difference between get_last_socket_error() and get_last_sys_error is that, on Windows, the former calls GetLastError() whilst the latter calls WSAGetLastError(). I'm not enough of a Windows programmer to know if one is preferred over the other. I don't think there are any error values saved immediately at the point of the IO call. But as long as we don't do any more system calls between where the error is and the point where we check it then that shouldn't be necessary.
There is also the function BIO_sock_non_fatal_error (poorly named) where it has the set of things for non-blocking IO - and it would make sense to me to follow the same style and have a function we pass the error to which makes the determination so we can add/remove items.
Ok - I can do that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The difference between get_last_socket_error() and get_last_sys_error is that, on Windows, the former calls GetLastError() whilst the latter calls WSAGetLastError()
Sorry, its the other way around.
|
I pushed a fixup commit that pulls the error code checking out into a separate function so that we can easily add new values. I also added a call to clear_sys_error() to make sure we don't pick up any stale state. |
|
Pushed. Thanks. |
If a client sends data to a server and then immediately closes without waiting to read the NewSessionTickets then the server can receive EPIPE when trying to write the tickets and never gets the opportunity to read the data that was sent. Therefore we ignore EPIPE when writing out the tickets in TLSv1.3 Fixes #6904 Reviewed-by: Tim Hudson <tjh@openssl.org> (Merged from #6944)
|
Using errno values won't work when the BIO is a memory stream, right? |
Well - yes - but then I don't think there is a problem if the BIO is a memory stream. We shouldn't get these errors writing to a mem BIO because there is no concept of the peer having closed the connection to the mem BIO. The application may then encounter EPIPE if it later attempts to copy the contents of the mem BIO into a socket fd. But that's really within the application's remit to handle. I don't think we can do much about that. |
* update openssl to v1.1.x * fix bug in Socket:shutdown * Improve tls test cases * add TLSv1_3 ciphers to DEFAULT_CIPHERS * update test keys and certificates to 2048 bits. * Ignore EPIPE and ECONNRESET for TLSv1_3 According to openssl/openssl#6944 * catch luv_tcp_getpeername error sometime, Socket:address() generate error EINVAL: invalid argument
* update openssl to v1.1.x * fix bug in Socket:shutdown * Improve tls test cases * add TLSv1_3 ciphers to DEFAULT_CIPHERS * update test keys and certificates to 2048 bits. * Ignore EPIPE and ECONNRESET for TLSv1_3 According to openssl/openssl#6944 * catch luv_tcp_getpeername error sometime, Socket:address() generate error EINVAL: invalid argument
* update openssl to v1.1.x * fix bug in Socket:shutdown * Improve tls test cases * add TLSv1_3 ciphers to DEFAULT_CIPHERS * update test keys and certificates to 2048 bits. * Ignore EPIPE and ECONNRESET for TLSv1_3 According to openssl/openssl#6944 * catch luv_tcp_getpeername error sometime, Socket:address() generate error EINVAL: invalid argument
This is important especially when TLS 1.3 is used. > TLSv1.3 sessions are not established during the main handshake. > Instead, after the main handshake is complete, a server may send some > NewSessionTicket messages to the client. If a client sends messages and then closes the connection immediately without reading any NewSessionTickets from the server, the server may not read the messages that were already sent by the client. This issue has been fixed in openssl/openssl#6944, but a full TCP shutdown is still required to make the server read the data. Also: openssl/openssl#6904 Signed-off-by: László Várady <laszlo.varady@protonmail.com>
This is important especially when TLS 1.3 is used. > TLSv1.3 sessions are not established during the main handshake. > Instead, after the main handshake is complete, a server may send some > NewSessionTicket messages to the client. If a client sends messages and then closes the connection immediately without reading any NewSessionTickets from the server, the server may not read the messages that were already sent by the client. This issue has been fixed in openssl/openssl#6944, but a full TCP shutdown is still required to make the server read the data. Also: openssl/openssl#6904 Backported from OSE: da03117a89aab7158d9a6db4d275d0358a71323c Signed-off-by: László Várady <laszlo.varady@protonmail.com> Signed-off-by: Attila Szakacs <attila.szakacs@oneidentity.com>
If a client sends data to a server and then immediately closes without
waiting to read the NewSessionTickets then the server can receive EPIPE
when trying to write the tickets and never gets the opportunity to read
the data that was sent. Therefore we ignore EPIPE when writing out the
tickets in TLSv1.3
Fixes #6904