Skip to content

Commit

Permalink
Prepopulate the system's security manager setters map. (elastic#80462)
Browse files Browse the repository at this point in the history
  • Loading branch information
ChrisHegarty authored Nov 10, 2021
1 parent 08324ed commit 4f00058
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
55 changes: 54 additions & 1 deletion server/src/main/java/org/elasticsearch/bootstrap/Security.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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!
Expand All @@ -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();
Expand Down Expand Up @@ -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<Class<?>, 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);
}
}
Original file line number Diff line number Diff line change
@@ -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));
}
}
}

0 comments on commit 4f00058

Please sign in to comment.