This is possible using byte code weaving with a library like Byte Buddy. Instead of using the standard ReflectPermission("suppressAccessChecks")
permission, you can create a custom permission and replace the AccessibleObject.setAccessible
methods with custom methods that check your custom permission using Byte Buddy transformations.
One possible way for this custom permission to work is for it to base access on the classloader of the caller and the object that access is being modified on. Using this allows isolated code (code loaded from a separate with it's own class loader) to call setAccessible
on classes in its own jar, but not Standard Java classes or your own application classes.
Such a permission might look like:
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 "";
}
}
This is how I chose to implement this permission, but it could instead be a package or class whitelist or blacklist.
Now with this permission you can create a stub class that will replace the AccessibleObject.setAcessible
method to instead use this permission.
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);
}
}
With these two classes in place you can now use Byte Buddy to build a transformer for transforming the Java AccessibleObject
to use your stub.
The first step to create the transformer is to create a Byte Buddy type pool that includes the bootstrap classes and a jar file containing your stubs.
final TypePool bootstrapTypePool = TypePool.Default.of(
new ClassFileLocator.Compound(
new ClassFileLocator.ForJarFile(jarFile),
ClassFileLocator.ForClassLoader.of(null)));
Next use reflections to get a reference to the AccessObject.setAccessible0
method. This is a private method that actually modifies the accessibility if the call to setAccessible
passes permission checks.
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);
}
With these two pieces the transformer can be built.
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()));
}
}
The final step is to then install the Byte Buddy Java agent and perform the transformation. The jar containing the stubs must also be appended to the bootstrap class path. This is necessary because the AccessibleObject
class will be loaded by the bootstrap loader, thus any stubs must be loaded there as well.
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();
This will work when using a SecurityManager and isolating both the stubs classes and the code that you are applying the selective permissions to in separate jars that are loaded at runtime. Having to load the jars at runtime rather than having them as standard dependencies or bundled libs complicates things a bit, but this seems to be a requirement for isolating untrusted code when using the SecurityManager
.
My Github repo sandbox-runtime has a full, in-depth, example of a sandboxed runtime environment with execution of isolated untrusted code and more selective reflection permissions. I also have a blog post with more detail on just the selective setAccessible permissions pieces.