Bootstrap check for OnOutOfMemoryError and seccomp

This commit adds a bootstrap check for the JVM option OnOutOfMemoryError
being in use and seccomp being enabled. These two options are
incompatible because OnOutOfMemoryError allows the user to specify an
arbitrary program to fork when the JVM encounters an
OutOfMemoryError, and seccomp enables system call filters that prevents
forking.

This commit also adds support for bootstrap checks that are always
enforced, whether or not Elasticsearch is in production mode.
This commit is contained in:
Jason Tedor 2016-06-05 23:46:59 -04:00
parent 619b8831f9
commit 9695caa3fb
4 changed files with 148 additions and 5 deletions

View File

@ -106,7 +106,7 @@ final class BootstrapCheck {
for (final Check check : checks) {
if (check.check()) {
if (!enforceLimits || (check.isSystemCheck() && ignoreSystemChecks)) {
if ((!enforceLimits || (check.isSystemCheck() && ignoreSystemChecks)) && !check.alwaysEnforce()) {
ignoredErrors.add(check.errorMessage());
} else {
errors.add(check.errorMessage());
@ -164,6 +164,7 @@ final class BootstrapCheck {
checks.add(new MaxMapCountCheck());
}
checks.add(new ClientJvmCheck());
checks.add(new OnOutOfMemoryErrorCheck());
return Collections.unmodifiableList(checks);
}
@ -194,6 +195,10 @@ final class BootstrapCheck {
*/
boolean isSystemCheck();
default boolean alwaysEnforce() {
return false;
}
}
static class HeapSizeCheck implements BootstrapCheck.Check {
@ -245,7 +250,6 @@ final class BootstrapCheck {
}
// visible for testing
static class FileDescriptorCheck implements Check {
private final int limit;
@ -288,7 +292,6 @@ final class BootstrapCheck {
}
// visible for testing
static class MlockallCheck implements Check {
private final boolean mlockallSet;
@ -504,4 +507,44 @@ final class BootstrapCheck {
}
static class OnOutOfMemoryErrorCheck implements BootstrapCheck.Check {
@Override
public boolean check() {
final String onOutOfMemoryError = onOutOfMemoryError();
return isSeccompInstalled() && onOutOfMemoryError != null && !onOutOfMemoryError.equals("");
}
// visible for testing
boolean isSeccompInstalled() {
return Natives.isSeccompInstalled();
}
// visible for testing
String onOutOfMemoryError() {
return JvmInfo.jvmInfo().onOutOfMemoryError();
}
@Override
public String errorMessage() {
return String.format(
Locale.ROOT,
"OnOutOfMemoryError [%s] requires forking but is prevented by system call filters ([%s=true]);" +
" upgrade to at least Java 8u92 and use ExitOnOutOfMemoryError",
onOutOfMemoryError(),
BootstrapSettings.SECCOMP_SETTING.getKey());
}
@Override
public boolean isSystemCheck() {
return false;
}
@Override
public boolean alwaysEnforce() {
return true;
}
}
}

View File

@ -115,6 +115,12 @@ public class JvmInfo implements Streamable, ToXContent {
Method vmOptionMethod = clazz.getMethod("getVMOption", String.class);
Method valueMethod = vmOptionClazz.getMethod("getValue");
try {
Object onOutOfMemoryError = vmOptionMethod.invoke(hotSpotDiagnosticMXBean, "OnOutOfMemoryError");
info.onOutOfMemoryError = (String) valueMethod.invoke(onOutOfMemoryError);
} catch (Exception ignored) {
}
try {
Object useCompressedOopsVmOption = vmOptionMethod.invoke(hotSpotDiagnosticMXBean, "UseCompressedOops");
info.useCompressedOops = (String) valueMethod.invoke(useCompressedOopsVmOption);
@ -179,6 +185,8 @@ public class JvmInfo implements Streamable, ToXContent {
String[] gcCollectors = Strings.EMPTY_ARRAY;
String[] memoryPools = Strings.EMPTY_ARRAY;
private String onOutOfMemoryError;
private String useCompressedOops = "unknown";
private String useG1GC = "unknown";
@ -314,6 +322,10 @@ public class JvmInfo implements Streamable, ToXContent {
return configuredMaxHeapSize;
}
public String onOutOfMemoryError() {
return onOutOfMemoryError;
}
/**
* The value of the JVM flag UseCompressedOops, if available otherwise
* "unknown". The value "unknown" indicates that an attempt was

View File

@ -30,6 +30,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
@ -383,7 +384,7 @@ public class BootstrapCheckTests extends ESTestCase {
}
};
RuntimeException e = expectThrows(
final RuntimeException e = expectThrows(
RuntimeException.class,
() -> BootstrapCheck.check(true, false, Collections.singletonList(check), "testClientJvmCheck"));
assertThat(
@ -395,8 +396,50 @@ public class BootstrapCheckTests extends ESTestCase {
BootstrapCheck.check(true, false, Collections.singletonList(check), "testClientJvmCheck");
}
public void testOnOutOfMemoryErrorCheck() {
final AtomicBoolean isSeccompInstalled = new AtomicBoolean();
final AtomicReference<String> onOutOfMemoryError = new AtomicReference<>();
final BootstrapCheck.Check check = new BootstrapCheck.OnOutOfMemoryErrorCheck() {
@Override
boolean isSeccompInstalled() {
return isSeccompInstalled.get();
}
@Override
String onOutOfMemoryError() {
return onOutOfMemoryError.get();
}
};
// if seccomp is disabled, nothing should happen
isSeccompInstalled.set(false);
onOutOfMemoryError.set(randomBoolean() ? "" : randomAsciiOfLength(16));
BootstrapCheck.check(true, false, Collections.singletonList(check), "testOnOutOfMemoryErrorCheck");
// if seccomp is enabled, and OnOutOfMemoryError is not, nothing
// should happen
isSeccompInstalled.set(true);
onOutOfMemoryError.set("");
BootstrapCheck.check(true, false, Collections.singletonList(check), "testOnOutOfMemoryErrorCheck");
// if seccomp is enabled, and OnOutOfMemoryError is, the check
// should be enforced, regardless of bootstrap checks being
// enabled or not
isSeccompInstalled.set(true);
final String command = randomAsciiOfLength(16);
onOutOfMemoryError.set(command);
final RuntimeException e = expectThrows(
RuntimeException.class,
() -> BootstrapCheck.check(randomBoolean(), randomBoolean(), Collections.singletonList(check), "testOnOutOfMemoryErrorCheck"));
assertThat(
e.getMessage(),
containsString(
"OnOutOfMemoryError [" + command + "] requires forking but is prevented by system call filters ([bootstrap.seccomp=true]);"
+ " upgrade to at least Java 8u92 and use ExitOnOutOfMemoryError"));
}
public void testIgnoringSystemChecks() {
BootstrapCheck.Check check = new BootstrapCheck.Check() {
final BootstrapCheck.Check check = new BootstrapCheck.Check() {
@Override
public boolean check() {
return true;
@ -430,4 +473,33 @@ public class BootstrapCheckTests extends ESTestCase {
verify(logger).warn("error");
}
public void testAlwaysEnforcedChecks() {
final BootstrapCheck.Check check = new BootstrapCheck.Check() {
@Override
public boolean check() {
return true;
}
@Override
public String errorMessage() {
return "error";
}
@Override
public boolean isSystemCheck() {
return randomBoolean();
}
@Override
public boolean alwaysEnforce() {
return true;
}
};
final RuntimeException alwaysEnforced = expectThrows(
RuntimeException.class,
() -> BootstrapCheck.check(randomBoolean(), randomBoolean(), Collections.singletonList(check), "testAlwaysEnforcedChecks"));
assertThat(alwaysEnforced, hasToString(containsString("error")));
}
}

View File

@ -16,6 +16,10 @@ checks that fail appear as warnings in the Elasticsearch log. If
Elasticsearch is in production mode, any bootstrap checks that fail will
cause Elasticsearch to refuse to start.
There are some bootstrap checks that are always enforced to prevent
Elasticsearch from running with incompatible settings. These checks are
documented individually.
[float]
=== Development vs. production mode
@ -152,3 +156,15 @@ JVM check, you must start Elasticsearch with the server VM. On modern
systems and operating systems, the server VM is the
default. Additionally, Elasticsearch is configured by default to force
the server VM.
=== OnOutOfMemoryError check
The JVM option `OnOutOfMemoryError` enables executing an arbitrary
command if the JVM throws an `OutOfMemoryError`. However, by default,
Elasticsearch system call filters (seccomp) are enabled and these
filters prevent forking. Thus, using `OnOutOfMemoryError` and system
call filters are incompatible. The `OnOutOfMemoryError` check prevents
Elasticsearch from starting if this JVM option is used and system call
filters are enabled. This check is always enforced. To pass this check
do not enable `OnOutOfMemoryError`; instead, upgrade to Java 8u92 and
use the JVM flag `ExitOnOutOfMemoryError`.