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..043136a91901 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/AccessControllerUtil.java @@ -0,0 +1,121 @@ +/* + * 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 java.security.AccessControlContext; +import java.security.ProtectionDomain; +import java.util.ArrayDeque; +import java.util.Objects; + +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; + +/** + * Stack for storing AccessControlContexts. Used in conjunction with + * {@code StackAccessControlContextVisitor}. + */ +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; + } + } + + /* Local AccessControlContext stack */ + private static final FastThreadLocalObject> stack; + + static { + @SuppressWarnings("unchecked") + Class> cls = (Class>) (Object) ArrayDeque.class; + stack = FastThreadLocalFactory.createObject(cls, "AccessControlContextStack"); + } + + @SuppressWarnings("unchecked") + private static ArrayDeque getStack() { + if (stack.get() == null) { + initializeStack(); + } + return stack.get(); + } + + private static void initializeStack() { + ArrayDeque tmp = new ArrayDeque<>(); + stack.set(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(); + } +} + +@SuppressWarnings({"unused"}) +public class AccessControllerUtil { + + /** + * Instance that is used to mark contexts that were disallowed in + * {@code AccessControlContextReplacerFeature.replaceAccessControlContext()} If this marker is + * passed to {@code AccessController.doPrivileged()} a runtime error will be thrown. + */ + 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); + } + } +} 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 03c931b8c9bf..f0a4ca3a062d 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 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 @@ -30,9 +30,7 @@ import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.security.AccessControlContext; -import java.security.AccessControlException; import java.security.CodeSource; -import java.security.DomainCombiner; import java.security.Permission; import java.security.PermissionCollection; import java.security.Permissions; @@ -50,11 +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; @@ -62,6 +59,8 @@ import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; import com.oracle.svm.core.annotate.TargetElement; +import com.oracle.svm.core.graal.snippets.CEntryPointSnippets; +import com.oracle.svm.core.thread.Target_java_lang_Thread; import com.oracle.svm.core.util.VMError; import com.oracle.svm.util.ReflectionUtil; @@ -79,133 +78,127 @@ final class Target_java_security_AccessController { @Substitute - private static T doPrivileged(PrivilegedAction action) throws Throwable { - try { - return action.run(); - } catch (Throwable ex) { - throw AccessControllerUtil.wrapCheckedExceptionForPrivilegedAction(ex); - } + @TargetElement(onlyWith = JDK11OrEarlier.class) + public static T doPrivileged(PrivilegedAction action) throws Throwable { + return executePrivileged(action, null, Target_jdk_internal_reflect_Reflection.getCallerClass()); } @Substitute - private static T doPrivilegedWithCombiner(PrivilegedAction action) throws Throwable { - try { - return action.run(); - } catch (Throwable ex) { - throw AccessControllerUtil.wrapCheckedExceptionForPrivilegedAction(ex); - } + @TargetElement(onlyWith = JDK11OrEarlier.class) + public static T doPrivileged(PrivilegedAction action, AccessControlContext context) throws Throwable { + Class caller = Target_jdk_internal_reflect_Reflection.getCallerClass(); + AccessControlContext acc = checkContext(context, caller); + return executePrivileged(action, acc, caller); } @Substitute - private static T doPrivileged(PrivilegedAction action, AccessControlContext context) throws Throwable { - try { - return action.run(); - } catch (Throwable ex) { - throw AccessControllerUtil.wrapCheckedExceptionForPrivilegedAction(ex); - } + @TargetElement(onlyWith = JDK11OrEarlier.class) + public static T doPrivileged(PrivilegedExceptionAction action) throws Throwable { + Class caller = Target_jdk_internal_reflect_Reflection.getCallerClass(); + return executePrivileged(action, null, caller); } @Substitute - private static T doPrivileged(PrivilegedAction action, AccessControlContext context, Permission... perms) throws Throwable { - try { - return action.run(); - } catch (Throwable ex) { - throw AccessControllerUtil.wrapCheckedExceptionForPrivilegedAction(ex); - } + @TargetElement(onlyWith = JDK11OrEarlier.class) + static T doPrivileged(PrivilegedExceptionAction action, AccessControlContext context) throws Throwable { + Class caller = Target_jdk_internal_reflect_Reflection.getCallerClass(); + AccessControlContext acc = checkContext(context, caller); + return executePrivileged(action, acc, caller); } @Substitute - private static T doPrivileged(PrivilegedExceptionAction action) throws Throwable { - try { - return action.run(); - } catch (Throwable ex) { - throw AccessControllerUtil.wrapCheckedException(ex); + @SuppressWarnings("deprecation") + static AccessControlContext getStackAccessControlContext() { + if (!CEntryPointSnippets.isIsolateInitialized()) { + /* + * If isolate still isn't initialized, we can assume that we are so early in the JDK + * initialization that any attempt at stalk walk will fail as not even the basic + * PrintWriter/Logging is available yet. This manifested when + * UseDedicatedVMOperationThread hosted option was set, triggering a runtime crash. + */ + return null; } + return StackAccessControlContextVisitor.getFromStack(); } @Substitute - private static T doPrivilegedWithCombiner(PrivilegedExceptionAction action) throws Throwable { - try { - return action.run(); - } catch (Throwable ex) { - throw AccessControllerUtil.wrapCheckedException(ex); - } + static AccessControlContext getInheritedAccessControlContext() { + return SubstrateUtil.cast(Thread.currentThread(), Target_java_lang_Thread.class).inheritedAccessControlContext; } @Substitute - private static T doPrivilegedWithCombiner(PrivilegedExceptionAction action, AccessControlContext context, Permission... perms) throws Throwable { - try { - return action.run(); - } catch (Throwable ex) { - throw AccessControllerUtil.wrapCheckedException(ex); - } + @TargetElement(onlyWith = JDK17OrLater.class) + private static ProtectionDomain getProtectionDomain(final Class caller) { + return caller.getProtectionDomain(); } @Substitute - private static T doPrivileged(PrivilegedExceptionAction action, AccessControlContext context) throws Throwable { + @TargetElement(onlyWith = JDK17OrLater.class) + @SuppressWarnings("deprecation") // deprecated starting JDK 17 + static T executePrivileged(PrivilegedExceptionAction action, AccessControlContext context, Class caller) throws Throwable { + if (action == null) { + throw new NullPointerException("Null action"); + } + + PrivilegedStack.push(context, caller); try { return action.run(); - } catch (Throwable ex) { - throw AccessControllerUtil.wrapCheckedException(ex); + } catch (RuntimeException ex) { + throw ex; + } catch (Exception ex) { + if (JavaVersionUtil.JAVA_SPEC > 11) { + throw ex; + } else { + throw new PrivilegedActionException(ex); + } + } finally { + PrivilegedStack.pop(); } } @Substitute - private static void checkPermission(Permission perm) throws AccessControlException { - } - - @Substitute - private static AccessControlContext getContext() { - return AccessControllerUtil.NO_CONTEXT_SINGLETON; - } - - @Substitute - private static AccessControlContext createWrapper(DomainCombiner combiner, Class caller, AccessControlContext parent, AccessControlContext context, Permission[] perms) { - return AccessControllerUtil.NO_CONTEXT_SINGLETON; - } -} - -@InternalVMMethod -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); + @TargetElement(onlyWith = JDK17OrLater.class) + @SuppressWarnings("deprecation") // deprecated starting JDK 17 + static T executePrivileged(PrivilegedAction action, AccessControlContext context, Class caller) throws Throwable { + if (action == null) { + throw new NullPointerException("Null action"); } - } - static Throwable wrapCheckedException(Throwable ex) { - if (ex instanceof Exception && !(ex instanceof RuntimeException)) { - return new PrivilegedActionException((Exception) ex); - } else { - return ex; + PrivilegedStack.push(context, caller); + try { + return action.run(); + } catch (RuntimeException ex) { + throw ex; + } catch (Exception ex) { + if (JavaVersionUtil.JAVA_SPEC > 11) { + throw ex; + } else { + throw new PrivilegedActionException(ex); + } + } finally { + PrivilegedStack.pop(); } } - static Throwable wrapCheckedExceptionForPrivilegedAction(Throwable ex) { - if (JavaVersionUtil.JAVA_SPEC <= 11) { - return wrapCheckedException(ex); + @Substitute + @TargetElement(onlyWith = JDK17OrLater.class) + @SuppressWarnings("deprecation") + static AccessControlContext checkContext(AccessControlContext context, Class caller) { + + if (context != null && context.equals(AccessControllerUtil.DISALLOWED_CONTEXT_MARKER)) { + 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 'AccessControlContextReplacerFeature.duringSetup'"); } - return ex; - } -} - -@AutomaticFeature -class AccessControlContextFeature implements Feature { - @Override - public void duringSetup(DuringSetupAccess access) { - access.registerObjectReplacer(AccessControlContextFeature::replaceAccessControlContext); - } - private static Object replaceAccessControlContext(Object obj) { - if (obj instanceof AccessControlContext) { - return AccessControllerUtil.NO_CONTEXT_SINGLETON; + // check if caller is authorized to create context + if (System.getSecurityManager() != null) { + throw VMError.unsupportedFeature("SecurityManager isn't supported"); } - return obj; + return context; } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/StackTraceUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/StackTraceUtils.java index a13842bb1129..5c57392d8f98 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/StackTraceUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/StackTraceUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 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 @@ -24,6 +24,9 @@ */ package com.oracle.svm.core.jdk; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.ProtectionDomain; import java.util.ArrayList; import org.graalvm.nativeimage.IsolateThread; @@ -31,7 +34,10 @@ import org.graalvm.word.Pointer; import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.core.annotate.NeverInline; import com.oracle.svm.core.code.FrameInfoQueryResult; +import com.oracle.svm.core.snippets.KnownIntrinsics; import com.oracle.svm.core.stack.JavaStackFrameVisitor; import com.oracle.svm.core.stack.JavaStackWalker; import com.oracle.svm.core.thread.JavaContinuations; @@ -312,3 +318,65 @@ private static boolean isExtensionOrPlatformLoader(ClassLoader classLoader) { return classLoader == Target_jdk_internal_loader_ClassLoaders.platformClassLoader(); } } + +/* Reimplementation of JVM_GetStackAccessControlContext from JDK15 */ +class StackAccessControlContextVisitor extends JavaStackFrameVisitor { + final ArrayList localArray; + boolean isPrivileged; + ProtectionDomain previousProtectionDomain; + AccessControlContext privilegedContext; + + StackAccessControlContextVisitor() { + localArray = new ArrayList<>(); + isPrivileged = false; + privilegedContext = null; + } + + @Override + public boolean visitFrame(final FrameInfoQueryResult frameInfo) { + if (!StackTraceUtils.shouldShowFrame(frameInfo, true, false, false)) { + return true; + } + + Class clazz = frameInfo.getSourceClass(); + String method = frameInfo.getSourceMethodName(); + + ProtectionDomain protectionDomain; + if (PrivilegedStack.length() > 0 && clazz.equals(AccessController.class) && method.equals("doPrivileged")) { + isPrivileged = true; + privilegedContext = PrivilegedStack.peekContext(); + protectionDomain = PrivilegedStack.peekCaller().getProtectionDomain(); + } else { + protectionDomain = clazz.getProtectionDomain(); + } + + if ((protectionDomain != null) && (previousProtectionDomain == null || !previousProtectionDomain.equals(protectionDomain))) { + localArray.add(protectionDomain); + previousProtectionDomain = protectionDomain; + } + + return !isPrivileged; + } + + @NeverInline("Starting a stack walk in the caller frame") + @SuppressWarnings({"deprecation"}) // deprecated starting JDK 17 + public static AccessControlContext getFromStack() { + StackAccessControlContextVisitor visitor = new StackAccessControlContextVisitor(); + JavaStackWalker.walkCurrentThread(KnownIntrinsics.readCallerStackPointer(), visitor); + Target_java_security_AccessControlContext wrapper; + + if (visitor.localArray.isEmpty()) { + if (visitor.isPrivileged && visitor.privilegedContext == null) { + return null; + } + wrapper = new Target_java_security_AccessControlContext(null, visitor.privilegedContext); + } else { + ProtectionDomain[] context = visitor.localArray.toArray(new ProtectionDomain[visitor.localArray.size()]); + wrapper = new Target_java_security_AccessControlContext(context, visitor.privilegedContext); + } + + wrapper.isPrivileged = visitor.isPrivileged; + wrapper.isAuthorized = true; + return SubstrateUtil.cast(wrapper, AccessControlContext.class); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_net_URLClassLoader.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_net_URLClassLoader.java index aa357131f51e..573616d980ce 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_net_URLClassLoader.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_net_URLClassLoader.java @@ -29,6 +29,7 @@ import java.io.InputStream; import java.net.URL; import java.net.URLClassLoader; +import java.security.AccessControlContext; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; @@ -55,6 +56,10 @@ final class Target_jdk_internal_loader_URLClassPath { @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.NewInstance, declClass = ArrayList.class)// private ArrayList path; + /* Reset acc to null, since contexts in image heap are replaced */ + @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset)// + private AccessControlContext acc; + @Substitute public Target_jdk_internal_loader_Resource getResource(String name, boolean check) { return ResourcesHelper.nameToResource(name); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_security_AccessControlContext.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_security_AccessControlContext.java index e07768dcb98f..2a53e38b78de 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_security_AccessControlContext.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_security_AccessControlContext.java @@ -32,12 +32,23 @@ import sun.security.util.Debug; +import java.security.AccessControlContext; +import java.security.ProtectionDomain; + @TargetClass(java.security.AccessControlContext.class) final class Target_java_security_AccessControlContext { @Alias // boolean isPrivileged; + @Alias // + protected boolean isAuthorized; + + @Alias // + @SuppressWarnings("unused") // + Target_java_security_AccessControlContext(ProtectionDomain[] context, AccessControlContext privilegedContext) { + } + /** * Avoid making the code for debug printing reachable. We do not need it, and it only increases * code size. If we ever want to enable debug printing, several classes related to it need to be diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_reflect_Reflection.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_reflect_Reflection.java index e5c7850edbee..fc0370e6e3b6 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_reflect_Reflection.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_jdk_internal_reflect_Reflection.java @@ -35,7 +35,7 @@ final class Target_jdk_internal_reflect_Reflection { @Substitute @NeverInline("Starting a stack walk in the caller frame") - private static Class getCallerClass() { + public static Class getCallerClass() { return StackTraceUtils.getCallerClass(KnownIntrinsics.readCallerStackPointer(), true); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java index 0f2381a83254..d5975112470b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 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 @@ -28,6 +28,8 @@ import static com.oracle.svm.core.snippets.KnownIntrinsics.readCallerStackPointer; import java.lang.Thread.UncaughtExceptionHandler; +import java.security.AccessControlContext; +import java.security.AccessController; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -745,16 +747,17 @@ public static void dispatchUncaughtException(Thread thread, Throwable throwable) * with these unsupported features removed: *
    *
  • No security manager: using the ContextClassLoader of the parent.
  • - *
  • Not implemented: inheritedAccessControlContext.
  • *
  • Not implemented: inheritableThreadLocals.
  • *
*/ + @SuppressWarnings({"deprecation"}) // AccessController is deprecated starting JDK 17 static void initializeNewThread( Target_java_lang_Thread tjlt, ThreadGroup groupArg, Runnable target, String name, - long stackSize) { + long stackSize, + AccessControlContext acc) { if (name == null) { throw new NullPointerException("name cannot be null"); } @@ -780,6 +783,8 @@ static void initializeNewThread( tjlt.contextClassLoader = parent.getContextClassLoader(); + tjlt.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext(); + /* Set thread ID */ tjlt.tid = Target_java_lang_Thread.nextThreadID(); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_Thread.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_Thread.java index 2817a0cddce4..380a168f35cf 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_Thread.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_Thread.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 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 @@ -136,7 +136,7 @@ public final class Target_java_lang_Thread { * inherit a (more or less random) access control context. */ @Alias @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Reset) // - private AccessControlContext inheritedAccessControlContext; + public AccessControlContext inheritedAccessControlContext; @Alias @TargetElement(onlyWith = NotLoomJDK.class) // @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = ThreadStatusRecomputation.class) // @@ -294,8 +294,8 @@ private Target_java_lang_Thread( this.blockerLock = new Object(); /* Injected Target_java_lang_Thread instance field initialization. */ this.threadData = new ThreadData(); - /* Initialize the rest of the Thread object, ignoring `acc` and `inheritThreadLocals`. */ - JavaThreads.initializeNewThread(this, g, target, name, stackSize); + /* Initialize the rest of the Thread object, ignoring `inheritThreadLocals`. */ + JavaThreads.initializeNewThread(this, g, target, name, stackSize, acc); } @Substitute @@ -315,8 +315,8 @@ private Target_java_lang_Thread( checkCharacteristics(characteristics); - /* Initialize the rest of the Thread object, ignoring `acc` and `characteristics`. */ - JavaThreads.initializeNewThread(this, g, target, name, stackSize); + /* Initialize the rest of the Thread object, ignoring `characteristics`. */ + JavaThreads.initializeNewThread(this, g, target, name, stackSize, acc); } /** 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..b4c32b7f7b16 --- /dev/null +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jdk/AccessControlContextReplacerFeature.java @@ -0,0 +1,172 @@ +/* + * 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.security.DomainCombiner; +import java.security.Permission; +import java.security.ProtectionDomain; +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 originate from JDK's static final fields, and they do not + * feature CodeSources, DomainCombiners etc. + * + * New JDK versions will remove old contexts (since AccessControlContext is marked as + * deprecated since JDK 17), so this method should be kept up-to-date. When a listed context + * is removed from JDK, an error message will be thrown from + * AccessControlContextReplacerFeature.allowContextIfExists, so maintaining this list should + * not be difficult (just adding upper bound for JAVA_SPEC in if statements below). + * + * In addition to these contexts, only the very simple contexts are permitted in the image + * heap (see isSimpleContext method). + */ + allowContextIfExists("java.util.Calendar$CalendarAccessControlContext", "INSTANCE"); + allowContextIfExists("javax.management.monitor.Monitor", "noPermissionsACC"); + + if (JavaVersionUtil.JAVA_SPEC <= 8) { + allowContextIfExists("sun.misc.InnocuousThread", "ACC"); + } + if (JavaVersionUtil.JAVA_SPEC >= 11) { + 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 >= 11 && 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 boolean isSimpleContext(AccessControlContext ctx) { + /* + * In addition to aforementioned allow-listed contexts we also allow inclusion of very + * strict subset of contexts that couldn't possibly leak sensitive information in the image + * heap. This set of rules is overly strict on purpose as we want to be on a safe side. + * + * Issues could arise only in cases where end-users manually marked classes that rely on + * doPrivileged invocation in static initializers for initialization at build time. At that + * point they will be presented with an error message from + * Target_java_security_AccessController.checkContext that will inform them about potential + * fixes. + */ + ProtectionDomain[] context = ReflectionUtil.readField(AccessControlContext.class, "context", ctx); + AccessControlContext privilegedContext = ReflectionUtil.readField(AccessControlContext.class, "privilegedContext", ctx); + DomainCombiner combiner = ReflectionUtil.readField(AccessControlContext.class, "combiner", ctx); + Permission[] permissions = ReflectionUtil.readField(AccessControlContext.class, "permissions", ctx); + AccessControlContext parent = ReflectionUtil.readField(AccessControlContext.class, "parent", ctx); + ProtectionDomain[] limitedContext = ReflectionUtil.readField(AccessControlContext.class, "limitedContext", ctx); + + if (context != null && context.length > 0) { + return checkPD(context); + } + if (combiner != null) { + return false; + } + if (parent != null) { + return isSimpleContext(parent); + } + if (limitedContext != null && limitedContext.length > 0) { + return checkPD(limitedContext); + } + if (privilegedContext != null) { + return isSimpleContext(privilegedContext); + } + return true; + } + + private static boolean checkPD(ProtectionDomain[] list) { + for (ProtectionDomain pd : list) { + if (pd.getCodeSource() != null) { + return false; + } + if (pd.getPrincipals().length > 0) { + return false; + } + if (pd.getPermissions() != null) { + return false; + /* + * Technically we could allow certain permissions but this could be fragile. + * Contexts from user code should be reinitialized at runtime anyways. + */ + } + } + return true; + } + + private static Object replaceAccessControlContext(Object obj) { + if (obj instanceof AccessControlContext && obj != AccessControllerUtil.DISALLOWED_CONTEXT_MARKER) { + if (allowedContexts.containsValue(obj)) { + return obj; + } else if (isSimpleContext((AccessControlContext) obj)) { + return obj; + } else { + return AccessControllerUtil.DISALLOWED_CONTEXT_MARKER; + } + } + return obj; + } +}