Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way for a SecurityManager in java to selectively grant ReflectPermission("suppressAccessChecks")?

Is there any way for a SecurityManager in Java to selectively grant ReflectPermission("suppressAccessChecks") depending on the details of what setAccessible() is being called on? I don't see any way for this to be done.

For some sandboxed code, it would be very useful (such as for running various dynamic JVM languages) to allow the setAccessible() reflection API to be called, but only when setAccessible() is called on a method/field of a class that originates in the sandboxed code.

Does anyone have any alternative suggestions other than selective granting of ReflectPermission("suppressAccessChecks") if this isn't possible? Perhaps it would be safe to grant in all cases if SecurityManager.checkMemberAccess() is sufficiently restrictive?

like image 840
Alex Schultz Avatar asked Feb 22 '10 23:02

Alex Schultz


2 Answers

Maybe looking at the call stack would be enough for your purposes? Something like:

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);
    }
}
like image 149
Jesse Merriman Avatar answered Nov 18 '22 00:11

Jesse Merriman


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.

like image 32
johnlcox Avatar answered Nov 18 '22 01:11

johnlcox