Replacement of sun.reflect.Reflection#getCallerClass

Peter Levart peter.levart at gmail.com
Thu Sep 19 12:03:31 UTC 2013


On 09/18/2013 05:21 PM, David M. Lloyd wrote:
> On 09/03/2013 12:16 PM, Peter Levart wrote:
> [...]
>> *AND* that Reflection.getCallerClass() can only be called from within
>>  methods annotated with @CallerSensitive.
>>
>> Now for that part, the public API equivalent
>> (StackTraceFrame.getCallerClass() or whatever it is called) need not
>> be restricted to methods annotated with any annotation, but that
>> means that this public API should not be used to implement security
>> decisions since MethodHandles API allows caller to be spoofed unless
>> looking-up a method annotated with @CallerSensitive...
>
> Peter, can you please elaborate on this a bit?  I could find nothing 
> in the MethodHandles API or its associated classes that would seem to 
> give the ability to call another method with a spoofed caller.  Yes 
> you can set up a Lookup for another class but I don't see how that 
> would affect the ability of (say) a security manager to make access 
> decisions based on the call stack/class context?
>

Hi David,

The thing with method handles is that they perform all security checks 
not at invoke-time (like reflection), but at lookup-time. If you have a 
hold on a MethodHandle object, you can always invoke it. All security 
checks must have been performed at lookup-time. This includes security 
checks that are based on what the "caller" of the method is. In case of 
method handles, the "caller" is the class associated with the Lookup 
object - the lookup class.

Try this example:

// --- the following be loaded by the bootstrap class loader
package sys;

import sun.reflect.CallerSensitive;
import sun.reflect.Reflection;

import java.lang.invoke.MethodHandles;

public class CSUtil
{
     public static final MethodHandles.Lookup lookup = 
MethodHandles.lookup();

     @CallerSensitive
     public static void printCallerClassLoader(String prefix) {
         Class<?> cc = Reflection.getCallerClass();
         System.err.println(prefix + ":\n  "
                            + cc + "\n  loaded by "
                            + cc.getClassLoader() + "\n");
     }

     public static class Nested {}
}


// ---the following be loaded by application class loader
package usr;

import sys.CSUtil;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;

public class CSTest
{
     public static final MethodHandles.Lookup lookup = 
MethodHandles.lookup();

     public static void main(String[] args) throws Throwable
     {
         MethodHandle mh1  = CSTest.lookup.findStatic(
             CSUtil.class,
             "printCallerClassLoader",
             MethodType.methodType(void.class, String.class)
         );

         mh1.invokeExact("mh1");

         MethodHandle mh2  = CSUtil.lookup.findStatic(
             CSUtil.class,
             "printCallerClassLoader",
             MethodType.methodType(void.class, String.class)
         );

         mh2.invokeExact("mh2");

         MethodHandle mh3  = 
CSUtil.lookup.in(CSUtil.Nested.class).findStatic(
             CSUtil.class,
             "printCallerClassLoader",
             MethodType.methodType(void.class, String.class)
         );

         mh3.invokeExact("mh3");

         MethodHandle mh4  = CSUtil.lookup.in(CSTest.class).findStatic(
             CSUtil.class,
             "printCallerClassLoader",
             MethodType.methodType(void.class, String.class)
         );

         mh4.invokeExact("mh4");
     }
}


...which prints:

mh1:
   class java.lang.invoke.MethodHandleImpl$BindCaller$T/523429237
   loaded by sun.misc.Launcher$AppClassLoader at 15db9742

mh2:
   class java.lang.invoke.MethodHandleImpl$BindCaller$T/1450495309
   loaded by null

mh3:
   class java.lang.invoke.MethodHandleImpl$BindCaller$T/2001049719
   loaded by null

Exception in thread "main" java.lang.IllegalAccessException: Attempt to 
lookup caller-sensitive method using restricted lookup object
     at 
java.lang.invoke.MethodHandles$Lookup.findBoundCallerClass(MethodHandles.java:1123)
     at 
java.lang.invoke.MethodHandles$Lookup.findStatic(MethodHandles.java:619)
     at usr.CSTest.main(CSTest.java:39)


You can see that the "caller" class it not actually the class doing 
lookup, but some anonymous class which is loaded by the same class 
loader as the lookup class and maybe it shares some other properties 
with it (it seems that this is sufficient for security checks). The mh3 
is interesting in that the "caller" is different from that of mh2. The 
Lookup.in(Class) method returns a Lookup object with different lookup 
class and so the "caller" class is different too. Although the 
printCallerClassLoader is a public method in a public class, the mh4 can 
not be looked-up, because this could be used to "spoof" the caller class 
loader for any such public method. Imagine:


         // this throws IllegalAccessException. If it didn't...
         MethodHandle mh = MethodHandles.lookup()
             .in(String.class)
             .findVirtual(
                 Field.class,
                 "get",
                 MethodType.methodType(Object.class, Object.class)
             );

         Field valueField = String.class.getDeclaredField("value");

         String abc = "abc";

         // ... then the following would invoke: valueField.get(abc) and 
pretend that
         // it was invoked from the String class, which would succeed...
         char[] abcArray = (char[])(Object) mh.invokeExact(valueField, 
(Object) abc);

         // ...and you could observe the following:
         abcArray[0] = 'A';
         assert abc.charAt(0) == 'A';



Regards, Peter




More information about the core-libs-dev mailing list