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.
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. –
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. –