mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-08-29 03:06:45 +08:00
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 #863
This commit is contained in:
parent
146a337b53
commit
ee08667e81
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -30,20 +30,28 @@ import org.springframework.boot.loader.archive.Archive.EntryFilter;
|
||||
|
||||
/**
|
||||
* Base class for executable archive {@link Launcher}s.
|
||||
*
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public abstract class ExecutableArchiveLauncher extends Launcher {
|
||||
|
||||
private final Archive archive;
|
||||
|
||||
private final JavaAgentDetector javaAgentDetector;
|
||||
|
||||
public ExecutableArchiveLauncher() {
|
||||
this(new InputArgumentsJavaAgentDetector());
|
||||
}
|
||||
|
||||
public ExecutableArchiveLauncher(JavaAgentDetector javaAgentDetector) {
|
||||
try {
|
||||
this.archive = createArchive();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
this.javaAgentDetector = javaAgentDetector;
|
||||
}
|
||||
|
||||
protected final Archive getArchive() {
|
||||
@ -74,7 +82,9 @@ public abstract class ExecutableArchiveLauncher extends Launcher {
|
||||
ClassLoader loader = getDefaultClassLoader();
|
||||
if (loader instanceof URLClassLoader) {
|
||||
for (URL url : ((URLClassLoader) loader).getURLs()) {
|
||||
copy.add(url);
|
||||
if (!this.javaAgentDetector.isJavaAgentJar(url)) {
|
||||
copy.add(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (URL url : urls) {
|
||||
|
@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.net.URL;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A {@link JavaAgentDetector} that detects jars supplied via the {@code -javaagent} JVM
|
||||
* input argument.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public class InputArgumentsJavaAgentDetector implements JavaAgentDetector {
|
||||
|
||||
private static final String JAVA_AGENT_PREFIX = "-javaagent:";
|
||||
|
||||
private final Set<URL> javaAgentJars;
|
||||
|
||||
public InputArgumentsJavaAgentDetector() {
|
||||
this(getInputArguments());
|
||||
}
|
||||
|
||||
InputArgumentsJavaAgentDetector(List<String> inputArguments) {
|
||||
this.javaAgentJars = getJavaAgentJars(inputArguments);
|
||||
}
|
||||
|
||||
private static List<String> getInputArguments() {
|
||||
try {
|
||||
return AccessController.doPrivileged(new PrivilegedAction<List<String>>() {
|
||||
@Override
|
||||
public List<String> run() {
|
||||
return ManagementFactory.getRuntimeMXBean().getInputArguments();
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return Collections.<String> emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
private Set<URL> getJavaAgentJars(List<String> inputArguments) {
|
||||
Set<URL> javaAgentJars = new HashSet<URL>();
|
||||
for (String argument : inputArguments) {
|
||||
String path = getJavaAgentJarPath(argument);
|
||||
if (path != null) {
|
||||
try {
|
||||
javaAgentJars.add(new File(path).getCanonicalFile().toURI().toURL());
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException(
|
||||
"Failed to determine canonical path of Java agent at path '"
|
||||
+ path + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
return javaAgentJars;
|
||||
}
|
||||
|
||||
private String getJavaAgentJarPath(String arg) {
|
||||
if (arg.startsWith(JAVA_AGENT_PREFIX)) {
|
||||
String path = arg.substring(JAVA_AGENT_PREFIX.length());
|
||||
int equalsIndex = path.indexOf('=');
|
||||
if (equalsIndex > -1) {
|
||||
path = path.substring(0, equalsIndex);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isJavaAgentJar(URL url) {
|
||||
return this.javaAgentJars.contains(url);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* A strategy for detecting Java agents
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*
|
||||
* @since 1.1
|
||||
*/
|
||||
public interface JavaAgentDetector {
|
||||
|
||||
/**
|
||||
* Returns {@code true} if {@code url} points to a Java agent jar file, otherwise
|
||||
* {@code false} is returned.
|
||||
*
|
||||
* @param url The url to examine
|
||||
*/
|
||||
public boolean isJavaAgentJar(URL url);
|
||||
}
|
@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.springframework.boot.loader.archive.Archive.Entry;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Tests for {@link ExecutableArchiveLauncher}
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class ExecutableArchiveLauncherTests {
|
||||
|
||||
@Mock
|
||||
private JavaAgentDetector javaAgentDetector;
|
||||
|
||||
private ExecutableArchiveLauncher launcher;
|
||||
|
||||
@Before
|
||||
public void setupMocks() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
this.launcher = new UnitTestExecutableArchiveLauncher(this.javaAgentDetector);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createdClassLoaderContainsUrlsFromThreadContextClassLoader()
|
||||
throws Exception {
|
||||
final URL[] urls = new URL[] { new URL("file:one"), new URL("file:two") };
|
||||
|
||||
doWithTccl(new URLClassLoader(urls), new Callable<Void>() {
|
||||
|
||||
@Override
|
||||
public Void call() throws Exception {
|
||||
ClassLoader classLoader = ExecutableArchiveLauncherTests.this.launcher
|
||||
.createClassLoader(new URL[0]);
|
||||
assertClassLoaderUrls(classLoader, urls);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void javaAgentJarsAreExcludedFromClasspath() throws Exception {
|
||||
URL javaAgent = new File("my-agent.jar").getCanonicalFile().toURI().toURL();
|
||||
final URL one = new URL("file:one");
|
||||
|
||||
when(this.javaAgentDetector.isJavaAgentJar(javaAgent)).thenReturn(true);
|
||||
|
||||
doWithTccl(new URLClassLoader(new URL[] { javaAgent, one }), new Callable<Void>() {
|
||||
|
||||
@Override
|
||||
public Void call() throws Exception {
|
||||
ClassLoader classLoader = ExecutableArchiveLauncherTests.this.launcher
|
||||
.createClassLoader(new URL[0]);
|
||||
assertClassLoaderUrls(classLoader, new URL[] { one });
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void assertClassLoaderUrls(ClassLoader classLoader, URL[] urls) {
|
||||
assertTrue(classLoader instanceof URLClassLoader);
|
||||
assertArrayEquals(urls, ((URLClassLoader) classLoader).getURLs());
|
||||
}
|
||||
|
||||
private static final class UnitTestExecutableArchiveLauncher extends
|
||||
ExecutableArchiveLauncher {
|
||||
|
||||
public UnitTestExecutableArchiveLauncher(JavaAgentDetector javaAgentDetector) {
|
||||
super(javaAgentDetector);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isNestedArchive(Entry entry) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void doWithTccl(ClassLoader classLoader, Callable<?> action) throws Exception {
|
||||
ClassLoader old = Thread.currentThread().getContextClassLoader();
|
||||
try {
|
||||
Thread.currentThread().setContextClassLoader(classLoader);
|
||||
action.call();
|
||||
}
|
||||
finally {
|
||||
Thread.currentThread().setContextClassLoader(old);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2012-2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.loader;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* Tests for {@link InputArgumentsJavaAgentDetector}
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class InputArgumentsJavaAgentDetectorTests {
|
||||
|
||||
@Test
|
||||
public void nonAgentJarsDoNotProduceFalsePositives() throws MalformedURLException,
|
||||
IOException {
|
||||
InputArgumentsJavaAgentDetector detector = new InputArgumentsJavaAgentDetector(
|
||||
Arrays.asList("-javaagent:my-agent.jar"));
|
||||
assertFalse(detector.isJavaAgentJar(new File("something-else.jar")
|
||||
.getCanonicalFile().toURI().toURL()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void singleJavaAgent() throws MalformedURLException, IOException {
|
||||
InputArgumentsJavaAgentDetector detector = new InputArgumentsJavaAgentDetector(
|
||||
Arrays.asList("-javaagent:my-agent.jar"));
|
||||
assertTrue(detector.isJavaAgentJar(new File("my-agent.jar").getCanonicalFile()
|
||||
.toURI().toURL()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void singleJavaAgentWithOptions() throws MalformedURLException, IOException {
|
||||
InputArgumentsJavaAgentDetector detector = new InputArgumentsJavaAgentDetector(
|
||||
Arrays.asList("-javaagent:my-agent.jar=a=alpha,b=bravo"));
|
||||
assertTrue(detector.isJavaAgentJar(new File("my-agent.jar").getCanonicalFile()
|
||||
.toURI().toURL()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleJavaAgents() throws MalformedURLException, IOException {
|
||||
InputArgumentsJavaAgentDetector detector = new InputArgumentsJavaAgentDetector(
|
||||
Arrays.asList("-javaagent:my-agent.jar", "-javaagent:my-other-agent.jar"));
|
||||
assertTrue(detector.isJavaAgentJar(new File("my-agent.jar").getCanonicalFile()
|
||||
.toURI().toURL()));
|
||||
assertTrue(detector.isJavaAgentJar(new File("my-other-agent.jar")
|
||||
.getCanonicalFile().toURI().toURL()));
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user