javaclassloaderclassnotfoundexceptionurlclassloadersecuritymanager

ClassNotFoundException issued by URLClassLoader when the Security Manager is enabled


For an experimental project, I am trying to load a class with URLClassLoader with the security manager enabled. Despite the fact that the URL and that the class do exist, the code always fails with a ClassNotFoundException.

This behaviour has been observed with at least the following versions of java (the ones available on my system, a MacOSX Mojave 10.14.6; not tested yet on Windows or Linux or with other Java implementations):

java version "1.8.0_181" Java(TM) SE Runtime Environment (build 1.8.0_181-b13) Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)

java version "10.0.2" 2018-07-17 Java(TM) SE Runtime Environment 18.3 (build 10.0.2+13) Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10.0.2+13, mixed mode)

java version "12.0.1" 2019-04-16 Java(TM) SE Runtime Environment (build 12.0.1+12) Java HotSpot(TM) 64-Bit Server VM (build 12.0.1+12, mixed mode, sharing)

After hours of troubleshooting (checking the paths, looking for invisible characters in the paths, reading the docs on class loaders, etc), I realized that the exception occurs ONLY when the security manager is enabled. When I disable it, the class is found without problem. I would have expected some kind of security exception, but not a ClassNotFoundException.

Obviously, the absence of a security manager severely limits the usefulness of a class loader (especially if one wants to load untrusted, signed classes and jars). The permission for creating a class loader is obviously granted in the security policy.

The code below is the minimal needed for reproducing the problem.

I am relatively new to Java. I may be doing something stupid. Has anyone observed this behaviour before? Is it on purpose? Am I missing anything?

Thanks for any help.

.
├── ClassLoaderTest.class
├── ClassLoaderTest.java
├── Makefile
├── security.policy
└── untrusted
    ├── Makefile
    ├── Untrusted.class
    └── Untrusted.java

1 directory, 7 files
// ClassLoaderTest.java

import java.net.URLClassLoader;
import java.net.URL;

public class ClassLoaderTest {

    public static void main(String[] args) throws Exception {
        URL[] urls = new URL[1];
        urls[0] = new URL("file:./untrusted/"); // untrusted/ could be anywhere; for the purpose of the test, it is in the working directory
        URLClassLoader loader = new URLClassLoader(urls);
        Class<?> cl = loader.loadClass("Untrusted"); // one tries to load: untrusted/Untrusted.class
        cl.getConstructor().newInstance(); // or cl.getDeclaredConstructor().newInstance()
        System.exit(0);
    }
}
public class Untrusted {
    public Untrusted() {
        System.out.println("Instantiation of Untrusted");
    }

    public static void main(String args[]) {
        Untrusted u = new Untrusted();
    }
}
// security.policy
// permissions granted to any code (no matter where it came from or whether it is signed)
// THIS IS OBVIOUSLY FOR TESTING ONLY, only trusted sources should be granted a createClassLoader permission
grant {
  permission java.lang.RuntimePermission "createClassLoader";
};

Here is the outcome of the program:

Without security manager: works

$ java ClassLoaderTest
Instantiation of Untrusted

With a security manager enabled: fails!

$ java -Djava.security.manager -Djava.security.policy=security.policy ClassLoaderTest
Exception in thread "main" java.lang.ClassNotFoundException: Untrusted
    at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:436)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:588)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
    at ClassLoaderTest.main(ClassLoaderTest.java:10)

Solution

  • After submitting the issue to Oracle, they solved the problem thanks to the following, very useful flag (for people working with the security manager): -Djava.security.debug=access

    https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8229532

    The problem is then much clearer:

    // ... more lines above (skipped for clarity)
    access: access denied ("java.util.PropertyPermission" "user.dir" "read") // shows up only in debug mode
    Exception in thread "main" java.lang.ClassNotFoundException: Untrusted
    ...
    

    One therefore must add a permission to the security.policy file:

    grant {
      permission java.lang.RuntimePermission "createClassLoader";
      permission java.util.PropertyPermission "user.dir", "read"; // new line
    };
    

    and it solves the problem.

    The issue was therefore marked as non existent and closed by the assignee at Oracle. While his reply was very helpful, I still think there is a bug, in the sense that the wrong exception is thrown by the SDK (not the application): ClassNotFoundException instead of a Permission or Security Exception.

    For people unaware of the -Djava.security.debug=access property (as well as users or administrators in a production context), that can be misleading and very confusing.

    In any case, my issue is closed and I hope this solution will be helpful to other people meeting similar problems.

    Best regards everybody.