Java security: Plugins de Sandboxing cargados a través de URLClassLoader


Resumen de la pregunta: ¿Cómo puedo modificar el código a continuación para que el código cargado dinámicamente y no confiable se ejecute en un sandbox de seguridad mientras el resto de la aplicación permanece sin restricciones? ¿Por qué URLClassLoader no lo maneja como dice que lo hace?

EDITAR: Actualizado para responder a Ani B.

EDICIÓN 2: Agregado PluginSecurityManager actualizado.

Mi aplicación tiene un mecanismo de plug-in donde un tercero puede proporcionar un JAR que contiene una clase que implementa una interfaz particular. Usando URLClassLoader, puedo cargar esa clase e instanciarla, no hay problema. Debido a que el código es potencialmente no confiable, necesito evitar que se comporte mal. Por ejemplo, corro el código del complemento en un hilo separado para que pueda matarlo si entra en un bucle infinito o simplemente toma demasiado tiempo. Pero tratar de establecer una caja de arena de seguridad para ellos para que no puedan hacer cosas como hacer conexiones de red o acceder a archivos en el disco duro me está haciendo positivamente batty. Mi los esfuerzos siempre resultan en no tener ningún efecto en el complemento (tiene los mismos permisos que la aplicación) o también restringir la aplicación. Quiero que el código de la aplicación principal sea capaz de hacer casi todo lo que quiera, pero el código del complemento que se bloquee.

La documentación y los recursos en línea sobre el tema son complejos, confusos y contradictorios. He leído en varios lugares (como esta pregunta) que necesito proporcionar un SecurityManager personalizado, pero cuando pruébelo Me encuentro con problemas porque el JVM perezoso-carga las clases en el FRASCO. Así que puedo instanciarlo muy bien, pero si llamo a un método en el objeto cargado que instancie otra clase del mismo TARRO, explota porque se le niega el derecho a leer del TARRO.

Teóricamente, podría poner un cheque en FilePermission en mi SecurityManager para ver si está tratando de cargar fuera de su propio TARRO. Eso está bien, pero la documentación de URLClassLoader dice: "Las clases que se cargan por defecto solo se les concede permiso para acceder a las URL especificadas cuando se creó el URLClassLoader."Entonces, ¿por qué necesito un SecurityManager personalizado? ¿No debería URLClassLoader manejar esto? ¿Por qué no?

Aquí hay un ejemplo simplificado que reproduce el problema:

Aplicación principal (de confianza)

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 (untrusted)

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

ACTUALIZACIÓN: Lo cambié para que justo antes de que el código del complemento esté a punto de ejecutarse, notifique al PluginSecurityManager para que sepa con qué fuente de clase está trabajando. Entonces solo permitirá accesos a archivos en archivos bajo esa clase ruta de origen. Esto también tiene la ventaja de que solo puedo configurar el administrador de seguridad una vez al comienzo de mi aplicación, y solo actualizarlo cuando ingrese y deje el código del complemento.

Esto casi resuelve el problema, pero no responde a mi otra pregunta: ¿Por qué URLClassLoader no maneja esto por mí como dice que lo hace? Dejaré esta pregunta abierta por un tiempo más para ver si alguien tiene una respuesta a esa pregunta. Si es así, esa persona recibirá la respuesta aceptada. De lo contrario, Lo adjudicaré a Ani B. en la presunción de que la documentación URLClassLoader miente y que su consejo para hacer un SecurityManager personalizado es correcto.

El PluginThread tendrá que establecer la propiedad classSource en el PluginSecurityManager, que es la ruta a los archivos de clase. PluginSecurityManager ahora se ve algo como esto:

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;
    }
}
Author: Community, 2010-10-16

3 answers

De los documentos:
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.

El URLClassLoader está haciendo exactamente lo que dice, el AccessControlContext es lo que necesita mirar. Básicamente, el hilo al que se hace referencia en AccessControlContext no tiene permisos para hacer lo que crees que hace.

 7
Author: Woot4Moo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-19 17:46:28

Utilizo el siguiente enfoque al ejecutar algún script Groovy dentro de una aplicación. Obviamente quiero evitar que el script ejecute (intencionalmente o no) un Sistema.exit

Instalo un java SecurityManager de la manera habitual:

-Djava.security.manager -Djava.security.policy=<policy file>

En el <policy file> doy a mi aplicación todos los permisos (confío plenamente en mi aplicación), es decir:

grant {
    permission java.security.AllPermission;
};

Limito las capacidades en la parte donde se ejecuta el script Groovy:

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

El allowedPermissionsAcc no cambia y por lo tanto los creo en un bloque estático

private static final AccessControlContext allowedPermissionsAcc; 
static {    // initialization of the allowed permissions
    PermissionCollection allowedPermissions = new Permissions();
    allowedPermissions.add(new RuntimePermission("accessDeclaredMembers"));
    // ... <many more permissions here> ...

    allowedPermissionsAcc = new AccessControlContext(new ProtectionDomain[] {
        new ProtectionDomain(null, allowedPermissions)});
}

Ahora la parte difícil es encontrar los permisos correctos.

Si desea permitir el acceso a ciertas bibliotecas, se dará cuenta rápidamente de que no se han escrito con un Administrador de seguridad en mente y no manejan uno con mucha gracia, y averiguar qué permisos necesitan puede ser bastante complicado. Se encontrará con problemas adicionales si desea ejecutar UnitTests a través del complemento Maven Surefire, o ejecutar en diferentes plataformas, como Linux / Windows, ya que el comportamiento puede variar :-(. Pero esas cuestiones son otro tema

 7
Author: Maze,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-08-24 07:58:28

Implementar un SecurityManager es probablemente la mejor manera de hacerlo. Tendrías que anular checkPermission. Ese método observaría el objeto Permission que se le pasa y determinaría si una determinada acción es peligrosa. De esta manera puede permitir algunos permisos y no permitir otros permisos.

¿Puede describir el personalizado SecurityManager que utilizó?

 5
Author: AniDev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-10-18 01:48:19