A Bug involving MethodHandles, Nestmates, Reflection and @CallerSensitive
Mandy Chung
mandy.chung at oracle.com
Tue Dec 8 04:40:56 UTC 2020
Thanks David. I was about to create one.
This is indeed a gap in injecting this trampoline class for supporting
@CallerSensitive methods. The InjectedInvoker ensures that the
InjectedInvoker class has the same class loader as the lookup class.
W.r.t. your patch, it seems okay but I have to consider and think
through the security implication carefully.
You mentioned "Some old software loves to set static final fields
through reflection" - can you share some example libraries about this?
This is really ugly hack!!
Mandy
On 12/7/20 8:13 PM, David Holmes wrote:
> Hi Johannes,
>
> I've filed this bug report for you:
>
> https://bugs.openjdk.java.net/browse/JDK-8257874
>
> Thanks,
> David
>
> On 8/12/2020 11:20 am, Johannes Kuhn wrote:
>> Let's start with the reproducer:
>>
>> public class NestmateBug {
>> private static int field = 0;
>> public static void main(String[] args) throws Throwable {
>> Lookup l = MethodHandles.lookup();
>> Field f = NestmateBug.class.getDeclaredField("field");
>> MethodHandle mh = l.findVirtual(Field.class, "setInt",
>> methodType(void.class, Object.class, int.class));
>> int newValue = 5;
>> mh.invokeExact(f, (Object) null, newValue);
>> }
>> }
>>
>> This throws a IAE in the last line:
>>
>> class test.se15.NestmateBug$$InjectedInvoker/0x0000000800bb5840 (in
>> module test.se15) cannot access a member of class
>> test.se15.NestmateBug (in module test.se15) with modifiers "private
>> static"
>> at
>> java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:385)
>>
>> at
>> java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:693)
>>
>> at java.base/java.lang.reflect.Field.checkAccess(Field.java:1096)
>> at java.base/java.lang.reflect.Field.setInt(Field.java:976)
>> at test.se15/test.se15.NestmateBug.main(NestmateBug.java:17)
>>
>> The reason for this behaviour is:
>> * Field.setInt (without setAccessible) requires that the caller class
>> is in the same nest for private members.
>> * Field.setInt is @CallerSensitive
>> * MethodHandles will bind to the caller by injecting an invoker for
>> @CallerSensitive methods.
>> * The injected invoker is NOT a nestmate of the original lookup class.
>> * The access check of Field.setInt fails - as the injected invoker is
>> now the caller.
>>
>> This is important because:
>> * Some old software loves to set static final fields through reflection.
>> * To do that, it usually hacks into Field.modifiers. Which is
>> filtered iirc since Java 12.
>> * I write Java agents to fix such things - removing final from static
>> fields, and intercept Class.getDeclaredField and Field.setInt by
>> replacing it with an invokedynamic instruction.
>> * The original target is passed as a MethodHandle bootstrap argument.
>> In the case of Field.setInt, it is guarded with a
>> MethodHandles.guardWithTest() - calling the original target if the
>> Field is not my canary.
>>
>> Suggested fix:
>> * Make the injected invoker a nestmate of lookup class.
>>
>> I did prepare a fix for that [1], but AFAIK the bug first needs to be
>> filled in the bug tracker.
>> And I don't have an account.
>>
>> - Johannes
>>
>>
>> [1]:
>> https://github.com/DasBrain/jdk/commit/3c4bb20c8e4cd9086e934128e5cb085a5cfbdb94
More information about the core-libs-dev
mailing list