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

Резюме вопроса: Как мне изменить приведенный ниже код, чтобы ненадежный, динамически загружаемый код выполнялся в изолированной программной среде безопасности, в то время как остальная часть приложения оставалась неограниченной? Почему не t 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
    }
}

ОБНОВЛЕНИЕ: «Загруженным классам по умолчанию предоставляется разрешение только на доступ к 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
    }
}

ОБНОВЛЕНИЕ: «Загруженным классам по умолчанию предоставляется разрешение только на доступ к 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;
    }
}
33
задан Community 23 May 2017 в 11:54
поделиться