Merge pull request #18756 from jasontedor/on-out-of-memory-error
Bootstrap check for OnOutOfMemoryError and seccomp
This commit is contained in:
commit
75d3b13790
|
@ -106,7 +106,7 @@ final class BootstrapCheck {
|
||||||
|
|
||||||
for (final Check check : checks) {
|
for (final Check check : checks) {
|
||||||
if (check.check()) {
|
if (check.check()) {
|
||||||
if (!enforceLimits || (check.isSystemCheck() && ignoreSystemChecks)) {
|
if ((!enforceLimits || (check.isSystemCheck() && ignoreSystemChecks)) && !check.alwaysEnforce()) {
|
||||||
ignoredErrors.add(check.errorMessage());
|
ignoredErrors.add(check.errorMessage());
|
||||||
} else {
|
} else {
|
||||||
errors.add(check.errorMessage());
|
errors.add(check.errorMessage());
|
||||||
|
@ -164,6 +164,8 @@ final class BootstrapCheck {
|
||||||
checks.add(new MaxMapCountCheck());
|
checks.add(new MaxMapCountCheck());
|
||||||
}
|
}
|
||||||
checks.add(new ClientJvmCheck());
|
checks.add(new ClientJvmCheck());
|
||||||
|
checks.add(new OnErrorCheck());
|
||||||
|
checks.add(new OnOutOfMemoryErrorCheck());
|
||||||
return Collections.unmodifiableList(checks);
|
return Collections.unmodifiableList(checks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,6 +196,10 @@ final class BootstrapCheck {
|
||||||
*/
|
*/
|
||||||
boolean isSystemCheck();
|
boolean isSystemCheck();
|
||||||
|
|
||||||
|
default boolean alwaysEnforce() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static class HeapSizeCheck implements BootstrapCheck.Check {
|
static class HeapSizeCheck implements BootstrapCheck.Check {
|
||||||
|
@ -245,7 +251,6 @@ final class BootstrapCheck {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// visible for testing
|
|
||||||
static class FileDescriptorCheck implements Check {
|
static class FileDescriptorCheck implements Check {
|
||||||
|
|
||||||
private final int limit;
|
private final int limit;
|
||||||
|
@ -288,7 +293,6 @@ final class BootstrapCheck {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// visible for testing
|
|
||||||
static class MlockallCheck implements Check {
|
static class MlockallCheck implements Check {
|
||||||
|
|
||||||
private final boolean mlockallSet;
|
private final boolean mlockallSet;
|
||||||
|
@ -504,4 +508,81 @@ final class BootstrapCheck {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static abstract class MightForkCheck implements BootstrapCheck.Check {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean check() {
|
||||||
|
return isSeccompInstalled() && mightFork();
|
||||||
|
}
|
||||||
|
|
||||||
|
// visible for testing
|
||||||
|
boolean isSeccompInstalled() {
|
||||||
|
return Natives.isSeccompInstalled();
|
||||||
|
}
|
||||||
|
|
||||||
|
// visible for testing
|
||||||
|
abstract boolean mightFork();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final boolean isSystemCheck() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final boolean alwaysEnforce() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static class OnErrorCheck extends MightForkCheck {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean mightFork() {
|
||||||
|
final String onError = onError();
|
||||||
|
return onError != null && !onError.equals("");
|
||||||
|
}
|
||||||
|
|
||||||
|
// visible for testing
|
||||||
|
String onError() {
|
||||||
|
return JvmInfo.jvmInfo().onError();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String errorMessage() {
|
||||||
|
return String.format(
|
||||||
|
Locale.ROOT,
|
||||||
|
"OnError [%s] requires forking but is prevented by system call filters ([%s=true]);" +
|
||||||
|
" upgrade to at least Java 8u92 and use ExitOnOutOfMemoryError",
|
||||||
|
onError(),
|
||||||
|
BootstrapSettings.SECCOMP_SETTING.getKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static class OnOutOfMemoryErrorCheck extends MightForkCheck {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean mightFork() {
|
||||||
|
final String onOutOfMemoryError = onOutOfMemoryError();
|
||||||
|
return onOutOfMemoryError != null && !onOutOfMemoryError.equals("");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,6 +115,18 @@ public class JvmInfo implements Streamable, ToXContent {
|
||||||
Method vmOptionMethod = clazz.getMethod("getVMOption", String.class);
|
Method vmOptionMethod = clazz.getMethod("getVMOption", String.class);
|
||||||
Method valueMethod = vmOptionClazz.getMethod("getValue");
|
Method valueMethod = vmOptionClazz.getMethod("getValue");
|
||||||
|
|
||||||
|
try {
|
||||||
|
Object onError = vmOptionMethod.invoke(hotSpotDiagnosticMXBean, "OnError");
|
||||||
|
info.onError = (String) valueMethod.invoke(onError);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Object onOutOfMemoryError = vmOptionMethod.invoke(hotSpotDiagnosticMXBean, "OnOutOfMemoryError");
|
||||||
|
info.onOutOfMemoryError = (String) valueMethod.invoke(onOutOfMemoryError);
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Object useCompressedOopsVmOption = vmOptionMethod.invoke(hotSpotDiagnosticMXBean, "UseCompressedOops");
|
Object useCompressedOopsVmOption = vmOptionMethod.invoke(hotSpotDiagnosticMXBean, "UseCompressedOops");
|
||||||
info.useCompressedOops = (String) valueMethod.invoke(useCompressedOopsVmOption);
|
info.useCompressedOops = (String) valueMethod.invoke(useCompressedOopsVmOption);
|
||||||
|
@ -179,6 +191,10 @@ public class JvmInfo implements Streamable, ToXContent {
|
||||||
String[] gcCollectors = Strings.EMPTY_ARRAY;
|
String[] gcCollectors = Strings.EMPTY_ARRAY;
|
||||||
String[] memoryPools = Strings.EMPTY_ARRAY;
|
String[] memoryPools = Strings.EMPTY_ARRAY;
|
||||||
|
|
||||||
|
private String onError;
|
||||||
|
|
||||||
|
private String onOutOfMemoryError;
|
||||||
|
|
||||||
private String useCompressedOops = "unknown";
|
private String useCompressedOops = "unknown";
|
||||||
|
|
||||||
private String useG1GC = "unknown";
|
private String useG1GC = "unknown";
|
||||||
|
@ -314,6 +330,14 @@ public class JvmInfo implements Streamable, ToXContent {
|
||||||
return configuredMaxHeapSize;
|
return configuredMaxHeapSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String onError() {
|
||||||
|
return onError;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String onOutOfMemoryError() {
|
||||||
|
return onOutOfMemoryError;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The value of the JVM flag UseCompressedOops, if available otherwise
|
* The value of the JVM flag UseCompressedOops, if available otherwise
|
||||||
* "unknown". The value "unknown" indicates that an attempt was
|
* "unknown". The value "unknown" indicates that an attempt was
|
||||||
|
|
|
@ -30,8 +30,10 @@ import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.allOf;
|
import static org.hamcrest.CoreMatchers.allOf;
|
||||||
import static org.hamcrest.CoreMatchers.containsString;
|
import static org.hamcrest.CoreMatchers.containsString;
|
||||||
|
@ -383,7 +385,7 @@ public class BootstrapCheckTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
RuntimeException e = expectThrows(
|
final RuntimeException e = expectThrows(
|
||||||
RuntimeException.class,
|
RuntimeException.class,
|
||||||
() -> BootstrapCheck.check(true, false, Collections.singletonList(check), "testClientJvmCheck"));
|
() -> BootstrapCheck.check(true, false, Collections.singletonList(check), "testClientJvmCheck"));
|
||||||
assertThat(
|
assertThat(
|
||||||
|
@ -395,8 +397,127 @@ public class BootstrapCheckTests extends ESTestCase {
|
||||||
BootstrapCheck.check(true, false, Collections.singletonList(check), "testClientJvmCheck");
|
BootstrapCheck.check(true, false, Collections.singletonList(check), "testClientJvmCheck");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testMightForkCheck() {
|
||||||
|
final AtomicBoolean isSeccompInstalled = new AtomicBoolean();
|
||||||
|
final AtomicBoolean mightFork = new AtomicBoolean();
|
||||||
|
final BootstrapCheck.MightForkCheck check = new BootstrapCheck.MightForkCheck() {
|
||||||
|
@Override
|
||||||
|
boolean isSeccompInstalled() {
|
||||||
|
return isSeccompInstalled.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean mightFork() {
|
||||||
|
return mightFork.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String errorMessage() {
|
||||||
|
return "error";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
runMightForkTest(
|
||||||
|
check,
|
||||||
|
isSeccompInstalled,
|
||||||
|
() -> mightFork.set(false),
|
||||||
|
() -> mightFork.set(true),
|
||||||
|
e -> assertThat(e.getMessage(), containsString("error")));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testOnErrorCheck() {
|
||||||
|
final AtomicBoolean isSeccompInstalled = new AtomicBoolean();
|
||||||
|
final AtomicReference<String> onError = new AtomicReference<>();
|
||||||
|
final BootstrapCheck.MightForkCheck check = new BootstrapCheck.OnErrorCheck() {
|
||||||
|
@Override
|
||||||
|
boolean isSeccompInstalled() {
|
||||||
|
return isSeccompInstalled.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String onError() {
|
||||||
|
return onError.get();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
final String command = randomAsciiOfLength(16);
|
||||||
|
runMightForkTest(
|
||||||
|
check,
|
||||||
|
isSeccompInstalled,
|
||||||
|
() -> onError.set(randomBoolean() ? "" : null),
|
||||||
|
() -> onError.set(command),
|
||||||
|
e -> assertThat(
|
||||||
|
e.getMessage(),
|
||||||
|
containsString(
|
||||||
|
"OnError [" + command + "] requires forking but is prevented by system call filters ([bootstrap.seccomp=true]);"
|
||||||
|
+ " upgrade to at least Java 8u92 and use ExitOnOutOfMemoryError")));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testOnOutOfMemoryErrorCheck() {
|
||||||
|
final AtomicBoolean isSeccompInstalled = new AtomicBoolean();
|
||||||
|
final AtomicReference<String> onOutOfMemoryError = new AtomicReference<>();
|
||||||
|
final BootstrapCheck.MightForkCheck check = new BootstrapCheck.OnOutOfMemoryErrorCheck() {
|
||||||
|
@Override
|
||||||
|
boolean isSeccompInstalled() {
|
||||||
|
return isSeccompInstalled.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String onOutOfMemoryError() {
|
||||||
|
return onOutOfMemoryError.get();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
final String command = randomAsciiOfLength(16);
|
||||||
|
runMightForkTest(
|
||||||
|
check,
|
||||||
|
isSeccompInstalled,
|
||||||
|
() -> onOutOfMemoryError.set(randomBoolean() ? "" : null),
|
||||||
|
() -> onOutOfMemoryError.set(command),
|
||||||
|
e -> 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")));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runMightForkTest(
|
||||||
|
final BootstrapCheck.MightForkCheck check,
|
||||||
|
final AtomicBoolean isSeccompInstalled,
|
||||||
|
final Runnable disableMightFork,
|
||||||
|
final Runnable enableMightFork,
|
||||||
|
final Consumer<RuntimeException> consumer) {
|
||||||
|
|
||||||
|
// if seccomp is disabled, nothing should happen
|
||||||
|
isSeccompInstalled.set(false);
|
||||||
|
if (randomBoolean()) {
|
||||||
|
disableMightFork.run();
|
||||||
|
} else {
|
||||||
|
enableMightFork.run();
|
||||||
|
}
|
||||||
|
BootstrapCheck.check(true, randomBoolean(), Collections.singletonList(check), "testMightFork");
|
||||||
|
|
||||||
|
// if seccomp is enabled, but we will not fork, nothing should
|
||||||
|
// happen
|
||||||
|
isSeccompInstalled.set(true);
|
||||||
|
disableMightFork.run();
|
||||||
|
BootstrapCheck.check(true, randomBoolean(), Collections.singletonList(check), "testMightFork");
|
||||||
|
|
||||||
|
// if seccomp is enabled, and we might fork, the check should
|
||||||
|
// be enforced, regardless of bootstrap checks being enabled or
|
||||||
|
// not
|
||||||
|
isSeccompInstalled.set(true);
|
||||||
|
enableMightFork.run();
|
||||||
|
|
||||||
|
final RuntimeException e = expectThrows(
|
||||||
|
RuntimeException.class,
|
||||||
|
() -> BootstrapCheck.check(randomBoolean(), randomBoolean(), Collections.singletonList(check), "testMightFork"));
|
||||||
|
consumer.accept(e);
|
||||||
|
}
|
||||||
|
|
||||||
public void testIgnoringSystemChecks() {
|
public void testIgnoringSystemChecks() {
|
||||||
BootstrapCheck.Check check = new BootstrapCheck.Check() {
|
final BootstrapCheck.Check check = new BootstrapCheck.Check() {
|
||||||
@Override
|
@Override
|
||||||
public boolean check() {
|
public boolean check() {
|
||||||
return true;
|
return true;
|
||||||
|
@ -430,4 +551,33 @@ public class BootstrapCheckTests extends ESTestCase {
|
||||||
verify(logger).warn("error");
|
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
|
Elasticsearch is in production mode, any bootstrap checks that fail will
|
||||||
cause Elasticsearch to refuse to start.
|
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]
|
[float]
|
||||||
=== Development vs. production mode
|
=== Development vs. production mode
|
||||||
|
|
||||||
|
@ -152,3 +156,19 @@ JVM check, you must start Elasticsearch with the server VM. On modern
|
||||||
systems and operating systems, the server VM is the
|
systems and operating systems, the server VM is the
|
||||||
default. Additionally, Elasticsearch is configured by default to force
|
default. Additionally, Elasticsearch is configured by default to force
|
||||||
the server VM.
|
the server VM.
|
||||||
|
|
||||||
|
=== OnError and OnOutOfMemoryError checks
|
||||||
|
|
||||||
|
The JVM options `OnError` and `OnOutOfMemoryError` enable executing
|
||||||
|
arbitrary commands if the JVM encounters a fatal error (`OnError`) or an
|
||||||
|
`OutOfMemoryError` (`OnOutOfMemoryError`). However, by default,
|
||||||
|
Elasticsearch system call filters (seccomp) are enabled and these
|
||||||
|
filters prevent forking. Thus, using `OnError` or `OnOutOfMemoryError`
|
||||||
|
and system call filters are incompatible. The `OnError` and
|
||||||
|
`OnOutOfMemoryError` checks prevent Elasticsearch from starting if
|
||||||
|
either of these JVM options are used and system call filters are
|
||||||
|
enabled. This check is always enforced. To pass this check do not enable
|
||||||
|
`OnError` nor `OnOutOfMemoryError`; instead, upgrade to Java 8u92 and
|
||||||
|
use the JVM flag `ExitOnOutOfMemoryError`. While this does not have the
|
||||||
|
full capabilities of `OnError` nor `OnOutOfMemoryError`, arbitrary
|
||||||
|
forking will not be supported with seccomp enabled.
|
||||||
|
|
Loading…
Reference in New Issue