From 4f00058d22c3e53324c1650091b32c400e93adeb Mon Sep 17 00:00:00 2001 From: Chris Hegarty <62058229+ChrisHegarty@users.noreply.github.com> Date: Wed, 10 Nov 2021 14:23:49 +0000 Subject: [PATCH] Prepopulate the system's security manager setters map. (#80462) --- .../bootstrap/Elasticsearch.java | 4 +- .../org/elasticsearch/bootstrap/Security.java | 55 ++++++++++++++++++- .../bootstrap/NoSecurityManagerTests.java | 27 +++++++++ 3 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 server/src/test/java/org/elasticsearch/bootstrap/NoSecurityManagerTests.java diff --git a/server/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java b/server/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java index 1a2acfd766848..ae40ea349edb8 100644 --- a/server/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java +++ b/server/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java @@ -60,12 +60,14 @@ class Elasticsearch extends EnvironmentAwareCommand { */ public static void main(final String[] args) throws Exception { overrideDnsCachePolicyProperties(); + org.elasticsearch.bootstrap.Security.prepopulateSecurityCaller(); + /* * We want the JVM to think there is a security manager installed so that if internal policy decisions that would be based on the * presence of a security manager or lack thereof act as if there is a security manager present (e.g., DNS cache policy). This * forces such policies to take effect immediately. */ - System.setSecurityManager(new SecurityManager() { + org.elasticsearch.bootstrap.Security.setSecurityManager(new SecurityManager() { @Override public void checkPermission(Permission perm) { diff --git a/server/src/main/java/org/elasticsearch/bootstrap/Security.java b/server/src/main/java/org/elasticsearch/bootstrap/Security.java index e404933b062a3..63cd595be3fd0 100644 --- a/server/src/main/java/org/elasticsearch/bootstrap/Security.java +++ b/server/src/main/java/org/elasticsearch/bootstrap/Security.java @@ -8,6 +8,7 @@ package org.elasticsearch.bootstrap; +import org.elasticsearch.ElasticsearchException; import org.elasticsearch.cli.Command; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.core.PathUtils; @@ -20,6 +21,10 @@ import org.elasticsearch.transport.TcpTransport; import java.io.IOException; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.lang.reflect.Field; import java.net.SocketPermission; import java.net.URISyntaxException; import java.net.URL; @@ -38,6 +43,7 @@ import java.util.Set; import java.util.function.Consumer; +import static java.lang.invoke.MethodType.methodType; import static org.elasticsearch.bootstrap.FilePermissionUtils.addDirectoryPath; import static org.elasticsearch.bootstrap.FilePermissionUtils.addSingleFilePath; @@ -92,6 +98,10 @@ final class Security { /** no instantiation */ private Security() {} + static void setSecurityManager(@SuppressWarnings("removal") SecurityManager sm) { + System.setSecurityManager(sm); + } + /** * Initializes SecurityManager for the environment * Can only happen once! @@ -117,7 +127,7 @@ static void configure(Environment environment, boolean filterBadDefaults) throws // SecureSM matches class names as regular expressions so we escape the $ that arises from the nested class name ElasticsearchUncaughtExceptionHandler.PrivilegedHaltAction.class.getName().replace("$", "\\$"), Command.class.getName() }; - System.setSecurityManager(new SecureSM(classesThatCanExit)); + setSecurityManager(new SecureSM(classesThatCanExit)); // do some basic tests selfTest(); @@ -333,4 +343,47 @@ static void selfTest() throws IOException { throw new SecurityException("Security misconfiguration: cannot access java.io.tmpdir", problem); } } + + /** + * Prepopulates the system's security manager callers map with this class as a caller. + * This is loathsome, but avoids the annoying warning message at run time. + * Returns true if the callers map has been populated. + */ + static boolean prepopulateSecurityCaller() { + Field f; + try { + f = getDeclaredField(Class.forName("java.lang.System$CallersHolder", true, null), "callers"); + } catch (NoSuchFieldException | ClassNotFoundException ignore) { + return false; + } + try { + Class c = Class.forName("sun.misc.Unsafe"); + MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(c, MethodHandles.lookup()); + VarHandle handle = lookup.findStaticVarHandle(c, "theUnsafe", c); + Object theUnsafe = handle.get(); + MethodHandle mh = lookup.findVirtual(c, "staticFieldBase", methodType(Object.class, Field.class)); + mh = mh.asType(mh.type().changeParameterType(0, Object.class)); + Object base = mh.invokeExact(theUnsafe, f); + mh = lookup.findVirtual(c, "staticFieldOffset", methodType(long.class, Field.class)); + mh = mh.asType(mh.type().changeParameterType(0, Object.class)); + long offset = (long) mh.invokeExact(theUnsafe, f); + mh = lookup.findVirtual(c, "getObject", methodType(Object.class, Object.class, long.class)); + mh = mh.asType(mh.type().changeParameterType(0, Object.class)); + Object callers = (Object) mh.invokeExact(theUnsafe, base, offset); + if (Map.class.isAssignableFrom(callers.getClass())) { + @SuppressWarnings("unchecked") + Map, Boolean> map = Map.class.cast(callers); + map.put(org.elasticsearch.bootstrap.Security.class, true); + return true; + } + } catch (Throwable t) { + throw new ElasticsearchException(t); + } + return false; + } + + @SuppressForbidden(reason = "access violation required") + private static Field getDeclaredField(Class c, String name) throws NoSuchFieldException { + return c.getDeclaredField(name); + } } diff --git a/server/src/test/java/org/elasticsearch/bootstrap/NoSecurityManagerTests.java b/server/src/test/java/org/elasticsearch/bootstrap/NoSecurityManagerTests.java new file mode 100644 index 0000000000000..0e0781622ce68 --- /dev/null +++ b/server/src/test/java/org/elasticsearch/bootstrap/NoSecurityManagerTests.java @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.bootstrap; + +import org.apache.lucene.util.LuceneTestCase; + +import static org.hamcrest.Matchers.is; + +public class NoSecurityManagerTests extends LuceneTestCase { + + public void testPrepopulateSecurityCaller() { + assumeTrue("Unexpected security manager:" + System.getSecurityManager(), System.getSecurityManager() == null); + boolean isAtLeastJava17 = Runtime.version().feature() >= 17; + boolean isPrepopulated = Security.prepopulateSecurityCaller(); + if (isAtLeastJava17) { + assertThat(isPrepopulated, is(true)); + } else { + assertThat(isPrepopulated, is(false)); + } + } +}