-
Notifications
You must be signed in to change notification settings - Fork 7.8k
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
OpenSSL: Make stream liveness check non-blocking #3223
Conversation
@DaveRandom @kelunik @bwoebi I have vague memories of you discussion stuff in this area at some point? |
In general, I don't think it makes sense to use I haven't had to time to fully consider the ramifications of this patch, but my gut says that this is going to cause problems in other other scenarios with non-blocking SSL streams. Regardless, I think there is a better solution here in order to make the use case work reliably, which is to simply switch the stream to blocking mode at the start of the Unless I'm missing something? |
@DaveRandom We use Changing the stream into blocking mode here breaks at least Amp, probably others. |
@DaveRandom |
@valga That's not what it does for non-blocking streams. It returns before the stream is at EOF. |
@kelunik Nope, when SSL packet is incomplete Just look at the bt and strace I provided. |
@valga I see the issue but not sure about the solution. Basically what you are doing is returning I think that it should always return that the stream is alive when the Also this needs a test before it can be merged - see https://github.com/php/php-src/blob/master/ext/openssl/tests/bug72333.phpt for an example how to make a non blocking test (filling kernel buffer so it doesn't return everything in one go). @DaveRandom I think it should be ok to merge it to the master if the above is fixed. The liveness doesn't need to block if |
@bukka It turned out that I've tested re-negotiation scenario: the patch survives it. As for buffer underflow, it's hard to test it without packet fragmentation. Your suggestion to flood a socket with data doesn't work, because |
@bukka I added a test that simulates packets fragmentation with a proxy that splits application data (0x17) packets in 3 parts. |
@valga Thanks for the fix and the test. I really like the introduction of a proxy to the test which can be useful for other non-blocking tests in the future. I will be quite busy next week but the week after I will try to find some time to test it. |
After playing about with this and thinking about it some more I am +1 on this patch, I was looking at it from the wrong end in my initial comments. 👍 |
The only other thing I would say is that this change means that the code can be simplified, as the loop is now redundant and would never cycle, so the do-while wrapper can be removed completely. |
Hmm, the problem here is bigger than I thought. I was interested why Amp uses The problem here is Consider following code: $chunk = fread($fp, $chunkSize);
if ($chunk === '' && feof($fp)) {
// handle EOF
} Now imagine that SSL packet arrives in 2 fragments with a tiny delay between them: first one is being consumed by
Line 2517 in 6400264
|
@bukka @DaveRandom I think we should remove @kelunik @bwoebi You might be interested in that as well, because using |
@valga Couldn't that break I think it might be better solution to expose |
@bukka In most cases you are using Lines 2055 to 2073 in 6400264
The main difference between them is that As for |
Yeah I know it's set by fread if it reads 0 bytes. However I think it would break the case when fread reads the whole buffer and then feof is done. For example if client writes only 256 bytes, then doing something like this in server:
would result in a different output unless I miss something. If that's the case, then it would be a BC break including the blocking socket so it would be really a no-go. Also it really should be consistent with standard xp_socket - see php-src/main/streams/xp_socket.c Line 336 in dbea6e1
If the 3rd party extensions deal directly with the php streams, then they should be probably updated I guess. Alternatively it could be done in the PHP code before doing poll on read. |
@bukka Reading 0 bytes is not enough for EOF. There must be also some error different from EAGAIN (the logic is almost identical with stream liveness check by the way). Quite unfortunately we can't peek with SSL, but we can use |
I need to check what OpenSSL does exactly but I don't really think we should be doing this kind of hack (basically trying to recreate what OpenSSL does already after SSL_read / SSL_peak) in the openssl ext just to make other 3rd party extension work. If those extensions want to work with streams, they should explicitly check if there are any pending bytes in the stream as Going forward it would be great to extend |
@valga TLS can be enabled / disabled at any time without closing the connection. I think currently there's no way to determine whether TLS has been properly shutdown, which makes truncation attacks possible. These are usually detected in case of HTTP, because HTTP has it's own request framing, but that might not be possible for other protocols. Instead if offering separate |
@bukka Considering typical usage pattern Anyway, the patch does what's advertised, but it breaks apps that rely on |
@kelunik As far as I can see, encryption can be disabled only from PHP side. Doing that from the other side results in |
@valga Either party can initiate renegotiation at any time, regardless of the implementation details, that is simply a property of the underlying protocols. It is this fact that exposes an (as yet unresolved) problem/hole in the PHP streams + SSL/TLS abstraction, in that if the remote party has initiated a renegotiation then there is no way to reliably differentiate between this and the remote party gracefully closing the socket. Specifically, the condition that for a raw TCP connection would indicate a remote shutdown (i.e. socket polls as readable, In many ways this is a separate problem, but what it absolutely does mean is that checking the "liveness" of an SSL stream must call a function that will progress any pending renegotiation (such as |
@valga I think the current implementation for disabling is broken, at least for non-blocking sockets. |
@DaveRandom Do you think it is ok to call @kelunik I think it is broken also for blocking sockets, because |
@valga in theory that should be true but in practice I don't think it always works like that, and I'm absolutely certain that More pertinent, however, is that it is at least in theory necessary to distinguish between None of this is to say that I disapprove of this patch in isolation, as long as we recognize that it is isn't addressing the issues outlined above. |
#3729 is a duplicate and basically the same patch. Let's get this in, please. Bug report is https://bugs.php.net/bug.php?id=77390. |
Comment on behalf of bukka at php.net: Updated version with integrated proxy test in #3752 |
The problem:
stream_get_contents()
blocks on SSL-enabled stream is some cases when the stream is in non-blocking mode. For example, it happens when SSL packets are fragmented heavily, so the stream liveness check blocks until whole packet is received. It also causes high CPU usage because ofread()
calls in a loop without poll or delay.strace:
backtrace: