java classloader securitymanager urlclassloader

Seguridad de Java: complementos de sandboxing cargados a través de URLClassLoader



securitymanager (3)

Resumen de la pregunta: ¿Cómo modifico el código a continuación para que el código cargado dinámicamente y no confiable se ejecute en un entorno limitado de seguridad mientras el resto de la aplicación no esté restringido? ¿Por qué URLClassLoader no lo maneja como dice que lo hace?

EDITAR: Actualizado para responder a Ani B.

EDITAR 2: Añadido PluginSecurityManager actualizado.

Mi aplicación tiene un mecanismo de complemento donde un tercero puede proporcionar un JAR que contiene una clase que implementa una interfaz particular. Al usar URLClassLoader, puedo cargar esa clase y crear una instancia, no hay problema. Debido a que el código no es confiable, debo evitar que se comporte mal. Por ejemplo, ejecuto el código del complemento en un subproceso separado para poder eliminarlo si entra en un bucle infinito o si solo toma demasiado tiempo. Pero tratar de establecer un recinto 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á volviendo una locura positiva. Mis 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 principal de la aplicación pueda hacer prácticamente todo lo que quiera, pero que el código del complemento esté bloqueado.

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 lo intento me encuentro con problemas porque la JVM carga las clases en el JAR. Así que puedo instanciarlo bien, pero si invoco un método en el objeto cargado que crea una instancia de otra clase del mismo JAR, explota porque se le niega el derecho a leer desde el JAR.

Teóricamente, podría poner un cheque en FilePermission en mi SecurityManager para ver si está intentando cargar fuera de su propio JAR. Eso está bien, pero la documentación de URLClassLoader dice: "Las clases que se cargan tienen permiso otorgado por defecto solo para acceder a las URL especificadas cuando se creó el URLClassLoader". Entonces, ¿por qué necesito un SecurityManager personalizado? ¿No debería URLClassLoader solo 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 (no confiable)

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 a PluginSecurityManager para que sepa con qué fuente de la clase está trabajando. Entonces solo permitirá el acceso a archivos en los archivos bajo esa ruta de origen de clase. Esto también tiene la gran ventaja de que puedo configurar el administrador de seguridad una vez al inicio de mi aplicación y actualizarlo cuando ingrese y deje el código del complemento.

Esto prácticamente 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 obtendrá la respuesta aceptada. De lo contrario, se lo otorgaré a Ani B. bajo la presunción de que la documentación de URLClassLoader se encuentra 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; } }


De la documentación:
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 se dice, AccessControlContext es lo que necesita mirar. Básicamente, el subproceso al que se hace referencia en AccessControlContext no tiene permisos para hacer lo que usted cree que hace.


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

¿Puede describir el SecurityManager personalizado que utilizó?


Utilizo el siguiente enfoque mientras ejecuto un script Groovy dentro de una aplicación. Obviamente, quiero evitar que el script ejecute (intencionalmente o no) un System.exit

Instalo un SecurityManager java de la forma habitual:

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

En el <policy file> doy a mi solicitud 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 pensando en un Administrador de seguridad y no las maneja con mucha gracia, y descubrir qué permisos necesitan puede ser bastante complicado. Tendrá 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 esos problemas son otro tema