From 36adb27cf8c10b013dfeca01d89fe17443a4f3d3 Mon Sep 17 00:00:00 2001 From: Ragnar Rova Date: Thu, 10 Nov 2022 00:35:33 +0100 Subject: [PATCH] Allow SDK to run in environments prohibiting use of sun.misc.Unsafe (#4902) Some applications run under strict java.security permissions which do not allow access to sun.misc.Unsafe. BatchSpanProcessor uses Unsafe via jctools, but has a fallback to ArrayBlockingQueue. Extending that fallback rule to cover java security exceptions as well. Co-authored-by: Jack Berg --- sdk/trace-shaded-deps/build.gradle.kts | 5 + .../sdk/trace/internal/JcTools.java | 18 ++- .../internal/JcToolsSecurityManagerTest.java | 38 ++++++ .../SunMiscProhibitedSecurityManager.java | 111 ++++++++++++++++++ .../SunMiscProhibitedSecurityManagerTest.java | 41 +++++++ 5 files changed, 211 insertions(+), 2 deletions(-) create mode 100644 sdk/trace-shaded-deps/src/test/java/io/opentelemetry/sdk/trace/internal/JcToolsSecurityManagerTest.java create mode 100644 sdk/trace-shaded-deps/src/test/java/io/opentelemetry/sdk/trace/internal/SunMiscProhibitedSecurityManager.java create mode 100644 sdk/trace-shaded-deps/src/test/java/io/opentelemetry/sdk/trace/internal/SunMiscProhibitedSecurityManagerTest.java diff --git a/sdk/trace-shaded-deps/build.gradle.kts b/sdk/trace-shaded-deps/build.gradle.kts index 14f73ca4b79..c4573c976b7 100644 --- a/sdk/trace-shaded-deps/build.gradle.kts +++ b/sdk/trace-shaded-deps/build.gradle.kts @@ -26,3 +26,8 @@ tasks { into("build/extracted/shadow") } } + +tasks.withType().configureEach { + // JcToolsSecurityManagerTest interferes with JcToolsTest + setForkEvery(1) +} diff --git a/sdk/trace-shaded-deps/src/main/java/io/opentelemetry/sdk/trace/internal/JcTools.java b/sdk/trace-shaded-deps/src/main/java/io/opentelemetry/sdk/trace/internal/JcTools.java index a293c437aff..06976ad13d5 100644 --- a/sdk/trace-shaded-deps/src/main/java/io/opentelemetry/sdk/trace/internal/JcTools.java +++ b/sdk/trace-shaded-deps/src/main/java/io/opentelemetry/sdk/trace/internal/JcTools.java @@ -5,9 +5,13 @@ package io.opentelemetry.sdk.trace.internal; +import java.util.Objects; import java.util.Queue; import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; +import java.util.logging.Level; +import java.util.logging.Logger; import org.jctools.queues.MessagePassingQueue; import org.jctools.queues.MpscArrayQueue; @@ -19,14 +23,24 @@ */ public final class JcTools { + private static final AtomicBoolean queueCreationWarningLogged = new AtomicBoolean(); + private static final Logger logger = Logger.getLogger(JcTools.class.getName()); + /** * Returns a new {@link Queue} appropriate for use with multiple producers and a single consumer. */ public static Queue newFixedSizeQueue(int capacity) { try { return new MpscArrayQueue<>(capacity); - } catch (java.lang.NoClassDefFoundError e) { - // Happens when modules such as jdk.unsupported are disabled in a custom JRE distribution + } catch (java.lang.NoClassDefFoundError | java.lang.ExceptionInInitializerError e) { + if (!queueCreationWarningLogged.getAndSet(true)) { + logger.log( + Level.WARNING, + "Cannot create high-performance queue, reverting to ArrayBlockingQueue ({0})", + Objects.toString(e, "unknown cause")); + } + // Happens when modules such as jdk.unsupported are disabled in a custom JRE distribution, + // or a security manager preventing access to Unsafe is installed. return new ArrayBlockingQueue<>(capacity); } } diff --git a/sdk/trace-shaded-deps/src/test/java/io/opentelemetry/sdk/trace/internal/JcToolsSecurityManagerTest.java b/sdk/trace-shaded-deps/src/test/java/io/opentelemetry/sdk/trace/internal/JcToolsSecurityManagerTest.java new file mode 100644 index 00000000000..eb2bd9199e3 --- /dev/null +++ b/sdk/trace-shaded-deps/src/test/java/io/opentelemetry/sdk/trace/internal/JcToolsSecurityManagerTest.java @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.trace.internal; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.internal.testing.slf4j.SuppressLogger; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Queue; +import java.util.concurrent.ArrayBlockingQueue; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledOnJre; +import org.junit.jupiter.api.condition.JRE; + +public class JcToolsSecurityManagerTest { + + @Test + @EnabledOnJre({JRE.JAVA_8, JRE.JAVA_11, JRE.JAVA_17}) + @SuppressLogger(JcTools.class) + void newFixedSizeQueue_SunMiscProhibited() { + assertThat(System.getSecurityManager()).isNull(); + SunMiscProhibitedSecurityManager testingSecurityManager = + new SunMiscProhibitedSecurityManager(); + try { + System.setSecurityManager(testingSecurityManager); + Queue queue = + AccessController.doPrivileged( + (PrivilegedAction>) () -> JcTools.newFixedSizeQueue(10)); + assertThat(queue).isInstanceOf(ArrayBlockingQueue.class); + } finally { + System.setSecurityManager(null); + } + } +} diff --git a/sdk/trace-shaded-deps/src/test/java/io/opentelemetry/sdk/trace/internal/SunMiscProhibitedSecurityManager.java b/sdk/trace-shaded-deps/src/test/java/io/opentelemetry/sdk/trace/internal/SunMiscProhibitedSecurityManager.java new file mode 100644 index 00000000000..a158cc02ec9 --- /dev/null +++ b/sdk/trace-shaded-deps/src/test/java/io/opentelemetry/sdk/trace/internal/SunMiscProhibitedSecurityManager.java @@ -0,0 +1,111 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.trace.internal; + +import java.io.FileDescriptor; +import java.net.InetAddress; +import java.security.AccessControlException; +import java.security.Permission; + +/** + * A security manager which disallows access to classes in sun.misc. Running the tests with a + * standard security manager is too invasive. + */ +public class SunMiscProhibitedSecurityManager extends SecurityManager { + + public SunMiscProhibitedSecurityManager() {} + + @Override + protected Class[] getClassContext() { + return super.getClassContext(); + } + + @Override + public void checkPermission(Permission perm) { + if (perm.getName().equals("accessClassInPackage.sun.misc")) { + throw new AccessControlException("access denied " + perm, perm); + } + } + + @Override + public void checkPermission(Permission perm, Object context) {} + + @Override + public void checkCreateClassLoader() {} + + @Override + public void checkAccess(Thread t) {} + + @Override + public void checkAccess(ThreadGroup g) {} + + @Override + public void checkExit(int status) {} + + @Override + public void checkExec(String cmd) {} + + @Override + public void checkLink(String lib) {} + + @Override + public void checkRead(FileDescriptor fd) {} + + @Override + public void checkRead(String file) {} + + @Override + public void checkRead(String file, Object context) {} + + @Override + public void checkWrite(FileDescriptor fd) {} + + @Override + public void checkWrite(String file) {} + + @Override + public void checkDelete(String file) {} + + @Override + public void checkConnect(String host, int port) {} + + @Override + public void checkConnect(String host, int port, Object context) {} + + @Override + public void checkListen(int port) {} + + @Override + public void checkAccept(String host, int port) {} + + @Override + public void checkMulticast(InetAddress maddr) {} + + @Override + public void checkPropertiesAccess() {} + + @Override + public void checkPropertyAccess(String key) {} + + @Override + public void checkPrintJobAccess() {} + + @Override + public void checkPackageAccess(String pkg) { + if (pkg.equals("sun.misc")) { + super.checkPackageAccess(pkg); + } + } + + @Override + public void checkPackageDefinition(String pkg) {} + + @Override + public void checkSetFactory() {} + + @Override + public void checkSecurityAccess(String target) {} +} diff --git a/sdk/trace-shaded-deps/src/test/java/io/opentelemetry/sdk/trace/internal/SunMiscProhibitedSecurityManagerTest.java b/sdk/trace-shaded-deps/src/test/java/io/opentelemetry/sdk/trace/internal/SunMiscProhibitedSecurityManagerTest.java new file mode 100644 index 00000000000..928d97410c3 --- /dev/null +++ b/sdk/trace-shaded-deps/src/test/java/io/opentelemetry/sdk/trace/internal/SunMiscProhibitedSecurityManagerTest.java @@ -0,0 +1,41 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.trace.internal; + +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.security.AccessControlException; +import org.junit.jupiter.api.Test; + +class SunMiscProhibitedSecurityManagerTest { + + @Test + public void checkPackageAccess_ProhibitsSunMisc() { + SunMiscProhibitedSecurityManager sm = new SunMiscProhibitedSecurityManager(); + assertThatThrownBy(() -> sm.checkPackageAccess("sun.misc")) + .isInstanceOf(AccessControlException.class) + .hasMessage( + "access denied (\"java.lang.RuntimePermission\" \"accessClassInPackage.sun.misc\")"); + } + + @Test + public void checkPackageAccess_ProhibitsSunMiscRuntimePermission() { + SunMiscProhibitedSecurityManager sm = new SunMiscProhibitedSecurityManager(); + + assertThatThrownBy( + () -> sm.checkPermission(new RuntimePermission("accessClassInPackage.sun.misc"))) + .isInstanceOf(AccessControlException.class) + .hasMessage( + "access denied (\"java.lang.RuntimePermission\" \"accessClassInPackage.sun.misc\")"); + } + + @Test + public void checkPackageAccess_AllowsOtherPackage() { + SunMiscProhibitedSecurityManager sm = new SunMiscProhibitedSecurityManager(); + assertThatNoException().isThrownBy(() -> sm.checkPackageAccess("io.opentelemetry.sdk")); + } +}