8

setAccessible() が呼び出されているものの詳細に応じて、Java の SecurityManager が ReflectPermission("suppressAccessChecks") を選択的に付与する方法はありますか? これを行う方法がわかりません。

一部のサンドボックス化されたコードでは、setAccessible() リフレクション API の呼び出しを許可すると非常に便利です (さまざまな動的 JVM 言語の実行など) 。サンドボックス化されたコードで。

これが不可能な場合、ReflectPermission("suppressAccessChecks") を選択的に付与する以外の代替提案はありますか? SecurityManager.checkMemberAccess() が十分に制限されている場合、すべての場合に許可しても安全でしょうか?

4

3 に答える 3

12

コール スタックを見るだけで目的が達成できるのではないでしょうか。何かのようなもの:

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);
    }
}
于 2011-02-05T20:49:21.463 に答える
3

これは、 Byte Buddyのようなライブラリを使用したバイト コード ウィービングを使用して可能です。標準のアクセス許可を使用する代わりに、カスタム アクセス許可を作成し、 Byte Buddy 変換を使用してカスタム アクセス許可をチェックするカスタム メソッドReflectPermission("suppressAccessChecks")にメソッドを置き換えることができます。AccessibleObject.setAccessible

このカスタム許可が機能する 1 つの考えられる方法は、呼び出し元のクラスローダーとアクセスが変更されているオブジェクトに基づいてアクセスすることです。これを使用すると、分離されたコード (独自のクラス ローダーを使用して個別にロードされたコード) が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);
  }
}

AccessibleObjectこれら 2 つのクラスを配置すると、Byte Buddy を使用して、スタブを使用するように Java を変換するためのトランスフォーマーを構築できます。

トランスフォーマーを作成する最初のステップは、ブートストラップ クラスを含む 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);
}

この2つの部品で変圧器を作ることができます。

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

最後のステップは、Byte Buddy Java エージェントをインストールして変換を実行することです。スタブを含む jar も、ブートストラップ クラス パスに追加する必要があります。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.

私の Github リポジトリsandbox-runtimeには、分離された信頼できないコードとより選択的なリフレクション権限を実行する、サンドボックス化されたランタイム環境の完全で詳細な例があります。選択的な setAccessible アクセス許可の部分だけについて詳しく説明したブログ投稿もあります。

于 2016-07-20T04:30:20.803 に答える
0

FWI:setAccessibleにはシリアル化の有効なユースケースしかないように思われるので、それを完全に否定するだけでうまくいくことがよくあると思います。

とは言うものの、私もこの種のことを一般的にどのように行うかに興味があります。なぜなら、動的にロードされたコードがアプリケーションコンテナコードで実行できる必要があることをブロックするセキュリティマネージャを作成する必要があるからです。

于 2010-02-22T23:57:22.057 に答える