2010-02-22 6 views
5

Gibt es eine Möglichkeit für einen SecurityManager in Java, selektiv ReflectPermission ("suppressAccessChecks") abhängig von den Details dessen, was setAccessible() aufgerufen wird, zu gewähren? Ich sehe keinen Weg, dies zu tun.Gibt es eine Möglichkeit für einen SecurityManager in Java, ReflectPermission selektiv zu erteilen ("suppressAccessChecks")?

Für einige Sandbox-Code, wäre es sehr nützlich sein (wie zum Beispiel für verschiedene dynamische JVM Sprachen ausgeführt wird) die setAccessible() Reflection-API zu ermöglichen, genannt zu werden, aber nur wenn setAccessible() auf einer Methode aufgerufen wird/Feld einer Klasse, die aus dem Sandbox-Code stammt.

Hat jemand andere alternative Vorschläge als das selektive Gewähren von ReflectPermission ("suppressAccessChecks"), wenn dies nicht möglich ist? Vielleicht wäre es in allen Fällen sicher zu gewähren, wenn SecurityManager.checkMemberAccess() ausreichend restriktiv ist?

Antwort

0

FWI: Da setAccessible nur einen gültigen Anwendungsfall mit Serialisierung zu haben scheint, würde ich denken, dass Sie oft damit davonkommen, es einfach zu leugnen.

Das heißt, ich bin daran interessiert, wie man diese Art von Sache im Allgemeinen macht, weil ich auch einen Sicherheitsmanager schreiben muss, um dynamisch geladenen Code von Dingen zu blockieren, die unser Anwendungscontainercode ausführen muss.

+0

Leider sind einige dynamische JVM-lanauges leider ziemlich eingestellt, glücklich und rufen sie sogar für öffentliche Methoden auf, für die sie nicht aufgerufen werden müssen. Außerdem gibt es Anwendungsfälle wie die erwähnte Serialisierung oder einige Betriebsmodi von Abhängigkeitsinjektions-Frameworks, die nicht unnötig blockiert werden sollten. –

+0

Hmmm. Keine Ahnung von diesen anderen Anwendungsfällen - Ich habe lange gedacht, dass setAccessible die größte Sicherheitsverlet zung ist, die Sun je mit Java gemacht hat. –

8

Vielleicht wäre es für Ihre Zwecke genug, sich den Callstack anzuschauen? Etwas wie:

import java.lang.reflect.ReflectPermission; 
import java.security.Permission; 

public class Test { 
    private static int foo; 

    public static void main(String[] args) throws Exception { 
     System.setSecurityManager(new SecurityManager() { 
      @Override 
      public void checkPermission(Permission perm) { 
       if (perm instanceof ReflectPermission && "suppressAccessChecks".equals(perm.getName())) { 
        for (StackTraceElement elem : Thread.currentThread().getStackTrace()) { 
         if ("Test".equals(elem.getClassName()) && "badSetAccessible".equals(elem.getMethodName())) { 
          throw new SecurityException(); 
         } 
        } 
       } 
      } 
     }); 

     goodSetAccessible(); // works 
     badSetAccessible(); // throws SecurityException 
    } 

    private static void goodSetAccessible() throws Exception { 
     Test.class.getDeclaredField("foo").setAccessible(true); 
    } 

    private static void badSetAccessible() throws Exception { 
     Test.class.getDeclaredField("foo").setAccessible(true); 
    } 
} 
2

Dies ist möglich, Byte-Code mit einer Bibliothek wie Byte Buddy Weberei. Anstatt die Standardberechtigung ReflectPermission("suppressAccessChecks") zu verwenden, können Sie eine benutzerdefinierte Berechtigung erstellen und die Methoden AccessibleObject.setAccessible durch benutzerdefinierte Methoden ersetzen, die Ihre benutzerdefinierten Berechtigungen mithilfe von Byte Buddy-Transformationen überprüfen.

Eine Möglichkeit, wie diese benutzerdefinierte Berechtigung funktioniert, ist, dass sie den Zugriff auf den Klassenlader des Aufrufers und auf das Objekt, auf das der Zugriff geändert wird, basiert. Dadurch kann isolierter Code (Code, der von einem eigenen Klassenladeprogramm geladen wurde) setAccessible für Klassen in seinem eigenen Jar aufgerufen werden, aber nicht für Standard-Java-Klassen oder Ihre eigenen Anwendungsklassen.

Eine solche Erlaubnis könnte wie folgt aussehen:

public class UserSetAccessiblePermission extends Permission { 
    private final ClassLoader loader; 

    public UserSetAccessiblePermission(ClassLoader loader) { 
    super("userSetAccessible"); 
    this.loader = loader; 
    } 

    @Override 
    public boolean implies(Permission permission) { 
    if (!(permission instanceof UserSetAccessiblePermission)) { 
     return false; 
    } 
    UserSetAccessiblePermission that = (UserSetAccessiblePermission) permission; 
    return that.loader == this.loader; 
    } 

    // equals and hashCode omitted 

    @Override 
    public String getActions() { 
    return ""; 
    } 
} 

Dies ist, wie ich wählte diese Erlaubnis zu implementieren, aber es könnte stattdessen ein Paket oder eine Klasse Black- oder White schwarzen Liste.

Mit dieser Berechtigung können Sie jetzt eine Stub-Klasse erstellen, die die AccessibleObject.setAcessible-Methode ersetzt, um stattdessen diese Berechtigung zu verwenden.

public class AccessibleObjectStub { 
    private final static Permission STANDARD_ACCESS_PERMISSION = 
     new ReflectPermission("suppressAccessChecks"); 

    public static void setAccessible(@This AccessibleObject ao, boolean flag) 
     throws SecurityException { 
    SecurityManager sm = System.getSecurityManager(); 
    if (sm != null) { 
     Permission permission = STANDARD_ACCESS_PERMISSION; 
     if (isFromUserLoader(ao)) { 
     try { 
      permission = getUserAccessPermission(ao); 
     } catch (Exception e) { 
      // Ignore. Use standard permission. 
     } 
     } 

     sm.checkPermission(permission); 
    } 
    } 

    private static Permission getUserAccessPermission(AccessibleObject ao) 
     throws IllegalAccessException, InvocationTargetException, InstantiationException, 
     NoSuchMethodException, ClassNotFoundException { 
    ClassLoader aoClassLoader = getAccessibleObjectLoader(ao); 
    return new UserSetAccessiblePermission(aoClassLoader); 
    } 

    private static ClassLoader getAccessibleObjectLoader(AccessibleObject ao) { 
    return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() { 
     @Override 
     public ClassLoader run() { 
     if (ao instanceof Executable) { 
      return ((Executable) ao).getDeclaringClass().getClassLoader(); 
     } else if (ao instanceof Field) { 
      return ((Field) ao).getDeclaringClass().getClassLoader(); 
     } 
     throw new IllegalStateException("Unknown AccessibleObject type: " + ao.getClass()); 
     } 
    }); 
    } 

    private static boolean isFromUserLoader(AccessibleObject ao) { 
    ClassLoader loader = getAccessibleObjectLoader(ao); 

    if (loader == null) { 
     return false; 
    } 

    // Check that the class loader instance is of a custom type 
    return UserClassLoaders.isUserClassLoader(loader); 
    } 
} 

Mit diesen beiden Klassen an Ort und Stelle können Sie nun Byte Buddy verwenden, um einen Transformator zu bauen, um die Java-Transformation AccessibleObject Ihre Stummel zu verwenden.

Der erste Schritt zum Erstellen des Transformers besteht darin, einen Byte Buddy-Typenpool zu erstellen, der die Bootstrap-Klassen und eine JAR-Datei mit Ihren Stubs enthält.

final TypePool bootstrapTypePool = TypePool.Default.of(
new ClassFileLocator.Compound(
    new ClassFileLocator.ForJarFile(jarFile), 
    ClassFileLocator.ForClassLoader.of(null))); 

Next Verwendung Reflexionen einen Verweis auf die AccessObject.setAccessible0 Methode zu erhalten. Dies ist eine private Methode, die die Barrierefreiheit tatsächlich ändert, wenn der Aufruf an setAccessible Berechtigungsprüfungen passiert.

Method setAccessible0Method; 
try { 
    String setAccessible0MethodName = "setAccessible0"; 
    Class[] paramTypes = new Class[2]; 
    paramTypes[0] = AccessibleObject.class; 
    paramTypes[1] = boolean.class; 
    setAccessible0Method = AccessibleObject.class 
     .getDeclaredMethod(setAccessible0MethodName, paramTypes); 
} catch (NoSuchMethodException e) { 
    throw new RuntimeException(e); 
} 

Mit diesen beiden Teilen kann der Transformator gebaut werden.

AgentBuilder.Transformer transformer = new AgentBuilder.Transformer() { 
    @Override 
    public DynamicType.Builder<?> transform(
     DynamicType.Builder<?> builder, 
     TypeDescription typeDescription, ClassLoader classLoader) { 
    return builder.method(
     ElementMatchers.named("setAccessible") 
      .and(ElementMatchers.takesArguments(boolean.class))) 
     .intercept(MethodDelegation.to(
      bootstrapTypePool.describe(
       "com.leacox.sandbox.security.stub.java.lang.reflect.AccessibleObjectStub") 
       .resolve()) 
      .andThen(MethodCall.invoke(setAccessible0Method).withThis().withAllArguments())); 
    } 
} 

Der letzte Schritt ist dann, den Byte Buddy Java Agent zu installieren und die Umwandlung durchzuführen. Das Jar, das die Stubs enthält, muss ebenfalls an den Bootstrap-Klassenpfad angehängt werden. Dies ist notwendig, da die Klasse AccessibleObject vom Bootstrap Loader geladen wird und somit auch alle Stubs geladen werden müssen.

Instrumentation instrumentation = ByteBuddyAgent.install(); 
// Append the jar containing the stub replacement to the bootstrap classpath 
instrumentation.appendToBootstrapClassLoaderSearch(jarFile); 

AgentBuilder agentBuilder = new AgentBuilder.Default() 
     .disableClassFormatChanges() 
     .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION) 
     .ignore(none()); // disable default ignores so we can transform Java classes 
     .type(ElementMatchers.named("java.lang.reflect.AccessibleObject")) 
     .transform(transformer) 
     .installOnByteBuddyAgent(); 

Dies funktioniert, wenn ein Securitymanager verwenden und sowohl die Stubs Klassen und den Code zu isolieren, dass Sie die selektiven Berechtigungen in separaten Gläsern anwenden, die zur Laufzeit geladen werden. Die Jars zur Laufzeit zu laden, anstatt sie als Standard-Abhängigkeiten oder gebündelte Bibliotheken zu verwenden, verkompliziert die Dinge ein wenig, aber dies scheint eine Voraussetzung für die Isolierung von nicht vertrauenswürdigem Code zu sein, wenn SecurityManager verwendet wird.

Mein Github Repo sandbox-runtime hat ein vollständiges, detailliertes Beispiel einer Sandbox-Laufzeitumgebung mit Ausführung von isoliertem nicht vertrauenswürdigem Code und selektiveren Reflektionsberechtigungen. Ich habe auch einen Blog-Post mit mehr Details nur über die selective setAccessible permissions Stücke.

Verwandte Themen