Bootstrap check for OnError and seccomp

This commit adds a bootstrap check for the JVM option OnError being in
use and seccomp being enabled. These two options are incompatible
because OnError allows the user to specify an arbitrary program to fork
when the JVM encounters an fatal error, and seccomp enables system call
filters that prevents forking.
This commit is contained in:
Jason Tedor 2016-06-06 18:28:34 -04:00
parent 9695caa3fb
commit e94408c0d2
4 changed files with 172 additions and 40 deletions

View File

@ -164,6 +164,7 @@ final class BootstrapCheck {
checks.add(new MaxMapCountCheck());
}
checks.add(new ClientJvmCheck());
checks.add(new OnErrorCheck());
checks.add(new OnOutOfMemoryErrorCheck());
return Collections.unmodifiableList(checks);
}
@ -507,12 +508,11 @@ final class BootstrapCheck {
}
static class OnOutOfMemoryErrorCheck implements BootstrapCheck.Check {
static abstract class MightForkCheck implements BootstrapCheck.Check {
@Override
public boolean check() {
final String onOutOfMemoryError = onOutOfMemoryError();
return isSeccompInstalled() && onOutOfMemoryError != null && !onOutOfMemoryError.equals("");
return isSeccompInstalled() && mightFork();
}
// visible for testing
@ -520,6 +520,54 @@ final class BootstrapCheck {
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();
@ -535,16 +583,6 @@ final class BootstrapCheck {
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 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);
@ -185,6 +191,8 @@ public class JvmInfo implements Streamable, ToXContent {
String[] gcCollectors = Strings.EMPTY_ARRAY;
String[] memoryPools = Strings.EMPTY_ARRAY;
private String onError;
private String onOutOfMemoryError;
private String useCompressedOops = "unknown";
@ -322,6 +330,10 @@ public class JvmInfo implements Streamable, ToXContent {
return configuredMaxHeapSize;
}
public String onError() {
return onError;
}
public String onOutOfMemoryError() {
return onOutOfMemoryError;
}

View File

@ -33,6 +33,7 @@ import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.containsString;
@ -396,10 +397,66 @@ public class BootstrapCheckTests extends ESTestCase {
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.Check check = new BootstrapCheck.OnOutOfMemoryErrorCheck() {
final BootstrapCheck.MightForkCheck check = new BootstrapCheck.OnOutOfMemoryErrorCheck() {
@Override
boolean isSeccompInstalled() {
return isSeccompInstalled.get();
@ -411,31 +468,52 @@ public class BootstrapCheckTests extends ESTestCase {
}
};
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);
onOutOfMemoryError.set(randomBoolean() ? "" : randomAsciiOfLength(16));
BootstrapCheck.check(true, false, Collections.singletonList(check), "testOnOutOfMemoryErrorCheck");
if (randomBoolean()) {
disableMightFork.run();
} else {
enableMightFork.run();
}
BootstrapCheck.check(true, randomBoolean(), Collections.singletonList(check), "testMightFork");
// if seccomp is enabled, and OnOutOfMemoryError is not, nothing
// should happen
// if seccomp is enabled, but we will not fork, nothing should
// happen
isSeccompInstalled.set(true);
onOutOfMemoryError.set("");
BootstrapCheck.check(true, false, Collections.singletonList(check), "testOnOutOfMemoryErrorCheck");
disableMightFork.run();
BootstrapCheck.check(true, randomBoolean(), Collections.singletonList(check), "testMightFork");
// if seccomp is enabled, and OnOutOfMemoryError is, the check
// should be enforced, regardless of bootstrap checks being
// enabled or not
// if seccomp is enabled, and we might fork, the check should
// be enforced, regardless of bootstrap checks being enabled or
// not
isSeccompInstalled.set(true);
final String command = randomAsciiOfLength(16);
onOutOfMemoryError.set(command);
enableMightFork.run();
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"));
() -> BootstrapCheck.check(randomBoolean(), randomBoolean(), Collections.singletonList(check), "testMightFork"));
consumer.accept(e);
}
public void testIgnoringSystemChecks() {

View File

@ -157,14 +157,18 @@ systems and operating systems, the server VM is the
default. Additionally, Elasticsearch is configured by default to force
the server VM.
=== OnOutOfMemoryError check
=== OnError and OnOutOfMemoryError checks
The JVM option `OnOutOfMemoryError` enables executing an arbitrary
command if the JVM throws an `OutOfMemoryError`. However, by default,
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 `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`.
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.