Безопасность Java: плагины для песочницы, загруженные с помощью URLClassLoader

Вопрос: Как мне изменить приведенный ниже код, чтобы ненадежный, динамически загруженный код работал в изолированной программной среде безопасности, в то время как остальная часть приложения остается неограниченной? Почему URLClassLoader просто обрабатывает его так, как будто он говорит?

EDIT: Обновлено, чтобы ответить Ани Б.

EDIT 2: добавлен обновленный модуль PluginSecurityManager.

Мое приложение имеет подключаемый механизм, в котором сторонняя сторона может предоставить JAR, содержащий class, который реализует конкретный интерфейс. Используя URLClassLoader, я могу загрузить этот class и создать его, без проблем. Поскольку код потенциально ненадежен, мне нужно предотвратить его неправильное поведение. Например, я запускаю код подключаемого модуля в отдельном streamе, чтобы я мог его убить, если он переходит в бесконечный цикл или занимает слишком много времени. Но пытаясь установить для них изолированную программную среду безопасности, чтобы они не могли делать такие вещи, как создание сетевых подключений или доступ к файлам на жестком диске, делает меня положительно батти. Мои усилия всегда приводят к тому, что они не влияют на подключаемый модуль (он имеет те же права, что и приложение), либо также ограничивает приложение. Я хочу, чтобы основной код приложения мог делать практически все, что он хочет, но код подключаемого модуля должен быть заблокирован.

Документация и онлайн-ресурсы по этому предмету сложны, запутанны и противоречивы. Я читал в разных местах (например, этот вопрос ), что мне нужно предоставить собственный SecurityManager, но когда я пытаюсь, я сталкиваюсь с проблемами, потому что JVM lazy загружает classы в JAR. Поэтому я могу создать экземпляр, но если я вызываю метод на загружаемом объекте, который создает экземпляр другого classа из того же JAR, он взрывается, потому что ему отказано в праве читать из JAR.

Теоретически, я могу поместить проверку на FilePermission в моем SecurityManager, чтобы проверить, пытается ли она загрузить из своего JAR. Все в порядке, но документация URLClassLoader гласит: «Загружаемые classы по умолчанию предоставляют разрешение только для доступа к 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 } } 

UPDATE: я изменил его так, что перед тем, как будет запущен код плагина, он уведомляет PluginSecurityManager, чтобы он знал, с каким classом он работает. Тогда он разрешит доступ к файлам только в файлах этого пути. Это также имеет хорошее преимущество, что я могу просто установить менеджера безопасности один раз в начале моего приложения и просто обновить его, когда я вхожу и оставляю код плагина.

Это в значительной степени решает проблему, но не отвечает на мой другой вопрос: почему URLClassLoader не обрабатывает это для меня, как будто он говорит, что это так? Я оставлю этот вопрос открытым дольше, чтобы узнать, есть ли у кого-нибудь ответ на этот вопрос. Если да, то этот человек получит принятый ответ. В противном случае я вручу его Ани Б. на предположение, что документация URLClassLoader лежит и что его совет по созданию настраиваемого SecurityManager является правильным.

ПлагинThread должен установить свойство classSource в PluginSecurityManager, который является путем к файлам classов. Теперь 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; } } 

    Из документов:
    The AccessControlContext of the thread that created the instance of URLClassLoader will be used when subsequently loading classes and resources.

    The classes that are loaded are by default granted permission only to access the URLs specified when the URLClassLoader was created.

    URLClassLoader выполняет именно так, как говорится, AccessControlContext – это то, что вам нужно посмотреть. В основном stream, на который ссылается в AccessControlContext, не имеет прав, чтобы делать то, что вы думаете.

    При использовании некоторого скрипта Groovy в приложении я использую следующий подход. Я, очевидно, хочу, чтобы сценарий не запускался (намеренно или непреднамеренно) System.exit

    Я устанавливаю Java SecurityManager обычным способом:

     -Djava.security.manager -Djava.security.policy= 

    В я предоставляю моему приложению все разрешения (я полностью доверяю своему приложению), то есть:

     grant { permission java.security.AllPermission; }; 

    Я ограничиваю возможности в той части, где запущен скрипт Groovy:

     list = AccessController.doPrivileged(new PrivilegedExceptionAction> () { public List run() throws Exception { return groovyToExecute.someFunction(); } }, allowedPermissionsAcc); 

    allowedPermissionsAcc не изменяется, и поэтому я создаю их в статическом блоке

     private static final AccessControlContext allowedPermissionsAcc; static { // initialization of the allowed permissions PermissionCollection allowedPermissions = new Permissions(); allowedPermissions.add(new RuntimePermission("accessDeclaredMembers")); // ...  ... allowedPermissionsAcc = new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, allowedPermissions)}); } 

    Теперь сложная часть – найти правильные разрешения.

    Если вы хотите разрешить доступ к определенным библиотекам, вы быстро поймете, что они не были написаны с помощью Менеджера безопасности в виду и не обрабатывают один очень изящно, и выяснение, какие разрешения, в которых они нуждаются, могут быть довольно сложными. У вас возникнут дополнительные проблемы, если вы хотите запускать UnitTests через плагин Maven Surefire или запускаться на разных платформах, таких как Linux / Windows, так как поведение может варьироваться 🙁 Но эти проблемы – другая тема

    Реализация SecurityManager , вероятно, лучший способ. Вам придется переопределить checkPermission . Этот метод будет рассматривать передаваемый ему объект Permission и определить, опасно ли какое-либо действие. Таким образом, вы можете разрешить некоторые разрешения и запретить другие разрешения.

    Можете ли вы описать пользовательский SecurityManager вы использовали?

    Давайте будем гением компьютера.