これは、 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 アクセス許可の部分だけについて詳しく説明したブログ投稿もあります。