On Wed, 7 Sep 2022 00:18:00 GMT, zzambers <d...@openjdk.org> wrote:

>> There is a race condition in JDK's SessionManager, which can lead to random 
>> exceptions.
>> 
>> **Exception:**
>> 
>> javax.net.ssl.SSLException: Internal error: close session with active objects
>>      at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:133)
>>      at 
>> java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:371)
>>      at 
>> java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:314)
>>      at 
>> java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:309)
>>      at 
>> java.base/sun.security.ssl.SSLSocketImpl.handleException(SSLSocketImpl.java:1707)
>>      at 
>> java.base/sun.security.ssl.SSLSocketImpl$AppInputStream.read(SSLSocketImpl.java:1080)
>>      at 
>> java.base/sun.security.ssl.SSLSocketImpl$AppInputStream.read(SSLSocketImpl.java:971)
>>      at SSLSocketServer.serverLoop(SSLSocketServer.java:133)
>>      at SSLSocketServer$1.run(SSLSocketServer.java:75)
>>      at java.base/java.lang.Thread.run(Thread.java:833)
>> Caused by: java.security.ProviderException: Internal error: close session 
>> with active objects
>>      at 
>> jdk.crypto.cryptoki/sun.security.pkcs11.Session.close(Session.java:127)
>>      at 
>> jdk.crypto.cryptoki/sun.security.pkcs11.Session.close(Session.java:114)
>>      at 
>> jdk.crypto.cryptoki/sun.security.pkcs11.SessionManager.closeSession(SessionManager.java:237)
>>      at 
>> jdk.crypto.cryptoki/sun.security.pkcs11.SessionManager$Pool.release(SessionManager.java:270)
>>      at 
>> jdk.crypto.cryptoki/sun.security.pkcs11.SessionManager.demoteObjSession(SessionManager.java:210)
>>      at 
>> jdk.crypto.cryptoki/sun.security.pkcs11.Session.removeObject(Session.java:101)
>>      at 
>> jdk.crypto.cryptoki/sun.security.pkcs11.SessionKeyRef.updateNativeKey(P11Key.java:1396)
>>      at 
>> jdk.crypto.cryptoki/sun.security.pkcs11.SessionKeyRef.removeNativeKey(P11Key.java:1377)
>>      at 
>> jdk.crypto.cryptoki/sun.security.pkcs11.NativeKeyHolder.releaseKeyID(P11Key.java:1329)
>>      at 
>> jdk.crypto.cryptoki/sun.security.pkcs11.P11Key.releaseKeyID(P11Key.java:156)
>>      at 
>> jdk.crypto.cryptoki/sun.security.pkcs11.P11AEADCipher.reset(P11AEADCipher.java:529)
>>      at 
>> jdk.crypto.cryptoki/sun.security.pkcs11.P11AEADCipher.ensureInitialized(P11AEADCipher.java:436)
>>      at 
>> jdk.crypto.cryptoki/sun.security.pkcs11.P11AEADCipher.implDoFinal(P11AEADCipher.java:732)
>>      at 
>> jdk.crypto.cryptoki/sun.security.pkcs11.P11AEADCipher.engineDoFinal(P11AEADCipher.java:624)
>>      at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2500)
>>      at 
>> java.base/sun.security.ssl.SSLCipher$T12GcmReadCipherGenerator$GcmReadCipher.decrypt(SSLCipher.java:1659)
>>      at 
>> java.base/sun.security.ssl.SSLSocketInputRecord.decodeInputRecord(SSLSocketInputRecord.java:260)
>>      at 
>> java.base/sun.security.ssl.SSLSocketInputRecord.decode(SSLSocketInputRecord.java:181)
>>      at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:111)
>>      at 
>> java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1508)
>>      at 
>> java.base/sun.security.ssl.SSLSocketImpl.readApplicationRecord(SSLSocketImpl.java:1479)
>>      at 
>> java.base/sun.security.ssl.SSLSocketImpl$AppInputStream.read(SSLSocketImpl.java:1064)
>>      ... 4 more
>> 
>> 
>> **Reproducibility:**
>> I started getting this exception quite reliably on JDK17 on my machine with 
>> one particular test setup using ssl-tests testsuite. Unfortunately setup 
>> itself needed some RH specific patches and also ability to reproduce depends 
>> on other factors such number keys in keystore, machine where testing was 
>> performed... I tried a bit to create some reproducer, but I couldn't find a 
>> way to easily reproduce this issue :(
>> 
>> **Problem:**
>> SunPKCS11 provider does session pooling. This is done in SessionManager [1] 
>> (one per SunPKCS11 provider). Released sessions are kept by SessionManager 
>> for a while, for reuse (in limited number). This however is a bit 
>> complicated as some sessions can own objects (e.g. keys). So there are 
>> actually 2 pools. One for sessions with objects ("objSessions") and one for 
>> sessions without objects ("opSessions"). This is because sessions without 
>> objects, which are not being used, can be safely closed (SessionManager only 
>> keeps around limited amount of these), while sessions with objects cannot be 
>> safely closed (until all objects are removed from them). Session manager has 
>> methods for getting Session for given purpose (object creation or just doing 
>> other operations), prioritizing appropriate pool. Each session has counter 
>> (called "createdObjects") to track how many objects it owns. When session is 
>> being returned to pool this counter is checked and session is placed to 
>> appropriate pool. Also wh
 en counter for some Session in "objSessions" pool reaches zero it is moved 
("demoted") to "opSessions" pool.
>> 
>> And here comes complicated part. As far as I understand it, 
>> Session.addObject() [2] (which increases "createdObjects" counter) is always 
>> being called by thread "holding" session which owns the created object. 
>> (That is: thread gets a session, uses it to create an object and calls 
>> Session.addObject() on that session to increase the counter, before 
>> returning the session to pool. See e.g.: [3]) However this is not true for 
>> Session.removeObject() [4]. (That is: thread gets session, which is not 
>> necessary the same one owning object being removed, performs object removal, 
>> but then calls Session.removeObject() on session which owned that object. 
>> See e.g.: [5]) That is Session.removeObject() can be called on Session which 
>> is in "objSessions" pool or which is being used be other thread. (object 
>> removal can happen as result of releasing key, either explicitly or as 
>> result of GC etc..). 
>> 
>> And finally, there is a problem in code handling object removal from a 
>> session. Session.removeObject() [4] first checks if "createdObjects" counter 
>> reached zero. If so, it calls SessionManager.demoteObjSession(this) [6], 
>> which attempts to remove Session from objSessions pool, if session is 
>> successfully removed from there, meaning no other thread "holds" this 
>> session, session is put to opSessions pool, if not (meaning other thread 
>> "holds" it), method just returns, since that other thread puts this session 
>> to appropriate pool, when it is done with it by calling 
>> SessionManager.releaseSession(session).
>> 
>> There is race condition here. Consider following scenario:
>> 
>> // Thread T1 runs:
>> Session.removeObject() // [4]
>> createdObjects.decrementAndGet() // returns zero
>> 
>> // Thread T2 steps in (operating on the same session instance):
>> Session.addObject() // increases "createdObjects" counter [2]
>> SessionManager.releaseSession(session) // releases session to objSessions 
>> pool
>> 
>> // Thread T1 continues:
>> SessionManager.demoteObjSession(this) // [6]
>> objSessions.remove(session) // returns true
>> opSessions.release(session)  // puts session (with objects!) to opSessions 
>> pool
>> // if opSessions is already full, close of session with objects is attempted 
>> throwing Exception..
>> 
>> 
>> **Fix:**
>> SessionManager.demoteObjSession [6] method was changed, so that check for 
>> objects is done once again if session was successfully removed from 
>> "objSessions" pool (now that it is out of pool and other threads should not 
>> be adding objects to it). Based on this check session is either released to 
>> "opSessions" pool or returned to "objSessions" pool. This can be achieved by 
>> calling releaseSession(session) instead of opSessions.release(session).
>> 
>> **Testing:**
>> jdk_security tests passed for me locally with this change.
>> I have also tested this change on top of custom JDK17 build which allows 
>> scenario, where I can reproduce this issue. Problem got fixed.
>> 
>> [1] 
>> https://github.com/openjdk/jdk/blob/9444a081cc9873caa7b5c6a78df0d1aecda6e4f1/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/SessionManager.java
>> [2] 
>> https://github.com/openjdk/jdk/blob/9444a081cc9873caa7b5c6a78df0d1aecda6e4f1/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/Session.java#L93
>> [3] 
>> https://github.com/openjdk/jdk/blob/9444a081cc9873caa7b5c6a78df0d1aecda6e4f1/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Key.java#L1339
>> [4] 
>> https://github.com/openjdk/jdk/blob/9444a081cc9873caa7b5c6a78df0d1aecda6e4f1/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/Session.java#L98
>> [5] 
>> https://github.com/openjdk/jdk/blob/9444a081cc9873caa7b5c6a78df0d1aecda6e4f1/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/P11Key.java#L1360
>> [6] 
>> https://github.com/openjdk/jdk/blob/9444a081cc9873caa7b5c6a78df0d1aecda6e4f1/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/SessionManager.java#L195
>
> zzambers has updated the pull request incrementally with one additional 
> commit since the last revision:
> 
>   Added comment, updated copyright date

Marked as reviewed by valeriep (Reviewer).

-------------

PR: https://git.openjdk.org/jdk/pull/10125

Reply via email to