前段时间写个东西遇到个情况.
大致是java中,class A extends B的话.
原则上来说A isinstanceosf B应该是返回true的.
类似的isassinablefrom应该也是返回true的.
而实际上的结果却是不预期.
这是个比较有趣的设计.
因为在jvm的实现当中,class的namespace其实是以classloader和class name共同定义的.
类似与一个classloader::class_name的定义.
实现上是klass的lookup是在symboltable的dictionary中通过hash(class_name) & classloader_data定位的.
所以这里实际上是隐式的classloader的naming isolation问题.
从隔离角度来说,这么做也不算难理解.
而且在这种情况下来说,是可以做一些比较tricky的保护机制的.
比如一些受限的class的访问.
在classloader的隔离机制下,即使能够想办法初始化出一些forbidden的类.
然后在调用的时候还是可以通过类型检查拒绝掉一部分的.
但是这样就带来一个问题.
从语义上来说,A extends B是不带有这种隔离信息暗示的.
当A B由不同的classloader定义,或者是不同的classloader环境的时候,类型系统就比较混乱了.
因为字面上的qualified name并没有完全reveal出namespace的区别.
这样的话,在写程序的时候就有比较大的心智负担.
尤其当涉及不同的classloader的instane之间做交互的时候.
而这里又有另外一个诡异的情况.
考虑来自不同classloader的A和B,且名义上A extends B.
那么,如果做cast肯定是有问题的.
但是如果做method invoke/function call的时候,又可能是没问题的.
因为在运行时或者说bytecode层面是没有type check的.
而如果这两个不同classloader的关于B的bytecode是兼容的,或者说涉及到的signature是相同的话,是没问题的.
因为算出来的dispatch table/offset是一致的.
但是如果是不兼容的,自然就又会导致运行时错误.
而顺着这个思路,对于一些restrict的环境,如果能够构造跟protected的class兼容的dispatch table的话.
理论上来说就是绕过这些限制.
比如一些加密场景.
即使密钥是安全可靠的话,也可能可以通过替换的方式绕过校验.
尤其对于校验接口是传入non primitive类型的情况.
因为这种情况下,instance的method invoke也不算是reliable的.
反过来说,如果能够完全控制某些上下游的所有涉及instance的classloader情况的话.
那至少从语法/语义层面上不存在明显的漏洞.
但这是一个相对难覆盖的问题.
因为如果能够约束class以及其所属的classloader的情况的话,也就意味这runtime是一种可追踪的情况.
如若不是的话,则无法保证某个时间点不回存在classloader泄露/逃逸的情况.
所以,在这种情况下,其实本身就已经是又一个sandbox机制去保证audit了.
有没有classloader其实关系不大.
那么为什么要设计这么一种问题比较明显的机制呢?
一种可能就是为了保证builtin class的integrity.
毕竟有个bootstrap/system classloader.
但不提供这个机制的话,也并不是不能保证完整性.
因为问题的关键不是谁load,而是load的是什么.
核心是是什么怎么定义.
类似go的method receiver和protocol的定义就比较好.
实际上就是脱离了类型这种比较古板的定义.
而是通过具有什么行为来定义区别同类.
实际上,抛开类型检查的语法和一些编译期的检查之外,运行时也基本上就是这么做的.
如果去掉这种类型系统的话,代价就是类似动态类型语言,缺少编译期的约束提醒.
以及可能对java生态更重要的类型系统的可追溯性.
这个是很多IDE和工具链所依赖的.
而如果去掉classloader的绑定的话,又为一些malicious的应用提供了一些方便.
或者从纯粹语言的角度来说,这就意味着名义上同一类型的instance,可能有着不同的行为.
而如果从contract的层面去约束class定义的话,那就意味着对于同名的class具有不同行为的场景予以拒绝.
所以,允许这种怪异行为的原因是为了使这种场景合理化么?
大概是类似于jsp这种需要code hot reload的场景.
于是一个可能比较安全的做法就是尽量去做类型的cast和从属关系判断.
非要这么做的话,可能就是需要自己做字面的递归回溯比较了.
这样至少是遵从字面语意的.
没有评论:
发表评论