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@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

Reply via email to