-
Notifications
You must be signed in to change notification settings - Fork 284
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
Ensure networkBio is closed in ConscryptEngine #893
Conversation
Since BioWrapper does not have a finalizer, we need to ensure that the native resources are freed when it goes away. In all paths, "networkBio.close()" needs to be called to free the native resources. This change rearranges some items to make sure that the native resource never gets leaked. Possible fix for leak identified in google#835
For direct BIO access, the NativeSsl was not checked to see if it was open resulting in an exception every once in a while. Exception in thread "Keep-Alive-SocketCleaner" java.lang.NullPointerException: ssl == null at org.conscrypt.NativeCrypto.ENGINE_SSL_write_BIO_direct(Native Method) at org.conscrypt.NativeSsl$BioWrapper.writeDirectByteBuffer(NativeSsl.java:666) at org.conscrypt.ConscryptEngine.writeEncryptedDataDirect(ConscryptEngine.java:1166) at org.conscrypt.ConscryptEngine.writeEncryptedDataHeap(ConscryptEngine.java:1193) at org.conscrypt.ConscryptEngine.writeEncryptedData(ConscryptEngine.java:1151) at org.conscrypt.ConscryptEngine.unwrap(ConscryptEngine.java:838)
e4a4c9b
to
30f9218
Compare
ssl, NativeSsl.this, bio, destAddress, destLength, handshakeCallbacks); | ||
} finally { | ||
lock.readLock().unlock(); | ||
} | ||
} | ||
|
||
void close() { |
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.
It seems like this should hold the lock.writeLock()
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.
Definitely needs synchronisation as the finaliser will run on a GC thread, and the write lock seems more appropriate.
ssl, NativeSsl.this, bio, destAddress, destLength, handshakeCallbacks); | ||
lock.readLock().lock(); | ||
try { | ||
if (isClosed()) { |
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.
Should this check for bio == 0L
?
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.
See above
ssl, NativeSsl.this, bio, address, length, handshakeCallbacks); | ||
lock.readLock().lock(); | ||
try { | ||
if (isClosed()) { |
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.
Should this check for bio == 0L
?
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.
Either that or make the native code throw on bio == 0
rather than return -1 as it does at present, which doesn't seem to be handled consistently.
@@ -663,13 +663,29 @@ int getPendingWrittenBytes() { | |||
} |
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.
Should we use lock.readLock()
in the check in getPendingWrittenBytes
?
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.
Yes, please.
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.
Thanks for digging into this!
@@ -1683,6 +1683,7 @@ private void closeAndFreeResources() { | |||
protected void finalize() throws Throwable { | |||
try { | |||
transitionTo(STATE_CLOSED); | |||
networkBio.close(); |
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.
Maybe call closeAndFreeResources()
? If we get to this point and still have a live BIO, then odds are we haven't closed the SSL either.
However this should only happen if an engine gets GCed without close()
ever getting called (or at least, never completing), is that enough to account for the memory leakage?
It's possible that #844 made this worse in some cases... For the happy case, it ensures the native resources get freed on close()
rather than waiting until GC, but if the socket blocks on write then close()
can also hang. However that would manifest itself as a thread leak... Are you or @knaccc seeing 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.
@@ -655,27 +655,45 @@ private BioWrapper() throws SSLException { | |||
} | |||
|
|||
int getPendingWrittenBytes() { | |||
if (bio != 0) { | |||
if (bio != 0L) { |
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.
Note that NativeCrypto.SSL_pending_written_bytes_in_BIO()
returns 0 anyway if bio
is 0.
@@ -663,13 +663,29 @@ int getPendingWrittenBytes() { | |||
} |
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.
Yes, please.
ssl, NativeSsl.this, bio, address, length, handshakeCallbacks); | ||
lock.readLock().lock(); | ||
try { | ||
if (isClosed()) { |
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.
Either that or make the native code throw on bio == 0
rather than return -1 as it does at present, which doesn't seem to be handled consistently.
ssl, NativeSsl.this, bio, destAddress, destLength, handshakeCallbacks); | ||
lock.readLock().lock(); | ||
try { | ||
if (isClosed()) { |
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.
See above
lock.readLock().lock(); | ||
try { | ||
if (isClosed()) { | ||
throw new SocketException("Socket is closed"); |
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.
- That error message won't make sense for apps using
SSLEngine
directly. - I think these checks (here and in
readDirect...
) should be at theConscryptEngine
level, both methods are only called from a single place.
ssl, NativeSsl.this, bio, destAddress, destLength, handshakeCallbacks); | ||
} finally { | ||
lock.readLock().unlock(); | ||
} | ||
} | ||
|
||
void close() { |
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.
Definitely needs synchronisation as the finaliser will run on a GC thread, and the write lock seems more appropriate.
@prbprbprb could you finish this PR? It doesn't appear I'll have time to respond this week. |
Sure! Are you mostly happy with my suggestions? |
Yes, they all looked reasonable.
… |
Contains @kruton's PR google#893 plus my comments. Differences: Doesn't throw from `writeDirectByteBuffer()` (or read) if `isClosed()`. This code is only ever called from blocks that are synchronized on `ssl` so `ssl` cannot be null here, which is what `isClosed()` checks. Calls `closeAndFreeResources()` from `ConscryptEngine.finalizer()` which should ensure resources are consistently freed even if engines aren't properly closed. `closeAndFreeResources()` handles null `ssl` or `networkBio` - because it is called from the finalizer it must handle incompletely constructed instances. Also closes `networkBio` regardless of whether `ssl` is closed in case the engine ever gets into this state (e.g. if `close()` throws). Both close methods are idempotent. Contrary to my suggestion, no check for `bio == 0L` on read and write because native code will already throw `NullPointerException` for that which seems correct.
Contains @kruton's PR google#893 plus my comments. Differences: Throw `SslEnginei` not `SocketException` on read and write to closed `ssl`. Calls `closeAndFreeResources()` from `ConscryptEngine.finalizer()` which should ensure resources are consistently freed even if engines aren't properly closed. `closeAndFreeResources()` handles null `ssl` or `networkBio` - because it is called from the finalizer it must handle incompletely constructed instances. Also closes `networkBio` regardless of whether `ssl` is closed in case the engine ever gets into this state (e.g. if `close()` throws). Both close methods are idempotent. Contrary to my suggestion, no check for `bio == 0L` on read and write because native code will already throw `NullPointerException` for that which seems correct.
Contains @kruton's PR google#893 plus my comments. Differences: Throw `SslException` not `SocketException` on read and write to closed `ssl` as these will propogate out from `SSLEnngine.wrap()` (and unwrap). Calls `closeAndFreeResources()` from `ConscryptEngine.finalizer()` which should ensure resources are consistently freed even if engines aren't properly closed. `closeAndFreeResources()` handles null `ssl` or `networkBio` - because it is called from the finalizer it must handle incompletely constructed instances. Also closes `networkBio` regardless of whether `ssl` is closed in case the engine ever gets into this state (e.g. if `close()` throws). Both close methods are idempotent. Contrary to my suggestion, no check for `bio == 0L` on read and write because native code will already throw `NullPointerException` for that which seems correct.
Contains @kruton's PR google#893 plus my comments. Differences: Throw `SslException` not `SocketException` on read and write to closed `ssl` as these will propogate out from `SSLEngine.wrap()` (and unwrap). Calls `closeAndFreeResources()` from `ConscryptEngine.finalizer()` which should ensure resources are consistently freed even if engines aren't properly closed. `closeAndFreeResources()` handles null `ssl` or `networkBio` - because it is called from the finalizer it must handle incompletely constructed instances. Also closes `networkBio` regardless of whether `ssl` is closed in case the engine ever gets into this state (e.g. if `close()` throws). Both close methods are idempotent. Contrary to my suggestion, no check for `bio == 0L` on read and write because native code will already throw `NullPointerException` for that which seems correct.
Any updates here? |
Superceded by #942 which I just merged. |
ok, we will test and see. thanks! |
Since BioWrapper does not have a finalizer, we need to ensure that the
native resources are freed when it goes away. In all paths,
"networkBio.close()" needs to be called to free the native resources.
This change rearranges some items to make sure that the native resource
never gets leaked.
Possible fix for leak identified in #835
This change is