TabooLib / taboolib

Powerful framework for creating multi-platform Minecraft plugin

Home Page:https://tabooproject.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[讨论] 如何“摆脱”隔离,自由地访问和使用“沙盒中的类”

TheFloodDragon opened this issue · comments

前言

沙盒限制

一旦开启沙盒模式,使用IsolatedClassLoader加载的所有类均不可以被其他插件访问,这也正是“沙盒”和“隔离”的意思。沙盒内部的类可以访问沙盒外部的类,而沙盒外部的类却无法访问沙盒内部的类。

以上内容节选自相关链接[1]:注意事项,由Sunshine_wzy等人写的

在一些特殊场景下,开发者不得不使用沙盒模式,但同时其限制又成了一个困扰点

虽然说可以通过SPI开放部分类,但其实现(相关链接[1]:优秀实践)却并不简单

这也就意味着,其所能提供的API终是有限的

因此,衍生出这样一个话题:如何“摆脱”隔离,自由地访问和使用“沙盒中的类” (不使用SPI)

论方法

目前,在我的理解范围之内,有以下几种方法:

通过IsolatedClassLoader获取类并反射调用

指直接通过IsolatedClassLoader#loadClass函数获取目标类

然后通过反射执行调用其方法获取和修改其字段等等操作

如: IsolatedClassLoader.loadClass("taboolib.common.PrimitiveLoader").getProperty("TABOOLIB_GROUP", isStatic=true)

面对小范围的沙盒类使用,此方法是可行的,也是较优选择

实现自定义类加载器,适应性提供类托管其他IsolatedClassLoder

原理: 通过自定义类加载器,根据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,导致报错

commented

我操,小作文

commented

一个问题

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,导致报错

坏黑的逆天逻辑

commented

使用沙盒本就是为了避免版本冲突的问题,如果还能直接访问岂不是变成弱智

commented

除非像 nms proxy 那种操作,实现一种针对其他 tb 插件的访问模式

使用沙盒本就是为了避免版本冲突的问题,如果还能直接访问岂不是变成弱智

这个就是ClassLoaderProvider的用处了,对包名进行过滤处理,仅通过插件包下的类,毕竟不同版本的库总是要区分的

实现一种针对其他 tb 插件的访问模式

上述方式其实都是。

无论是通过反射,CompatibleClassLoader托管其他Taboolib的IsolatedClassLoader,或是暴力修改parent(破坏逻辑)从而选择性开放部分类