Skip to content

Commit ee08667

Browse files
committed
Exclude Java agent jars from class path of launching class loader
ExecutableArchiveLauncher creates a ClassLoader that is used by the Launcher to load an application’s classes. During the creation of this ClassLoader URLs from another ClassLoader are copied over. This was resulting in Java agents that are added to the system class loader via the -javaagent launch option being available on both the system class loader and the created class loader. Java agents are intended to always be loaded by the system class loader. Making them available on another class loader breaks this model. This commit updates ExecutableArchiveLauncher so that it skips the URLs of any Java agents (found by examining the JVM’s input arguments) when copying URLs over to the new classloader, thereby ensuring that Java agents are only ever loaded by the system class loader. Fixes spring-projects#863
1 parent 146a337 commit ee08667

File tree

5 files changed

+340
-3
lines changed

5 files changed

+340
-3
lines changed

spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ExecutableArchiveLauncher.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2013 the original author or authors.
2+
* Copyright 2012-2014 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,20 +30,28 @@
3030

3131
/**
3232
* Base class for executable archive {@link Launcher}s.
33-
*
33+
*
3434
* @author Phillip Webb
35+
* @author Andy Wilkinson
3536
*/
3637
public abstract class ExecutableArchiveLauncher extends Launcher {
3738

3839
private final Archive archive;
3940

41+
private final JavaAgentDetector javaAgentDetector;
42+
4043
public ExecutableArchiveLauncher() {
44+
this(new InputArgumentsJavaAgentDetector());
45+
}
46+
47+
public ExecutableArchiveLauncher(JavaAgentDetector javaAgentDetector) {
4148
try {
4249
this.archive = createArchive();
4350
}
4451
catch (Exception ex) {
4552
throw new IllegalStateException(ex);
4653
}
54+
this.javaAgentDetector = javaAgentDetector;
4755
}
4856

4957
protected final Archive getArchive() {
@@ -74,7 +82,9 @@ protected ClassLoader createClassLoader(URL[] urls) throws Exception {
7482
ClassLoader loader = getDefaultClassLoader();
7583
if (loader instanceof URLClassLoader) {
7684
for (URL url : ((URLClassLoader) loader).getURLs()) {
77-
copy.add(url);
85+
if (!this.javaAgentDetector.isJavaAgentJar(url)) {
86+
copy.add(url);
87+
}
7888
}
7989
}
8090
for (URL url : urls) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Copyright 2012-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.loader;
18+
19+
import java.io.File;
20+
import java.io.IOException;
21+
import java.lang.management.ManagementFactory;
22+
import java.net.URL;
23+
import java.security.AccessController;
24+
import java.security.PrivilegedAction;
25+
import java.util.Collections;
26+
import java.util.HashSet;
27+
import java.util.List;
28+
import java.util.Set;
29+
30+
/**
31+
* A {@link JavaAgentDetector} that detects jars supplied via the {@code -javaagent} JVM
32+
* input argument.
33+
*
34+
* @author Andy Wilkinson
35+
* @since 1.1.0
36+
*/
37+
public class InputArgumentsJavaAgentDetector implements JavaAgentDetector {
38+
39+
private static final String JAVA_AGENT_PREFIX = "-javaagent:";
40+
41+
private final Set<URL> javaAgentJars;
42+
43+
public InputArgumentsJavaAgentDetector() {
44+
this(getInputArguments());
45+
}
46+
47+
InputArgumentsJavaAgentDetector(List<String> inputArguments) {
48+
this.javaAgentJars = getJavaAgentJars(inputArguments);
49+
}
50+
51+
private static List<String> getInputArguments() {
52+
try {
53+
return AccessController.doPrivileged(new PrivilegedAction<List<String>>() {
54+
@Override
55+
public List<String> run() {
56+
return ManagementFactory.getRuntimeMXBean().getInputArguments();
57+
}
58+
});
59+
}
60+
catch (Exception ex) {
61+
return Collections.<String> emptyList();
62+
}
63+
}
64+
65+
private Set<URL> getJavaAgentJars(List<String> inputArguments) {
66+
Set<URL> javaAgentJars = new HashSet<URL>();
67+
for (String argument : inputArguments) {
68+
String path = getJavaAgentJarPath(argument);
69+
if (path != null) {
70+
try {
71+
javaAgentJars.add(new File(path).getCanonicalFile().toURI().toURL());
72+
}
73+
catch (IOException ex) {
74+
throw new IllegalStateException(
75+
"Failed to determine canonical path of Java agent at path '"
76+
+ path + "'");
77+
}
78+
}
79+
}
80+
return javaAgentJars;
81+
}
82+
83+
private String getJavaAgentJarPath(String arg) {
84+
if (arg.startsWith(JAVA_AGENT_PREFIX)) {
85+
String path = arg.substring(JAVA_AGENT_PREFIX.length());
86+
int equalsIndex = path.indexOf('=');
87+
if (equalsIndex > -1) {
88+
path = path.substring(0, equalsIndex);
89+
}
90+
return path;
91+
}
92+
93+
return null;
94+
}
95+
96+
@Override
97+
public boolean isJavaAgentJar(URL url) {
98+
return this.javaAgentJars.contains(url);
99+
}
100+
101+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2012-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.loader;
18+
19+
import java.net.URL;
20+
21+
/**
22+
* A strategy for detecting Java agents
23+
*
24+
* @author Andy Wilkinson
25+
*
26+
* @since 1.1
27+
*/
28+
public interface JavaAgentDetector {
29+
30+
/**
31+
* Returns {@code true} if {@code url} points to a Java agent jar file, otherwise
32+
* {@code false} is returned.
33+
*
34+
* @param url The url to examine
35+
*/
36+
public boolean isJavaAgentJar(URL url);
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*
2+
* Copyright 2012-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.loader;
18+
19+
import java.io.File;
20+
import java.net.URL;
21+
import java.net.URLClassLoader;
22+
import java.util.concurrent.Callable;
23+
24+
import org.junit.Before;
25+
import org.junit.Test;
26+
import org.mockito.Mock;
27+
import org.mockito.MockitoAnnotations;
28+
import org.springframework.boot.loader.archive.Archive.Entry;
29+
30+
import static org.junit.Assert.assertArrayEquals;
31+
import static org.junit.Assert.assertTrue;
32+
import static org.mockito.Mockito.when;
33+
34+
/**
35+
* Tests for {@link ExecutableArchiveLauncher}
36+
*
37+
* @author Andy Wilkinson
38+
*/
39+
public class ExecutableArchiveLauncherTests {
40+
41+
@Mock
42+
private JavaAgentDetector javaAgentDetector;
43+
44+
private ExecutableArchiveLauncher launcher;
45+
46+
@Before
47+
public void setupMocks() {
48+
MockitoAnnotations.initMocks(this);
49+
50+
this.launcher = new UnitTestExecutableArchiveLauncher(this.javaAgentDetector);
51+
}
52+
53+
@Test
54+
public void createdClassLoaderContainsUrlsFromThreadContextClassLoader()
55+
throws Exception {
56+
final URL[] urls = new URL[] { new URL("file:one"), new URL("file:two") };
57+
58+
doWithTccl(new URLClassLoader(urls), new Callable<Void>() {
59+
60+
@Override
61+
public Void call() throws Exception {
62+
ClassLoader classLoader = ExecutableArchiveLauncherTests.this.launcher
63+
.createClassLoader(new URL[0]);
64+
assertClassLoaderUrls(classLoader, urls);
65+
return null;
66+
}
67+
});
68+
}
69+
70+
@Test
71+
public void javaAgentJarsAreExcludedFromClasspath() throws Exception {
72+
URL javaAgent = new File("my-agent.jar").getCanonicalFile().toURI().toURL();
73+
final URL one = new URL("file:one");
74+
75+
when(this.javaAgentDetector.isJavaAgentJar(javaAgent)).thenReturn(true);
76+
77+
doWithTccl(new URLClassLoader(new URL[] { javaAgent, one }), new Callable<Void>() {
78+
79+
@Override
80+
public Void call() throws Exception {
81+
ClassLoader classLoader = ExecutableArchiveLauncherTests.this.launcher
82+
.createClassLoader(new URL[0]);
83+
assertClassLoaderUrls(classLoader, new URL[] { one });
84+
return null;
85+
}
86+
});
87+
}
88+
89+
private void assertClassLoaderUrls(ClassLoader classLoader, URL[] urls) {
90+
assertTrue(classLoader instanceof URLClassLoader);
91+
assertArrayEquals(urls, ((URLClassLoader) classLoader).getURLs());
92+
}
93+
94+
private static final class UnitTestExecutableArchiveLauncher extends
95+
ExecutableArchiveLauncher {
96+
97+
public UnitTestExecutableArchiveLauncher(JavaAgentDetector javaAgentDetector) {
98+
super(javaAgentDetector);
99+
}
100+
101+
@Override
102+
protected boolean isNestedArchive(Entry entry) {
103+
return false;
104+
}
105+
}
106+
107+
private void doWithTccl(ClassLoader classLoader, Callable<?> action) throws Exception {
108+
ClassLoader old = Thread.currentThread().getContextClassLoader();
109+
try {
110+
Thread.currentThread().setContextClassLoader(classLoader);
111+
action.call();
112+
}
113+
finally {
114+
Thread.currentThread().setContextClassLoader(old);
115+
}
116+
}
117+
118+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright 2012-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.loader;
18+
19+
import java.io.File;
20+
import java.io.IOException;
21+
import java.net.MalformedURLException;
22+
import java.util.Arrays;
23+
24+
import org.junit.Test;
25+
26+
import static org.junit.Assert.assertFalse;
27+
import static org.junit.Assert.assertTrue;
28+
29+
/**
30+
* Tests for {@link InputArgumentsJavaAgentDetector}
31+
*
32+
* @author Andy Wilkinson
33+
*/
34+
public class InputArgumentsJavaAgentDetectorTests {
35+
36+
@Test
37+
public void nonAgentJarsDoNotProduceFalsePositives() throws MalformedURLException,
38+
IOException {
39+
InputArgumentsJavaAgentDetector detector = new InputArgumentsJavaAgentDetector(
40+
Arrays.asList("-javaagent:my-agent.jar"));
41+
assertFalse(detector.isJavaAgentJar(new File("something-else.jar")
42+
.getCanonicalFile().toURI().toURL()));
43+
}
44+
45+
@Test
46+
public void singleJavaAgent() throws MalformedURLException, IOException {
47+
InputArgumentsJavaAgentDetector detector = new InputArgumentsJavaAgentDetector(
48+
Arrays.asList("-javaagent:my-agent.jar"));
49+
assertTrue(detector.isJavaAgentJar(new File("my-agent.jar").getCanonicalFile()
50+
.toURI().toURL()));
51+
}
52+
53+
@Test
54+
public void singleJavaAgentWithOptions() throws MalformedURLException, IOException {
55+
InputArgumentsJavaAgentDetector detector = new InputArgumentsJavaAgentDetector(
56+
Arrays.asList("-javaagent:my-agent.jar=a=alpha,b=bravo"));
57+
assertTrue(detector.isJavaAgentJar(new File("my-agent.jar").getCanonicalFile()
58+
.toURI().toURL()));
59+
}
60+
61+
@Test
62+
public void multipleJavaAgents() throws MalformedURLException, IOException {
63+
InputArgumentsJavaAgentDetector detector = new InputArgumentsJavaAgentDetector(
64+
Arrays.asList("-javaagent:my-agent.jar", "-javaagent:my-other-agent.jar"));
65+
assertTrue(detector.isJavaAgentJar(new File("my-agent.jar").getCanonicalFile()
66+
.toURI().toURL()));
67+
assertTrue(detector.isJavaAgentJar(new File("my-other-agent.jar")
68+
.getCanonicalFile().toURI().toURL()));
69+
}
70+
71+
}

0 commit comments

Comments
 (0)