From 1c248cd668e421717d9daeb8ed6b03ae696fe440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lazar=20Mitrovi=C4=87?= Date: Fri, 6 Aug 2021 14:24:15 +0200 Subject: [PATCH 1/2] Proper getStackAccessControlContext implementation Fixed imports and warnings in SecuritySubstitutions Implemented PrivilegedStack and use FastThreadLocal Added missing getProtectionDomain method Recompute contexts from static initializers in runtime Disallow NO_CONTEXT_SINGLETON in executePrivileged Work around crash when ProcessPropertiesSupport is missing getExecutable impementation Provide dummy setters for substituted contexts as their values are constant. --- .../impl/ProcessPropertiesSupport.java | 6 +- .../oracle/svm/core/jdk/RecomputedFields.java | 117 ++++++++- .../svm/core/jdk/SecuritySubstitutions.java | 245 ++++++++++++++---- .../oracle/svm/core/jdk/StackTraceUtils.java | 81 +++++- .../oracle/svm/core/thread/JavaThreads.java | 10 +- .../core/thread/Target_java_lang_Thread.java | 14 +- 6 files changed, 403 insertions(+), 70 deletions(-) diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ProcessPropertiesSupport.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ProcessPropertiesSupport.java index a868470c5d6f..94c041516673 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ProcessPropertiesSupport.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/ProcessPropertiesSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -45,7 +45,9 @@ import org.graalvm.nativeimage.c.function.CEntryPointLiteral; public interface ProcessPropertiesSupport { - String getExecutableName(); + default String getExecutableName() { + return "java"; + } long getProcessID(); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/RecomputedFields.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/RecomputedFields.java index 84c5399e46ca..955c493fa01e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/RecomputedFields.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/RecomputedFields.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 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 @@ -33,8 +33,10 @@ import java.lang.ref.WeakReference; import java.lang.reflect.Field; import java.lang.reflect.Modifier; +import java.net.SocketPermission; import java.nio.charset.CharsetDecoder; import java.nio.charset.CoderResult; +import java.security.AccessControlContext; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -46,6 +48,7 @@ import java.util.concurrent.locks.ReentrantLock; import java.util.function.Consumer; +import org.graalvm.compiler.phases.common.LazyValue; import org.graalvm.compiler.serviceprovider.GraalUnsafeAccess; import org.graalvm.compiler.serviceprovider.JavaVersionUtil; import org.graalvm.nativeimage.ImageSingletons; @@ -360,6 +363,118 @@ public static int getCommonPoolParallelism() { } } +/** + * Since AccessControlContextFeature replaces all AccessControlContext objects with + * NO_CONTEXT_SINGLETON, we need to reinitialize them in runtime. + */ + +@TargetClass(className = "java.security.AccessController$AccHolder", onlyWith = JDK11OrLater.class) +@SuppressWarnings("unused") // +final class Target_java_security_AccessController_AccHolder { + @Alias @InjectAccessors(AccessControllerUtil.INNOCUOUS_ACC.class) static AccessControlContext innocuousAcc; +} + +@TargetClass(className = "java.util.Calendar$CalendarAccessControlContext") +@SuppressWarnings("unused") // +final class Target_java_util_Calendar_CalendarAccessControlContext { + @Alias @InjectAccessors(CalendarAccessControlContextAcc.class) static AccessControlContext INSTANCE; +} + +class CalendarAccessControlContextAcc { + static LazyValue acc = new LazyValue<>(() -> AccessControllerUtil.contextWithPermissions( + new RuntimePermission("accessClassInPackage.sun.util.calendar"))); + + static AccessControlContext get() { + return acc.get(); + } +} + +@TargetClass(className = "java.util.concurrent.ForkJoinPool$DefaultForkJoinWorkerThreadFactory", onlyWith = JDK11OrLater.class) +@SuppressWarnings("unused") // +final class Target_java_util_concurrent_ForkJoinPool_DefaultForkJoinWorkerThreadFactory { + @Alias @InjectAccessors(DefaultForkJoinWorkerThreadFactoryAcc.class) static AccessControlContext ACC; +} + +class DefaultForkJoinWorkerThreadFactoryAcc { + static LazyValue acc = new LazyValue<>(() -> AccessControllerUtil.contextWithPermissions( + new RuntimePermission("getClassLoader"), + new RuntimePermission("setContextClassLoader"))); + + static AccessControlContext get() { + return acc.get(); + } +} + +@TargetClass(className = "java.util.concurrent.ForkJoinPool$InnocuousForkJoinWorkerThreadFactory", onlyWith = JDK11OrLater.class) +@SuppressWarnings("unused") // +final class Target_java_util_concurrent_ForkJoinPool_InnocuousForkJoinWorkerThreadFactory { + @Alias @InjectAccessors(InnocuousForkJoinWorkerThreadFactoryAcc.class) static AccessControlContext ACC; +} + +class InnocuousForkJoinWorkerThreadFactoryAcc { + static LazyValue acc = new LazyValue<>(() -> AccessControllerUtil.contextWithPermissions( + new RuntimePermission("modifyThread"), + new RuntimePermission("enableContextClassLoaderOverride"), + new RuntimePermission("modifyThreadGroup"), + new RuntimePermission("getClassLoader"), + new RuntimePermission("setContextClassLoader"))); + + static AccessControlContext get() { + return acc.get(); + } +} + +@TargetClass(className = "java.util.concurrent.ForkJoinWorkerThread") +@SuppressWarnings("unused") // +final class Target_java_util_concurrent_ForkJoinWorkerThread { + @Alias @InjectAccessors(AccessControllerUtil.INNOCUOUS_ACC.class) static AccessControlContext INNOCUOUS_ACC; +} + +@TargetClass(className = "sun.misc.InnocuousThread", onlyWith = JDK8OrEarlier.class) +@SuppressWarnings("unused") // +final class Target_sun_misc_InnocuousThread { + @Alias @InjectAccessors(AccessControllerUtil.INNOCUOUS_ACC.class) static AccessControlContext ACC; +} + +@TargetClass(className = "jdk.internal.misc.InnocuousThread", onlyWith = JDK11OrLater.class) +@SuppressWarnings("unused") // +final class Target_jdk_internal_misc_InnocuousThread { + @Alias @InjectAccessors(AccessControllerUtil.INNOCUOUS_ACC.class) static AccessControlContext ACC; +} + +@TargetClass(className = "javax.management.Monitor", onlyWith = PlatformHasClass.class) +@SuppressWarnings("unused") // +final class Target_javax_management_Monitor { + @Alias @InjectAccessors(AccessControllerUtil.NO_PERMISSIONS_CONTEXT.class) static AccessControlContext noPermissionsACC; +} + +@TargetClass(className = "java.rmi.activation.ActivationID") +@SuppressWarnings("unused") // +final class Target_java_rmi_activation_ActivationID { + @Alias @InjectAccessors(AccessControllerUtil.NO_PERMISSIONS_CONTEXT.class) static AccessControlContext NOPERMS_ACC; +} + +@TargetClass(className = "sun.rmi.transport.DGCCClient", onlyWith = PlatformHasClass.class) +@SuppressWarnings("unused") // +final class Target_sun_rmi_transport_DGCCClient { + @Alias @InjectAccessors(SocketAcc.class) static AccessControlContext SOCKET_ACC; +} + +class SocketAcc { + static LazyValue acc = new LazyValue<>(() -> AccessControllerUtil.contextWithPermissions( + new SocketPermission("*", "connect,resolve"))); + + static AccessControlContext get() { + return acc.get(); + } +} + +@TargetClass(className = "sun.rmi.transport.tcp.TCPTransport") +@SuppressWarnings("unused") // +final class Target_sun_rmi_transport_tcp_TCPTransport { + @Alias @InjectAccessors(AccessControllerUtil.NO_PERMISSIONS_CONTEXT.class) static AccessControlContext NOPERMS_ACC; +} + /** * An injected field to replace ForkJoinPool.common. * 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 428371a714b9..65331d1fdc21 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 @@ -28,9 +28,7 @@ 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; @@ -41,19 +39,23 @@ import java.security.ProtectionDomain; import java.security.Provider; import java.security.SecureRandom; +import java.util.ArrayDeque; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Predicate; import jdk.vm.ci.meta.MetaAccessProvider; import jdk.vm.ci.meta.ResolvedJavaField; +import org.graalvm.compiler.phases.common.LazyValue; 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; @@ -63,6 +65,9 @@ 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.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; @@ -82,93 +87,106 @@ final class Target_java_security_AccessController { @Substitute - private static T doPrivileged(PrivilegedAction action) throws Throwable { - try { - return action.run(); - } catch (Throwable ex) { - throw AccessControllerUtil.wrapCheckedException(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.wrapCheckedException(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.wrapCheckedException(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.wrapCheckedException(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); - } + static AccessControlContext getStackAccessControlContext() { + 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 = JDK14OrLater.class) + private static ProtectionDomain getProtectionDomain(final Class caller) { + return caller.getProtectionDomain(); } @Substitute - private static T doPrivileged(PrivilegedExceptionAction action, AccessControlContext context) throws Throwable { + @TargetElement(onlyWith = JDK14OrLater.class) + static T executePrivileged(PrivilegedExceptionAction action, AccessControlContext context, Class caller) throws Throwable { + if (action == null) { + throw new NullPointerException("Null action"); + } + + if (context != null && context.equals(AccessControllerUtil.NO_CONTEXT_SINGLETON)) { + VMError.shouldNotReachHere("Invoked AccessControlContext was replaced at build time but wasn't reinitialized at run time."); + } + + AccessControllerUtil.PrivilegedStack.push(context, caller); try { return action.run(); - } catch (Throwable ex) { - throw AccessControllerUtil.wrapCheckedException(ex); + } catch (Exception e) { + throw AccessControllerUtil.wrapCheckedException(e); + } finally { + AccessControllerUtil.PrivilegedStack.pop(); } } @Substitute - private static void checkPermission(Permission perm) throws AccessControlException { - } + @TargetElement(onlyWith = JDK14OrLater.class) + static T executePrivileged(PrivilegedAction action, AccessControlContext context, Class caller) throws Throwable { + if (action == null) { + throw new NullPointerException("Null action"); + } - @Substitute - private static AccessControlContext getContext() { - return AccessControllerUtil.NO_CONTEXT_SINGLETON; + if (context != null && context.equals(AccessControllerUtil.NO_CONTEXT_SINGLETON)) { + VMError.shouldNotReachHere("Invoked AccessControlContext was replaced at build time but wasn't reinitialized at run time."); + } + + AccessControllerUtil.PrivilegedStack.push(context, caller); + try { + return action.run(); + } catch (Exception e) { + throw AccessControllerUtil.wrapCheckedException(e); + } finally { + AccessControllerUtil.PrivilegedStack.pop(); + } } @Substitute - private static AccessControlContext createWrapper(DomainCombiner combiner, Class caller, AccessControlContext parent, AccessControlContext context, Permission[] perms) { - return AccessControllerUtil.NO_CONTEXT_SINGLETON; + @TargetElement(onlyWith = JDK14OrLater.class) + @SuppressWarnings("unused") + static AccessControlContext checkContext(AccessControlContext context, Class caller) { + // check if caller is authorized to create context + if (System.getSecurityManager() != null) { + throw VMError.shouldNotReachHere("Needs to be implemented when SecurityManager is supported"); + } + return context; } } @InternalVMMethod +@SuppressWarnings("unused") class AccessControllerUtil { static final AccessControlContext NO_CONTEXT_SINGLETON; @@ -181,6 +199,111 @@ class AccessControllerUtil { } } + static class INNOCUOUS_ACC { + static LazyValue acc = new LazyValue<>(() -> new AccessControlContext(new ProtectionDomain[]{new ProtectionDomain(null, null)})); + + static AccessControlContext get() { + return acc.get(); + } + + static void set(AccessControlContext ctx) { + } + } + + static class NO_PERMISSIONS_CONTEXT { + static LazyValue acc = new LazyValue<>(() -> AccessControllerUtil.contextWithPermissions(new Permission[0])); + + static AccessControlContext get() { + return acc.get(); + } + + static void set(AccessControlContext ctx) { + } + } + + static class GET_CLASS_LOADER_CONTEXT { + static LazyValue acc = new LazyValue<>(() -> AccessControllerUtil.contextWithPermissions(new RuntimePermission("getClassLoader"))); + + static AccessControlContext get() { + return acc.get(); + } + + static void set(AccessControlContext ctx) { + } + } + + static class GET_LOOKUP_CONTEXT { + static LazyValue acc = new LazyValue<>(() -> AccessControllerUtil.contextWithPermissions(new RuntimePermission("dynalink.getLookup"))); + + static AccessControlContext get() { + return acc.get(); + } + + static void set(AccessControlContext ctx) { + } + } + + 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); + + @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(); + } + } + + public static AccessControlContext contextWithPermissions(Permission... perms) { + Permissions permissions = new Permissions(); + for (Permission perm : perms) { + permissions.add(perm); + } + return new AccessControlContext(new ProtectionDomain[]{new ProtectionDomain(null, permissions)}); + } + static Throwable wrapCheckedException(Throwable ex) { if (ex instanceof Exception && !(ex instanceof RuntimeException)) { return new PrivilegedActionException((Exception) ex); @@ -191,6 +314,7 @@ static Throwable wrapCheckedException(Throwable ex) { } @AutomaticFeature +@SuppressWarnings({"unused"}) class AccessControlContextFeature implements Feature { @Override public void duringSetup(DuringSetupAccess access) { @@ -206,9 +330,14 @@ private static Object replaceAccessControlContext(Object obj) { } @TargetClass(java.security.AccessControlContext.class) +@SuppressWarnings({"unused"}) final class Target_java_security_AccessControlContext { - @Alias protected boolean isPrivileged; + @Alias protected boolean isAuthorized; + + @Alias + Target_java_security_AccessControlContext(ProtectionDomain[] context, AccessControlContext privilegedContext) { + } } @TargetClass(SecurityManager.class) @@ -391,6 +520,7 @@ final class Target_javax_crypto_JceSecurity_IdentityWrapper { class JceSecurityAccessor { private static volatile SecureRandom RANDOM; + @SuppressWarnings({"unused"}) static SecureRandom get() { SecureRandom result = RANDOM; if (result == null) { @@ -488,6 +618,7 @@ final class Target_java_security_Policy_PolicyInfo { } @TargetClass(java.security.Policy.class) +@SuppressWarnings({"unused"}) final class Target_java_security_Policy { @Delete @TargetElement(onlyWith = JDK8OrEarlier.class) // @@ -585,6 +716,7 @@ private void engineRefresh() { @Delete("Substrate VM does not use SecurityManager, so loading a security policy file would be misleading") @TargetClass(className = "sun.security.provider.PolicyFile") +@SuppressWarnings({"unused"}) final class Target_sun_security_provider_PolicyFile { } @@ -661,5 +793,6 @@ public Object transform(MetaAccessProvider metaAccess, ResolvedJavaField origina } /** Dummy class to have a class with the file's name. */ +@SuppressWarnings({"unused"}) public final class SecuritySubstitutions { } 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 fd0712617a24..6d7501469c8c 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,10 @@ */ package com.oracle.svm.core.jdk; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.Permissions; +import java.security.ProtectionDomain; import java.util.ArrayList; import org.graalvm.compiler.serviceprovider.JavaVersionUtil; @@ -32,7 +36,12 @@ 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.graal.snippets.CEntryPointSnippets; +import com.oracle.svm.core.jdk.AccessControllerUtil.PrivilegedStack; +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; @@ -42,6 +51,10 @@ import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; +// Checkstyle: stop +import sun.security.util.SecurityConstants; +// Checkstyle: resume + public class StackTraceUtils { private static final Class[] NO_CLASSES = new Class[0]; @@ -314,3 +327,69 @@ private static boolean isExtensionOrPlatformLoader(ClassLoader classLoader) { return false; } } + +/* 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") + public static AccessControlContext getFromStack() { + if (!CEntryPointSnippets.isIsolateInitialized()) { + Permissions perms = new Permissions(); + perms.add(SecurityConstants.ALL_PERMISSION); + return new AccessControlContext(new ProtectionDomain[]{new ProtectionDomain(null, perms)}); + } + 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/thread/JavaThreads.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java index bb208acc802d..a9d3ca6e83d2 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; @@ -704,7 +706,6 @@ 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.
  • *
*/ @@ -713,7 +714,8 @@ static void initializeNewThread( ThreadGroup groupArg, Runnable target, String name, - long stackSize) { + long stackSize, + AccessControlContext acc) { if (name == null) { throw new NullPointerException("name cannot be null"); } @@ -739,6 +741,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 5f45e94a4b90..d4df9094d703 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 @@ -137,7 +137,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) // @@ -274,7 +274,7 @@ private void init(ThreadGroup groupArg, Runnable targetArg, String nameArg, long this.unsafeParkEvent = new AtomicReference<>(); this.sleepParkEvent = new AtomicReference<>(); /* Initialize the rest of the Thread object. */ - JavaThreads.initializeNewThread(this, groupArg, targetArg, nameArg, stackSizeArg); + JavaThreads.initializeNewThread(this, groupArg, targetArg, nameArg, stackSizeArg, null); } @Alias @@ -296,8 +296,8 @@ private Target_java_lang_Thread( /* Injected Target_java_lang_Thread instance field initialization. */ this.unsafeParkEvent = new AtomicReference<>(); this.sleepParkEvent = new AtomicReference<>(); - /* 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 @@ -318,8 +318,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); } /** From 2ad1de667f3dcab8ee357d3d59234bee5164a127 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lazar=20Mitrovi=C4=87?= Date: Sat, 11 Sep 2021 00:38:50 +0200 Subject: [PATCH 2/2] Add allowlist for known-good JDK contexts. --- .../oracle/svm/core/jdk/RecomputedFields.java | 115 ---------------- .../svm/core/jdk/SecuritySubstitutions.java | 128 ++++++++++-------- 2 files changed, 71 insertions(+), 172 deletions(-) diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/RecomputedFields.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/RecomputedFields.java index 955c493fa01e..d6e3aba0745b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/RecomputedFields.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/RecomputedFields.java @@ -33,10 +33,8 @@ import java.lang.ref.WeakReference; import java.lang.reflect.Field; import java.lang.reflect.Modifier; -import java.net.SocketPermission; import java.nio.charset.CharsetDecoder; import java.nio.charset.CoderResult; -import java.security.AccessControlContext; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -48,7 +46,6 @@ import java.util.concurrent.locks.ReentrantLock; import java.util.function.Consumer; -import org.graalvm.compiler.phases.common.LazyValue; import org.graalvm.compiler.serviceprovider.GraalUnsafeAccess; import org.graalvm.compiler.serviceprovider.JavaVersionUtil; import org.graalvm.nativeimage.ImageSingletons; @@ -363,118 +360,6 @@ public static int getCommonPoolParallelism() { } } -/** - * Since AccessControlContextFeature replaces all AccessControlContext objects with - * NO_CONTEXT_SINGLETON, we need to reinitialize them in runtime. - */ - -@TargetClass(className = "java.security.AccessController$AccHolder", onlyWith = JDK11OrLater.class) -@SuppressWarnings("unused") // -final class Target_java_security_AccessController_AccHolder { - @Alias @InjectAccessors(AccessControllerUtil.INNOCUOUS_ACC.class) static AccessControlContext innocuousAcc; -} - -@TargetClass(className = "java.util.Calendar$CalendarAccessControlContext") -@SuppressWarnings("unused") // -final class Target_java_util_Calendar_CalendarAccessControlContext { - @Alias @InjectAccessors(CalendarAccessControlContextAcc.class) static AccessControlContext INSTANCE; -} - -class CalendarAccessControlContextAcc { - static LazyValue acc = new LazyValue<>(() -> AccessControllerUtil.contextWithPermissions( - new RuntimePermission("accessClassInPackage.sun.util.calendar"))); - - static AccessControlContext get() { - return acc.get(); - } -} - -@TargetClass(className = "java.util.concurrent.ForkJoinPool$DefaultForkJoinWorkerThreadFactory", onlyWith = JDK11OrLater.class) -@SuppressWarnings("unused") // -final class Target_java_util_concurrent_ForkJoinPool_DefaultForkJoinWorkerThreadFactory { - @Alias @InjectAccessors(DefaultForkJoinWorkerThreadFactoryAcc.class) static AccessControlContext ACC; -} - -class DefaultForkJoinWorkerThreadFactoryAcc { - static LazyValue acc = new LazyValue<>(() -> AccessControllerUtil.contextWithPermissions( - new RuntimePermission("getClassLoader"), - new RuntimePermission("setContextClassLoader"))); - - static AccessControlContext get() { - return acc.get(); - } -} - -@TargetClass(className = "java.util.concurrent.ForkJoinPool$InnocuousForkJoinWorkerThreadFactory", onlyWith = JDK11OrLater.class) -@SuppressWarnings("unused") // -final class Target_java_util_concurrent_ForkJoinPool_InnocuousForkJoinWorkerThreadFactory { - @Alias @InjectAccessors(InnocuousForkJoinWorkerThreadFactoryAcc.class) static AccessControlContext ACC; -} - -class InnocuousForkJoinWorkerThreadFactoryAcc { - static LazyValue acc = new LazyValue<>(() -> AccessControllerUtil.contextWithPermissions( - new RuntimePermission("modifyThread"), - new RuntimePermission("enableContextClassLoaderOverride"), - new RuntimePermission("modifyThreadGroup"), - new RuntimePermission("getClassLoader"), - new RuntimePermission("setContextClassLoader"))); - - static AccessControlContext get() { - return acc.get(); - } -} - -@TargetClass(className = "java.util.concurrent.ForkJoinWorkerThread") -@SuppressWarnings("unused") // -final class Target_java_util_concurrent_ForkJoinWorkerThread { - @Alias @InjectAccessors(AccessControllerUtil.INNOCUOUS_ACC.class) static AccessControlContext INNOCUOUS_ACC; -} - -@TargetClass(className = "sun.misc.InnocuousThread", onlyWith = JDK8OrEarlier.class) -@SuppressWarnings("unused") // -final class Target_sun_misc_InnocuousThread { - @Alias @InjectAccessors(AccessControllerUtil.INNOCUOUS_ACC.class) static AccessControlContext ACC; -} - -@TargetClass(className = "jdk.internal.misc.InnocuousThread", onlyWith = JDK11OrLater.class) -@SuppressWarnings("unused") // -final class Target_jdk_internal_misc_InnocuousThread { - @Alias @InjectAccessors(AccessControllerUtil.INNOCUOUS_ACC.class) static AccessControlContext ACC; -} - -@TargetClass(className = "javax.management.Monitor", onlyWith = PlatformHasClass.class) -@SuppressWarnings("unused") // -final class Target_javax_management_Monitor { - @Alias @InjectAccessors(AccessControllerUtil.NO_PERMISSIONS_CONTEXT.class) static AccessControlContext noPermissionsACC; -} - -@TargetClass(className = "java.rmi.activation.ActivationID") -@SuppressWarnings("unused") // -final class Target_java_rmi_activation_ActivationID { - @Alias @InjectAccessors(AccessControllerUtil.NO_PERMISSIONS_CONTEXT.class) static AccessControlContext NOPERMS_ACC; -} - -@TargetClass(className = "sun.rmi.transport.DGCCClient", onlyWith = PlatformHasClass.class) -@SuppressWarnings("unused") // -final class Target_sun_rmi_transport_DGCCClient { - @Alias @InjectAccessors(SocketAcc.class) static AccessControlContext SOCKET_ACC; -} - -class SocketAcc { - static LazyValue acc = new LazyValue<>(() -> AccessControllerUtil.contextWithPermissions( - new SocketPermission("*", "connect,resolve"))); - - static AccessControlContext get() { - return acc.get(); - } -} - -@TargetClass(className = "sun.rmi.transport.tcp.TCPTransport") -@SuppressWarnings("unused") // -final class Target_sun_rmi_transport_tcp_TCPTransport { - @Alias @InjectAccessors(AccessControllerUtil.NO_PERMISSIONS_CONTEXT.class) static AccessControlContext NOPERMS_ACC; -} - /** * An injected field to replace ForkJoinPool.common. * 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 65331d1fdc21..2860b8bec9c6 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 @@ -40,6 +40,7 @@ import java.security.Provider; import java.security.SecureRandom; import java.util.ArrayDeque; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; @@ -48,7 +49,7 @@ import jdk.vm.ci.meta.MetaAccessProvider; import jdk.vm.ci.meta.ResolvedJavaField; -import org.graalvm.compiler.phases.common.LazyValue; +import org.graalvm.collections.Pair; import org.graalvm.compiler.serviceprovider.JavaVersionUtil; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -160,7 +161,8 @@ static T executePrivileged(PrivilegedAction action, AccessControlContext } if (context != null && context.equals(AccessControllerUtil.NO_CONTEXT_SINGLETON)) { - VMError.shouldNotReachHere("Invoked AccessControlContext was replaced at build time but wasn't reinitialized at run time."); + VMError.shouldNotReachHere("Invoked AccessControlContext was replaced at build time but wasn't reinitialized at run time.\n" + + "This might be an indicator of improper build time initialization, or of a non-compatible JDK version."); } AccessControllerUtil.PrivilegedStack.push(context, caller); @@ -199,50 +201,6 @@ class AccessControllerUtil { } } - static class INNOCUOUS_ACC { - static LazyValue acc = new LazyValue<>(() -> new AccessControlContext(new ProtectionDomain[]{new ProtectionDomain(null, null)})); - - static AccessControlContext get() { - return acc.get(); - } - - static void set(AccessControlContext ctx) { - } - } - - static class NO_PERMISSIONS_CONTEXT { - static LazyValue acc = new LazyValue<>(() -> AccessControllerUtil.contextWithPermissions(new Permission[0])); - - static AccessControlContext get() { - return acc.get(); - } - - static void set(AccessControlContext ctx) { - } - } - - static class GET_CLASS_LOADER_CONTEXT { - static LazyValue acc = new LazyValue<>(() -> AccessControllerUtil.contextWithPermissions(new RuntimePermission("getClassLoader"))); - - static AccessControlContext get() { - return acc.get(); - } - - static void set(AccessControlContext ctx) { - } - } - - static class GET_LOOKUP_CONTEXT { - static LazyValue acc = new LazyValue<>(() -> AccessControllerUtil.contextWithPermissions(new RuntimePermission("dynalink.getLookup"))); - - static AccessControlContext get() { - return acc.get(); - } - - static void set(AccessControlContext ctx) { - } - } - public static class PrivilegedStack { public static class StackElement { @@ -263,7 +221,7 @@ public Class getCaller() { } } - @SuppressWarnings("rawtypes") private static final FastThreadLocalObject stack = FastThreadLocalFactory.createObject(ArrayDeque.class); + @SuppressWarnings("rawtypes") private static final FastThreadLocalObject stack = FastThreadLocalFactory.createObject(ArrayDeque.class, "accStack"); @SuppressWarnings("unchecked") private static ArrayDeque getStack() { @@ -296,14 +254,6 @@ public static int length() { } } - public static AccessControlContext contextWithPermissions(Permission... perms) { - Permissions permissions = new Permissions(); - for (Permission perm : perms) { - permissions.add(perm); - } - return new AccessControlContext(new ProtectionDomain[]{new ProtectionDomain(null, permissions)}); - } - static Throwable wrapCheckedException(Throwable ex) { if (ex instanceof Exception && !(ex instanceof RuntimeException)) { return new PrivilegedActionException((Exception) ex); @@ -316,14 +266,78 @@ static Throwable wrapCheckedException(Throwable ex) { @AutomaticFeature @SuppressWarnings({"unused"}) class AccessControlContextFeature implements Feature { + + static List> allowedContexts; + + @Override + public void beforeAnalysis(BeforeAnalysisAccess 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(access, "java.util.Calendar$CalendarAccessControlContext", "INSTANCE"); + allowContextIfExists(access, "javax.management.monitor.Monitor", "noPermissionsACC"); + + if (JavaVersionUtil.JAVA_SPEC < 9) { + allowContextIfExists(access, "sun.misc.InnocuousThread", "ACC"); + } + if (JavaVersionUtil.JAVA_SPEC >= 9) { + allowContextIfExists(access, "java.security.AccessController$AccHolder", "innocuousAcc"); + allowContextIfExists(access, "java.util.concurrent.ForkJoinPool$DefaultForkJoinWorkerThreadFactory", "ACC"); + } + if (JavaVersionUtil.JAVA_SPEC < 17) { + allowContextIfExists(access, "java.util.concurrent.ForkJoinWorkerThread", "INNOCUOUS_ACC"); + } + if (JavaVersionUtil.JAVA_SPEC >= 9 && JavaVersionUtil.JAVA_SPEC < 17) { + allowContextIfExists(access, "java.util.concurrent.ForkJoinPool$InnocuousForkJoinWorkerThreadFactory", "ACC"); + } + if (JavaVersionUtil.JAVA_SPEC >= 17) { + allowContextIfExists(access, "java.util.concurrent.ForkJoinPool$WorkQueue", "INNOCUOUS_ACC"); + allowContextIfExists(access, "java.util.concurrent.ForkJoinPool$DefaultCommonPoolForkJoinWorkerThreadFactory", "ACC"); + } + } + + static void allowContextIfExists(BeforeAnalysisAccess access, String className, String fieldName) { + if (allowedContexts == null) { + allowedContexts = new ArrayList<>(); + } + try { + // Checkstyle: stop + Class clazz = Class.forName(className); + // Checkstyle: resume + String description = className + "." + fieldName; + access.registerReachabilityHandler(access1 -> { // Use only reachable + // AccessControlContexts + try { + AccessControlContext acc = ReflectionUtil.readStaticField(clazz, fieldName); + allowedContexts.add(Pair.create(description, acc)); + } catch (ReflectionUtil.ReflectionUtilError e) { + VMError.shouldNotReachHere("Following field isn't present in JDK" + JavaVersionUtil.JAVA_SPEC + ": " + description); + } + }, clazz); + + } catch (ReflectiveOperationException e) { + VMError.shouldNotReachHere("Following class isn't present in JDK" + JavaVersionUtil.JAVA_SPEC + ": " + className); + } + } + @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; + if (allowedContexts == null) { // allowedContexts wasn't populated yet + return obj; + } + if (obj instanceof AccessControlContext && obj != AccessControllerUtil.NO_CONTEXT_SINGLETON) { + if (allowedContexts.stream().anyMatch((e) -> obj.equals(e.getRight()))) { + return obj; + } else { + return AccessControllerUtil.NO_CONTEXT_SINGLETON; + } } return obj; }