7197546: (proxy) Reflect about creating reflective proxies
authormchung
Fri, 02 Nov 2012 16:50:23 -0700
changeset 16087 89b565a23835
parent 16086 0a1053497df1
child 16088 8edf3fb360ea
7197546: (proxy) Reflect about creating reflective proxies Reviewed-by: alanb, jdn, jrose
jdk/src/share/classes/java/lang/Class.java
jdk/src/share/classes/java/lang/invoke/MethodHandleProxies.java
jdk/src/share/classes/java/lang/reflect/Proxy.java
jdk/src/share/classes/sun/reflect/misc/ReflectUtil.java
jdk/test/java/rmi/server/RMIClassLoader/loadProxyClasses/security.policy
--- a/jdk/src/share/classes/java/lang/Class.java	Tue Oct 30 17:05:45 2012 +0400
+++ b/jdk/src/share/classes/java/lang/Class.java	Fri Nov 02 16:50:23 2012 -0700
@@ -60,7 +60,9 @@
 import sun.reflect.generics.scope.ClassScope;
 import sun.security.util.SecurityConstants;
 import java.lang.annotation.Annotation;
+import java.lang.reflect.Proxy;
 import sun.reflect.annotation.*;
+import sun.reflect.misc.ReflectUtil;
 
 /**
  * Instances of the class {@code Class} represent classes and
@@ -247,11 +249,11 @@
                                    ClassLoader loader)
         throws ClassNotFoundException
     {
-        if (loader == null) {
+        if (sun.misc.VM.isSystemDomainLoader(loader)) {
             SecurityManager sm = System.getSecurityManager();
             if (sm != null) {
                 ClassLoader ccl = ClassLoader.getCallerClassLoader();
-                if (ccl != null) {
+                if (!sun.misc.VM.isSystemDomainLoader(ccl)) {
                     sm.checkPermission(
                         SecurityConstants.GET_CLASSLOADER_PERMISSION);
                 }
@@ -316,7 +318,7 @@
         throws InstantiationException, IllegalAccessException
     {
         if (System.getSecurityManager() != null) {
-            checkMemberAccess(Member.PUBLIC, ClassLoader.getCallerClassLoader());
+            checkMemberAccess(Member.PUBLIC, ClassLoader.getCallerClassLoader(), false);
         }
         return newInstance0();
     }
@@ -1295,7 +1297,7 @@
         // be very careful not to change the stack depth of this
         // checkMemberAccess call for security reasons
         // see java.lang.SecurityManager.checkMemberAccess
-        checkMemberAccess(Member.PUBLIC, ClassLoader.getCallerClassLoader());
+        checkMemberAccess(Member.PUBLIC, ClassLoader.getCallerClassLoader(), false);
 
         // Privileged so this implementation can look at DECLARED classes,
         // something the caller might not have privilege to do.  The code here
@@ -1370,7 +1372,7 @@
         // be very careful not to change the stack depth of this
         // checkMemberAccess call for security reasons
         // see java.lang.SecurityManager.checkMemberAccess
-        checkMemberAccess(Member.PUBLIC, ClassLoader.getCallerClassLoader());
+        checkMemberAccess(Member.PUBLIC, ClassLoader.getCallerClassLoader(), true);
         return copyFields(privateGetPublicFields(null));
     }
 
@@ -1421,7 +1423,7 @@
         // be very careful not to change the stack depth of this
         // checkMemberAccess call for security reasons
         // see java.lang.SecurityManager.checkMemberAccess
-        checkMemberAccess(Member.PUBLIC, ClassLoader.getCallerClassLoader());
+        checkMemberAccess(Member.PUBLIC, ClassLoader.getCallerClassLoader(), true);
         return copyMethods(privateGetPublicMethods());
     }
 
@@ -1470,7 +1472,7 @@
         // be very careful not to change the stack depth of this
         // checkMemberAccess call for security reasons
         // see java.lang.SecurityManager.checkMemberAccess
-        checkMemberAccess(Member.PUBLIC, ClassLoader.getCallerClassLoader());
+        checkMemberAccess(Member.PUBLIC, ClassLoader.getCallerClassLoader(), true);
         return copyConstructors(privateGetDeclaredConstructors(true));
     }
 
@@ -1529,7 +1531,7 @@
         // be very careful not to change the stack depth of this
         // checkMemberAccess call for security reasons
         // see java.lang.SecurityManager.checkMemberAccess
-        checkMemberAccess(Member.PUBLIC, ClassLoader.getCallerClassLoader());
+        checkMemberAccess(Member.PUBLIC, ClassLoader.getCallerClassLoader(), true);
         Field field = getField0(name);
         if (field == null) {
             throw new NoSuchFieldException(name);
@@ -1614,7 +1616,7 @@
         // be very careful not to change the stack depth of this
         // checkMemberAccess call for security reasons
         // see java.lang.SecurityManager.checkMemberAccess
-        checkMemberAccess(Member.PUBLIC, ClassLoader.getCallerClassLoader());
+        checkMemberAccess(Member.PUBLIC, ClassLoader.getCallerClassLoader(), true);
         Method method = getMethod0(name, parameterTypes);
         if (method == null) {
             throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
@@ -1668,7 +1670,7 @@
         // be very careful not to change the stack depth of this
         // checkMemberAccess call for security reasons
         // see java.lang.SecurityManager.checkMemberAccess
-        checkMemberAccess(Member.PUBLIC, ClassLoader.getCallerClassLoader());
+        checkMemberAccess(Member.PUBLIC, ClassLoader.getCallerClassLoader(), true);
         return getConstructor0(parameterTypes, Member.PUBLIC);
     }
 
@@ -1710,7 +1712,7 @@
         // be very careful not to change the stack depth of this
         // checkMemberAccess call for security reasons
         // see java.lang.SecurityManager.checkMemberAccess
-        checkMemberAccess(Member.DECLARED, ClassLoader.getCallerClassLoader());
+        checkMemberAccess(Member.DECLARED, ClassLoader.getCallerClassLoader(), false);
         return getDeclaredClasses0();
     }
 
@@ -1754,7 +1756,7 @@
         // be very careful not to change the stack depth of this
         // checkMemberAccess call for security reasons
         // see java.lang.SecurityManager.checkMemberAccess
-        checkMemberAccess(Member.DECLARED, ClassLoader.getCallerClassLoader());
+        checkMemberAccess(Member.DECLARED, ClassLoader.getCallerClassLoader(), true);
         return copyFields(privateGetDeclaredFields(false));
     }
 
@@ -1802,7 +1804,7 @@
         // be very careful not to change the stack depth of this
         // checkMemberAccess call for security reasons
         // see java.lang.SecurityManager.checkMemberAccess
-        checkMemberAccess(Member.DECLARED, ClassLoader.getCallerClassLoader());
+        checkMemberAccess(Member.DECLARED, ClassLoader.getCallerClassLoader(), true);
         return copyMethods(privateGetDeclaredMethods(false));
     }
 
@@ -1847,7 +1849,7 @@
         // be very careful not to change the stack depth of this
         // checkMemberAccess call for security reasons
         // see java.lang.SecurityManager.checkMemberAccess
-        checkMemberAccess(Member.DECLARED, ClassLoader.getCallerClassLoader());
+        checkMemberAccess(Member.DECLARED, ClassLoader.getCallerClassLoader(), true);
         return copyConstructors(privateGetDeclaredConstructors(false));
     }
 
@@ -1891,7 +1893,7 @@
         // be very careful not to change the stack depth of this
         // checkMemberAccess call for security reasons
         // see java.lang.SecurityManager.checkMemberAccess
-        checkMemberAccess(Member.DECLARED, ClassLoader.getCallerClassLoader());
+        checkMemberAccess(Member.DECLARED, ClassLoader.getCallerClassLoader(), true);
         Field field = searchFields(privateGetDeclaredFields(false), name);
         if (field == null) {
             throw new NoSuchFieldException(name);
@@ -1946,7 +1948,7 @@
         // be very careful not to change the stack depth of this
         // checkMemberAccess call for security reasons
         // see java.lang.SecurityManager.checkMemberAccess
-        checkMemberAccess(Member.DECLARED, ClassLoader.getCallerClassLoader());
+        checkMemberAccess(Member.DECLARED, ClassLoader.getCallerClassLoader(), true);
         Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes);
         if (method == null) {
             throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
@@ -1996,7 +1998,7 @@
         // be very careful not to change the stack depth of this
         // checkMemberAccess call for security reasons
         // see java.lang.SecurityManager.checkMemberAccess
-        checkMemberAccess(Member.DECLARED, ClassLoader.getCallerClassLoader());
+        checkMemberAccess(Member.DECLARED, ClassLoader.getCallerClassLoader(), true);
         return getConstructor0(parameterTypes, Member.DECLARED);
     }
 
@@ -2166,18 +2168,26 @@
      * <p> Default policy: allow all clients access with normal Java access
      * control.
      */
-    private void checkMemberAccess(int which, ClassLoader ccl) {
+    private void checkMemberAccess(int which, ClassLoader ccl, boolean checkProxyInterfaces) {
         SecurityManager s = System.getSecurityManager();
         if (s != null) {
             s.checkMemberAccess(this, which);
             ClassLoader cl = getClassLoader0();
-            if (sun.reflect.misc.ReflectUtil.needsPackageAccessCheck(ccl, cl)) {
+            if (ReflectUtil.needsPackageAccessCheck(ccl, cl)) {
                 String name = this.getName();
                 int i = name.lastIndexOf('.');
                 if (i != -1) {
-                    s.checkPackageAccess(name.substring(0, i));
+                    // skip the package access check on a proxy class in default proxy package
+                    String pkg = name.substring(0, i);
+                    if (!Proxy.isProxyClass(this) || !pkg.equals(ReflectUtil.PROXY_PACKAGE)) {
+                        s.checkPackageAccess(pkg);
+                    }
                 }
             }
+            // check package access on the proxy interfaces
+            if (checkProxyInterfaces && Proxy.isProxyClass(this)) {
+                ReflectUtil.checkProxyPackageAccess(ccl, this.getInterfaces());
+            }
         }
     }
 
--- a/jdk/src/share/classes/java/lang/invoke/MethodHandleProxies.java	Tue Oct 30 17:05:45 2012 +0400
+++ b/jdk/src/share/classes/java/lang/invoke/MethodHandleProxies.java	Fri Nov 02 16:50:23 2012 -0700
@@ -26,8 +26,12 @@
 package java.lang.invoke;
 
 import java.lang.reflect.*;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
 import sun.invoke.WrapperInstance;
 import java.util.ArrayList;
+import sun.reflect.Reflection;
+import sun.reflect.misc.ReflectUtil;
 
 /**
  * This class consists exclusively of static methods that help adapt
@@ -137,6 +141,18 @@
     <T> T asInterfaceInstance(final Class<T> intfc, final MethodHandle target) {
         if (!intfc.isInterface() || !Modifier.isPublic(intfc.getModifiers()))
             throw new IllegalArgumentException("not a public interface: "+intfc.getName());
+        SecurityManager smgr = System.getSecurityManager();
+        if (smgr != null) {
+            final int CALLER_FRAME = 2; // 0: Reflection, 1: asInterfaceInstance, 2: caller
+            final Class<?> caller = Reflection.getCallerClass(CALLER_FRAME);
+            final ClassLoader ccl = caller.getClassLoader();
+            ReflectUtil.checkProxyPackageAccess(ccl, intfc);
+        }
+        ClassLoader proxyLoader = intfc.getClassLoader();
+        if (proxyLoader == null) {
+            ClassLoader cl = Thread.currentThread().getContextClassLoader(); // avoid use of BCP
+            proxyLoader = cl != null ? cl : ClassLoader.getSystemClassLoader();
+        }
         final Method[] methods = getSingleNameMethods(intfc);
         if (methods == null)
             throw new IllegalArgumentException("not a single-method interface: "+intfc.getName());
@@ -148,27 +164,44 @@
             checkTarget = checkTarget.asType(checkTarget.type().changeReturnType(Object.class));
             vaTargets[i] = checkTarget.asSpreader(Object[].class, smMT.parameterCount());
         }
-        return intfc.cast(Proxy.newProxyInstance(
-                intfc.getClassLoader(),
-                new Class<?>[]{ intfc, WrapperInstance.class },
-                new InvocationHandler() {
-                    private Object getArg(String name) {
-                        if ((Object)name == "getWrapperInstanceTarget")  return target;
-                        if ((Object)name == "getWrapperInstanceType")    return intfc;
-                        throw new AssertionError();
+        final InvocationHandler ih = new InvocationHandler() {
+                private Object getArg(String name) {
+                    if ((Object)name == "getWrapperInstanceTarget")  return target;
+                    if ((Object)name == "getWrapperInstanceType")    return intfc;
+                    throw new AssertionError();
+                }
+                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+                    for (int i = 0; i < methods.length; i++) {
+                        if (method.equals(methods[i]))
+                            return vaTargets[i].invokeExact(args);
                     }
-                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
-                        for (int i = 0; i < methods.length; i++) {
-                            if (method.equals(methods[i]))
-                                return vaTargets[i].invokeExact(args);
-                        }
-                        if (method.getDeclaringClass() == WrapperInstance.class)
-                            return getArg(method.getName());
-                        if (isObjectMethod(method))
-                            return callObjectMethod(proxy, method, args);
-                        throw new InternalError("bad proxy method: "+method);
-                    }
-                }));
+                    if (method.getDeclaringClass() == WrapperInstance.class)
+                        return getArg(method.getName());
+                    if (isObjectMethod(method))
+                        return callObjectMethod(proxy, method, args);
+                    throw new InternalError("bad proxy method: "+method);
+                }
+            };
+
+        Object proxy;
+        if (smgr != null) {
+            // sun.invoke.WrapperInstance is a restricted interface not accessible
+            // by any non-null class loader.
+            final ClassLoader loader = proxyLoader;
+            proxy = AccessController.doPrivileged(new PrivilegedAction<Object>() {
+                public Object run() {
+                    return Proxy.newProxyInstance(
+                            loader,
+                            new Class<?>[]{ intfc, WrapperInstance.class },
+                            ih);
+                }
+            });
+        } else {
+            proxy = Proxy.newProxyInstance(proxyLoader,
+                                           new Class<?>[]{ intfc, WrapperInstance.class },
+                                           ih);
+        }
+        return intfc.cast(proxy);
     }
 
     /**
--- a/jdk/src/share/classes/java/lang/reflect/Proxy.java	Tue Oct 30 17:05:45 2012 +0400
+++ b/jdk/src/share/classes/java/lang/reflect/Proxy.java	Fri Nov 02 16:50:23 2012 -0700
@@ -27,6 +27,9 @@
 
 import java.lang.ref.Reference;
 import java.lang.ref.WeakReference;
+import java.security.AccessController;
+import java.security.Permission;
+import java.security.PrivilegedAction;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
@@ -36,6 +39,9 @@
 import java.util.List;
 import java.util.WeakHashMap;
 import sun.misc.ProxyGenerator;
+import sun.reflect.Reflection;
+import sun.reflect.misc.ReflectUtil;
+import sun.security.util.SecurityConstants;
 
 /**
  * {@code Proxy} provides static methods for creating dynamic proxy
@@ -265,9 +271,69 @@
      * @param   h the invocation handler for this proxy instance
      */
     protected Proxy(InvocationHandler h) {
+        doNewInstanceCheck();
         this.h = h;
     }
 
+    private static class ProxyAccessHelper {
+        // The permission is implementation specific.
+        static final Permission PROXY_PERMISSION =
+            new ReflectPermission("proxyConstructorNewInstance");
+        // These system properties are defined to provide a short-term
+        // workaround if customers need to disable the new security checks.
+        static final boolean allowNewInstance;
+        static final boolean allowNullLoader;
+        static {
+            allowNewInstance = getBooleanProperty("sun.reflect.proxy.allowsNewInstance");
+            allowNullLoader = getBooleanProperty("sun.reflect.proxy.allowsNullLoader");
+        }
+
+        private static boolean getBooleanProperty(final String key) {
+            String s = AccessController.doPrivileged(new PrivilegedAction<String>() {
+                public String run() {
+                    return System.getProperty(key);
+                }
+            });
+            return Boolean.valueOf(s);
+        }
+
+        static boolean needsNewInstanceCheck(Class<?> proxyClass) {
+            if (!Proxy.isProxyClass(proxyClass) || allowNewInstance) {
+                return false;
+            }
+
+            if (proxyClass.getName().startsWith(ReflectUtil.PROXY_PACKAGE + ".")) {
+                // all proxy interfaces are public
+                return false;
+            }
+            for (Class<?> intf : proxyClass.getInterfaces()) {
+                if (!Modifier.isPublic(intf.getModifiers())) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    /*
+     * Access check on a proxy class that implements any non-public interface.
+     *
+     * @throws  SecurityException if a security manager exists, and
+     *          the caller does not have the permission.
+     */
+    private void doNewInstanceCheck() {
+        SecurityManager sm = System.getSecurityManager();
+        Class<?> proxyClass = this.getClass();
+        if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(proxyClass)) {
+            try {
+                sm.checkPermission(ProxyAccessHelper.PROXY_PERMISSION);
+            } catch (SecurityException e) {
+                throw new SecurityException("Not allowed to construct a Proxy "
+                        + "instance that implements a non-public interface", e);
+            }
+        }
+    }
+
     /**
      * Returns the {@code java.lang.Class} object for a proxy class
      * given a class loader and an array of interfaces.  The proxy class
@@ -346,6 +412,51 @@
                                          Class<?>... interfaces)
         throws IllegalArgumentException
     {
+        return getProxyClass0(loader, interfaces); // stack walk magic: do not refactor
+    }
+
+    private static void checkProxyLoader(ClassLoader ccl,
+                                         ClassLoader loader)
+    {
+        SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            if (loader == null && ccl != null) {
+                if (!ProxyAccessHelper.allowNullLoader) {
+                    sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION);
+                }
+            }
+        }
+    }
+
+    /*
+     * Generate a proxy class (caller-sensitive).
+     *
+     * To define a proxy class, it performs the access checks as in
+     * Class.forName (VM will invoke ClassLoader.checkPackageAccess):
+     * 1. "getClassLoader" permission check if loader == null
+     * 2. checkPackageAccess on the interfaces it implements
+     *
+     * To get a constructor and new instance of a proxy class, it performs
+     * the package access check on the interfaces it implements
+     * as in Class.getConstructor.
+     *
+     * If an interface is non-public, the proxy class must be defined by
+     * the defining loader of the interface.  If the caller's class loader
+     * is not the same as the defining loader of the interface, the VM
+     * will throw IllegalAccessError when the generated proxy class is
+     * being defined via the defineClass0 method.
+     */
+    private static Class<?> getProxyClass0(ClassLoader loader,
+                                           Class<?>... interfaces) {
+        SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            final int CALLER_FRAME = 3; // 0: Reflection, 1: getProxyClass0 2: Proxy 3: caller
+            final Class<?> caller = Reflection.getCallerClass(CALLER_FRAME);
+            final ClassLoader ccl = caller.getClassLoader();
+            checkProxyLoader(ccl, loader);
+            ReflectUtil.checkProxyPackageAccess(ccl, interfaces);
+        }
+
         if (interfaces.length > 65535) {
             throw new IllegalArgumentException("interface limit exceeded");
         }
@@ -497,8 +608,9 @@
                 }
             }
 
-            if (proxyPkg == null) {     // if no non-public proxy interfaces,
-                proxyPkg = "";          // use the unnamed package
+            if (proxyPkg == null) {
+                // if no non-public proxy interfaces, use sun.proxy package
+                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
             }
 
             {
@@ -598,22 +710,46 @@
         /*
          * Look up or generate the designated proxy class.
          */
-        Class<?> cl = getProxyClass(loader, interfaces);
+        Class<?> cl = getProxyClass0(loader, interfaces); // stack walk magic: do not refactor
 
         /*
          * Invoke its constructor with the designated invocation handler.
          */
         try {
-            Constructor<?> cons = cl.getConstructor(constructorParams);
-            return cons.newInstance(new Object[] { h });
-        } catch (NoSuchMethodException |
-                 IllegalAccessException |
-                 InstantiationException |
-                 InvocationTargetException e) {
+            final Constructor<?> cons = cl.getConstructor(constructorParams);
+            final InvocationHandler ih = h;
+            SecurityManager sm = System.getSecurityManager();
+            if (sm != null && ProxyAccessHelper.needsNewInstanceCheck(cl)) {
+                // create proxy instance with doPrivilege as the proxy class may
+                // implement non-public interfaces that requires a special permission
+                return AccessController.doPrivileged(new PrivilegedAction<Object>() {
+                    public Object run() {
+                        return newInstance(cons, ih);
+                    }
+                });
+            } else {
+                return newInstance(cons, ih);
+            }
+        } catch (NoSuchMethodException e) {
             throw new InternalError(e.toString(), e);
         }
     }
 
+    private static Object newInstance(Constructor<?> cons, InvocationHandler h) {
+        try {
+            return cons.newInstance(new Object[] {h} );
+        } catch (IllegalAccessException | InstantiationException e) {
+            throw new InternalError(e.toString(), e);
+        } catch (InvocationTargetException e) {
+            Throwable t = e.getCause();
+            if (t instanceof RuntimeException) {
+                throw (RuntimeException) t;
+            } else {
+                throw new InternalError(t.toString(), t);
+            }
+        }
+    }
+
     /**
      * Returns true if and only if the specified class was dynamically
      * generated to be a proxy class using the {@code getProxyClass}
--- a/jdk/src/share/classes/sun/reflect/misc/ReflectUtil.java	Tue Oct 30 17:05:45 2012 +0400
+++ b/jdk/src/share/classes/sun/reflect/misc/ReflectUtil.java	Fri Nov 02 16:50:23 2012 -0700
@@ -178,4 +178,29 @@
 
         return !isAncestor(from, to);
     }
+
+    /**
+     * Access check on the interfaces that a proxy class implements and throw
+     * {@code SecurityException} if it accesses a restricted package.
+     *
+     * @param ccl the caller's class loader
+     * @param interfaces the list of interfaces that a proxy class implements
+     *
+     * @see Proxy#checkProxyAccess
+     */
+    public static void checkProxyPackageAccess(ClassLoader ccl,
+                                               Class<?>... interfaces)
+    {
+        SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            for (Class<?> intf : interfaces) {
+                ClassLoader cl = intf.getClassLoader();
+                if (needsPackageAccessCheck(ccl, cl)) {
+                    checkPackageAccess(intf);
+                }
+            }
+        }
+    }
+
+    public static final String PROXY_PACKAGE = "sun.proxy";
 }
--- a/jdk/test/java/rmi/server/RMIClassLoader/loadProxyClasses/security.policy	Tue Oct 30 17:05:45 2012 +0400
+++ b/jdk/test/java/rmi/server/RMIClassLoader/loadProxyClasses/security.policy	Fri Nov 02 16:50:23 2012 -0700
@@ -13,6 +13,7 @@
     permission java.io.FilePermission ".${/}-", "read,write,delete";
 
     permission java.lang.RuntimePermission "createClassLoader";
+    permission java.lang.RuntimePermission "getClassLoader";
     permission java.lang.RuntimePermission "setContextClassLoader";
 
     // used by TestLibrary to determine test environment