6826789: SecureClassLoader should not use CodeSource URLs as HashMap keys
authormullan
Fri, 12 Jun 2015 12:50:41 -0400
changeset 31141 ef5ddf021137
parent 31140 26e746beb490
child 31143 35889b7bb6e9
6826789: SecureClassLoader should not use CodeSource URLs as HashMap keys Reviewed-by: weijun
jdk/src/java.base/share/classes/java/security/CodeSource.java
jdk/src/java.base/share/classes/java/security/SecureClassLoader.java
jdk/test/java/security/SecureClassLoader/DefineClass.java
jdk/test/java/security/SecureClassLoader/DefineClass.policy
--- a/jdk/src/java.base/share/classes/java/security/CodeSource.java	Fri Jun 12 14:28:21 2015 +0800
+++ b/jdk/src/java.base/share/classes/java/security/CodeSource.java	Fri Jun 12 12:50:41 2015 -0400
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2015, 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
@@ -34,6 +34,7 @@
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.security.cert.*;
+import sun.net.util.URLUtil;
 
 /**
  *
@@ -73,6 +74,15 @@
     private transient CertificateFactory factory = null;
 
     /**
+     * A String form of the URL for use as a key in HashMaps/Sets. The String
+     * form should be behave in the same manner as the URL when compared for
+     * equality in a HashMap/Set, except that no nameservice lookup is done
+     * on the hostname (only string comparison), and the fragment is not
+     * considered.
+     */
+    private transient String locationNoFragString;
+
+    /**
      * Constructs a CodeSource and associates it with the specified
      * location and set of certificates.
      *
@@ -83,6 +93,9 @@
      */
     public CodeSource(URL url, java.security.cert.Certificate certs[]) {
         this.location = url;
+        if (url != null) {
+            this.locationNoFragString = URLUtil.urlNoFragString(url);
+        }
 
         // Copy the supplied certs
         if (certs != null) {
@@ -102,6 +115,9 @@
      */
     public CodeSource(URL url, CodeSigner[] signers) {
         this.location = url;
+        if (url != null) {
+            this.locationNoFragString = URLUtil.urlNoFragString(url);
+        }
 
         // Copy the supplied signers
         if (signers != null) {
@@ -169,6 +185,13 @@
     }
 
     /**
+     * Returns a String form of the URL for use as a key in HashMaps/Sets.
+     */
+    String getLocationNoFragString() {
+        return locationNoFragString;
+    }
+
+    /**
      * Returns the certificates associated with this CodeSource.
      * <p>
      * If this CodeSource object was created using the
@@ -588,6 +611,10 @@
         } catch (IOException ioe) {
             // no signers present
         }
+
+        if (location != null) {
+            locationNoFragString = URLUtil.urlNoFragString(location);
+        }
     }
 
     /*
--- a/jdk/src/java.base/share/classes/java/security/SecureClassLoader.java	Fri Jun 12 14:28:21 2015 +0800
+++ b/jdk/src/java.base/share/classes/java/security/SecureClassLoader.java	Fri Jun 12 12:50:41 2015 -0400
@@ -47,10 +47,16 @@
      */
     private final boolean initialized;
 
-    // HashMap that maps CodeSource to ProtectionDomain
-    // @GuardedBy("pdcache")
-    private final HashMap<CodeSource, ProtectionDomain> pdcache =
-                        new HashMap<>(11);
+    /*
+     * HashMap that maps the CodeSource URL (as a String) to ProtectionDomain.
+     * We use a String instead of a CodeSource/URL as the key to avoid
+     * potential expensive name service lookups. This does mean that URLs that
+     * are equivalent after nameservice lookup will be placed in separate
+     * ProtectionDomains; however during policy enforcement these URLs will be
+     * canonicalized and resolved resulting in a consistent set of granted
+     * permissions.
+     */
+    private final HashMap<String, ProtectionDomain> pdcache = new HashMap<>(11);
 
     private static final Debug debug = Debug.getInstance("scl");
 
@@ -196,16 +202,22 @@
      * Returned cached ProtectionDomain for the specified CodeSource.
      */
     private ProtectionDomain getProtectionDomain(CodeSource cs) {
-        if (cs == null)
+        if (cs == null) {
             return null;
+        }
 
         ProtectionDomain pd = null;
         synchronized (pdcache) {
-            pd = pdcache.get(cs);
+            // Use a String form of the URL as the key. It should behave in the
+            // same manner as the URL when compared for equality except that no
+            // nameservice lookup is done on the hostname (String comparison
+            // only), and the fragment is not considered.
+            String key = cs.getLocationNoFragString();
+            pd = pdcache.get(key);
             if (pd == null) {
                 PermissionCollection perms = getPermissions(cs);
                 pd = new ProtectionDomain(cs, perms, this, null);
-                pdcache.put(cs, pd);
+                pdcache.put(key, pd);
                 if (debug != null) {
                     debug.println(" getPermissions "+ pd);
                     debug.println("");
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/security/SecureClassLoader/DefineClass.java	Fri Jun 12 12:50:41 2015 -0400
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2015, 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.
+ *
+ * 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.
+ */
+
+import java.io.IOException;
+import java.net.URL;
+import java.security.CodeSource;
+import java.security.Permission;
+import java.security.Policy;
+import java.security.ProtectionDomain;
+import java.security.SecureClassLoader;
+import java.security.cert.Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.List;
+import java.util.PropertyPermission;
+
+/*
+ * @test
+ * @bug 6826789
+ * @summary Make sure equivalent ProtectionDomains are granted the same
+ *          permissions when the CodeSource URLs are different but resolve
+ *          to the same ip address after name service resolution.
+ * @run main/othervm/java.security.policy=DefineClass.policy DefineClass
+ */
+
+public class DefineClass {
+
+    // permissions that are expected to be granted by the policy file
+    private final static Permission[] GRANTED_PERMS = new Permission[] {
+        new PropertyPermission("user.home", "read"),
+        new PropertyPermission("user.name", "read")
+    };
+
+    // Base64 encoded bytes of a simple class: "public class Foo {}"
+    private final static String FOO_CLASS =
+        "yv66vgAAADQADQoAAwAKBwALBwAMAQAGPGluaXQ+AQADKClWAQAEQ29kZQEA" +
+        "D0xpbmVOdW1iZXJUYWJsZQEAClNvdXJjZUZpbGUBAAhGb28uamF2YQwABAAF" +
+        "AQADRm9vAQAQamF2YS9sYW5nL09iamVjdAAhAAIAAwAAAAAAAQABAAQABQAB" +
+        "AAYAAAAdAAEAAQAAAAUqtwABsQAAAAEABwAAAAYAAQAAAAEAAQAIAAAAAgAJ";
+
+    // Base64 encoded bytes of a simple class: "public class Bar {}"
+    private final static String BAR_CLASS =
+        "yv66vgAAADQADQoAAwAKBwALBwAMAQAGPGluaXQ+AQADKClWAQAEQ29kZQEA" +
+        "D0xpbmVOdW1iZXJUYWJsZQEAClNvdXJjZUZpbGUBAAhCYXIuamF2YQwABAAF" +
+        "AQADQmFyAQAQamF2YS9sYW5nL09iamVjdAAhAAIAAwAAAAAAAQABAAQABQAB" +
+        "AAYAAAAdAAEAAQAAAAUqtwABsQAAAAEABwAAAAYAAQAAAAEAAQAIAAAAAgAJ";
+
+    public static void main(String[] args) throws Exception {
+
+        MySecureClassLoader scl = new MySecureClassLoader();
+        Policy p = Policy.getPolicy();
+        ArrayList<Permission> perms1 = getPermissions(scl, p,
+                                                      "http://localhost/",
+                                                      "Foo", FOO_CLASS);
+        checkPerms(perms1, GRANTED_PERMS);
+        ArrayList<Permission> perms2 = getPermissions(scl, p,
+                                                      "http://127.0.0.1/",
+                                                      "Bar", BAR_CLASS);
+        checkPerms(perms2, GRANTED_PERMS);
+        assert(perms1.equals(perms2));
+    }
+
+    // returns the permissions granted to the codebase URL
+    private static ArrayList<Permission> getPermissions(MySecureClassLoader scl,
+                                                        Policy p, String url,
+                                                        String className,
+                                                        String classBytes)
+                                                        throws IOException {
+        CodeSource cs = new CodeSource(new URL(url), (Certificate[])null);
+        Base64.Decoder bd = Base64.getDecoder();
+        byte[] bytes = bd.decode(classBytes);
+        Class<?> c = scl.defineMyClass(className, bytes, cs);
+        ProtectionDomain pd = c.getProtectionDomain();
+        return Collections.list(p.getPermissions(pd).elements());
+    }
+
+    private static void checkPerms(List<Permission> perms,
+                                   Permission... grantedPerms)
+        throws Exception
+    {
+        if (!perms.containsAll(Arrays.asList(grantedPerms))) {
+            throw new Exception("Granted permissions not correct");
+        }
+    }
+
+    // A SecureClassLoader that allows the test to define its own classes
+    private static class MySecureClassLoader extends SecureClassLoader {
+        Class<?> defineMyClass(String name, byte[] b, CodeSource cs) {
+            return super.defineClass(name, b, 0, b.length, cs);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/security/SecureClassLoader/DefineClass.policy	Fri Jun 12 12:50:41 2015 -0400
@@ -0,0 +1,11 @@
+grant {
+    permission java.lang.RuntimePermission "createClassLoader";
+    permission java.lang.RuntimePermission "getProtectionDomain";
+    permission java.security.SecurityPermission "getPolicy";
+};
+grant codebase "http://localhost/" {
+    permission java.util.PropertyPermission "user.home", "read";
+};
+grant codebase "http://127.0.0.1/" {
+    permission java.util.PropertyPermission "user.name", "read";
+};