taboolib
taboolib copied to clipboard
[讨论] 如何“摆脱”隔离,自由地访问和使用“沙盒中的类”
前言
沙盒限制
一旦开启沙盒模式,使用IsolatedClassLoader加载的所有类均不可以被其他插件访问,这也正是“沙盒”和“隔离”的意思。沙盒内部的类可以访问沙盒外部的类,而沙盒外部的类却无法访问沙盒内部的类。
以上内容节选自相关链接[1]:注意事项,由Sunshine_wzy等人写的
在一些特殊场景下,开发者不得不使用沙盒模式,但同时其限制又成了一个困扰点
虽然说可以通过SPI开放部分类,但其实现(相关链接[1]:优秀实践)却并不简单
这也就意味着,其所能提供的API终是有限的
因此,衍生出这样一个话题:如何“摆脱”隔离,自由地访问和使用“沙盒中的类” (不使用SPI)
论方法
目前,在我的理解范围之内,有以下几种方法:
通过IsolatedClassLoader获取类并反射调用
指直接通过IsolatedClassLoader#loadClass函数获取目标类
然后通过反射执行调用其方法、获取和修改其字段等等操作
如: IsolatedClassLoader.loadClass("taboolib.common.PrimitiveLoader").getProperty("TABOOLIB_GROUP", isStatic=true)
面对小范围的沙盒类使用,此方法是可行的,也是较优选择
实现自定义类加载器,适应性提供类
原理: 通过自定义类加载器,根据ClassLoaderProvider(也可以是ClassProvider)获取想要获得的类
其主要包括: 由ClassLoaderProvider通过全限类定名提供类加载器,由CompatibleClassLoader通过selfLoad加载特定类(受委托的类),来使用ClassLoaderProvider所能提供的类
限制: 还是那样,受委托的类可以调用“沙盒中的类”的什么什么,却无法向外部提供“沙盒中的类”
因此,对于事件的注册,请使用目标插件的InternalEvent
好处: 简化反射流程,可以直接调用沙盒类的东西,可以根据需求决定能访问到的类
类加载器示例:
class ClassLoaderProvider(
val function: Function<String, ClassLoader?>
) : Function<String, ClassLoader?> {
override fun apply(t: String) = function.apply(t)
}
class CompatibleClassLoader(urls: Array<URL>, parent: ClassLoader) : URLClassLoader(urls, parent) {
constructor(clazz: Class<*>, parent: ClassLoader) : this(arrayOf<URL>(clazz.protectionDomain.codeSource.location), parent)
/**
* key - 优先级
* value - 同一优先级下的 [ClassLoaderProvider]
*/
val providers: NavigableMap<Byte, MutableList<ClassLoaderProvider>> = Collections.synchronizedNavigableMap(TreeMap())
/**
* 添加 [ClassLoaderProvider]
* @param provider 类加载器提供者 [ClassLoaderProvider]
* @param priority [provider] 的优先级
*/
fun addProvider(provider: ClassLoaderProvider, priority: Byte = 0) = providers.computeIfAbsent(priority) { mutableListOf() }.add(provider)
/**
* 删除 [ClassLoaderProvider]
*/
fun removeProvider(provider: ClassLoaderProvider) = providers.values.forEach { it.remove(provider) }
/**
* 自主加载条件
*/
var selfLoadCondition = java.util.function.Function<String, Boolean> { false }
override fun loadClass(name: String) = loadClass(name, false)
override fun loadClass(name: String, resolve: Boolean) = loadClass(name, resolve, false)
internal fun loadClass(name: String, resolve: Boolean, forceSelfLoad: Boolean): Class<*> = synchronized(getClassLoadingLock(name)) {
// 自身寻找加载过的类
var c = findLoadedClass(name)
// 未被加载过
if (c == null) {
// 优先加载可被自身加载的类
if (selfLoadCondition.apply(name) || forceSelfLoad) {
c = kotlin.runCatching { findClass(name) }.getOrNull()
} else {
// 尝试通过注册的ClassLoaderProvider加载 (有优先级)
providers.forEach { entry ->
for (loader in entry.value) {
// 由[ClassLoaderProvider], 提供全限类定名([name])以使它动态智能分配 [ClassLoader]
c = loader.apply(name)?.loadClassOrNull(name)
// 直到类能被加载为止
if (c != null) break
}
}
}
// 尝试让父类加载器加载, 无法时加载抛出异常
if (c == null) c = parent.loadClassOrNull(name) ?: throw ClassNotFoundException()
// 连接类
if (resolve) this.resolveClass(c)
}
return c
}
}
使用方法:
val loader = CompatibleClassLoader(IsolatedClassLoader.parent) // parent 根据实际情况而定
// 添加 ClassLoaderProvider
loader.addProvider(0, ClassLoaderProvider { name ->
if (name.startsWith("me.arasple.mc.trchat"))
me.arasple.mc.trchat.taboolib.common.classloader.IsolatedClassLoader.INSTANCE
else null
})
// 加载并执行run方法 (其受委托的类必须selfLoad)
loader.load("org.example.ManagedClass", false, true).invokeMethod("run")
// 受委托的类
@Ghost
object ManagedClass {
fun run() {
println("[R] TrChat | Test Hook")
println(this::class.java.classLoader)
println(me.arasple.mc.trchat.taboolib.common.PrimitiveLoader.TABOOLIB_GROUP)
}
}
侵入式破坏加载顺序 (设想)
在观察Bukkit、Velocity、Bungee的PluginClassLoader后,发现一个规律: 在加载类时,都会首先使用super.loadClass(name, resolve)
, 走“双亲委派”那一套?
所以,是不是可以在这做文章,修改parent
,破坏其加载顺序?
其他
待补充。。。
相关链接
ps: 我菜,各位大佬有什么想法尽管提!
一个问题
RuntimeEnv:
// 在非隔离模式下检查 Kotlin 环境
if (!PrimitiveSettings.IS_ISOLATED_MODE) {
// 加载 Kotlin 环境
if (!KOTLIN_VERSION.equals("null") && !TabooLib.isKotlinEnvironment()) {
ENV.loadDependency("org.jetbrains.kotlin:kotlin-stdlib:" + KOTLIN_VERSION, rel);
}
// 加载 Kotlin Coroutines 环境
if (!KOTLIN_COROUTINES_VERSION.equals("null") && !TabooLib.isKotlinCoroutinesEnvironment()) {
ENV.loadDependency("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:" + KOTLIN_COROUTINES_VERSION, false, rel);
}
}
其中仅仅在非隔离模式下加载Kotlin环境,开启隔离模式后貌似就无法使用Kotlin,导致报错
我操,小作文
一个问题
RuntimeEnv:
// 在非隔离模式下检查 Kotlin 环境 if (!PrimitiveSettings.IS_ISOLATED_MODE) { // 加载 Kotlin 环境 if (!KOTLIN_VERSION.equals("null") && !TabooLib.isKotlinEnvironment()) { ENV.loadDependency("org.jetbrains.kotlin:kotlin-stdlib:" + KOTLIN_VERSION, rel); } // 加载 Kotlin Coroutines 环境 if (!KOTLIN_COROUTINES_VERSION.equals("null") && !TabooLib.isKotlinCoroutinesEnvironment()) { ENV.loadDependency("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:" + KOTLIN_COROUTINES_VERSION, false, rel); } }
其中仅仅在非隔离模式下加载Kotlin环境,开启隔离模式后貌似就无法使用Kotlin,导致报错
坏黑的逆天逻辑
使用沙盒本就是为了避免版本冲突的问题,如果还能直接访问岂不是变成弱智
除非像 nms proxy 那种操作,实现一种针对其他 tb 插件的访问模式
使用沙盒本就是为了避免版本冲突的问题,如果还能直接访问岂不是变成弱智
这个就是ClassLoaderProvider
的用处了,对包名进行过滤处理,仅通过插件包下的类,毕竟不同版本的库总是要区分的
实现一种针对其他 tb 插件的访问模式
上述方式其实都是。
无论是通过反射,CompatibleClassLoader托管其他Taboolib的IsolatedClassLoader,或是暴力修改parent(破坏逻辑)从而选择性开放部分类