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

Ensure networkBio is closed in ConscryptEngine #893

Closed
wants to merge 5 commits into from

Conversation

kruton
Copy link
Contributor

@kruton kruton commented Sep 11, 2020

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 Reviewable

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)
ssl, NativeSsl.this, bio, destAddress, destLength, handshakeCallbacks);
} finally {
lock.readLock().unlock();
}
}

void close() {
Copy link
Contributor Author

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()

Copy link
Collaborator

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()) {
Copy link
Contributor Author

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 ?

Copy link
Collaborator

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()) {
Copy link
Contributor Author

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 ?

Copy link
Collaborator

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() {
}
Copy link
Contributor Author

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 ?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, please.

Copy link
Collaborator

@prbprbprb prbprbprb left a 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();
Copy link
Collaborator

@prbprbprb prbprbprb Sep 14, 2020

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?

Choose a reason for hiding this comment

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

#88

@@ -655,27 +655,45 @@ private BioWrapper() throws SSLException {
}

int getPendingWrittenBytes() {
if (bio != 0) {
if (bio != 0L) {
Copy link
Collaborator

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() {
}
Copy link
Collaborator

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()) {
Copy link
Collaborator

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()) {
Copy link
Collaborator

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");
Copy link
Collaborator

@prbprbprb prbprbprb Sep 14, 2020

Choose a reason for hiding this comment

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

  1. That error message won't make sense for apps using SSLEngine directly.
  2. I think these checks (here and in readDirect...) should be at the ConscryptEngine level, both methods are only called from a single place.

ssl, NativeSsl.this, bio, destAddress, destLength, handshakeCallbacks);
} finally {
lock.readLock().unlock();
}
}

void close() {
Copy link
Collaborator

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.

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
@kruton
Copy link
Contributor Author

kruton commented Sep 28, 2020

@prbprbprb could you finish this PR? It doesn't appear I'll have time to respond this week.

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
@prbprbprb
Copy link
Collaborator

Sure! Are you mostly happy with my suggestions?

@kruton
Copy link
Contributor Author

kruton commented Sep 28, 2020 via email

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
prbprbprb added a commit to prbprbprb/conscrypt that referenced this pull request Dec 21, 2020
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.
prbprbprb added a commit to prbprbprb/conscrypt that referenced this pull request Dec 21, 2020
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.
prbprbprb added a commit to prbprbprb/conscrypt that referenced this pull request Dec 21, 2020
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.
prbprbprb added a commit to prbprbprb/conscrypt that referenced this pull request Dec 21, 2020
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.
@pparth
Copy link

pparth commented Jan 21, 2021

Any updates here?

@prbprbprb
Copy link
Collaborator

Any updates here?

Superceded by #942 which I just merged.

@prbprbprb prbprbprb closed this Jan 22, 2021
@pparth
Copy link

pparth commented Jan 25, 2021

ok, we will test and see. thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants