Сводка вопроса: Как изменить приведенный ниже код так, чтобы ненадежный динамически загружаемый код выполнялся в изолированной программной среде безопасности, а остальная часть приложения оставалась неограниченной?Почему URLClassLoader не обрабатывает его так, как он говорит?
РЕДАКТИРОВАТЬ: Обновлено, чтобы отвечать Ани Б.
РЕДАКТИРОВАТЬ 2: Добавлен обновленный PluginSecurityManager.
Мое приложение имеет механизм плагинов, в котором третья сторона может предоставить JAR, содержащий класс, который реализует определенный интерфейс.Используя URLClassLoader, я могу загрузить этот класс и создать его экземпляр, никаких проблем.Поскольку код потенциально ненадежен, я должен предотвратить его неправильное поведение.Например, я запускаю плагин-код в отдельном потоке, чтобы я мог его убить, если он заходит в бесконечный цикл или просто занимает слишком много времени.Но попытка установить для них изолированную программную среду безопасности, чтобы они не могли делать такие вещи, как создание сетевых подключений или доступ к файлам на жестком диске, заставляет меня решительно настроиться.Мои усилия всегда приводят либо к тому, что не влияют на плагин (он имеет те же права доступа, что и приложение), либо также ограничивают приложение.Я хочу, чтобы основной код приложения мог выполнять практически все, что ему нужно, но код подключаемого модуля должен быть заблокирован.
Документация и онлайн-ресурсы по этой теме сложны, запутаны и противоречивы.Я читал в разных местах (например, этот вопрос ), что мне нужно предоставить собственный SecurityManager, но когда я пытаюсь это сделать, у меня возникают проблемы, потому что JVM лениво загружает классы в JAR.Так что я могу просто создать его экземпляр, но если я вызову метод для загруженного объекта, который создает экземпляр другого класса из того же JAR, он взорвется, потому что ему будет отказано в праве на чтение из JAR.
Теоретически ямог бы поставить проверку FilePermission в моем SecurityManager, чтобы увидеть, пытается ли он загрузить его из собственного JAR-файла.Это нормально, но документация URLClassLoader гласит: «Загруженные классы по умолчанию предоставляют разрешение только на доступ к URL-адресам, указанным при создании URLClassLoader».Так зачем мне вообще нужен собственный SecurityManager?Разве URLClassLoader не должен просто обрабатывать это?Почему нет?
Вот упрощенный пример, который воспроизводит проблему:
Основное приложение (доверенное)
PluginTest.java
package test.app;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import test.api.Plugin;
public class PluginTest {
public static void pluginTest(String pathToJar) {
try {
File file = new File(pathToJar);
URL url = file.toURI().toURL();
URLClassLoader cl = new URLClassLoader(new java.net.URL[] { url });
Class<?> clazz = cl.loadClass("test.plugin.MyPlugin");
final Plugin plugin = (Plugin) clazz.newInstance();
PluginThread thread = new PluginThread(new Runnable() {
@Override
public void run() {
plugin.go();
}
});
thread.start();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
Plugin.java
package test.api;
public interface Plugin {
public void go();
}
PluginSecurityManager.java
package test.app;
public class PluginSecurityManager extends SecurityManager {
private boolean _sandboxed;
@Override
public void checkPermission(Permission perm) {
check(perm);
}
@Override
public void checkPermission(Permission perm, Object context) {
check(perm);
}
private void check(Permission perm) {
if (!_sandboxed) {
return;
}
// I *could* check FilePermission here, but why doesn't
// URLClassLoader handle it like it says it does?
throw new SecurityException("Permission denied");
}
void enableSandbox() {
_sandboxed = true;
}
void disableSandbox() {
_sandboxed = false;
}
}
PluginThread.java
package test.app;
class PluginThread extends Thread {
PluginThread(Runnable target) {
super(target);
}
@Override
public void run() {
SecurityManager old = System.getSecurityManager();
PluginSecurityManager psm = new PluginSecurityManager();
System.setSecurityManager(psm);
psm.enableSandbox();
super.run();
psm.disableSandbox();
System.setSecurityManager(old);
}
}
Плагин JAR (ненадежный)
MyPlugin.java
package test.plugin;
public MyPlugin implements Plugin {
@Override
public void go() {
new AnotherClassInTheSamePlugin(); // ClassNotFoundException with a SecurityManager
doSomethingDangerous(); // permitted without a SecurityManager
}
private void doSomethingDangerous() {
// use your imagination
}
}
ОБНОВЛЕНИЕ: Я изменил его так, что перед самым запуском кода плагина он уведомляет PluginSecurityManager, чтобы он знал, с каким источником класса он работает,Тогда он разрешит доступ к файлам только для файлов с исходным путем этого класса.Это также имеет то преимущество, что я могу просто установить диспетчер безопасности один раз в начале своего приложения и просто обновить его, когда я ввожу и оставляю код плагина.
Это в значительной степени решает проблему, но нене могу ответить на другой мой вопрос: почему URLClassLoader не обрабатывает это для меня так, как говорит?Я оставлю этот вопрос открытым на некоторое время, чтобы узнать, есть ли у кого-нибудь ответ на этот вопрос.Если так, то этот человек получит принятый ответ.В противном случае я передам его Ани Б., исходя из предположения, что документация URLClassLoader является ложной и что его совет по созданию пользовательского SecurityManager верен.
PluginThread должен будет установить свойство classSource для PluginSecurityManager,который является путем к файлам класса.PluginSecurityManager теперь выглядит примерно так:
package test.app;
public class PluginSecurityManager extends SecurityManager {
private String _classSource;
@Override
public void checkPermission(Permission perm) {
check(perm);
}
@Override
public void checkPermission(Permission perm, Object context) {
check(perm);
}
private void check(Permission perm) {
if (_classSource == null) {
// Not running plugin code
return;
}
if (perm instanceof FilePermission) {
// Is the request inside the class source?
String path = perm.getName();
boolean inClassSource = path.startsWith(_classSource);
// Is the request for read-only access?
boolean readOnly = "read".equals(perm.getActions());
if (inClassSource && readOnly) {
return;
}
}
throw new SecurityException("Permission denied: " + perm);
}
void setClassSource(String classSource) {
_classSource = classSource;
}
}