JDK-8179515 : Class java.util.concurrent.ThreadLocalRandom fails to Initialize when using SecurityManager
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.util.concurrent
  • Affected Version: 8,9
  • Priority: P2
  • Status: Closed
  • Resolution: Fixed
  • OS: generic
  • CPU: generic
  • Submitted: 2017-04-28
  • Updated: 2017-11-29
  • Resolved: 2017-05-12
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 10 JDK 8 JDK 9
10Fixed 8u152Fixed 9 b170Fixed
Related Reports
Relates :  
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.8.0_121"
Java(TM) SE Runtime Environment (build 1.8.0_121-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)


ADDITIONAL OS VERSION INFORMATION :
Linux 2.6.32-696.el6.x86_64

A DESCRIPTION OF THE PROBLEM :
The Java VM was launched with a SecurityManager enabled.  The following Java system properties were set on the command line used to launch the Java VM:
-Djava.security.policy=my.policy -Djava.security.manager

The ThreadLocalRandom class failed to initialize.  The following stack trace was seen:

Caused by: java.lang.NullPointerException
       at java.util.concurrent.ThreadLocalRandom.getProbe(ThreadLocalRandom.java:981)
       at java.util.concurrent.ConcurrentHashMap.fullAddCount(ConcurrentHashMap.java:2526)
       at java.util.concurrent.ConcurrentHashMap.addCount(ConcurrentHashMap.java:2266)
       at java.util.concurrent.ConcurrentHashMap.replaceNode(ConcurrentHashMap.java:1166)
       at java.util.concurrent.ConcurrentHashMap.remove(ConcurrentHashMap.java:1097)
       at java.security.ProtectionDomain$PDCache.processQueue(ProtectionDomain.java:522)
       at java.security.ProtectionDomain$PDCache.get(ProtectionDomain.java:507)
       at sun.security.provider.PolicyFile.implies(PolicyFile.java:1080)
       at java.security.ProtectionDomain.implies(ProtectionDomain.java:285)
       at java.security.AccessControlContext.checkPermission(AccessControlContext.java:450)
       at java.security.AccessController.checkPermission(AccessController.java:884)
       at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)
       at com.iontrading.arc.bootstrap.security.VerboseSecurityManager.checkPermission(VerboseSecurityManager.java:23)
       at java.lang.SecurityManager.checkPropertyAccess(SecurityManager.java:1294)
       at java.lang.System.getProperty(System.java:717)
       at sun.security.action.GetPropertyAction.run(GetPropertyAction.java:84)
       at sun.security.action.GetPropertyAction.run(GetPropertyAction.java:49)
       at java.security.AccessController.doPrivileged(Native Method)
       at java.util.concurrent.ThreadLocalRandom.initialSeed(ThreadLocalRandom.java:138)
       at java.util.concurrent.ThreadLocalRandom.<clinit>(ThreadLocalRandom.java:135)
       ... 29 more

All but one of the above code lines come from classes in the standard Oracle JDK version 8u121.  The class com.iontrading.arc.bootstrap.security.VerboseSecurityManager is a lightweight wrapper around class java.lang.SecurityManager which it extends.  The code line cited in the stack trace (VerboseSecurityManager.java:23) simply delegates to the superclass (calls super.checkPermission).  So I claim that the VerboseSecurityManager is not relevant to the issue and the issue could occur with the standard SecurityManager.

The problem appears to me to be caused as follows: java.util.concurrent.ThreadLocalRandom class initializer requires to use the java.lang.SecurityManager to read a system property.  The SecurityManager requires ThreadLocalRandom class to process the policy file.  So there appears to be a cyclic dependency in the Oracle JDK.



REGRESSION.  Last worked in version 8u111

ADDITIONAL REGRESSION INFORMATION: 
java version "1.8.0_102"
Java(TM) SE Runtime Environment (build 1.8.0_102-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.102-b14, mixed mode)

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
I do not have a step by step process to reproduce what I describe in the description field of this form.  I have supplied sample code below to reproduce a problem when using java.util.concurrent.ThreadLocalRandom class in the SecurityManager class.


ERROR MESSAGES/STACK TRACES THAT OCCUR :
Please see description above.

REPRODUCIBILITY :
This bug can be reproduced occasionally.

---------- BEGIN SOURCE ----------
The following sample source code illustrates the problem with using the  class java.util.concurrent.ThreadLocalRandom in the SecurityManager.  The sample source does not do exactly the same as the java.lang.SecurityManager class and it does not recreate the stack trace that I have provided in the description of this bug.  However I claim that the sample source shows the nature of the problem found in the Oracle JDK.

file TLR.java :

public class TLR {

    public static void main(String[] args) {
        java.util.concurrent.ThreadLocalRandom tlr = java.util.concurrent.ThreadLocalRandom.current();
        System.out.println(tlr.nextLong());
    }
}

file MySecMan.java

import java.security.Permission;

public class MySecMan extends SecurityManager {

    @Override
    public void checkPermission(final Permission p) {
        java.util.concurrent.ThreadLocalRandom tlr = java.util.concurrent.ThreadLocalRandom.current();
        System.out.println(tlr.nextLong() + String.valueOf(p));
        super.checkPermission(p);
    }
    
    @Override
    public void checkPermission(Permission p, Object context) {
        java.util.concurrent.ThreadLocalRandom tlr = java.util.concurrent.ThreadLocalRandom.current();
        System.out.println(tlr.nextLong() + String.valueOf(p));
        super.checkPermission(p, context);
    }
    
}

command line to execute:
java -cp . -Djava.security.manager=MySecMan TLR

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
No workaround found.


Comments
Thanks Martin but I'll need to create the changeset based on actual response to the RFR - which is going out now.
11-05-2017

The "reproducer" works with the fix, but also needs to put MySecMan on the bootclasspath.
11-05-2017

You can hg qimport the changeset below, or I can commit it if you like. http://cr.openjdk.java.net/~martin/webrevs/openjdk10/jsr166-integration/patches/jsr166-integration-ThreadLocalRandom
11-05-2017

This is approved for JDK 9.
11-05-2017

Impact = High - can't execute app Likelihood = Medium - use of TLR in a security context Workaround = Medium - need to try and decouple classes somehow ILW = HMM = P2
11-05-2017

Fix Request The dependency between the utility class ThreadLocalRandom and the security manager framework has always been potentially brittle. That brittleness was exposed by the recent change in JDK-8085903 which has highlighted how easy it is to get a cyclic dependency between TLR and the SecurityManager implementation. As TLR should be a generally useful utility class that can used safely from various core library and application contexts, the dependency on the security manager framework is problematic - that dependency stems from a single use of doPrivileged to read a system property. Doug Lea has provided a simple fix (above) using Alan Bateman's suggestion to use the VM.getSavedProperty internal API. This avoids any link to the security manager framework and so greatly improves the usability of TLR in various contexts. The fix is extremely low risk. There is no functional change in behaviour, only a change in the mechanism used to read the system property. The fix is tested by the existing sub-tests in ./java/util/concurrent/tck/JSR166TestCase.java The fix has been reviewed by myself and martin Buchholz and will go out for public review.
11-05-2017

We need to go through the JDK 9 RDP2 approval process: http://mail.openjdk.java.net/pipermail/jdk9-dev/2017-March/005689.html
09-05-2017

I'm not sure how we make progress from here. Do we want to simply commit this change as is to jdk9?
09-05-2017

We need to fix in 9 - that will then be auto-forwarded to 10. The 8u update can be handled by someone in Oracle.
09-05-2017

So, we have a fix checked in to jsr166 CVS, and here's a changeset for jdk10: http://cr.openjdk.java.net/~martin/webrevs/openjdk10/jsr166-integration/ThreadLocalRandom/ BUT ... I see this is a regression for jdk8 and we don't normally address those... and perhaps there should be a test ... can someone take ownership to shepherd this into jdk8+ ?
09-05-2017

Using VM.getSavedProperty seems like a good solution. Let's do it. Diffs: *** ThreadLocalRandom.java.~1.53.~ 2017-03-02 19:43:56.792169646 -0500 --- ThreadLocalRandom.java 2017-05-09 07:03:40.487972539 -0400 *************** *** 20,25 **** --- 20,26 ---- import java.util.stream.LongStream; import java.util.stream.StreamSupport; import jdk.internal.misc.Unsafe; + import jdk.internal.misc.VM; /** * A random number generator isolated to the current thread. Like the *************** *** 1064,1074 **** // at end of <clinit> to survive static initialization circularity static { ! if (java.security.AccessController.doPrivileged( ! new java.security.PrivilegedAction<>() { ! public Boolean run() { ! return Boolean.getBoolean("java.util.secureRandomSeed"); ! }})) { byte[] seedBytes = java.security.SecureRandom.getSeed(8); long s = (long)seedBytes[0] & 0xffL; for (int i = 1; i < 8; ++i) --- 1065,1072 ---- // at end of <clinit> to survive static initialization circularity static { ! String sec = VM.getSavedProperty("java.util.secureRandomSeed"); ! if (sec != null && Boolean.parseBoolean(sec)) { byte[] seedBytes = java.security.SecureRandom.getSeed(8); long s = (long)seedBytes[0] & 0xffL; for (int i = 1; i < 8; ++i)
09-05-2017

TLR could use VM.getSavedProperty to read system properties set on the command line without needing a privileged block.
09-05-2017

I think we need a way to break the link between ThreadLocalRandom and the security framework. Unfortunately we need to use doPrivileged to read the system property to get java.util.secureRandomSeed. ThreadLocalRandom should be a useful utility class that can be used anywhere in the core libraries or application code, but the connection to the security framework makes that problematic.
09-05-2017

I agree that the introduced cyclic dependency is potentially problematic.
09-05-2017

Additional information by submitter: As the person who reported the bug, I claim that the bug is a regression. The bug was reproducible intermittently using JDK 8u121; after downgrading to JDK 8u102 the bug has not been reproduced. The bug has been introduced in Oracle JDK 8u112 to fix this issue: https://bugs.openjdk.java.net/browse/JDK-8085903 The bug fix list for Oracle JDK 8u112 lists the above bug: http://www.oracle.com/technetwork/java/javase/2col/8u112-bugfixes-3124974.html The JDK code commit that caused the problem is here: http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/edb55dacef3e I claim that the use of java.util.concurrent.ConcurrentHashMap by class java.security.ProtectionDomain is problematic because it causes a cyclic dependency in the class initialization for java.util.concurrent.ThreadLocalRandom. Do you agree?
09-05-2017

I believe the basic problem is similar to that in JDK-8165753 (not the crash - that was a result of the problem). The custom security manager is not privileged and delegates to the default SecurityManager to check access - but because the custom security manager is not privileged we take different code paths in the security manager and hit the recursive use of ThreadLocalRandom from inside the ThreadLocalRandom <clinit>. Workaround: Try adding the real custom security manager to the bootclasspath. The potential for cycles between ThreadLocalRandom and SecurityManager still seems problematic though.
02-05-2017

For the output on 9 see the discussion in JDK-8165753.
02-05-2017

The "reproducer" is not valid because it explicitly introduces a cyclic dependency between the ThreadLocalRandom class and the SecurityManager class.
02-05-2017

Removed the regression label as same results obtained for JDK 8 as well as JDK 8u131. JDK 8 - Fail JDK 8u131 - Fail JDK 9-ea+161 - fail Output on JDK 8u131: ------------------------------- >java -cp . -Djava.security.manager=MySecMan TLR Error occurred during initialization of VM java.lang.ExceptionInInitializerError at MySecMan.checkPermission(MySecMan.java:7) at java.lang.SecurityManager.checkPropertyAccess(SecurityManager.java:1294) at java.lang.System.getProperty(System.java:717) at sun.misc.Launcher.<clinit>(Launcher.java:55) at java.lang.ClassLoader.initSystemClassLoader(ClassLoader.java:1451) at java.lang.ClassLoader.getSystemClassLoader(ClassLoader.java:1436) Caused by: java.lang.NullPointerException at java.util.concurrent.ThreadLocalRandom.current(ThreadLocalRandom.java:222) at MySecMan.checkPermission(MySecMan.java:7) at java.lang.SecurityManager.checkPropertyAccess(SecurityManager.java:1294) at java.lang.System.getProperty(System.java:717) at sun.security.action.GetPropertyAction.run(GetPropertyAction.java:84) at sun.security.action.GetPropertyAction.run(GetPropertyAction.java:49) at java.security.AccessController.doPrivileged(Native Method) at java.util.concurrent.ThreadLocalRandom.initialSeed(ThreadLocalRandom.java:138) at java.util.concurrent.ThreadLocalRandom.<clinit>(ThreadLocalRandom.java:135) at MySecMan.checkPermission(MySecMan.java:7) at java.lang.SecurityManager.checkPropertyAccess(SecurityManager.java:1294) at java.lang.System.getProperty(System.java:717) at sun.misc.Launcher.<clinit>(Launcher.java:55) at java.lang.ClassLoader.initSystemClassLoader(ClassLoader.java:1451) at java.lang.ClassLoader.getSystemClassLoader(ClassLoader.java:1436) Output on JDK9 ----------------------- -6538572733034583270("java.util.PropertyPermission" "*" "read,write") Error occurred during initialization of VM java.lang.ExceptionInInitializerError at jdk.internal.misc.Unsafe.ensureClassInitialized0(java.base@9-ea/Native Method) at jdk.internal.misc.Unsafe.ensureClassInitialized(java.base@9-ea/Unsafe.java:1023) at java.lang.invoke.DirectMethodHandle.shouldBeInitialized(java.base@9-ea/DirectMethodHandle.java:309) at java.lang.invoke.DirectMethodHandle.preparedLambdaForm(java.base@9-ea/DirectMethodHandle.java:170) at java.lang.invoke.DirectMethodHandle.make(java.base@9-ea/DirectMethodHandle.java:88) at java.lang.invoke.MethodHandles$Lookup.getDirectMethodCommon(java.base@9-ea/MethodHandles.java:2192) at java.lang.invoke.MethodHandles$Lookup.getDirectMethodNoSecurityManager(java.base@9-ea/MethodHandles.java:2149) at java.lang.invoke.MethodHandles$Lookup.getDirectMethodForConstant(java.base@9-ea/MethodHandles.java:2380) at java.lang.invoke.MethodHandles$Lookup.linkMethodHandleConstant(java.base@9-ea/MethodHandles.java:2329) at java.lang.invoke.MethodHandleNatives.linkMethodHandleConstant(java.base@9-ea/MethodHandleNatives.java:501) at MySecMan.checkPermission(MySecMan.java:8) at java.lang.SecurityManager.checkPropertyAccess(java.base@9-ea/SecurityManager.java:1304) at java.lang.System.getProperty(java.base@9-ea/System.java:762) at java.lang.ClassLoader.initSystemClassLoader(java.base@9-ea/ClassLoader.java:1934) at java.lang.System.initPhase3(java.base@9-ea/System.java:1976) Caused by: java.security.AccessControlException: access denied ("java.util.PropertyPermission" "*" "read,write") at java.security.AccessControlContext.checkPermission(java.base@9-ea/AccessControlContext.java:471) at java.security.AccessController.checkPermission(java.base@9-ea/AccessController.java:894) at java.lang.SecurityManager.checkPermission(java.base@9-ea/SecurityManager.java:560) at MySecMan.checkPermission(MySecMan.java:9) at java.lang.SecurityManager.checkPropertiesAccess(java.base@9-ea/SecurityManager.java:1272) at java.lang.System.getProperties(java.base@9-ea/System.java:675) at sun.security.action.GetPropertyAction$1.run(java.base@9-ea/GetPropertyAction.java:153) at sun.security.action.GetPropertyAction$1.run(java.base@9-ea/GetPropertyAction.java:151) at java.security.AccessController.doPrivileged(java.base@9-ea/Native Method) at sun.security.action.GetPropertyAction.privilegedGetProperties(java.base@9-ea/GetPropertyAction.java:150) at java.lang.invoke.StringConcatFactory.<clinit>(java.base@9-ea/StringConcatFactory.java:200) at jdk.internal.misc.Unsafe.ensureClassInitialized0(java.base@9-ea/Native Method) at jdk.internal.misc.Unsafe.ensureClassInitialized(java.base@9-ea/Unsafe.java:1023) at java.lang.invoke.DirectMethodHandle.shouldBeInitialized(java.base@9-ea/DirectMethodHandle.java:309) at java.lang.invoke.DirectMethodHandle.preparedLambdaForm(java.base@9-ea/DirectMethodHandle.java:170) at java.lang.invoke.DirectMethodHandle.make(java.base@9-ea/DirectMethodHandle.java:88) at java.lang.invoke.MethodHandles$Lookup.getDirectMethodCommon(java.base@9-ea/MethodHandles.java:2192) at java.lang.invoke.MethodHandles$Lookup.getDirectMethodNoSecurityManager(java.base@9-ea/MethodHandles.java:2149) at java.lang.invoke.MethodHandles$Lookup.getDirectMethodForConstant(java.base@9-ea/MethodHandles.java:2380) at java.lang.invoke.MethodHandles$Lookup.linkMethodHandleConstant(java.base@9-ea/MethodHandles.java:2329) at java.lang.invoke.MethodHandleNatives.linkMethodHandleConstant(java.base@9-ea/MethodHandleNatives.java:501) at MySecMan.checkPermission(MySecMan.java:8) at java.lang.SecurityManager.checkPropertyAccess(java.base@9-ea/SecurityManager.java:1304) at java.lang.System.getProperty(java.base@9-ea/System.java:762) at java.lang.ClassLoader.initSystemClassLoader(java.base@9-ea/ClassLoader.java:1934) at java.lang.System.initPhase3(java.base@9-ea/System.java:1976)
02-05-2017