Hi Volker, this is the behavior of the verifier since 6 (when the split verifier becomes the default one).
Let say you have a code like this, public class TestVerifier { static class B extends A { } static class A { } public static void main(String[] args) { A a; if (args.length == 0) { a = new A(); } else { a = new B(); } System.out.println(a.toString()); } } if you compile and then remove TestVerifier$B.class, you will get Error: A JNI error has occurred, please check your installation and try again Exception in thread "main" java.lang.NoClassDefFoundError: TestVerifier$B at java.lang.Class.getDeclaredMethods0(Native Method) at java.lang.Class.privateGetDeclaredMethods(Class.java:2701) at java.lang.Class.privateGetMethodRecursive(Class.java:3048) at java.lang.Class.getMethod0(Class.java:3018) at java.lang.Class.getMethod(Class.java:1784) at sun.launcher.LauncherHelper.validateMainClass(LauncherHelper.java:544) at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:526) Caused by: java.lang.ClassNotFoundException: TestVerifier$B at java.net.URLClassLoader.findClass(URLClassLoader.java:381) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ... 7 more whatever the comand line arguments because it's the verifier that throws an error. There is a workaround for you, if you use an interface instead of a class (in my example, make A an interface), it will work ! The verifier does not verify interface at verification time, the checks in done once by the VM at runtime. so if there is a bug, it's in the way the verifier actually works i.e. if it can not find a class or do not see a class because of the module encapsulation, it should postpone the check at runtime instead of reporting an error at compile time. Thats said, i'm not sure it's a good idea. cheers, Rémi ----- Mail original ----- > De: "Volker Simonis" <volker.simo...@gmail.com> > À: "jigsaw-dev" <jigsaw-...@openjdk.java.net>, "security-dev" > <security-dev@openjdk.java.net> > Envoyé: Mardi 23 Mai 2017 09:27:22 > Objet: JPMS Access Checks, Verification and the Security Manager > Hi, > > my question is if it is OK for a program which otherwise runs fine to > fail during class verification if running with a security manager > because of module access restrictions? > > As the following write-up got a little lengthy, I've also uploaded a > html version for a nicer reading experience :) > > http://cr.openjdk.java.net/~simonis/webrevs/2017/veri_with_secman/sm.html > > Consider the following small, self-contained program which statically > references com.sun.crypto.provider.SunJCE: > > import java.security.Provider; > import com.sun.crypto.provider.SunJCE; > > public class Tricky { > > public static void main(String args[]) throws Exception { > try { > System.out.println(Tricky.class + " (" + > Tricky.class.getClassLoader() + ")" + args[0]); > } > catch (Exception e) { > Provider p = new SunJCE(); > System.out.println(p + " (" + p.toString() + ")"); > } > } > } > > This program easily compiles and runs with Oracle/OpenJDK 8: > > $ javac Tricky > $ java Tricky "" > class Tricky (sun.misc.Launcher$AppClassLoader@4e0e2f2a) > $ java Tricky > SunJCE version 1.8 (SunJCE version 1.8) > > The second invocation (without argument) will cause an exception (when > trying to access the first element of the zero length argument array > at 'args[0]') which will be caught in the exception handler where we > create a new 'SunJCE' object and print its string representation to > stdout. The first invocation (with an empty argument) doesn't provoke > an exception and will just print the class itself together with its > class loader. > > When we run the same (jdk8 compiled) program with jdk9, we'll see the > following result: > > $ jdk9/java Tricky "" > class Tricky (jdk.internal.loader.ClassLoaders$AppClassLoader@ba8a1dc) > $ jdk9/java Tricky > Exception in thread "main" java.lang.IllegalAccessError: class Tricky > (in unnamed module @0x3b192d32) cannot access class > com.sun.crypto.provider.SunJCE (in module java.base) because module > java.base does not export com.sun.crypto.provider to unnamed module > @0x3b192d32 > at Tricky.main(Tricky.java:11) > $ jdk9/java --add-exports java.base/com.sun.crypto.provider=ALL-UNNAMED Tricky > SunJCE version 9 (SunJCE version 9) > > The first invocation (with an empty argument) still works because we > have lazy class loading and 'SunJCE' provider is actually never used. > The second invocation (without argument) obviously fails with jdk9 > because 'java.base' doesn't export the package com.sun.crypto.provider > anymore. This can be fixed by adding '--add-exports > java.base/com.sun.crypto.provider=ALL-UNNAMED' to the command line as > demonstrated in the third invocation. > > So far so good - everything worked as expected till now. Now let's run > the same program with the default security manager: > > $ java -Djava.security.manager Tricky "" > class Tricky (sun.misc.Launcher$AppClassLoader@4e0e2f2a) > $ java -Djava.security.manager Tricky > SunJCE version 1.8 (SunJCE version 1.8) > > The security manager doesn't change anything for jdk8! So let's try with jdk9: > > $ jdk9/java -Djava.security.manager Tricky "" > Error: A JNI error has occurred, please check your installation and try again > Exception in thread "main" java.security.AccessControlException: > access denied ("java.lang.RuntimePermission" > "accessClassInPackage.com.sun.crypto.provider") > at > > java.base/java.security.AccessControlContext.checkPermission(AccessControlContext.java:471) > at > > java.base/java.security.AccessController.checkPermission(AccessController.java:894) > at > java.base/java.lang.SecurityManager.checkPermission(SecurityManager.java:561) > at > > java.base/java.lang.SecurityManager.checkPackageAccess(SecurityManager.java:1534) > at java.base/java.lang.ClassLoader$1.run(ClassLoader.java:671) > at java.base/java.lang.ClassLoader$1.run(ClassLoader.java:669) > at java.base/java.security.AccessController.doPrivileged(Native Method) > at > java.base/java.lang.ClassLoader.checkPackageAccess(ClassLoader.java:669) > at java.base/java.lang.Class.getDeclaredMethods0(Native Method) > at java.base/java.lang.Class.privateGetDeclaredMethods(Class.java:3129) > at java.base/java.lang.Class.getMethodsRecursive(Class.java:3270) > at java.base/java.lang.Class.getMethod0(Class.java:3256) > at java.base/java.lang.Class.getMethod(Class.java:2057) > at > > java.base/sun.launcher.LauncherHelper.validateMainClass(LauncherHelper.java:712) > at > > java.base/sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:570) > > The first invocation (with an empty argument) crashes with a strange > JNI error. The call stack unveils that we haven't even entered the > main method of our program. Instead we've crashed in the jdk-internal > launcher 'sun.launcher.LauncherHelper' during 'checkAndLoadMain()' > with a 'java.security.AccessControlException' because we couldn't > access the 'com.sun.crypto.provider' package. That's strange because > we shouldn't need to load SunJCE provider for this invocation because > of lazy class loading. > > A little reasoning and the right Xlog parameter unveils that the > exception happens during class verification: > > $ jdk9/java -Djava.security.manager -Xlog:verification Tricky "" > ... > [1,382s][info][verification] locals: { '[Ljava/lang/String;', > 'java/lang/Exception', 'com/sun/crypto/provider/SunJCE' } > [1,382s][info][verification] stack: { 'java/io/PrintStream', > 'java/lang/StringBuilder', 'com/sun/crypto/provider/SunJCE' } > [1,382s][info][verification] offset = 77, opcode = invokevirtual > [1,589s][info][verification] Verification for Tricky has exception > pending java.security.AccessControlException > [1,589s][info][verification] End class verification for: Tricky > Error: A JNI error has occurred, please check your installation and try again > Exception in thread "main" java.security.AccessControlException: > access denied ("java.lang.RuntimePermission" > "accessClassInPackage.com.sun.crypto.provider") > ... > > And indeed, switching off class verification will fix the problem: > > $ jdk9/java -Djava.security.manager -Xlog:verification -noverify Tricky "" > class Tricky (jdk.internal.loader.ClassLoaders$AppClassLoader@1b9e1916) > > But switching off class verification is not something we usually want to do! > > So we can try to use '--add-exports > java.base/com.sun.crypto.provider=ALL-UNNAMED' (although this wasn't > needed without security manager): > > $ jdk9/java -Djava.security.manager --add-exports > java.base/com.sun.crypto.provider=ALL-UNNAMED Tricky "" > Error: A JNI error has occurred, please check your installation and try again > Exception in thread "main" java.security.AccessControlException: > access denied ("java.lang.RuntimePermission" > "accessClassInPackage.com.sun.crypto.provider") > ... > > but the result is still the same. If you feel that '--add-exports > java.base/com.sun.crypto.provider=ALL-UNNAMED' should have fixed the > problem, you're not alone. That issue is tracked under JDK-8174766 > [1]. It was discussed in the review [2] for JDK-8055206 [3] which > introduced the problem. > > Currently, the only way to fix the problem is to add additional > permissions through a custom security policy: > > my.policy > --------- > grant { > permission java.lang.RuntimePermission > "accessClassInPackage.com.sun.crypto.provider"; > }; > > $ jdk9/java -Djava.security.manager -Djava.security.policy=my.policy Tricky "" > class Tricky (jdk.internal.loader.ClassLoaders$AppClassLoader@1b9e1916) > > But the real question remains: why does class verification fails with > a security manager and succeeds without? > > With the right Xlog parameters we can verify, that even without > security manager, the 'SunJCE' class is successfully loaded by the > verifier for the purpose of verification, and the execution of the > program succeeds if we don't reference 'SunJCE' during execution: > > $ jdk9/java -Xlog:verification -Xlog:class+load Tricky "" > ... > [1,193s][info][verification] locals: { '[Ljava/lang/String;', > 'java/lang/Exception', 'com/sun/crypto/provider/SunJCE' } > [1,193s][info][verification] stack: { 'java/io/PrintStream', > 'java/lang/StringBuilder', 'com/sun/crypto/provider/SunJCE' } > [1,193s][info][verification] offset = 77, opcode = invokevirtual > [1,196s][info][class,load ] java.security.Provider source: jrt:/java.base > [1,197s][info][class,load ] com.sun.crypto.provider.SunJCE source: > jrt:/java.base > ... > [1,198s][info][verification] End class verification for: Tricky > ... > class Tricky (jdk.internal.loader.ClassLoaders$AppClassLoader@ba8a1dc) > > So how does the presence of a security manager changes the class > verification process? > > With a security manager, there are two additional > 'accessClassInPackage' checks during class loading. If we recall the > verifier log: > > [1,193s][info][verification] locals: { '[Ljava/lang/String;', > 'java/lang/Exception', 'com/sun/crypto/provider/SunJCE' } > [1,193s][info][verification] stack: { 'java/io/PrintStream', > 'java/lang/StringBuilder', 'com/sun/crypto/provider/SunJCE' } > [1,193s][info][verification] offset = 77, opcode = invokevirtual > [1,196s][info][class,load ] java.security.Provider source: jrt:/java.base > [1,197s][info][class,load ] com.sun.crypto.provider.SunJCE source: > jrt:/java.base > > we see that the verifier has to prove that > 'com/sun/crypto/provider/SunJCE' (which is on top of the stack) can be > assigned (i.e. is assign-compatible) to 'java.security.Provider' and > it is therefor safe to call 'Provider::toString()' on it. Therefor, in > order to load the class 'com.sun.crypto.provider.SunJCE', the verifier > calls 'SystemDictionary::resolve_or_fail()' which in the end calls > 'ClassLoader.loadClass(_class_name)' on the class loader of the class > under verification. If that class is a user class, this will call > 'jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass()' in Java > 9 because that's the actual application class loader (aka system class > loader) and ClassLoaders$AppClassLoader.loadClass() will perform the > following check if the VM runs with a security manager: > > @Override > protected Class loadClass(String cn, boolean resolve) throws > ClassNotFoundException > { > SecurityManager sm = System.getSecurityManager(); > if (sm != null) { > int i = cn.lastIndexOf('.'); > if (i != -1) { > sm.checkPackageAccess(cn.substring(0, i)); > } > } > return super.loadClass(cn, resolve); > } > > Here's the corresponding VM stack trace: > > j java.lang.SecurityManager.checkPackageAccess(Ljava/lang/String;)V+46 > java.base@9-internal > j > jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(Ljava/lang/String;Z)Ljava/lang/Class;+8 > java.base@9-internal > j java.lang.ClassLoader.loadClass(Ljava/lang/String;)Ljava/lang/Class;+3 > java.base@9-internal > v ~StubRoutines::call_stub > V [libjvm.so+0xc6c75d] JavaCalls::call_helper(JavaValue*, > methodHandle const&, JavaCallArguments*, Thread*)+0x6a5 > V [libjvm.so+0x1055bab] os::os_exception_wrapper(void > (*)(JavaValue*, methodHandle const&, JavaCallArguments*, Thread*), > JavaValue*, methodHandle const&, JavaCallArguments*, Thread*)+0x41 > V [libjvm.so+0xc6c0a2] JavaCalls::call(JavaValue*, methodHandle > const&, JavaCallArguments*, Thread*)+0xaa > V [libjvm.so+0xc6b1b3] JavaCalls::call_virtual(JavaValue*, > KlassHandle, Symbol*, Symbol*, JavaCallArguments*, Thread*)+0x1f1 > V [libjvm.so+0xc6b3f0] JavaCalls::call_virtual(JavaValue*, Handle, > KlassHandle, Symbol*, Symbol*, Handle, Thread*)+0xd2 > V [libjvm.so+0x1201c56] > SystemDictionary::load_instance_class(Symbol*, Handle, Thread*)+0x93c > V [libjvm.so+0x11feda0] > SystemDictionary::resolve_instance_class_or_null(Symbol*, Handle, > Handle, Thread*)+0x8c0 > V [libjvm.so+0x11fd298] SystemDictionary::resolve_or_null(Symbol*, > Handle, Handle, Thread*)+0x262 > V [libjvm.so+0x11fcca3] SystemDictionary::resolve_or_fail(Symbol*, > Handle, Handle, bool, Thread*)+0x45 > V [libjvm.so+0x1286586] > VerificationType::resolve_and_check_assignability(instanceKlassHandle, > Symbol*, Symbol*, bool, bool, bool, Thread*)+0x258 > V [libjvm.so+0x128682e] > VerificationType::is_reference_assignable_from(VerificationType > const&, ClassVerifier*, bool, Thread*) const+0x212 > V [libjvm.so+0x118fa55] > VerificationType::is_assignable_from(VerificationType const&, > ClassVerifier*, bool, Thread*) const+0x191 > V [libjvm.so+0x129a98f] StackMapFrame::pop_stack(VerificationType, > Thread*)+0x81 > V [libjvm.so+0x12978f3] > ClassVerifier::verify_invoke_instructions(RawBytecodeStream*, unsigned > int, StackMapFrame*, bool, bool*, VerificationType, constantPoolHandle > const&, StackMapTable*, Thread*)+0x1249 > V [libjvm.so+0x129096b] ClassVerifier::verify_method(methodHandle > const&, Thread*)+0x6b71 > V [libjvm.so+0x1289d1a] ClassVerifier::verify_class(Thread*)+0x12a > V [libjvm.so+0x1287c9e] Verifier::verify(instanceKlassHandle, > Verifier::Mode, bool, Thread*)+0x2c0 > V [libjvm.so+0xc2b9b0] > InstanceKlass::verify_code(instanceKlassHandle, bool, Thread*)+0x7c > V [libjvm.so+0xc2c228] > InstanceKlass::link_class_impl(instanceKlassHandle, bool, > Thread*)+0x55e > V [libjvm.so+0xc2bb3d] InstanceKlass::link_class(Thread*)+0xdf > V [libjvm.so+0xcf5f80] get_class_declared_methods_helper(JNIEnv_*, > _jclass*, unsigned char, bool, Klass*, Thread*)+0x155 > V [libjvm.so+0xcf6570] JVM_GetClassDeclaredMethods+0x1d9 > j java.lang.Class.getDeclaredMethods0(Z)[Ljava/lang/reflect/Method;+0 > java.base@9-internal > j java.lang.Class.privateGetDeclaredMethods(Z)[Ljava/lang/reflect/Method;+34 > java.base@9-internal > j > java.lang.Class.getMethodsRecursive(Ljava/lang/String;[Ljava/lang/Class;Z)Ljava/lang/PublicMethods$MethodList;+2 > java.base@9-internal > j > java.lang.Class.getMethod0(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;+14 > java.base@9-internal > j > java.lang.Class.getMethod(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;+26 > java.base@9-internal > j sun.launcher.LauncherHelper.validateMainClass(Ljava/lang/Class;)V+12 > java.base@9-internal > j > sun.launcher.LauncherHelper.checkAndLoadMain(ZILjava/lang/String;)Ljava/lang/Class;+54 > java.base@9-internal > > This check succeeds, because class-loading is triggered from > sun.launcher.LauncherHelper which is in the 'java.base' package, just > like the 'com.sun.crypto.provider.SunJCE' package. > > But when running with a security manager, there's also a second check, > which is performed in the VM by > 'SystemDictionary::resolve_instance_class_or_null()', right before it > returns the loaded class, by calling > 'SystemDictionary::validate_protection_domain()'. This method in turn > calls 'java.lang.ClassLoader.checkPackageAccess()' to validate the > package access: > > // Invoked by the VM after loading class with this loader. > private void checkPackageAccess(Class cls, ProtectionDomain pd) { > final SecurityManager sm = System.getSecurityManager(); > if (sm != null) { > ... > final String name = cls.getName(); > final int i = name.lastIndexOf('.'); > if (i != -1) { > AccessController.doPrivileged(new PrivilegedAction<>() { > public Void run() { > sm.checkPackageAccess(name.substring(0, i)); > return null; > } > }, new AccessControlContext(new ProtectionDomain[] {pd})); > } > } > } > > This check is done with the protection domain of the initial class > under verification ('Tricky' in our case) which is from the unnamed > module and doesn't have the rights to access the unexported classes > from java.base. Therefor the check fails the second time. Here's the > corresponding VM stack trace: > > j > java.lang.ClassLoader.checkPackageAccess(Ljava/lang/Class;Ljava/security/ProtectionDomain;)V+106 > java.base@9-internal > v ~StubRoutines::call_stub > V [libjvm.so+0xc6c75d] JavaCalls::call_helper(JavaValue*, > methodHandle const&, JavaCallArguments*, Thread*)+0x6a5 > V [libjvm.so+0x1055bab] os::os_exception_wrapper(void > (*)(JavaValue*, methodHandle const&, JavaCallArguments*, Thread*), > JavaValue*, methodHandle const&, JavaCallArguments*, Thread*)+0x41 > V [libjvm.so+0xc6c0a2] JavaCalls::call(JavaValue*, methodHandle > const&, JavaCallArguments*, Thread*)+0xaa > V [libjvm.so+0xc6b6a9] JavaCalls::call_special(JavaValue*, > KlassHandle, Symbol*, Symbol*, JavaCallArguments*, Thread*)+0x173 > V [libjvm.so+0xc6ba04] JavaCalls::call_special(JavaValue*, Handle, > KlassHandle, Symbol*, Symbol*, Handle, Handle, Thread*)+0xf6 > V [libjvm.so+0x11fdcb0] > SystemDictionary::validate_protection_domain(instanceKlassHandle, > Handle, Handle, Thread*)+0x22e > V [libjvm.so+0x11ff40a] > SystemDictionary::resolve_instance_class_or_null(Symbol*, Handle, > Handle, Thread*)+0xf2a > V [libjvm.so+0x11fd298] SystemDictionary::resolve_or_null(Symbol*, > Handle, Handle, Thread*)+0x262 > V [libjvm.so+0x11fcca3] SystemDictionary::resolve_or_fail(Symbol*, > Handle, Handle, bool, Thread*)+0x45 > V [libjvm.so+0x1286586] > VerificationType::resolve_and_check_assignability(instanceKlassHandle, > Symbol*, Symbol*, bool, bool, bool, Thread*)+0x258 > V [libjvm.so+0x128682e] > VerificationType::is_reference_assignable_from(VerificationType > const&, ClassVerifier*, bool, Thread*) const+0x212 > V [libjvm.so+0x118fa55] > VerificationType::is_assignable_from(VerificationType const&, > ClassVerifier*, bool, Thread*) const+0x191 > V [libjvm.so+0x129a98f] StackMapFrame::pop_stack(VerificationType, > Thread*)+0x81 > V [libjvm.so+0x12978f3] > ClassVerifier::verify_invoke_instructions(RawBytecodeStream*, unsigned > int, StackMapFrame*, bool, bool*, VerificationType, constantPoolHandle > const&, StackMapTable*, Thread*)+0x1249 > V [libjvm.so+0x129096b] ClassVerifier::verify_method(methodHandle > const&, Thread*)+0x6b71 > V [libjvm.so+0x1289d1a] ClassVerifier::verify_class(Thread*)+0x12a > V [libjvm.so+0x1287c9e] Verifier::verify(instanceKlassHandle, > Verifier::Mode, bool, Thread*)+0x2c0 > V [libjvm.so+0xc2b9b0] > InstanceKlass::verify_code(instanceKlassHandle, bool, Thread*)+0x7c > V [libjvm.so+0xc2c228] > InstanceKlass::link_class_impl(instanceKlassHandle, bool, > Thread*)+0x55e > V [libjvm.so+0xc2bb3d] InstanceKlass::link_class(Thread*)+0xdf > V [libjvm.so+0xcf5f80] get_class_declared_methods_helper(JNIEnv_*, > _jclass*, unsigned char, bool, Klass*, Thread*)+0x155 > V [libjvm.so+0xcf6570] JVM_GetClassDeclaredMethods+0x1d9 > j java.lang.Class.getDeclaredMethods0(Z)[Ljava/lang/reflect/Method;+0 > java.base@9-internal > j java.lang.Class.privateGetDeclaredMethods(Z)[Ljava/lang/reflect/Method;+34 > java.base@9-internal > j > java.lang.Class.getMethodsRecursive(Ljava/lang/String;[Ljava/lang/Class;Z)Ljava/lang/PublicMethods$MethodList;+2 > java.base@9-internal > j > java.lang.Class.getMethod0(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;+14 > java.base@9-internal > j > java.lang.Class.getMethod(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;+26 > java.base@9-internal > j sun.launcher.LauncherHelper.validateMainClass(Ljava/lang/Class;)V+12 > java.base@9-internal > j > sun.launcher.LauncherHelper.checkAndLoadMain(ZILjava/lang/String;)Ljava/lang/Class;+54 > java.base@9-internal > > It is clear that the verifier sometimes has to load classes in order > to accomplish its duty, even if these classes won't be used later on, > at run-time. > > But the question remains if it is OK for an application to fail just > because the verifier is unable to load classes required for > verification because of security manager restrictions or if the > verifier should run with higher privileges which allow such accesses? > > If the answer to this question is "Yes" (i.e. it's OK to fail), the > consequence of running with a security manager will be that > '--add-exports/--add-opens/--illegal-access=permit' may be not sharp > enough knifes to achieve Java 8 backward compatibility. We may also > have to grant some additional security permissions to our application > classes. > > If the answer will be "No" (i.e. it's not OK to fail in the verifier) > we may have to fix the VM to elevate the permissions used to load > classes from within the verifier. > > Any comments? > > Thank you and best regards, > Volker > > [1] https://bugs.openjdk.java.net/browse/JDK-8174766 > [2] > http://mail.openjdk.java.net/pipermail/security-dev/2017-January/thread.html#15416 > [3] https://bugs.openjdk.java.net/browse/JDK-8055206