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

SslHandler and OpenSslEngine miscalculation of wrap destination buffe… #6492

Closed
wants to merge 1 commit into from

Conversation

Scottmitch
Copy link
Member

…r size

Motivation:
When we do a wrap operation we calculate the maximum size of the destination buffer ahead of time, and return a BUFFER_OVERFLOW exception if the destination buffer is not big enough. However if there is a CompositeByteBuf the wrap operation may consist of multiple ByteBuffers and each incurs its own overhead during the encryption. We currently don't account for the overhead required for encryption if there are multiple ByteBuffers and we assume the overhead will only apply once to the entire input size. If there is not enough room to write an entire encrypted packed into the BIO SSL_write will return -1 despite having actually written content to the BIO. We then attempt to retry the write with a bigger buffer, but because SSL_write is stateful the remaining bytes from the previous operation are put into the BIO. This results in sending the second half of the encrypted data being sent to the peer which is not of proper format and the peer will be confused and ultimately not get the expected data (which may result in a fatal error). In this case because SSL_write returns -1 we have no way to know how many bytes were actually consumed and so the best we can do is ensure that we always allocate a destination buffer with enough space so we are guaranteed to complete the write operation synchronously.

Modifications:

  • SslHandler#allocateNetBuf should take into account how many ByteBuffers will be wrapped and apply the encryption overhead for each
  • Include the TLS header length in the overhead computation

Result:
Fixes #6481

@Scottmitch Scottmitch added this to the 4.0.45.Final milestone Mar 3, 2017
@Scottmitch Scottmitch self-assigned this Mar 3, 2017

private void compositeBufSizeEstimationGuaranteesSynchronousWrite(SslProvider serverProvider,
SslProvider clientProvider)
throws CertificateException, SSLException {
Copy link
Member

Choose a reason for hiding this comment

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

formatting looks strange

Copy link
Member Author

Choose a reason for hiding this comment

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

how would you prefer this be formatted?

Copy link
Member

Choose a reason for hiding this comment

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

private void compositeBufSizeEstimationGuaranteesSynchronousWrite(
        SslProvider serverProvider, SslProvider clientProvider) throws CertificateException, SSLException {

Something like this maybe ?

Copy link
Member Author

Choose a reason for hiding this comment

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

done

@Scottmitch Scottmitch force-pushed the ssl_bug branch 2 times, most recently from d750e50 to 05df78b Compare March 3, 2017 19:30
group.shutdownGracefully();

ReferenceCountUtil.release(sslServerCtx);
ReferenceCountUtil.release(sslClientCtx);
Copy link
Member

Choose a reason for hiding this comment

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

call ssc.delete();

Copy link
Member Author

Choose a reason for hiding this comment

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

done

@@ -107,11 +107,12 @@
* allow up to 255 bytes. 16 bytes is the max for PKC#5 (which handles it the same way as PKC#7) as we use a block
* size of 16. See <a href="https://tools.ietf.org/html/rfc5652#section-6.3">rfc5652#section-6.3</a>.
*
* 16 (IV) + 48 (MAC) + 1 (Padding_length field) + 15 (Padding) + 1 (ContentType) + 2 (ProtocolVersion) + 2 (Length)
* 5 (TLS Header) + 16 (IV) + 48 (MAC) + 1 (Padding_length field) + 15 (Padding) + 1 (ContentType) +
Copy link
Member

Choose a reason for hiding this comment

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

So we missed the 5 bytes ?

Copy link
Member Author

Choose a reason for hiding this comment

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

yes

static int calculateOutNetBufSize(int pendingBytes) {
return min(MAX_ENCRYPTED_PACKET_LENGTH, MAX_ENCRYPTION_OVERHEAD_LENGTH
static int calculateOutNetBufSize(int pendingBytes, int numComponents) {
return min(MAX_ENCRYPTED_PACKET_LENGTH, MAX_ENCRYPTION_OVERHEAD_LENGTH * numComponents
Copy link
Member

Choose a reason for hiding this comment

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

why do we need to multiply this by the number of components ? Shouldn't it not matter as we only need to worry about the summary of the readable bytes of all of them as we can only create one record with all of the components in it.

Copy link
Member Author

@Scottmitch Scottmitch Mar 3, 2017

Choose a reason for hiding this comment

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

This is described in the commit message but if we have a CompositeByteBuf we break that down into ByteBuffer[] and call SSL_write for each of these. So each ByteBuffer will incur the tls record overhead. I wanted to review this more because it may be the case that we can apply the encryption overhead to all the bytes, and other apply the tls record overhead (header) for each ByteBuffer. I think this case ... so let me refine this estimate.

@@ -560,4 +564,127 @@ public void operationComplete(Future<Channel> future) {
ReferenceCountUtil.release(sslClientCtx);
}
}

@Test(timeout = 30000)
public void testCompositeBufSizeEstimationGuaranteesSynchronousWrite() throws CertificateException, SSLException {
Copy link
Member

Choose a reason for hiding this comment

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

also would be nice if we had this as test that not depends on the SslHandler but only uses the SSLEngine itself

Copy link
Member Author

Choose a reason for hiding this comment

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

SslHandler is involved in the estimation process so it has a role to play in this failure.

@Scottmitch
Copy link
Member Author

@normanmaurer - PTAL

return min(MAX_ENCRYPTED_PACKET_LENGTH, MAX_ENCRYPTION_OVERHEAD_LENGTH
+ min(MAX_ENCRYPTION_OVERHEAD_DIFF, pendingBytes));
static int calculateOutNetBufSize(int pendingBytes, int numComponents) {
int amt = pendingBytes + MAX_TLS_RECORD_OVERHEAD_LENGTH * numComponents;
Copy link
Member Author

@Scottmitch Scottmitch Mar 3, 2017

Choose a reason for hiding this comment

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

@normanmaurer - I consulted with @davidben regarding the question in #6492 (comment) ... each call to SSL_write should account for the encryption overhead + tls header length amount of overhead.

Copy link
Contributor

Choose a reason for hiding this comment

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

Is Integer.MAX_VALUE safe?

Copy link
Member

Choose a reason for hiding this comment

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

@Scottmitch ok thanks for verify

return TCNATIVE;
}
return JDK;
return engine instanceof ReferenceCountedOpenSslEngine ? TCNATIVE : JDK;
Copy link
Contributor

@johnou johnou Mar 3, 2017

Choose a reason for hiding this comment

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

JDK SslEngine when using OpenSslEngine and finalizer? 😕 Shouldn't this instance of check remain OpenSslEngine?

Copy link
Member Author

Choose a reason for hiding this comment

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

JDK SslEngine when using OpenSslEngine and finalizer?

I don't understand this statement. Can you explain.

Basically ReferenceCountedOpenSslEngine is the base class of OpenSslEngine and is the class which provides the interface which SslEngineType#TCNATIVE depends upon.

Copy link
Contributor

Choose a reason for hiding this comment

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

eugh, I had it in my head that ReferenceCountedOpenSslEngine extends OpenSslEngine.

@@ -193,7 +193,7 @@ SSLEngineResult unwrap(SslHandler handler, ByteBuf in, int readerIndex, int len,
* we can use a special {@link OpenSslEngine#unwrap(ByteBuffer[], ByteBuffer[])} method
* that accepts multiple {@link ByteBuffer}s without additional memory copies.
*/
OpenSslEngine opensslEngine = (OpenSslEngine) handler.engine;
ReferenceCountedOpenSslEngine opensslEngine = (ReferenceCountedOpenSslEngine) handler.engine;
Copy link
Contributor

Choose a reason for hiding this comment

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

classcastexception when using openssl with finalizer?

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't think so. ReferenceCountedOpenSslEngine is the base class of OpenSslEngine.

…r size

Motivation:
When we do a wrap operation we calculate the maximum size of the destination buffer ahead of time, and return a BUFFER_OVERFLOW exception if the destination buffer is not big enough. However if there is a CompositeByteBuf the wrap operation may consist of multiple ByteBuffers and each incurs its own overhead during the encryption. We currently don't account for the overhead required for encryption if there are multiple ByteBuffers and we assume the overhead will only apply once to the entire input size. If there is not enough room to write an entire encrypted packed into the BIO SSL_write will return -1 despite having actually written content to the BIO. We then attempt to retry the write with a bigger buffer, but because SSL_write is stateful the remaining bytes from the previous operation are put into the BIO. This results in sending the second half of the encrypted data being sent to the peer which is not of proper format and the peer will be confused and ultimately not get the expected data (which may result in a fatal error). In this case because SSL_write returns -1 we have no way to know how many bytes were actually consumed and so the best we can do is ensure that we always allocate a destination buffer with enough space so we are guaranteed to complete the write operation synchronously.

Modifications:
- SslHandler#allocateNetBuf should take into account how many ByteBuffers will be wrapped and apply the encryption overhead for each
- Include the TLS header length in the overhead computation

Result:
Fixes netty#6481
@Scottmitch
Copy link
Member Author

4.1 (53fc693) 4.0 (ccd6098)

@Scottmitch Scottmitch closed this Mar 6, 2017
@Scottmitch Scottmitch deleted the ssl_bug branch March 6, 2017 16:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants