Set context class loader for plugin initialization (#63185)

Plugins are loaded in isolated child class loaders of the root class loader. However, some libraries depend on the context class loader being set. This commit sets the context class loader for the duration of calling each plugins constructor.

relates #52320

Co-authored-by: Ryan Ernst <ryan@iernst.net>
This commit is contained in:
Dawid Weiss 2020-10-07 02:59:35 +02:00 committed by Ryan Ernst
parent f17ca18dfa
commit dbcbdcc029
No known key found for this signature in database
GPG Key ID: 5F7EA39E15F54DCE
3 changed files with 36 additions and 9 deletions

View File

@ -53,6 +53,8 @@ import java.net.URLClassLoader;
import java.nio.file.DirectoryStream; import java.nio.file.DirectoryStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@ -637,15 +639,31 @@ public class PluginsService implements ReportingService<PluginsAndModules> {
// reload SPI with any new services from the plugin // reload SPI with any new services from the plugin
reloadLuceneSPI(loader); reloadLuceneSPI(loader);
Class<? extends Plugin> pluginClass = loadPluginClass(bundle.plugin.getClassname(), loader); ClassLoader cl = Thread.currentThread().getContextClassLoader();
if (loader != pluginClass.getClassLoader()) { try {
throw new IllegalStateException("Plugin [" + name + "] must reference a class loader local Plugin class [" // Set context class loader to plugin's class loader so that plugins
+ bundle.plugin.getClassname() // that have dependencies with their own SPI endpoints have a chance to load
+ "] (class loader [" + pluginClass.getClassLoader() + "])"); // and initialize them appropriately.
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
Thread.currentThread().setContextClassLoader(loader);
return null;
});
Class<? extends Plugin> pluginClass = loadPluginClass(bundle.plugin.getClassname(), loader);
if (loader != pluginClass.getClassLoader()) {
throw new IllegalStateException("Plugin [" + name + "] must reference a class loader local Plugin class ["
+ bundle.plugin.getClassname()
+ "] (class loader [" + pluginClass.getClassLoader() + "])");
}
Plugin plugin = loadPlugin(pluginClass, settings, configPath);
loaded.put(name, plugin);
return plugin;
} finally {
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
Thread.currentThread().setContextClassLoader(cl);
return null;
});
} }
Plugin plugin = loadPlugin(pluginClass, settings, configPath);
loaded.put(name, plugin);
return plugin;
} }
/** /**
@ -668,7 +686,7 @@ public class PluginsService implements ReportingService<PluginsAndModules> {
private Class<? extends Plugin> loadPluginClass(String className, ClassLoader loader) { private Class<? extends Plugin> loadPluginClass(String className, ClassLoader loader) {
try { try {
return loader.loadClass(className).asSubclass(Plugin.class); return Class.forName(className, false, loader).asSubclass(Plugin.class);
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
throw new ElasticsearchException("Could not find plugin class [" + className + "]", e); throw new ElasticsearchException("Could not find plugin class [" + className + "]", e);
} }

View File

@ -28,6 +28,13 @@ grant codeBase "${codebase.elasticsearch-secure-sm}" {
permission java.security.AllPermission; permission java.security.AllPermission;
}; };
//// Elasticsearch core:
//// These are only allowed inside the server jar, not in plugins
grant codeBase "${codebase.elasticsearch}" {
// needed for loading plugins which may expect the context class loader to be set
permission java.lang.RuntimePermission "setContextClassLoader";
};
//// Very special jar permissions: //// Very special jar permissions:
//// These are dangerous permissions that we don't want to grant to everything. //// These are dangerous permissions that we don't want to grant to everything.

View File

@ -139,6 +139,8 @@ public class BootstrapForTesting {
// read test-framework permissions // read test-framework permissions
Map<String, URL> codebases = Security.getCodebaseJarMap(JarHell.parseClassPath()); Map<String, URL> codebases = Security.getCodebaseJarMap(JarHell.parseClassPath());
// when testing server, the main elasticsearch code is not yet in a jar, so we need to manually add it
addClassCodebase(codebases,"elasticsearch", "org.elasticsearch.plugins.PluginsService");
if (System.getProperty("tests.gradle") == null) { if (System.getProperty("tests.gradle") == null) {
// intellij and eclipse don't package our internal libs, so we need to set the codebases for them manually // intellij and eclipse don't package our internal libs, so we need to set the codebases for them manually
addClassCodebase(codebases,"plugin-classloader", "org.elasticsearch.plugins.ExtendedPluginsClassLoader"); addClassCodebase(codebases,"plugin-classloader", "org.elasticsearch.plugins.ExtendedPluginsClassLoader");