Java-Sicherheit: Sandboxing-Plugins werden über URLClassLoader @ gelad

Frage Summary: Wie ändere ich den folgenden Code, damit nicht vertrauenswürdiger, dynamisch geladener Code in einer Sicherheits-Sandbox ausgeführt wird, während der Rest der Anwendung uneingeschränkt bleibt? Warum behandelt URLClassLoader es nicht einfach so, wie es sagt?

EDIT: Aktualisiert, um auf Ani B. zu antworten.

EDIT 2: Aktualisierter PluginSecurityManager hinzugefügt.

Meine Anwendung verfügt über einen Plug-In-Mechanismus, mit dem ein Drittanbieter eine JAR-Datei bereitstellen kann, die eine Klasse enthält, die eine bestimmte Schnittstelle implementiert. Mit URLClassLoader kann ich diese Klasse laden und instanziieren, kein Problem. Da der Code möglicherweise nicht vertrauenswürdig ist, muss verhindert werden, dass er sich schlecht verhält. Ich führe beispielsweise Plug-in-Code in einem separaten Thread aus, damit ich ihn beenden kann, wenn er in eine Endlosschleife gerät oder einfach zu lange dauert. Aber der Versuch, eine Sicherheits-Sandbox für sie einzurichten, damit sie keine Netzwerkverbindungen herstellen oder auf Dateien auf der Festplatte zugreifen können, macht mich verrückt. Meine Bemühungen führen immer dazu, dass entweder das Plug-In nicht beeinflusst wird (es hat die gleichen Berechtigungen wie die Anwendung) oder dass die Anwendung eingeschränkt wird. Ich möchte, dass der Hauptanwendungscode so ziemlich alles kann, was er will, aber der Plug-in-Code muss gesperrt sein.

Dokumentation und Online-Ressourcen zu diesem Thema sind komplex, verwirrend und widersprüchlich. Ich habe an verschiedenen Orten gelesen (wiediese Frag) dass ich einen benutzerdefinierten SecurityManager bereitstellen muss, aber wenn ich es versuche, stoße ich auf Probleme, weil die JVM die Klassen in der JAR faul lädt. Ich kann es also problemlos instanziieren, aber wenn ich eine Methode für das geladene Objekt aufrufe, die eine andere Klasse aus derselben JAR instanziiert, wird sie in die Luft gesprengt, weil ihr das Recht verweigert wird, aus der JAR zu lesen.

Theoretisch könnte ich FilePermission in meinem SecurityManager überprüfen, um festzustellen, ob versucht wird, aus seiner eigenen JAR-Datei zu laden. Das ist in Ordnung, aberthe URLClassLoader documentation sagt: "Den geladenen Klassen wird standardmäßig nur die Berechtigung erteilt, auf die URLs zuzugreifen, die beim Erstellen des URLClassLoader angegeben wurden." Warum brauche ich überhaupt einen benutzerdefinierten SecurityManager? Sollte URLClassLoader nicht einfach damit umgehen? Warum nicht?

Hier ist ein vereinfachtes Beispiel, das das Problem reproduziert:

Hauptanwendung (vertrauenswürdig) 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);
    }
}
Plugin JAR (nicht vertrauenswürdig) 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
    }
}

AKTUALISIEREN Ich habe es so geändert, dass es den PluginSecurityManager benachrichtigt, bevor der Plugin-Code ausgeführt wird, damit er weiß, mit welcher Klassenquelle er arbeitet. Dann werden nur Dateizugriffe auf Dateien unter diesem Klassenquellpfad zugelassen. Dies hat auch den schönen Vorteil, dass ich den Sicherheitsmanager zu Beginn meiner Anwendung nur einmal einstellen und aktualisieren kann, wenn ich den Plugin-Code eingebe und verlasse.

Dies löst das Problem so ziemlich, beantwortet aber nicht meine andere Frage: Warum behandelt URLClassLoader das für mich nicht so, wie es es verspricht? Ich werde diese Frage noch eine Weile offen lassen, um zu sehen, ob jemand eine Antwort auf diese Frage hat. In diesem Fall erhält diese Person die akzeptierte Antwort. Ansonsten werde ich es Ani B. in der Annahme zuteilen, dass die URLClassLoader-Dokumentation lügt und sein Rat, einen benutzerdefinierten SecurityManager zu erstellen, korrekt ist.

Der PluginThread muss die classSource -Eigenschaft im PluginSecurityManager festlegen. Dies ist der Pfad zu den Klassendateien. PluginSecurityManager sieht jetzt ungefähr so aus:

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;
    }
}

Antworten auf die Frage(6)

Ihre Antwort auf die Frage