Есть ли способ для SecurityManager в Java для выборочного предоставления ReflectPermission («suppressAccessChecks»)? - PullRequest
7 голосов
/ 23 февраля 2010

Есть ли способ для SecurityManager в Java выборочно предоставлять ReflectPermission («suppressAccessChecks») в зависимости от деталей того, что вызывается setAccessible ()? Я не вижу способа сделать это.

Для некоторого изолированного кода было бы очень полезно (например, для запуска различных динамических языков JVM) разрешить вызов API отражения setAccessible (), но только при вызове setAccessible () метод / поле класса, который происходит из изолированного кода.

Есть ли у кого-нибудь альтернативные предложения, кроме выборочного предоставления ReflectPermission ("suppressAccessChecks"), если это невозможно? Возможно, было бы безопасно предоставить во всех случаях, если SecurityManager.checkMemberAccess () достаточно ограничительный?

Ответы [ 3 ]

10 голосов
/ 05 февраля 2011

Может, вам будет достаточно посмотреть на стек вызовов? Что-то вроде:

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);
    }
}
3 голосов
/ 20 июля 2016

Это возможно, используя переплетение байтового кода с такой библиотекой, как Byte Buddy . Вместо использования стандартного разрешения ReflectPermission("suppressAccessChecks") вы можете создать собственное разрешение и заменить методы AccessibleObject.setAccessible пользовательскими методами, которые проверяют ваше специальное разрешение с помощью преобразований Byte Buddy.

Один из возможных способов работы этого настраиваемого разрешения состоит в том, чтобы оно основывало доступ на загрузчике классов вызывающей стороны и объекте, доступ к которому изменяется. Использование этого позволяет изолированному коду (коду, загруженному из отдельного приложения со своим собственным загрузчиком классов) вызывать setAccessible для классов в своем собственном jar-файле, но не для стандартных классов Java или собственных классов приложений.

Такое разрешение может выглядеть так:

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 "";
  }
}

Вот как я решил реализовать это разрешение, но вместо этого он может быть белым или черным списком пакета или класса.

Теперь с этим разрешением вы можете создать класс-заглушку, который заменит метод AccessibleObject.setAcessible, чтобы вместо этого использовать это разрешение.

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);
  }
}

С этими двумя классами теперь вы можете использовать Byte Buddy для создания преобразователя для преобразования Java AccessibleObject в вашу заглушку.

Первым шагом для создания преобразователя является создание пула типов Byte Buddy, который включает в себя классы начальной загрузки и файл jar, содержащий ваши заглушки.

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

Далее используйте отражения, чтобы получить ссылку на метод AccessObject.setAccessible0. Это частный метод, который фактически изменяет доступность, если вызов setAccessible проходит проверку прав доступа.

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);
}

Из этих двух частей можно построить трансформатор.

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()));
  }
}

Последний шаг - установить Java-агент Byte Buddy и выполнить преобразование. Кувшин, содержащий заглушки, также должен быть добавлен в путь к классу начальной загрузки. Это необходимо, потому что класс AccessibleObject будет загружен загрузчиком начальной загрузки, поэтому любые заглушки также должны быть загружены туда.

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();

Это будет работать при использовании SecurityManager и изоляции классов-заглушек и кода, к которому вы применяете избирательные разрешения, в отдельных jar-файлах, загружаемых во время выполнения. Необходимость загружать файлы jar во время выполнения вместо того, чтобы иметь их в качестве стандартных зависимостей или связанных библиотек, несколько усложняет ситуацию, но, похоже, это является требованием для изоляции ненадежного кода при использовании SecurityManager.

My Github repo sandbox-runtime содержит полный подробный пример среды выполнения в песочнице с выполнением изолированного ненадежного кода и более избирательными разрешениями на отражение. У меня также есть пост в блоге с более подробной информацией о выборочном набореДоступные разрешения штук.

0 голосов
/ 23 февраля 2010

FWI: Так как setAccessible, кажется, имеет только допустимый вариант использования с сериализацией, я думаю, что вам часто может сойти с рук простое отрицание этого.

Тем не менее, меня интересует, как вообще можно делать подобные вещи, потому что мне тоже нужно написать менеджер безопасности, чтобы блокировать динамически загружаемый код от действий, которые должен выполнять код нашего контейнера приложения.

...