/* * Copyright (c) 2020, 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. */ /* * @test * @bug 8246778 * @summary Test that security checks occur for getPermittedSubclasses * @library /test/lib * @modules java.compiler * @build jdk.test.lib.compiler.CompilerUtils jdk.test.lib.compiler.ModuleInfoMaker TestSecurityManagerChecks * @run main/othervm -Djava.security.manager=allow --enable-preview TestSecurityManagerChecks named * @run main/othervm -Djava.security.manager=allow --enable-preview TestSecurityManagerChecks unnamed */ import java.io.IOException; import java.lang.module.Configuration; import java.lang.module.ModuleFinder; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; import jdk.test.lib.compiler.*; public class TestSecurityManagerChecks { private static final ClassLoader OBJECT_CL = null; public static void main(String[] args) throws Throwable { if ("named".equals(args[0])) { runNamedModuleTest(); } else { runUnnamedModuleTest(); } } private static void runNamedModuleTest() throws Throwable { Path classes = compileNamedModuleTest(); URL[] testClassPath = getTestClassPath(); //need to use a different ClassLoader to run the test, so that the checks are performed: ClassLoader testCL = new URLClassLoader(testClassPath, OBJECT_CL); testCL.loadClass("TestSecurityManagerChecks") .getDeclaredMethod("doRunNamedModuleTest", Path.class) .invoke(null, classes); } public static void doRunNamedModuleTest(Path classes) throws Throwable { Configuration testConfig = ModuleLayer.boot() .configuration() .resolve(ModuleFinder.of(), ModuleFinder.of(classes), List.of("test")); ModuleLayer testLayer = ModuleLayer.boot() .defineModulesWithOneLoader(testConfig, OBJECT_CL); // First get hold of the target classes before we enable security Class sealed = Class.forName(testLayer.findModule("test").get(), "test.Base"); //try without a SecurityManager: checkPermittedSubclasses(sealed, "test.a.ImplA1", "test.a.ImplA2", "test.b.ImplB"); String[] denyPackageAccess = new String[1]; int[] checkPackageAccessCallCount = new int[1]; //try with a SecurityManager: SecurityManager sm = new SecurityManager() { @Override public void checkPackageAccess(String pkg) { if (pkg.startsWith("test.")) { checkPackageAccessCallCount[0]++; } if (Objects.equals(denyPackageAccess[0], pkg)) { throw new SecurityException(); } } }; System.setSecurityManager(sm); denyPackageAccess[0] = "test"; //passes - does not return a class from package "test": checkPermittedSubclasses(sealed, "test.a.ImplA1", "test.a.ImplA2", "test.b.ImplB"); if (checkPackageAccessCallCount[0] != 2) { throw new AssertionError("Unexpected call count: " + checkPackageAccessCallCount[0]); } denyPackageAccess[0] = "test.a"; try { sealed.getPermittedSubclasses(); throw new Error("getPermittedSubclasses incorrectly succeeded for " + sealed.getName()); } catch (SecurityException e) { System.out.println("OK - getPermittedSubclasses for " + sealed.getName() + " got expected exception: " + e); } } private static Path compileNamedModuleTest() throws IOException { Path base = Paths.get(".", "named"); Path src = base.resolve("src"); Path classes = base.resolve("classes"); ModuleInfoMaker maker = new ModuleInfoMaker(src); maker.writeJavaFiles("test", "module test {}", "package test; public sealed interface Base permits test.a.ImplA1, test.a.ImplA2, test.b.ImplB, test.c.ImplC {}", "package test.a; public final class ImplA1 implements test.Base {}", "package test.a; public final class ImplA2 implements test.Base {}", "package test.b; public final class ImplB implements test.Base {}", "package test.c; public final class ImplC implements test.Base {}" ); if (!CompilerUtils.compile(src, classes.resolve("test"), "--enable-preview", "-source", System.getProperty("java.specification.version"))) { throw new AssertionError("Compilation didn't succeed!"); } Files.delete(classes.resolve("test").resolve("test").resolve("c").resolve("ImplC.class")); return classes; } private static void runUnnamedModuleTest() throws Throwable { Path classes = compileUnnamedModuleTest(); URL[] testClassPath = getTestClassPath(); //need to use a different ClassLoader to run the test, so that the checks are performed: ClassLoader testCL = new URLClassLoader(testClassPath, OBJECT_CL); testCL.loadClass("TestSecurityManagerChecks") .getDeclaredMethod("doRunUnnamedModuleTest", Path.class) .invoke(null, classes); } public static void doRunUnnamedModuleTest(Path classes) throws Throwable { ClassLoader unnamedModuleCL = new URLClassLoader(new URL[] {classes.toUri().toURL()}, OBJECT_CL); // First get hold of the target classes before we enable security Class sealed = unnamedModuleCL.loadClass("test.Base"); //try without a SecurityManager: checkPermittedSubclasses(sealed, "test.ImplA1", "test.ImplA2", "test.ImplB"); String[] denyPackageAccess = new String[1]; int[] checkPackageAccessCallCount = new int[1]; //try with a SecurityManager: SecurityManager sm = new SecurityManager() { @Override public void checkPackageAccess(String pkg) { if (pkg.equals("test")) { checkPackageAccessCallCount[0]++; } if (Objects.equals(denyPackageAccess[0], pkg)) { throw new SecurityException(); } } }; System.setSecurityManager(sm); denyPackageAccess[0] = "test.unknown"; //passes - does not return a class from package "test.unknown": checkPermittedSubclasses(sealed, "test.ImplA1", "test.ImplA2", "test.ImplB"); if (checkPackageAccessCallCount[0] != 1) { throw new AssertionError("Unexpected call count: " + checkPackageAccessCallCount[0]); } denyPackageAccess[0] = "test"; try { sealed.getPermittedSubclasses(); throw new Error("getPermittedSubclasses incorrectly succeeded for " + sealed.getName()); } catch (SecurityException e) { System.out.println("OK - getPermittedSubclasses for " + sealed.getName() + " got expected exception: " + e); } } private static Path compileUnnamedModuleTest() throws IOException { Path base = Paths.get(".", "unnamed"); Path src = base.resolve("src"); Path classes = base.resolve("classes"); ModuleInfoMaker maker = new ModuleInfoMaker(src); maker.writeJavaFiles("test", "module test {}", "package test; public sealed interface Base permits ImplA1, ImplA2, ImplB, ImplC {}", "package test; public final class ImplA1 implements test.Base {}", "package test; public final class ImplA2 implements test.Base {}", "package test; public final class ImplB implements test.Base {}", "package test; public final class ImplC implements test.Base {}" ); Files.delete(src.resolve("test").resolve("module-info.java")); if (!CompilerUtils.compile(src.resolve("test"), classes, "--enable-preview", "-source", System.getProperty("java.specification.version"))) { throw new AssertionError("Compilation didn't succeed!"); } Files.delete(classes.resolve("test").resolve("ImplC.class")); return classes; } private static void checkPermittedSubclasses(Class c, String... expected) { Class[] subclasses = c.getPermittedSubclasses(); List subclassesNames = Arrays.stream(subclasses) .map(Class::getName) .collect(Collectors.toList()); if (!subclassesNames.equals(Arrays.asList(expected))) { throw new AssertionError("Incorrect permitted subclasses: " + subclassesNames); } } private static URL[] getTestClassPath() { return Arrays.stream(System.getProperty("test.class.path") .split(System.getProperty("path.separator"))) .map(TestSecurityManagerChecks::path2URL) .toArray(s -> new URL[s]); } private static URL path2URL(String p) { try { return Path.of(p).toUri().toURL(); } catch (MalformedURLException ex) { throw new AssertionError(ex); } } }