Skip to content

Commit 96f5115

Browse files
Add bootstrap checks (#1593)
* Add bootstrap check for disabled bytecode verification Co-authored-by: Sylvain Juge <[email protected]>
1 parent 9ba4872 commit 96f5115

File tree

13 files changed

+496
-131
lines changed

13 files changed

+496
-131
lines changed

CHANGELOG.asciidoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ any further configuration. Supports log4j1, log4j2 and Logback. {pull}1261[#1261
4646
* Add support to Spring AMQP - {pull}1657[#1657]
4747
* Adds the ability to automatically configure usage of the OpenTracing bridge in systems using ServiceLoader - {pull}1708[#1708]
4848
* Update to async-profiler 1.8.5
49+
* Add a warning on startup when `-Xverify:none` or `-noverify` flags are set as this can lead to crashes that are very difficult to debug - {pull}1593[#1593]
50+
In an upcoming version, the agent will not start when these flags are set, unless the system property `elastic.apm.disable_bootstrap_checks` is set to true.
4951
5052
[float]
5153
===== Bug fixes

apm-agent-core/src/main/java/co/elastic/apm/agent/bci/IndyBootstrap.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import co.elastic.apm.agent.bci.classloading.ExternalPluginClassLoader;
2828
import co.elastic.apm.agent.bci.classloading.IndyPluginClassLoader;
2929
import co.elastic.apm.agent.bci.classloading.LookupExposer;
30+
import co.elastic.apm.agent.premain.JavaVersionBootstrapCheck;
3031
import co.elastic.apm.agent.sdk.state.GlobalState;
3132
import co.elastic.apm.agent.premain.JvmRuntimeInfo;
3233
import co.elastic.apm.agent.util.PackageScanner;
@@ -156,7 +157,7 @@
156157
* <li>
157158
* The {@code INVOKEDYNAMIC} support of early Java 7 versions is not reliable.
158159
* That's why we disable the agent on them.
159-
* See also {@link JvmRuntimeInfo#isJavaVersionSupported}
160+
* See also {@link JavaVersionBootstrapCheck}
160161
* </li>
161162
* <li>
162163
* There are some things to watch out for when writing plugins,
@@ -224,7 +225,7 @@ private static Class<?> initIndyBootstrap(final Logger logger) throws Exception
224225
indyBootstrapDispatcherClass = Class.forName(INDY_BOOTSTRAP_CLASS_NAME, false, null);
225226
}
226227

227-
if (JvmRuntimeInfo.getMajorVersion() >= 9 && JvmRuntimeInfo.isJ9VM()) {
228+
if (JvmRuntimeInfo.ofCurrentVM().getMajorVersion() >= 9 && JvmRuntimeInfo.ofCurrentVM().isJ9VM()) {
228229
try {
229230
logger.info("Overriding IndyBootstrapDispatcher class's module to java.base module. This is required in J9 VMs.");
230231
setJavaBaseModule(indyBootstrapDispatcherClass);

apm-agent-core/src/main/java/co/elastic/apm/agent/impl/ElasticApmTracer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -526,7 +526,7 @@ public synchronized void start(boolean premain) {
526526
}
527527

528528
private boolean shouldDelayOnPremain() {
529-
return JvmRuntimeInfo.getMajorVersion() <= 8 &&
529+
return JvmRuntimeInfo.ofCurrentVM().getMajorVersion() <= 8 &&
530530
ClassLoader.getSystemClassLoader().getResource("org/apache/catalina/startup/Bootstrap.class") != null;
531531
}
532532

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*-
2+
* #%L
3+
* Elastic APM Java agent
4+
* %%
5+
* Copyright (C) 2018 - 2020 Elastic and contributors
6+
* %%
7+
* Licensed to Elasticsearch B.V. under one or more contributor
8+
* license agreements. See the NOTICE file distributed with
9+
* this work for additional information regarding copyright
10+
* ownership. Elasticsearch B.V. licenses this file to you under
11+
* the Apache License, Version 2.0 (the "License"); you may
12+
* not use this file except in compliance with the License.
13+
* You may obtain a copy of the License at
14+
*
15+
* http://www.apache.org/licenses/LICENSE-2.0
16+
*
17+
* Unless required by applicable law or agreed to in writing,
18+
* software distributed under the License is distributed on an
19+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20+
* KIND, either express or implied. See the License for the
21+
* specific language governing permissions and limitations
22+
* under the License.
23+
* #L%
24+
*/
25+
package co.elastic.apm.agent.bci;
26+
27+
import co.elastic.apm.agent.premain.BootstrapCheck;
28+
import co.elastic.apm.agent.premain.VerifyNoneBootstrapCheck;
29+
import org.junit.jupiter.api.Test;
30+
31+
import java.lang.management.RuntimeMXBean;
32+
import java.util.Collections;
33+
34+
import static org.assertj.core.api.Assertions.assertThat;
35+
import static org.mockito.Mockito.mock;
36+
import static org.mockito.Mockito.when;
37+
38+
class VerifyNoneBootstrapCheckTest {
39+
40+
private final BootstrapCheck.BootstrapCheckResult result = new BootstrapCheck.BootstrapCheckResult();
41+
42+
@Test
43+
void testNoverify() {
44+
RuntimeMXBean bean = mock(RuntimeMXBean.class);
45+
when(bean.getInputArguments()).thenReturn(Collections.singletonList("-noverify"));
46+
VerifyNoneBootstrapCheck check = new VerifyNoneBootstrapCheck(bean);
47+
check.doBootstrapCheck(result);
48+
assertThat(result.hasWarnings()).isTrue();
49+
assertThat(result.hasErrors()).isFalse();
50+
}
51+
52+
@Test
53+
void testVerifyNone() {
54+
RuntimeMXBean bean = mock(RuntimeMXBean.class);
55+
when(bean.getInputArguments()).thenReturn(Collections.singletonList("-Xverify:none"));
56+
VerifyNoneBootstrapCheck check = new VerifyNoneBootstrapCheck(bean);
57+
check.doBootstrapCheck(result);
58+
assertThat(result.hasWarnings()).isTrue();
59+
assertThat(result.hasErrors()).isFalse();
60+
}
61+
62+
@Test
63+
void testVerifyAll() {
64+
RuntimeMXBean bean = mock(RuntimeMXBean.class);
65+
when(bean.getInputArguments()).thenReturn(Collections.singletonList("-Xverify:all"));
66+
VerifyNoneBootstrapCheck check = new VerifyNoneBootstrapCheck(bean);
67+
check.doBootstrapCheck(result);
68+
assertThat(result.isEmpty()).isTrue();
69+
}
70+
}

apm-agent-plugins/apm-jdk-httpclient-plugin/src/main/java/co/elastic/apm/agent/httpclient/HttpClientInstrumentation.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public class HttpClientInstrumentation extends AbstractHttpClientInstrumentation
4646

4747
@Override
4848
public ElementMatcher<? super NamedElement> getTypeMatcherPreFilter() {
49-
return new BooleanMatcher<>(JvmRuntimeInfo.getMajorVersion() >= 11).and(nameContains("HttpClient"));
49+
return new BooleanMatcher<>(JvmRuntimeInfo.ofCurrentVM().getMajorVersion() >= 11).and(nameContains("HttpClient"));
5050
}
5151

5252
@Override

apm-agent-premain/src/main/java/co/elastic/apm/agent/premain/AgentMain.java

Lines changed: 6 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -75,30 +75,8 @@ public synchronized static void init(String agentArguments, Instrumentation inst
7575
return;
7676
}
7777

78-
if (!JvmRuntimeInfo.isJavaVersionSupported()) {
79-
// Gracefully abort agent startup is better than unexpected failure down the road when we known a given JVM
80-
// version is not supported. Agent might trigger known JVM bugs causing JVM crashes, notably on early Java 8
81-
// versions (but fixed in later versions), given those versions are obsolete and agent can't have workarounds
82-
// for JVM internals, there is no other option but to use an up-to-date JVM instead.
83-
84-
String msgTemplate;
85-
86-
boolean doDisable;
87-
if (Boolean.parseBoolean(System.getProperty("elastic.apm.disable_bootstrap_checks"))) {
88-
// safety check disabled, warn end user that it might
89-
doDisable = false;
90-
msgTemplate = "WARNING : JVM version unknown or not supported, safety check disabled - %s %s %s";
91-
} else {
92-
doDisable = true;
93-
msgTemplate = "Failed to start agent - JVM version not supported: %s %s %s.\nTo override Java version verification, set the 'elastic.apm.disable_bootstrap_checks' System property to 'true'.";
94-
}
95-
96-
System.err.println(String.format(msgTemplate,
97-
JvmRuntimeInfo.getJavaVersion(), JvmRuntimeInfo.getJavaVmName(), JvmRuntimeInfo.getJavaVmVersion()));
98-
99-
if (doDisable) {
100-
return;
101-
}
78+
if (!BootstrapChecks.defaults().isPassing()) {
79+
return;
10280
}
10381

10482
// workaround for classloader deadlock https://bugs.openjdk.java.net/browse/JDK-8194653
@@ -130,12 +108,13 @@ public synchronized static void init(String agentArguments, Instrumentation inst
130108
* @return {@code true} for any Java 7 and early Java 8 HotSpot JVMs, {@code false} for all others
131109
*/
132110
static boolean shouldDelayOnPremain() {
133-
int majorVersion = JvmRuntimeInfo.getMajorVersion();
111+
JvmRuntimeInfo runtimeInfo = JvmRuntimeInfo.ofCurrentVM();
112+
int majorVersion = runtimeInfo.getMajorVersion();
134113
return
135114
(majorVersion == 7) ||
136115
// In case bootstrap checks were disabled
137-
(majorVersion == 8 && JvmRuntimeInfo.isHpUx() && JvmRuntimeInfo.getUpdateVersion() < 2) ||
138-
(majorVersion == 8 && JvmRuntimeInfo.isHotSpot() && JvmRuntimeInfo.getUpdateVersion() < 40);
116+
(majorVersion == 8 && runtimeInfo.isHotSpot() && runtimeInfo.getUpdateVersion() < 2) ||
117+
(majorVersion == 8 && runtimeInfo.isHotSpot() && runtimeInfo.getUpdateVersion() < 40);
139118
}
140119

141120
private static void delayAndInitAgentAsync(final String agentArguments, final Instrumentation instrumentation,
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*-
2+
* #%L
3+
* Elastic APM Java agent
4+
* %%
5+
* Copyright (C) 2018 - 2020 Elastic and contributors
6+
* %%
7+
* Licensed to Elasticsearch B.V. under one or more contributor
8+
* license agreements. See the NOTICE file distributed with
9+
* this work for additional information regarding copyright
10+
* ownership. Elasticsearch B.V. licenses this file to you under
11+
* the Apache License, Version 2.0 (the "License"); you may
12+
* not use this file except in compliance with the License.
13+
* You may obtain a copy of the License at
14+
*
15+
* http://www.apache.org/licenses/LICENSE-2.0
16+
*
17+
* Unless required by applicable law or agreed to in writing,
18+
* software distributed under the License is distributed on an
19+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20+
* KIND, either express or implied. See the License for the
21+
* specific language governing permissions and limitations
22+
* under the License.
23+
* #L%
24+
*/
25+
package co.elastic.apm.agent.premain;
26+
27+
import java.util.ArrayList;
28+
import java.util.List;
29+
30+
/**
31+
* A check that gets executed when the agent starts up.
32+
* If any check fails, the agent won't start unless the {@code elastic.apm.disable_bootstrap_checks} system property is set to true.
33+
*/
34+
public interface BootstrapCheck {
35+
36+
/**
37+
* Performs the bootstrap check.
38+
*
39+
* @param result Add an error message to avoid the agent from starting up.
40+
*/
41+
void doBootstrapCheck(BootstrapCheckResult result);
42+
43+
class BootstrapCheckResult {
44+
private final List<String> errors = new ArrayList<>();
45+
private final List<String> warnings = new ArrayList<>();
46+
47+
void addError(String message) {
48+
errors.add(message);
49+
}
50+
51+
void addWarn(String message) {
52+
warnings.add(message);
53+
}
54+
55+
public List<String> getErrors() {
56+
return errors;
57+
}
58+
59+
public List<String> getWarnings() {
60+
return warnings;
61+
}
62+
63+
public boolean isEmpty() {
64+
return warnings.isEmpty() && errors.isEmpty();
65+
}
66+
67+
public boolean hasWarnings() {
68+
return !warnings.isEmpty();
69+
}
70+
71+
public boolean hasErrors() {
72+
return !errors.isEmpty();
73+
}
74+
}
75+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*-
2+
* #%L
3+
* Elastic APM Java agent
4+
* %%
5+
* Copyright (C) 2018 - 2020 Elastic and contributors
6+
* %%
7+
* Licensed to Elasticsearch B.V. under one or more contributor
8+
* license agreements. See the NOTICE file distributed with
9+
* this work for additional information regarding copyright
10+
* ownership. Elasticsearch B.V. licenses this file to you under
11+
* the Apache License, Version 2.0 (the "License"); you may
12+
* not use this file except in compliance with the License.
13+
* You may obtain a copy of the License at
14+
*
15+
* http://www.apache.org/licenses/LICENSE-2.0
16+
*
17+
* Unless required by applicable law or agreed to in writing,
18+
* software distributed under the License is distributed on an
19+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
20+
* KIND, either express or implied. See the License for the
21+
* specific language governing permissions and limitations
22+
* under the License.
23+
* #L%
24+
*/
25+
package co.elastic.apm.agent.premain;
26+
27+
import java.lang.management.ManagementFactory;
28+
import java.util.Arrays;
29+
import java.util.List;
30+
31+
class BootstrapChecks {
32+
33+
private final List<BootstrapCheck> bootstrapChecks;
34+
private final boolean bootstrapChecksEnabled;
35+
36+
BootstrapChecks(boolean bootstrapChecksEnabled, BootstrapCheck... bootstrapChecks) {
37+
this.bootstrapChecks = Arrays.asList(bootstrapChecks);
38+
this.bootstrapChecksEnabled = bootstrapChecksEnabled;
39+
}
40+
41+
static BootstrapChecks defaults() {
42+
return new BootstrapChecks(!Boolean.parseBoolean(System.getProperty("elastic.apm.disable_bootstrap_checks")),
43+
new JavaVersionBootstrapCheck(JvmRuntimeInfo.ofCurrentVM()), new VerifyNoneBootstrapCheck(ManagementFactory.getRuntimeMXBean()));
44+
}
45+
46+
/**
47+
* Returns {@code true} if the current VM passes the {@linkplain BootstrapChecks#defaults default bootstrap checks}
48+
* or if bootstrap checks are disabled.
49+
*/
50+
boolean isPassing() {
51+
BootstrapCheck.BootstrapCheckResult result = new BootstrapCheck.BootstrapCheckResult();
52+
for (BootstrapCheck check : bootstrapChecks) {
53+
check.doBootstrapCheck(result);
54+
}
55+
56+
if (result.isEmpty()) {
57+
return true;
58+
}
59+
boolean isPassing = true;
60+
if (result.hasErrors()) {
61+
if (bootstrapChecksEnabled) {
62+
isPassing = false;
63+
System.err.println("ERROR - Failed to start agent because of failing bootstrap checks.");
64+
System.err.println("To override Java version verification, set the 'elastic.apm.disable_bootstrap_checks' System property to 'true'.");
65+
} else {
66+
System.err.println("WARNING - Bootstrap checks have failed. The agent will still start because bootstrap check have been disabled.");
67+
}
68+
System.err.println("Note that we can not offer support for issues related to disabled bootstrap checks.");
69+
for (String msg : result.getErrors()) {
70+
System.err.println(msg);
71+
}
72+
}
73+
if (result.hasWarnings()) {
74+
for (String msg : result.getWarnings()) {
75+
System.err.println(msg);
76+
}
77+
}
78+
return isPassing;
79+
}
80+
81+
}

0 commit comments

Comments
 (0)