8020801: Apply the restriction of invoking MethodHandles.lookup to j.l.r.Method.invoke
authormchung
Mon, 08 May 2017 21:21:39 -0700
changeset 45027 0cf367e546fb
parent 45026 bc9b3e93b6c5
child 45028 b0ea3c0bfb81
8020801: Apply the restriction of invoking MethodHandles.lookup to j.l.r.Method.invoke Reviewed-by: plevart, psandoz
jdk/src/java.base/share/classes/java/lang/invoke/MethodHandles.java
jdk/src/java.base/share/classes/jdk/internal/reflect/ReflectionFactory.java
jdk/test/java/lang/invoke/lookup/ReflectiveLookupTest.java
jdk/test/java/lang/invoke/lookup/java.base/java/lang/LookupTest.java
--- a/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandles.java	Sun May 07 19:01:13 2017 -0700
+++ b/jdk/src/java.base/share/classes/java/lang/invoke/MethodHandles.java	Mon May 08 21:21:39 2017 -0700
@@ -113,6 +113,19 @@
     }
 
     /**
+     * This reflected$lookup method is the alternate implementation of
+     * the lookup method when being invoked by reflection.
+     */
+    @CallerSensitive
+    private static Lookup reflected$lookup() {
+        Class<?> caller = Reflection.getCallerClass();
+        if (caller.getClassLoader() == null) {
+            throw newIllegalArgumentException("illegal lookupClass: "+caller);
+        }
+        return new Lookup(caller);
+    }
+
+    /**
      * Returns a {@link Lookup lookup object} which is trusted minimally.
      * The lookup has the {@code PUBLIC} and {@code UNCONDITIONAL} modes.
      * It can only be used to create method handles to public members of
@@ -747,7 +760,7 @@
         Lookup(Class<?> lookupClass) {
             this(lookupClass, FULL_POWER_MODES);
             // make sure we haven't accidentally picked up a privileged class:
-            checkUnprivilegedlookupClass(lookupClass, FULL_POWER_MODES);
+            checkUnprivilegedlookupClass(lookupClass);
         }
 
         private Lookup(Class<?> lookupClass, int allowedModes) {
@@ -827,7 +840,7 @@
                 newModes = 0;
             }
 
-            checkUnprivilegedlookupClass(requestedLookupClass, newModes);
+            checkUnprivilegedlookupClass(requestedLookupClass);
             return new Lookup(requestedLookupClass, newModes);
         }
 
@@ -979,25 +992,10 @@
          */
         static final Lookup PUBLIC_LOOKUP = new Lookup(Object.class, (PUBLIC|UNCONDITIONAL));
 
-        private static void checkUnprivilegedlookupClass(Class<?> lookupClass, int allowedModes) {
+        private static void checkUnprivilegedlookupClass(Class<?> lookupClass) {
             String name = lookupClass.getName();
             if (name.startsWith("java.lang.invoke."))
                 throw newIllegalArgumentException("illegal lookupClass: "+lookupClass);
-
-            // For caller-sensitive MethodHandles.lookup() disallow lookup from
-            // restricted packages.  This a fragile and blunt approach.
-            // TODO replace with a more formal and less fragile mechanism
-            // that does not bluntly restrict classes under packages within
-            // java.base from looking up MethodHandles or VarHandles.
-            if (allowedModes == FULL_POWER_MODES && lookupClass.getClassLoader() == null) {
-                if ((name.startsWith("java.") &&
-                     !name.equals("java.lang.Thread") &&
-                     !name.startsWith("java.util.concurrent.")) ||
-                    (name.startsWith("sun.") &&
-                     !name.startsWith("sun.invoke."))) {
-                    throw newIllegalArgumentException("illegal lookupClass: " + lookupClass);
-                }
-            }
         }
 
         /**
--- a/jdk/src/java.base/share/classes/jdk/internal/reflect/ReflectionFactory.java	Sun May 07 19:01:13 2017 -0700
+++ b/jdk/src/java.base/share/classes/jdk/internal/reflect/ReflectionFactory.java	Mon May 08 21:21:39 2017 -0700
@@ -135,6 +135,24 @@
         return soleInstance;
     }
 
+    /**
+     * Returns an alternate reflective Method instance for the given method
+     * intended for reflection to invoke, if present.
+     *
+     * A trusted method can define an alternate implementation for a method `foo`
+     * by defining a method named "reflected$foo" that will be invoked
+     * reflectively.
+     */
+    private static Method findMethodForReflection(Method method) {
+        String altName = "reflected$" + method.getName();
+        try {
+           return method.getDeclaringClass()
+                        .getDeclaredMethod(altName, method.getParameterTypes());
+        } catch (NoSuchMethodException ex) {
+            return null;
+        }
+    }
+
     //--------------------------------------------------------------------------
     //
     // Routines used by java.lang.reflect
@@ -161,6 +179,13 @@
     public MethodAccessor newMethodAccessor(Method method) {
         checkInitted();
 
+        if (Reflection.isCallerSensitive(method)) {
+            Method altMethod = findMethodForReflection(method);
+            if (altMethod != null) {
+                method = altMethod;
+            }
+        }
+
         if (noInflation && !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {
             return new MethodAccessorGenerator().
                 generateMethod(method.getDeclaringClass(),
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/invoke/lookup/ReflectiveLookupTest.java	Mon May 08 21:21:39 2017 -0700
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8020801
+ * @summary Restriction on reflective call to MethodHandles.lookup method
+ * @run main java.base/java.lang.LookupTest
+ * @run main ReflectiveLookupTest
+ * @run main/othervm -Dsun.reflect.noInflation=true ReflectiveLookupTest
+ */
+
+import java.lang.invoke.*;
+import java.lang.invoke.MethodHandles.Lookup;
+import java.lang.reflect.Method;
+
+import static java.lang.invoke.MethodType.*;
+
+/*
+ * Lookup object can be obtained statically or reflectively.
+ */
+public class ReflectiveLookupTest {
+    public static void main(String... args) throws Throwable {
+        // Get a full power lookup
+        Lookup lookup1 =  MethodHandles.lookup();
+        MethodHandle mh1 = lookup1.findStatic(lookup1.lookupClass(),
+                                              "foo",
+                                              methodType(String.class));
+        assertEquals((String) mh1.invokeExact(), foo());
+
+        Method lookupMethod =  MethodHandles.class.getMethod("lookup");
+        System.out.println("reflection method: " + lookupMethod);
+        if (!lookupMethod.getName().equals("lookup")) {
+            throw new RuntimeException("Unexpected name: " + lookupMethod.getName());
+        }
+
+        // Get a full power Lookup reflectively.
+        Lookup lookup2 = (Lookup) lookupMethod.invoke(null);
+        assertEquals(lookup1.lookupClass(), lookup2.lookupClass());
+        assertEquals(lookup1.lookupModes(), lookup2.lookupModes());
+        MethodHandle mh2 = lookup2.findStatic(lookup2.lookupClass(),
+                                             "foo",
+                                              methodType(String.class));
+        assertEquals((String) mh2.invokeExact(), foo());
+    }
+
+    static String foo() {
+        return "foo!";
+    }
+
+    static void assertEquals(Object o1, Object o2) {
+        if (!o1.equals(o2)) {
+            throw new RuntimeException(o1 + " != " + o2);
+        }
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/invoke/lookup/java.base/java/lang/LookupTest.java	Mon May 08 21:21:39 2017 -0700
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.lang;
+
+import java.lang.invoke.*;
+import java.lang.invoke.MethodHandles.Lookup;
+import java.lang.reflect.InvocationTargetException;
+
+import static java.lang.invoke.MethodType.*;
+
+/*
+ * Verify that a Lookup object can be obtained statically from java.base
+ * but fails when it's obtained via reflection from java.base.
+ */
+public class LookupTest {
+    public static void main(String... args) throws Throwable {
+        // Get a full power lookup
+        Lookup lookup1 =  MethodHandles.lookup();
+        MethodHandle mh1 = lookup1.findStatic(lookup1.lookupClass(),
+                                              "foo",
+                                              methodType(String.class));
+        assertEquals((String) mh1.invokeExact(), foo());
+
+        // access protected member
+        MethodHandle mh2 = lookup1.findVirtual(java.lang.ClassLoader.class,
+                                               "getPackage",
+                                               methodType(Package.class, String.class));
+        ClassLoader loader = ClassLoader.getPlatformClassLoader();
+        Package pkg = (Package)mh2.invokeExact(loader, "java.lang");
+        assertEquals(pkg.getName(), "java.lang");
+
+        // MethodHandles.lookup will fail if it's called reflectively
+        try {
+            MethodHandles.class.getMethod("lookup").invoke(null);
+        } catch (InvocationTargetException e) {
+            if (!(e.getCause() instanceof IllegalArgumentException)) {
+                throw e.getCause();
+            }
+        }
+    }
+
+    static String foo() { return "foo!"; }
+
+    static void assertEquals(Object o1, Object o2) {
+        if (!o1.equals(o2)) {
+            throw new RuntimeException(o1 + " != " + o2);
+        }
+    }
+}