JDK-8202113 : Reflection API is causing caller classes to leak
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.lang:reflect
  • Affected Version: 9,11
  • Priority: P4
  • Status: Closed
  • Resolution: Fixed
  • OS: linux
  • CPU: x86_64
  • Submitted: 2018-04-22
  • Updated: 2018-07-20
  • Resolved: 2018-05-11
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 11
11 b14Fixed
Related Reports
Relates :  
Relates :  
Relates :  
Description
A DESCRIPTION OF THE PROBLEM :
When using reflection API there are security checks to the callers (unless .setAccessible(true) is called before),
those checks are cached in `Executable::securityCheckCache` field, and that field is pointing to the caller class (and its class loader).

The problem occurs when the method is first invoked, and at that a JNI accessor is created (`NativeMethodAccessorImpl`).

The bug is that instead of passing the root method, the code is passing the child method, which could contain such `securityCheckCache` cache.

Before JDK 9 this bug was happening only for protected method and constructors, because there was a quick path to avoid security cache (`Reflection.quickCheckMemberAccess`) which was removed in JDK 9.

Since JDK 9 it is happening for all variations. The following code `Thread.class.getMethod("currentThread").invoke(null)` will cause the caller to leak.

For methods and constructors the path to root GC is kept as long as `NativeMethodAccessorImpl` (or `NativeConstructorAccessorImpl`) is kept,
which is gone after inflationThreshold (15) calls to the method (or constuctor).
Afterwards the accessor is turned into bytecode, and no longer holds reference to Java objects.

But for fields the leak will stay forever, and will always leak the first caller, if that caller required security check.

The fix is passing the parent to the accessor, since it contains the same information required, but won't cause any leaks for caller classes.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
I've attached patch which adds a unit-test and fixes the problem.




CUSTOMER SUBMITTED WORKAROUND :
A workaround to bypass the bug is to make sure to invoke the method which will be used in future class loaders before they use it. Because then the accessor is pointing to the app class loader, and there can't be any leak.

This workaround is very targeted, and you must know which methods are being used in your application server.
If you have a memory dump, you can use Eclipse Memory Analyzer to run the following OQL script to find those methods:

SELECT * FROM jdk.internal.reflect.NativeMethodAccessorImpl s WHERE
((s.method.securityCheckCache != null) and
 (s.method.root.methodAccessor.delegate = s) and
 (s.method.securityCheckCaches.@clazz.@classLoaderId != s.method.clazz.@classLoaderId))


FREQUENCY : always



Comments
The fix was revised not to break any existing code depending on modifying the private modifiers field of Field object. A warning has been issued for those code changing a private field of java.lang.reflect.Field that may break in the future when illegal access is denied. So this would allow existing code to modify their code until the default of illegal access is changed to deny.
12-05-2018

Review thread: http://mail.openjdk.java.net/pipermail/core-libs-dev/2018-April/052873.html The proposed change has a potential incompatibility. Before the change, one can change a Field object of a static final field to a static non-final field as follows: Field f = Field.class.getDeclaredField("modifiers"); f.setAccessible(true); f.setInt(finalStaticField, f.getInt(f) - Modifier.FINAL); // make static final field non-final finalStaticField.set(null, newValue); This hack no longer works with the proposed patch because the root Field is passed to create the field accessor object that will check the modifiers in the root object which has the original modifiers (i.e. static final). There is no impact to instance final field.
02-05-2018

Additional Information from submitter: Patch to fix this bug + unit-tests for jdk: patch is attched 8202113.patch
23-04-2018