Issue #1146 - jetty.server.HttpInput deadlock.

Moved calls to close(Throwable) outside synchronized blocks, since
they mail fail callbacks that reenter into caller code, causing
deadlocks.
This commit is contained in:
Simone Bordet 2016-12-20 10:39:36 +01:00
parent 3b73265331
commit 753aa5ca37
1 changed files with 349 additions and 340 deletions

View File

@ -509,100 +509,191 @@ public class SslConnection extends AbstractConnection
}
@Override
public synchronized int fill(ByteBuffer buffer) throws IOException
public int fill(ByteBuffer buffer) throws IOException
{
try
{
// Do we already have some decrypted data?
if (BufferUtil.hasContent(_decryptedInput))
return BufferUtil.append(buffer,_decryptedInput);
// We will need a network buffer
if (_encryptedInput == null)
_encryptedInput = _bufferPool.acquire(_sslEngine.getSession().getPacketBufferSize(), _encryptedDirectBuffers);
else
BufferUtil.compact(_encryptedInput);
// We also need an app buffer, but can use the passed buffer if it is big enough
ByteBuffer app_in;
if (BufferUtil.space(buffer) > _sslEngine.getSession().getApplicationBufferSize())
app_in = buffer;
else if (_decryptedInput == null)
app_in = _decryptedInput = _bufferPool.acquire(_sslEngine.getSession().getApplicationBufferSize(), _decryptedDirectBuffers);
else
app_in = _decryptedInput;
// loop filling and unwrapping until we have something
while (true)
synchronized (this)
{
// Let's try reading some encrypted data... even if we have some already.
int net_filled = getEndPoint().fill(_encryptedInput);
decryption: while (true)
try
{
// Let's unwrap even if we have no net data because in that
// case we want to fall through to the handshake handling
int pos = BufferUtil.flipToFill(app_in);
SSLEngineResult unwrapResult;
try
{
unwrapResult = _sslEngine.unwrap(_encryptedInput, app_in);
}
finally
{
BufferUtil.flipToFlush(app_in, pos);
}
if (LOG.isDebugEnabled())
{
LOG.debug("{} net={} unwrap {}", SslConnection.this, net_filled, unwrapResult.toString().replace('\n',' '));
LOG.debug("{} filled {}",SslConnection.this,BufferUtil.toHexSummary(buffer));
}
// Do we already have some decrypted data?
if (BufferUtil.hasContent(_decryptedInput))
return BufferUtil.append(buffer,_decryptedInput);
HandshakeStatus handshakeStatus = _sslEngine.getHandshakeStatus();
HandshakeStatus unwrapHandshakeStatus = unwrapResult.getHandshakeStatus();
Status unwrapResultStatus = unwrapResult.getStatus();
// We will need a network buffer
if (_encryptedInput == null)
_encryptedInput = _bufferPool.acquire(_sslEngine.getSession().getPacketBufferSize(), _encryptedDirectBuffers);
else
BufferUtil.compact(_encryptedInput);
// Extra check on unwrapResultStatus == OK with zero bytes consumed
// or produced is due to an SSL client on Android (see bug #454773).
_underFlown = unwrapResultStatus == Status.BUFFER_UNDERFLOW ||
unwrapResultStatus == Status.OK && unwrapResult.bytesConsumed() == 0 && unwrapResult.bytesProduced() == 0;
// We also need an app buffer, but can use the passed buffer if it is big enough
ByteBuffer app_in;
if (BufferUtil.space(buffer) > _sslEngine.getSession().getApplicationBufferSize())
app_in = buffer;
else if (_decryptedInput == null)
app_in = _decryptedInput = _bufferPool.acquire(_sslEngine.getSession().getApplicationBufferSize(), _decryptedDirectBuffers);
else
app_in = _decryptedInput;
if (_underFlown)
// loop filling and unwrapping until we have something
while (true)
{
if (net_filled < 0)
closeInbound();
if (net_filled <= 0)
return net_filled;
}
// Let's try reading some encrypted data... even if we have some already.
int net_filled = getEndPoint().fill(_encryptedInput);
switch (unwrapResultStatus)
{
case CLOSED:
decryption: while (true)
{
switch (handshakeStatus)
// Let's unwrap even if we have no net data because in that
// case we want to fall through to the handshake handling
int pos = BufferUtil.flipToFill(app_in);
SSLEngineResult unwrapResult;
try
{
case NOT_HANDSHAKING:
unwrapResult = _sslEngine.unwrap(_encryptedInput, app_in);
}
finally
{
BufferUtil.flipToFlush(app_in, pos);
}
if (LOG.isDebugEnabled())
{
LOG.debug("{} net={} unwrap {}", SslConnection.this, net_filled, unwrapResult.toString().replace('\n',' '));
LOG.debug("{} filled {}",SslConnection.this,BufferUtil.toHexSummary(buffer));
}
HandshakeStatus handshakeStatus = _sslEngine.getHandshakeStatus();
HandshakeStatus unwrapHandshakeStatus = unwrapResult.getHandshakeStatus();
Status unwrapResultStatus = unwrapResult.getStatus();
// Extra check on unwrapResultStatus == OK with zero bytes consumed
// or produced is due to an SSL client on Android (see bug #454773).
_underFlown = unwrapResultStatus == Status.BUFFER_UNDERFLOW ||
unwrapResultStatus == Status.OK && unwrapResult.bytesConsumed() == 0 && unwrapResult.bytesProduced() == 0;
if (_underFlown)
{
if (net_filled < 0)
closeInbound();
if (net_filled <= 0)
return net_filled;
}
switch (unwrapResultStatus)
{
case CLOSED:
{
// We were not handshaking, so just tell the app we are closed
return -1;
switch (handshakeStatus)
{
case NOT_HANDSHAKING:
{
// We were not handshaking, so just tell the app we are closed
return -1;
}
case NEED_TASK:
{
_sslEngine.getDelegatedTask().run();
continue;
}
case NEED_WRAP:
{
// We need to send some handshake data (probably the close handshake).
// We return -1 so that the application can drive the close by flushing
// or shutting down the output.
return -1;
}
case NEED_UNWRAP:
{
// We expected to read more, but we got closed.
// Return -1 to indicate to the application to drive the close.
return -1;
}
default:
{
throw new IllegalStateException();
}
}
}
case NEED_TASK:
case BUFFER_UNDERFLOW:
case OK:
{
_sslEngine.getDelegatedTask().run();
continue;
}
case NEED_WRAP:
{
// We need to send some handshake data (probably the close handshake).
// We return -1 so that the application can drive the close by flushing
// or shutting down the output.
return -1;
}
case NEED_UNWRAP:
{
// We expected to read more, but we got closed.
// Return -1 to indicate to the application to drive the close.
return -1;
if (unwrapHandshakeStatus == HandshakeStatus.FINISHED && !_handshaken)
{
_handshaken = true;
if (LOG.isDebugEnabled())
LOG.debug("{} {} handshake succeeded {}/{}", SslConnection.this,
_sslEngine.getUseClientMode() ? "client" : "resumed server",
_sslEngine.getSession().getProtocol(),_sslEngine.getSession().getCipherSuite());
notifyHandshakeSucceeded(_sslEngine);
}
// Check whether renegotiation is allowed
if (_handshaken && handshakeStatus != HandshakeStatus.NOT_HANDSHAKING && !isRenegotiationAllowed())
{
if (LOG.isDebugEnabled())
LOG.debug("{} renegotiation denied", SslConnection.this);
closeInbound();
return -1;
}
// If bytes were produced, don't bother with the handshake status;
// pass the decrypted data to the application, which will perform
// another call to fill() or flush().
if (unwrapResult.bytesProduced() > 0)
{
if (app_in == buffer)
return unwrapResult.bytesProduced();
return BufferUtil.append(buffer,_decryptedInput);
}
switch (handshakeStatus)
{
case NOT_HANDSHAKING:
{
if (_underFlown)
break decryption;
continue;
}
case NEED_TASK:
{
_sslEngine.getDelegatedTask().run();
continue;
}
case NEED_WRAP:
{
// If we are called from flush()
// return to let it do the wrapping.
if (_flushRequiresFillToProgress)
return 0;
_fillRequiresFlushToProgress = true;
flush(BufferUtil.EMPTY_BUFFER);
if (BufferUtil.isEmpty(_encryptedOutput))
{
// The flush wrote all the encrypted bytes so continue to fill.
_fillRequiresFlushToProgress = false;
if (_underFlown)
break decryption;
continue;
}
else
{
// The flush did not complete, return from fill()
// and let the write completion mechanism to kick in.
return 0;
}
}
case NEED_UNWRAP:
{
if (_underFlown)
break decryption;
continue;
}
default:
{
throw new IllegalStateException();
}
}
}
default:
{
@ -610,136 +701,49 @@ public class SslConnection extends AbstractConnection
}
}
}
case BUFFER_UNDERFLOW:
case OK:
{
if (unwrapHandshakeStatus == HandshakeStatus.FINISHED && !_handshaken)
{
_handshaken = true;
if (LOG.isDebugEnabled())
LOG.debug("{} {} handshake succeeded {}/{}", SslConnection.this,
_sslEngine.getUseClientMode() ? "client" : "resumed server",
_sslEngine.getSession().getProtocol(),_sslEngine.getSession().getCipherSuite());
notifyHandshakeSucceeded(_sslEngine);
}
// Check whether renegotiation is allowed
if (_handshaken && handshakeStatus != HandshakeStatus.NOT_HANDSHAKING && !isRenegotiationAllowed())
{
if (LOG.isDebugEnabled())
LOG.debug("{} renegotiation denied", SslConnection.this);
closeInbound();
return -1;
}
// If bytes were produced, don't bother with the handshake status;
// pass the decrypted data to the application, which will perform
// another call to fill() or flush().
if (unwrapResult.bytesProduced() > 0)
{
if (app_in == buffer)
return unwrapResult.bytesProduced();
return BufferUtil.append(buffer,_decryptedInput);
}
switch (handshakeStatus)
{
case NOT_HANDSHAKING:
{
if (_underFlown)
break decryption;
continue;
}
case NEED_TASK:
{
_sslEngine.getDelegatedTask().run();
continue;
}
case NEED_WRAP:
{
// If we are called from flush()
// return to let it do the wrapping.
if (_flushRequiresFillToProgress)
return 0;
_fillRequiresFlushToProgress = true;
flush(BufferUtil.EMPTY_BUFFER);
if (BufferUtil.isEmpty(_encryptedOutput))
{
// The flush wrote all the encrypted bytes so continue to fill.
_fillRequiresFlushToProgress = false;
if (_underFlown)
break decryption;
continue;
}
else
{
// The flush did not complete, return from fill()
// and let the write completion mechanism to kick in.
return 0;
}
}
case NEED_UNWRAP:
{
if (_underFlown)
break decryption;
continue;
}
default:
{
throw new IllegalStateException();
}
}
}
default:
{
throw new IllegalStateException();
}
}
}
catch (SSLHandshakeException x)
{
notifyHandshakeFailed(_sslEngine, x);
throw x;
}
catch (SSLException x)
{
if (!_handshaken)
{
x = (SSLException)new SSLHandshakeException(x.getMessage()).initCause(x);
notifyHandshakeFailed(_sslEngine, x);
}
throw x;
}
finally
{
// If we are handshaking, then wake up any waiting write as well as it may have been blocked on the read
if (_flushRequiresFillToProgress)
{
_flushRequiresFillToProgress = false;
getExecutor().execute(_runCompletWrite);
}
if (_encryptedInput != null && !_encryptedInput.hasRemaining())
{
_bufferPool.release(_encryptedInput);
_encryptedInput = null;
}
if (_decryptedInput != null && !_decryptedInput.hasRemaining())
{
_bufferPool.release(_decryptedInput);
_decryptedInput = null;
}
}
}
}
catch (SSLHandshakeException x)
{
notifyHandshakeFailed(_sslEngine, x);
close(x);
throw x;
}
catch (SSLException x)
{
if (!_handshaken)
{
x = (SSLException)new SSLHandshakeException(x.getMessage()).initCause(x);
notifyHandshakeFailed(_sslEngine, x);
}
close(x);
throw x;
}
catch (Throwable x)
{
close(x);
throw x;
}
finally
{
// If we are handshaking, then wake up any waiting write as well as it may have been blocked on the read
if (_flushRequiresFillToProgress)
{
_flushRequiresFillToProgress = false;
getExecutor().execute(_runCompletWrite);
}
if (_encryptedInput != null && !_encryptedInput.hasRemaining())
{
_bufferPool.release(_encryptedInput);
_encryptedInput = null;
}
if (_decryptedInput != null && !_decryptedInput.hasRemaining())
{
_bufferPool.release(_decryptedInput);
_decryptedInput = null;
}
}
}
private void closeInbound()
@ -755,7 +759,7 @@ public class SslConnection extends AbstractConnection
}
@Override
public synchronized boolean flush(ByteBuffer... appOuts) throws IOException
public boolean flush(ByteBuffer... appOuts) throws IOException
{
// The contract for flush does not require that all appOuts bytes are written
// or even that any appOut bytes are written! If the connection is write block
@ -772,159 +776,164 @@ public class SslConnection extends AbstractConnection
try
{
if (_cannotAcceptMoreAppDataToFlush)
synchronized (this)
{
if (_sslEngine.isOutboundDone())
throw new EofException(new ClosedChannelException());
return false;
}
// We will need a network buffer
if (_encryptedOutput == null)
_encryptedOutput = _bufferPool.acquire(_sslEngine.getSession().getPacketBufferSize(), _encryptedDirectBuffers);
while (true)
{
// We call sslEngine.wrap to try to take bytes from appOut buffers and encrypt them into the _netOut buffer
BufferUtil.compact(_encryptedOutput);
int pos = BufferUtil.flipToFill(_encryptedOutput);
SSLEngineResult wrapResult;
try
{
wrapResult = _sslEngine.wrap(appOuts, _encryptedOutput);
if (_cannotAcceptMoreAppDataToFlush)
{
if (_sslEngine.isOutboundDone())
throw new EofException(new ClosedChannelException());
return false;
}
// We will need a network buffer
if (_encryptedOutput == null)
_encryptedOutput = _bufferPool.acquire(_sslEngine.getSession().getPacketBufferSize(), _encryptedDirectBuffers);
while (true)
{
// We call sslEngine.wrap to try to take bytes from appOut buffers and encrypt them into the _netOut buffer
BufferUtil.compact(_encryptedOutput);
int pos = BufferUtil.flipToFill(_encryptedOutput);
SSLEngineResult wrapResult;
try
{
wrapResult = _sslEngine.wrap(appOuts, _encryptedOutput);
}
finally
{
BufferUtil.flipToFlush(_encryptedOutput, pos);
}
if (LOG.isDebugEnabled())
LOG.debug("{} wrap {}", SslConnection.this, wrapResult.toString().replace('\n',' '));
Status wrapResultStatus = wrapResult.getStatus();
boolean allConsumed=true;
for (ByteBuffer b : appOuts)
if (BufferUtil.hasContent(b))
allConsumed=false;
// and deal with the results returned from the sslEngineWrap
switch (wrapResultStatus)
{
case CLOSED:
{
// The SSL engine has close, but there may be close handshake that needs to be written
if (BufferUtil.hasContent(_encryptedOutput))
{
_cannotAcceptMoreAppDataToFlush = true;
getEndPoint().flush(_encryptedOutput);
getEndPoint().shutdownOutput();
// If we failed to flush the close handshake then we will just pretend that
// the write has progressed normally and let a subsequent call to flush
// (or WriteFlusher#onIncompleteFlushed) to finish writing the close handshake.
// The caller will find out about the close on a subsequent flush or fill.
if (BufferUtil.hasContent(_encryptedOutput))
return false;
}
// otherwise we have written, and the caller will close the underlying connection
else
{
getEndPoint().shutdownOutput();
}
return allConsumed;
}
case BUFFER_UNDERFLOW:
{
throw new IllegalStateException();
}
default:
{
if (LOG.isDebugEnabled())
LOG.debug("{} wrap {} {}", SslConnection.this, wrapResultStatus, BufferUtil.toHexSummary(_encryptedOutput));
if (wrapResult.getHandshakeStatus() == HandshakeStatus.FINISHED && !_handshaken)
{
_handshaken = true;
if (LOG.isDebugEnabled())
LOG.debug("{} {} handshake succeeded {}/{}", SslConnection.this,
_sslEngine.getUseClientMode() ? "resumed client" : "server",
_sslEngine.getSession().getProtocol(),_sslEngine.getSession().getCipherSuite());
notifyHandshakeSucceeded(_sslEngine);
}
HandshakeStatus handshakeStatus = _sslEngine.getHandshakeStatus();
// Check whether renegotiation is allowed
if (_handshaken && handshakeStatus != HandshakeStatus.NOT_HANDSHAKING && !isRenegotiationAllowed())
{
if (LOG.isDebugEnabled())
LOG.debug("{} renegotiation denied", SslConnection.this);
getEndPoint().shutdownOutput();
return allConsumed;
}
// if we have net bytes, let's try to flush them
if (BufferUtil.hasContent(_encryptedOutput))
if (!getEndPoint().flush(_encryptedOutput))
getEndPoint().flush(_encryptedOutput); // one retry
// But we also might have more to do for the handshaking state.
switch (handshakeStatus)
{
case NOT_HANDSHAKING:
// If we have not consumed all and had just finished handshaking, then we may
// have just flushed the last handshake in the encrypted buffers, so we should
// try again.
if (!allConsumed && wrapResult.getHandshakeStatus()==HandshakeStatus.FINISHED && BufferUtil.isEmpty(_encryptedOutput))
continue;
// Return true if we consumed all the bytes and encrypted are all flushed
return allConsumed && BufferUtil.isEmpty(_encryptedOutput);
case NEED_TASK:
// run the task and continue
_sslEngine.getDelegatedTask().run();
continue;
case NEED_WRAP:
// Hey we just wrapped! Oh well who knows what the sslEngine is thinking, so continue and we will wrap again
continue;
case NEED_UNWRAP:
// Ah we need to fill some data so we can write.
// So if we were not called from fill and the app is not reading anyway
if (!_fillRequiresFlushToProgress && !getFillInterest().isInterested())
{
// Tell the onFillable method that there might be a write to complete
_flushRequiresFillToProgress = true;
fill(BufferUtil.EMPTY_BUFFER);
// Check if after the fill() we need to wrap again
if (_sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_WRAP)
continue;
}
return allConsumed && BufferUtil.isEmpty(_encryptedOutput);
case FINISHED:
throw new IllegalStateException();
}
}
}
}
}
catch (SSLHandshakeException x)
{
notifyHandshakeFailed(_sslEngine, x);
throw x;
}
finally
{
BufferUtil.flipToFlush(_encryptedOutput, pos);
}
if (LOG.isDebugEnabled())
LOG.debug("{} wrap {}", SslConnection.this, wrapResult.toString().replace('\n',' '));
Status wrapResultStatus = wrapResult.getStatus();
boolean allConsumed=true;
for (ByteBuffer b : appOuts)
if (BufferUtil.hasContent(b))
allConsumed=false;
// and deal with the results returned from the sslEngineWrap
switch (wrapResultStatus)
{
case CLOSED:
{
// The SSL engine has close, but there may be close handshake that needs to be written
if (BufferUtil.hasContent(_encryptedOutput))
{
_cannotAcceptMoreAppDataToFlush = true;
getEndPoint().flush(_encryptedOutput);
getEndPoint().shutdownOutput();
// If we failed to flush the close handshake then we will just pretend that
// the write has progressed normally and let a subsequent call to flush
// (or WriteFlusher#onIncompleteFlushed) to finish writing the close handshake.
// The caller will find out about the close on a subsequent flush or fill.
if (BufferUtil.hasContent(_encryptedOutput))
return false;
}
// otherwise we have written, and the caller will close the underlying connection
else
{
getEndPoint().shutdownOutput();
}
return allConsumed;
}
case BUFFER_UNDERFLOW:
{
throw new IllegalStateException();
}
default:
{
if (LOG.isDebugEnabled())
LOG.debug("{} wrap {} {}", SslConnection.this, wrapResultStatus, BufferUtil.toHexSummary(_encryptedOutput));
if (wrapResult.getHandshakeStatus() == HandshakeStatus.FINISHED && !_handshaken)
{
_handshaken = true;
if (LOG.isDebugEnabled())
LOG.debug("{} {} handshake succeeded {}/{}", SslConnection.this,
_sslEngine.getUseClientMode() ? "resumed client" : "server",
_sslEngine.getSession().getProtocol(),_sslEngine.getSession().getCipherSuite());
notifyHandshakeSucceeded(_sslEngine);
}
HandshakeStatus handshakeStatus = _sslEngine.getHandshakeStatus();
// Check whether renegotiation is allowed
if (_handshaken && handshakeStatus != HandshakeStatus.NOT_HANDSHAKING && !isRenegotiationAllowed())
{
if (LOG.isDebugEnabled())
LOG.debug("{} renegotiation denied", SslConnection.this);
getEndPoint().shutdownOutput();
return allConsumed;
}
// if we have net bytes, let's try to flush them
if (BufferUtil.hasContent(_encryptedOutput))
if (!getEndPoint().flush(_encryptedOutput))
getEndPoint().flush(_encryptedOutput); // one retry
// But we also might have more to do for the handshaking state.
switch (handshakeStatus)
{
case NOT_HANDSHAKING:
// If we have not consumed all and had just finished handshaking, then we may
// have just flushed the last handshake in the encrypted buffers, so we should
// try again.
if (!allConsumed && wrapResult.getHandshakeStatus()==HandshakeStatus.FINISHED && BufferUtil.isEmpty(_encryptedOutput))
continue;
// Return true if we consumed all the bytes and encrypted are all flushed
return allConsumed && BufferUtil.isEmpty(_encryptedOutput);
case NEED_TASK:
// run the task and continue
_sslEngine.getDelegatedTask().run();
continue;
case NEED_WRAP:
// Hey we just wrapped! Oh well who knows what the sslEngine is thinking, so continue and we will wrap again
continue;
case NEED_UNWRAP:
// Ah we need to fill some data so we can write.
// So if we were not called from fill and the app is not reading anyway
if (!_fillRequiresFlushToProgress && !getFillInterest().isInterested())
{
// Tell the onFillable method that there might be a write to complete
_flushRequiresFillToProgress = true;
fill(BufferUtil.EMPTY_BUFFER);
// Check if after the fill() we need to wrap again
if (_sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_WRAP)
continue;
}
return allConsumed && BufferUtil.isEmpty(_encryptedOutput);
case FINISHED:
throw new IllegalStateException();
}
}
releaseEncryptedOutputBuffer();
}
}
}
catch (SSLHandshakeException x)
{
notifyHandshakeFailed(_sslEngine, x);
close(x);
throw x;
}
catch (Throwable x)
{
close(x);
throw x;
}
finally
{
releaseEncryptedOutputBuffer();
}
}
private void releaseEncryptedOutputBuffer()