РЕДАКТИРОВАТЬ: обновлено, чтобы ответить Ани Б.
РЕДАКТИРОВАТЬ 2: добавлен обновленный PluginSecurityManager.
В моем приложении есть механизм подключаемых модулей, который может предоставить третье лицо JAR-файл, содержащий класс, реализующий определенный интерфейс. Используя URLClassLoader, я могу загрузить этот класс и создать его экземпляр, без проблем. Поскольку код потенциально ненадежен, мне нужно предотвратить его некорректное поведение. Например, я запускаю код подключаемого модуля в отдельном потоке, чтобы я мог убить его, если он перейдет в бесконечный цикл или просто займет слишком много времени. Но попытка установить для них изолированную программную среду, чтобы они не могли делать такие вещи, как сетевые подключения или доступ к файлам на жестком диске, заставляет меня определенно нервничать. Мои усилия всегда приводят либо к тому, что плагин не действует (у него те же разрешения, что и приложение), либо к тому же ограничивает приложение. Я хочу, чтобы основной код приложения мог делать практически все, что он хочет, но чтобы код подключаемого модуля был заблокирован.
Документация и онлайн-ресурсы по этой теме сложны, запутаны и противоречивы. Я читал в разных местах (например, этот вопрос ), что мне нужно предоставить специальный SecurityManager, но когда я пробую его, я сталкиваюсь с проблемами, потому что JVM лениво загружает классы в JAR. Так что я могу создать его экземпляр, но если я вызываю метод загруженного объекта, который создает экземпляр другого класса из того же JAR, он взрывается, потому что ему отказано в праве на чтение из JAR.
Теоретически, Я мог бы проверить FilePermission в моем SecurityManager, чтобы узнать, пытается ли он загружаться из собственного JAR. Это нормально, но документация URLClassLoader говорит: «Загружаемые классы по умолчанию получают разрешение только на доступ к URL-адресам, указанным при создании URLClassLoader». Так зачем мне вообще нужен специальный SecurityManager? Разве URLClassLoader не должен справиться с этим? Почему нет?
Вот упрощенный пример, воспроизводящий проблему:
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();
}
}
}
package test.api;
public interface Plugin {
public void go();
}
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;
}
}
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);
}
}
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
}
}
ОБНОВЛЕНИЕ: «Загруженным классам по умолчанию предоставляется разрешение только на доступ к URL-адресам, указанным при создании URLClassLoader». Так зачем мне вообще нужен специальный SecurityManager? Разве URLClassLoader не должен справиться с этим? Почему нет?
Вот упрощенный пример, воспроизводящий проблему:
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();
}
}
}
package test.api;
public interface Plugin {
public void go();
}
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;
}
}
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);
}
}
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
}
}
ОБНОВЛЕНИЕ: «Загруженным классам по умолчанию предоставляется разрешение только на доступ к URL-адресам, указанным при создании URLClassLoader». Так зачем мне вообще нужен специальный SecurityManager? Разве URLClassLoader не должен справиться с этим? Почему нет?
Вот упрощенный пример, воспроизводящий проблему:
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();
}
}
}
package test.api;
public interface Plugin {
public void go();
}
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;
}
}
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);
}
}
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;
}
}