Skip to content
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

bpo-31711: Fix for calling SSLSocket.send with empty input. #7559

Closed
wants to merge 6 commits into from

Conversation

rkondratenko
Copy link

@rkondratenko rkondratenko commented Jun 9, 2018

SSLSocket in Python wraps ordinary sockets and provides SSLSocket.send with same behaviour (except for nonblocking differences mentiond in the documentation). Internally cPython uses OpenSSL as SSL library.
SSLSocket.send() calls to SSL_write() with data to send. However calling SSL_write() with zero-length buffer is undefined behaviour. To emulate ordinary socket behaviour in this case and not to trigger
undefined behaviour from OpenSSL we explicitly check for zero-length buffer before calling SSL_write().

https://bugs.python.org/issue31711

It should behave as ordinary socket as promised in the docs.
@the-knights-who-say-ni
Copy link

Hello, and thanks for your contribution!

I'm a bot set up to make sure that the project can legally accept your contribution by verifying you have signed the PSF contributor agreement (CLA).

Unfortunately we couldn't find an account corresponding to your GitHub username on bugs.python.org (b.p.o) to verify you have signed the CLA (this might be simply due to a missing "GitHub Name" entry in your b.p.o account settings). This is necessary for legal reasons before we can look at your contribution. Please follow the steps outlined in the CPython devguide to rectify this issue.

When your account is ready, please add a comment in this pull request
and a Python core developer will remove the CLA not signed label
to make the bot check again.

Thanks again to your contribution and we look forward to looking at it!

@rkondratenko
Copy link
Author

Signed CLA

@njsmith
Copy link
Contributor

njsmith commented Jun 9, 2018

Thanks for taking this on! Two suggestions:

  • I think the code will be simpler and easier to understand if you move the check for b->len much earlier, before we get into the actual I/O operations. It looks like a good place would be next to the check for if (b->len > INT_MAX) { ... }

  • You'll need to add a test, to Lib/test/ssl.py

@rkondratenko
Copy link
Author

@njsmith I adopted your suggestions. Please take a look.

@@ -2578,6 +2578,14 @@ def test_echo(self):
chatty=True, connectionchatty=True,
sni_name=hostname)

## Testing that SSLSopcet can handle empty input
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this meant to be SSLSocket?

@rkondratenko
Copy link
Author

Any onther suggestions? Are you ready to merge?

@njsmith
Copy link
Contributor

njsmith commented Aug 1, 2018

The PR looks good to me, and I think the change is a good idea. @tiran, are you OK with this change? (Conversation in [bpo-31711](https://bugs.python.org/issue31711) was inconclusive.)

@tiran
Copy link
Member

tiran commented Aug 1, 2018

I see a potential issue with this patch. Simply speaking, TLS/SSL has two different kinds of messages. There is application data and protocol data. SSL_write and SSL_read not only transmits application data, but they also flush and check for protocol data. That's why a read operation may also send data and a write operation may also read data.

This patch breaks the assumption that send() also takes care of protocol data. With TLS 1.2, it's not a big issue. Most critical protocol data like session and client cert auth are handled in the TLS handshake. However in TLS 1.3, the server side sends a TLS client auth request much later. Usually it occurs after the first write. Speaking of handshake, with this patch, send() may no longer establish a handshake with do_handshake_on_connect=False, too.

I'm -1 on this patch. Let's figure out how to flush / check protocol data and handle implicit handshake correctly.

Modules/_ssl.c Outdated
@@ -2247,6 +2247,13 @@ _ssl__SSLSocket_write_impl(PySSLSocket *self, Py_buffer *b)
"string longer than %d bytes", INT_MAX);
goto error;
}
if (b->len == 0) {
/*Sending 0 bytes to SSL_write is undefined behaviour per OpenSSL documentation.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after /*.

* SSLSocket imitates normal socket in Python.
* We have to guard against empty input here. */
Py_XDECREF(sock);
return PyLong_FromLong(0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This breaks implicit handshake and possible TLS 1.3 edge cases, see comment.

@bedevere-bot
Copy link

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

@njsmith
Copy link
Contributor

njsmith commented Aug 1, 2018

But in the current module, sslsock.send(b"") doesn't do any of those things, it just invokes "undefined behavior" and kills the connection. So surely this patch is still an improvement? And is it even possible to do any of those things you said without actually sending or receiving data?

@rkondratenko
Copy link
Author

I did a quick check of OpenSSL docs. There seems to be no easy way to process protocol messages without any other side-effects. Best candidate seems to be SSL_peek[_ex] but is has standard warning:

When a read function operation has to be repeated because SSL_get_error(3) returned SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE, it must be repeated with the same arguments.

This means that we would have to save a flag in SSLSocket to call SSL_peek again with the same parameters on next calls. This would probably violate even more implicit assumtions.

Also, to send data before the end of the handshake in TLS 1.3 one has to explicitly call SSL_write_early_data. Current module doesn't do this anyway.

@slingamn
Copy link

I recently encountered this issue and was very surprised by it. I very much agree that this patch improves on the current behavior.

Can anything be done to move forward with this?

@sylver
Copy link

sylver commented Dec 4, 2019

Just encountered this problem. What's the status of this issue ?
It seems staled for a year and a half now.

@njsmith
Copy link
Contributor

njsmith commented Dec 4, 2019

@tiran This still seems like an improvement to me, and I don't think your concerns are applicable to this case, but I don't want to just ignore them either...

@tiran
Copy link
Member

tiran commented Apr 19, 2021

Thanks to PEP 644 the issue will be fixed in 3.10 by using SSL_read_ex and SSL_write_ex() functions. I couldn't use the functions earlier because Python had to support older OpenSSL versions and LibreSSL. #25468

@tiran tiran closed this Apr 19, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants