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:
parent
619b8831f9
commit
9695caa3fb
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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`.
|
||||
|
|
Loading…
Reference in New Issue