diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java index aedb38d44f26..17055d3c3d45 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/hub/DynamicHub.java @@ -55,16 +55,14 @@ import java.util.Set; import java.util.StringJoiner; -import com.oracle.svm.core.BaseProcessPropertiesSupport; import org.graalvm.compiler.core.common.NumUtil; import org.graalvm.compiler.core.common.SuppressFBWarnings; import org.graalvm.compiler.serviceprovider.JavaVersionUtil; -import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.ImageInfo; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.nativeimage.ProcessProperties; import org.graalvm.nativeimage.c.function.CFunctionPointer; -import org.graalvm.nativeimage.impl.ProcessPropertiesSupport; import org.graalvm.util.DirectAnnotationAccess; import com.oracle.svm.core.RuntimeAssertionsSupport; @@ -329,6 +327,7 @@ public void setModule(Object module) { this.module = module; } + static final boolean IS_EXECUTABLE = ImageInfo.isExecutable(); /** * Final fields in substituted classes are treated as implicitly RecomputeFieldValue even when * not annotated with @RecomputeFieldValue. Their name must not match a field in the original @@ -337,20 +336,21 @@ public void setModule(Object module) { static final LazyFinalReference allPermDomainReference = new LazyFinalReference<>(() -> { java.security.Permissions perms = new java.security.Permissions(); perms.add(SecurityConstants.ALL_PERMISSION); - CodeSource cs = null; + URL url = null; - if (ImageSingletons.lookup(ProcessPropertiesSupport.class) instanceof BaseProcessPropertiesSupport) { + if (IS_EXECUTABLE) { // Try to use executable image's name as code source for the class. // The file location can be used by Java code to determine its location on disk, similar // to argv[0]. try { - cs = new CodeSource(new File(ProcessProperties.getExecutableName()).toURI().toURL(), (Certificate[]) null); + url = new File(ProcessProperties.getExecutableName()).toURI().toURL(); } catch (MalformedURLException e) { // This should not really happen; the file is cannonicalized, absolute, so it should // always have file:// URL. } } + CodeSource cs = new CodeSource(url, (Certificate[]) null); return new java.security.ProtectionDomain(cs, perms); }); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/AccessControllerUtil.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/AccessControllerUtil.java new file mode 100644 index 000000000000..2962813893e2 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/AccessControllerUtil.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2021, 2021, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package com.oracle.svm.core.jdk; + +import com.oracle.svm.core.threadlocal.FastThreadLocalFactory; +import com.oracle.svm.core.threadlocal.FastThreadLocalObject; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.util.ReflectionUtil; + +import java.security.AccessControlContext; +import java.security.PrivilegedActionException; +import java.security.ProtectionDomain; +import java.util.ArrayDeque; +import java.util.Objects; + +@InternalVMMethod +@SuppressWarnings({"unused"}) +public class AccessControllerUtil { + + public static final AccessControlContext DISALLOWED_CONTEXT_MARKER; + + static { + try { + DISALLOWED_CONTEXT_MARKER = ReflectionUtil.lookupConstructor(AccessControlContext.class, ProtectionDomain[].class, boolean.class).newInstance(new ProtectionDomain[0], true); + } catch (ReflectiveOperationException ex) { + throw VMError.shouldNotReachHere(ex); + } + } + + public static class PrivilegedStack { + + public static class StackElement { + protected AccessControlContext context; + protected Class caller; + + StackElement(AccessControlContext context, Class caller) { + this.context = context; + this.caller = caller; + } + + public AccessControlContext getContext() { + return context; + } + + public Class getCaller() { + return caller; + } + } + + @SuppressWarnings("rawtypes") private static final FastThreadLocalObject stack = FastThreadLocalFactory.createObject(ArrayDeque.class, "AccessControlContextStack"); + + @SuppressWarnings("unchecked") + private static ArrayDeque getStack() { + ArrayDeque tmp = stack.get(); + if (tmp == null) { + tmp = new ArrayDeque<>(); + stack.set(tmp); + } + return tmp; + } + + public static void push(AccessControlContext context, Class caller) { + getStack().push(new StackElement(context, caller)); + } + + public static void pop() { + getStack().pop(); + } + + public static AccessControlContext peekContext() { + return Objects.requireNonNull(getStack().peek()).getContext(); + } + + public static Class peekCaller() { + return Objects.requireNonNull(getStack().peek()).getCaller(); + } + + public static int length() { + return getStack().size(); + } + } + + static Throwable wrapCheckedException(Throwable ex) { + if (ex instanceof Exception && !(ex instanceof RuntimeException)) { + return new PrivilegedActionException((Exception) ex); + } else { + return ex; + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java index a9fbb4d01bba..bf06bb51cf1b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/SecuritySubstitutions.java @@ -34,16 +34,12 @@ import java.security.Permissions; import java.security.Policy; import java.security.PrivilegedAction; -import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.security.ProtectionDomain; import java.security.Provider; import java.security.SecureRandom; -import java.util.ArrayDeque; -import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Predicate; @@ -52,12 +48,10 @@ import org.graalvm.compiler.serviceprovider.JavaVersionUtil; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; -import org.graalvm.nativeimage.hosted.Feature; import org.graalvm.word.Pointer; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.annotate.Alias; -import com.oracle.svm.core.annotate.AutomaticFeature; import com.oracle.svm.core.annotate.Delete; import com.oracle.svm.core.annotate.InjectAccessors; import com.oracle.svm.core.annotate.NeverInline; @@ -66,10 +60,7 @@ import com.oracle.svm.core.annotate.TargetClass; import com.oracle.svm.core.annotate.TargetElement; import com.oracle.svm.core.thread.Target_java_lang_Thread; -import com.oracle.svm.core.threadlocal.FastThreadLocalFactory; -import com.oracle.svm.core.threadlocal.FastThreadLocalObject; import com.oracle.svm.core.util.VMError; -import com.oracle.svm.util.ReflectionUtil; // Checkstyle: stop import sun.security.jca.ProviderList; @@ -171,13 +162,13 @@ static T executePrivileged(PrivilegedAction action, AccessControlContext @SuppressWarnings({"unused", "deprecation"}) static AccessControlContext checkContext(AccessControlContext context, Class caller) { - if (context != null && context.equals(AccessControllerUtil.NO_CONTEXT_SINGLETON)) { - VMError.shouldNotReachHere("Non-allowed AccessControlContext that was replaced with a blank one at build time was invoked without being reinitialized at run time.\n" + + if (context != null && context.equals(AccessControllerUtil.DISALLOWED_CONTEXT_MARKER)) { + throw VMError.shouldNotReachHere("Non-allowed AccessControlContext that was replaced with a blank one at build time was invoked without being reinitialized at run time.\n" + "This might be an indicator of improper build time initialization, or of a non-compatible JDK version.\n" + "In order to fix this you can either:\n" + " * Annotate the offending context's field with @RecomputeFieldValue\n" + " * Implement a custom runtime accessor and annotate said field with @InjectAccessors\n" + - " * If this context originates from the JDK, and it doesn't leak sensitive info, you can allow it in 'AccessControlContextFeature.duringSetup'"); + " * If this context originates from the JDK, and it doesn't leak sensitive info, you can allow it in 'AccessControlContextReplacerFeature.duringSetup'"); } // check if caller is authorized to create context @@ -188,149 +179,6 @@ static AccessControlContext checkContext(AccessControlContext context, Class } } -@InternalVMMethod -@SuppressWarnings({"unused"}) -class AccessControllerUtil { - - static final AccessControlContext NO_CONTEXT_SINGLETON; - - static { - try { - NO_CONTEXT_SINGLETON = ReflectionUtil.lookupConstructor(AccessControlContext.class, ProtectionDomain[].class, boolean.class).newInstance(new ProtectionDomain[0], true); - } catch (ReflectiveOperationException ex) { - throw VMError.shouldNotReachHere(ex); - } - } - - public static class PrivilegedStack { - - public static class StackElement { - protected AccessControlContext context; - protected Class caller; - - StackElement(AccessControlContext context, Class caller) { - this.context = context; - this.caller = caller; - } - - public AccessControlContext getContext() { - return context; - } - - public Class getCaller() { - return caller; - } - } - - @SuppressWarnings("rawtypes") private static final FastThreadLocalObject stack = FastThreadLocalFactory.createObject(ArrayDeque.class, "AccessControlContextStack"); - - @SuppressWarnings("unchecked") - private static ArrayDeque getStack() { - ArrayDeque tmp = stack.get(); - if (tmp == null) { - tmp = new ArrayDeque<>(); - stack.set(tmp); - } - return tmp; - } - - public static void push(AccessControlContext context, Class caller) { - getStack().push(new StackElement(context, caller)); - } - - public static void pop() { - getStack().pop(); - } - - public static AccessControlContext peekContext() { - return Objects.requireNonNull(getStack().peek()).getContext(); - } - - public static Class peekCaller() { - return Objects.requireNonNull(getStack().peek()).getCaller(); - } - - public static int length() { - return getStack().size(); - } - } - - static Throwable wrapCheckedException(Throwable ex) { - if (ex instanceof Exception && !(ex instanceof RuntimeException)) { - return new PrivilegedActionException((Exception) ex); - } else { - return ex; - } - } -} - -@AutomaticFeature -@SuppressWarnings({"unused"}) -class AccessControlContextFeature implements Feature { - - static Map allowedContexts = new HashMap<>(); - - static void allowContextIfExists(String className, String fieldName) { - try { - // Checkstyle: stop - Class clazz = Class.forName(className); - // Checkstyle: resume - String description = className + "." + fieldName; - try { - AccessControlContext acc = ReflectionUtil.readStaticField(clazz, fieldName); - allowedContexts.put(description, acc); - } catch (ReflectionUtil.ReflectionUtilError e) { - VMError.shouldNotReachHere("Following field isn't present in JDK" + JavaVersionUtil.JAVA_SPEC + ": " + description); - } - - } catch (ReflectiveOperationException e) { - VMError.shouldNotReachHere("Following class isn't present in JDK" + JavaVersionUtil.JAVA_SPEC + ": " + className); - } - } - - @Override - public void duringSetup(DuringSetupAccess access) { - // Following AccessControlContexts are allowed in the image heap since they cannot leak - // sensitive information. - // They mostly originate from JDK's static final fields, and they do not feature - // CodeSources, DomainCombiners etc. - // New JDK versions can feature new or remove old contexts, so this method should be kept - // up-to-date. - allowContextIfExists("java.util.Calendar$CalendarAccessControlContext", "INSTANCE"); - allowContextIfExists("javax.management.monitor.Monitor", "noPermissionsACC"); - - if (JavaVersionUtil.JAVA_SPEC < 9) { - allowContextIfExists("sun.misc.InnocuousThread", "ACC"); - } - if (JavaVersionUtil.JAVA_SPEC >= 9) { - allowContextIfExists("java.security.AccessController$AccHolder", "innocuousAcc"); - allowContextIfExists("java.util.concurrent.ForkJoinPool$DefaultForkJoinWorkerThreadFactory", "ACC"); - } - if (JavaVersionUtil.JAVA_SPEC < 17) { - allowContextIfExists("java.util.concurrent.ForkJoinWorkerThread", "INNOCUOUS_ACC"); - } - if (JavaVersionUtil.JAVA_SPEC >= 9 && JavaVersionUtil.JAVA_SPEC < 17) { - allowContextIfExists("java.util.concurrent.ForkJoinPool$InnocuousForkJoinWorkerThreadFactory", "ACC"); - } - if (JavaVersionUtil.JAVA_SPEC >= 17) { - allowContextIfExists("java.util.concurrent.ForkJoinPool$WorkQueue", "INNOCUOUS_ACC"); - allowContextIfExists("java.util.concurrent.ForkJoinPool$DefaultCommonPoolForkJoinWorkerThreadFactory", "ACC"); - } - access.registerObjectReplacer(AccessControlContextFeature::replaceAccessControlContext); - } - - private static Object replaceAccessControlContext(Object obj) { - if (obj instanceof AccessControlContext && obj != AccessControllerUtil.NO_CONTEXT_SINGLETON) { - if (allowedContexts.containsValue(obj)) { - return obj; - } else { - return AccessControllerUtil.NO_CONTEXT_SINGLETON; - } - } - return obj; - } -} - @TargetClass(java.security.AccessControlContext.class) @SuppressWarnings({"unused"}) final class Target_java_security_AccessControlContext { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/AccessControlContextReplacerFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/AccessControlContextReplacerFeature.java new file mode 100644 index 000000000000..d90a9509f2c5 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/AccessControlContextReplacerFeature.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2021, 2021, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package com.oracle.svm.hosted.jdk; + +import com.oracle.svm.core.annotate.AutomaticFeature; +import com.oracle.svm.core.jdk.AccessControllerUtil; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.util.ReflectionUtil; +import org.graalvm.compiler.serviceprovider.JavaVersionUtil; +import org.graalvm.nativeimage.hosted.Feature; + +import java.security.AccessControlContext; +import java.util.HashMap; +import java.util.Map; + +@AutomaticFeature +@SuppressWarnings({"unused"}) +class AccessControlContextReplacerFeature implements Feature { + + static Map allowedContexts = new HashMap<>(); + + static void allowContextIfExists(String className, String fieldName) { + try { + // Checkstyle: stop + Class clazz = Class.forName(className); + // Checkstyle: resume + String description = className + "." + fieldName; + try { + AccessControlContext acc = ReflectionUtil.readStaticField(clazz, fieldName); + allowedContexts.put(description, acc); + } catch (ReflectionUtil.ReflectionUtilError e) { + throw VMError.shouldNotReachHere("Following field isn't present in JDK" + JavaVersionUtil.JAVA_SPEC + ": " + description); + } + + } catch (ReflectiveOperationException e) { + throw VMError.shouldNotReachHere("Following class isn't present in JDK" + JavaVersionUtil.JAVA_SPEC + ": " + className); + } + } + + @Override + public void duringSetup(DuringSetupAccess access) { + // Following AccessControlContexts are allowed in the image heap since they cannot leak + // sensitive information. + // They mostly originate from JDK's static final fields, and they do not feature + // CodeSources, DomainCombiners etc. + // New JDK versions can feature new or remove old contexts, so this method should be kept + // up-to-date. + allowContextIfExists("java.util.Calendar$CalendarAccessControlContext", "INSTANCE"); + allowContextIfExists("javax.management.monitor.Monitor", "noPermissionsACC"); + + if (JavaVersionUtil.JAVA_SPEC < 9) { + allowContextIfExists("sun.misc.InnocuousThread", "ACC"); + } + if (JavaVersionUtil.JAVA_SPEC >= 9) { + allowContextIfExists("java.security.AccessController$AccHolder", "innocuousAcc"); + allowContextIfExists("java.util.concurrent.ForkJoinPool$DefaultForkJoinWorkerThreadFactory", "ACC"); + } + if (JavaVersionUtil.JAVA_SPEC < 17) { + allowContextIfExists("java.util.concurrent.ForkJoinWorkerThread", "INNOCUOUS_ACC"); + } + if (JavaVersionUtil.JAVA_SPEC >= 9 && JavaVersionUtil.JAVA_SPEC < 17) { + allowContextIfExists("java.util.concurrent.ForkJoinPool$InnocuousForkJoinWorkerThreadFactory", "ACC"); + } + if (JavaVersionUtil.JAVA_SPEC >= 17) { + allowContextIfExists("java.util.concurrent.ForkJoinPool$WorkQueue", "INNOCUOUS_ACC"); + allowContextIfExists("java.util.concurrent.ForkJoinPool$DefaultCommonPoolForkJoinWorkerThreadFactory", "ACC"); + } + access.registerObjectReplacer(AccessControlContextReplacerFeature::replaceAccessControlContext); + } + + private static Object replaceAccessControlContext(Object obj) { + if (obj instanceof AccessControlContext && obj != AccessControllerUtil.DISALLOWED_CONTEXT_MARKER) { + if (allowedContexts.containsValue(obj)) { + return obj; + } else { + return AccessControllerUtil.DISALLOWED_CONTEXT_MARKER; + } + } + return obj; + } +}