南京大学 静态软件分析(static program analyzes)-- Soundness and Soundiness 学习笔记
一、Soundness and Soundiness
Soundness
所谓Soundness:分析能捕获程序所有可能的行为(无漏报)
- 学术界:几乎所有发表的全程序分析研究都是unsound的
- 工业界:几乎所有的全程序分析工具都是unsound的
这是由于很多语言拥有的部分高级特性对于静态分析来说是难以分析的:
- Java
- Reflection
- native code
- dynamic class loading, etc.
- JavaScript
- eval
- document object model (DOM), etc.
- C/C++
- Pointer arithmetic
- function pointers, etc.
这导致了现有的研究主要有以下“规避”的现象:
- 声称自己的研究是sound的,但其实并不sound
- 直接省略了sound部分(直接不说soundness部分,或者说不处理这部分了)
- 不处理一些Hard Language Feature,比如说如果不处理Java反射,则会给分析结果带来巨大影响
这些现象会误导学术界专家或者工业界工程师对研究方法的评价:
- For non-experts, they may erroneously conclude that the analysis is sound and confidently rely on the analysis results
- For experts, it is still hard for them to interpret the analysis results(how sound, fast, precise is the analysis)without a clear explanation about how they treat those important and hard language features
例如对C/C++中的一个指针p,加上某个经过运算的偏移量x,为了分析的安全,只能假设p+x可以指向内存中的任何一个地方。
为了解决这一问题,专家们提出一个新的概念Soundiness,对应的形容词是Soundy。
Soundiness
从「理想情况」、「现实情况」、到「过于现实的情况」,它们的比较如下:
- 完全理想情况:A sound analysis requires to capture all dynamic behaviors
- 现实情况:A soundy analysis aims to capture all dynamic behaviors with certain hard language features unsoundly handled within reason
- 过于现实的情况:An unsound analysis deliberately ignores certain behaviors in its design for better efficiency, precision or accessibility
二、Hard Language Feature: Java Reflection
Java Reflection
什么是Java反射机制?
- Java反射机制使Java能够在运行时动态获得具体的类,而不用提前在代码中写死,增加了代码的灵活性;为了使程序更容易维护,降低耦合,大型Java程序一般大量使用反射机制。(耦合(Coupling)表示两个子系统(或类)之间的关联程度,当一个子系统(或类)发生变化时对另一个子系统(或类)的影响很小,则称它们是低耦合的)。
- Java反射机制关键在于运行时。
下面举一个例子,
首先,右上角Run-time代码块中的前三行的Class、Method和Field都是Metaobject。然后就能够以Metaobject执行一系列操作,如c.newInstance()就相当于左下角的new Person()。
为什么静态分析难以处理Java反射机制?
- 对于使用反射机制的代码,静态分析时不能直接确定具体的类,从而无法进行具体的方法Dispatch、Field分配等操作,导致静态分析无法进行。比如在上图中,静态分析难以确定c的类型(假设“Person”是动态获得的,比如用户输入),此时就无法对与其相关的语句进行静态分析。
- 这个问题已经是一个“公开难”的问题了。
Why We Need to Analyze Java Reflection
- 可能会错失检测出某些bug的机会(忽略
m.invoke
所引入的调用时发生) - 可能导致分析的结果不安全(忽略
f.set(a,a)
的作用时发生,将会错误地认为箭头指向处的cast可以优化掉)。
How to Analyze Java Reflection?
方法1:String Contant analysis + Pointer Analysis(Reflection Analysis for Java——APLAS 2005)
- 通过String Contant analysis能够得到反射时使用的常量字符串的类、调用的方法、以及域,从而进行静态分析。
- 但是这种方法只能处理静态常量字符串,对于在外部动态获取的字符串(比如配置文件中、互联网上获取、命令行输入等情况)无能为力
方法2:Type Inference + String analysis + Pointer Analysis(Self-Inferencing Reflection Resolution for Java——ECOOP 2014)
- 主要思想:“生时不可知,用时亦可推”,虽然反射类New时不知道具体的类别,但在调用其方法或者使用其Field时,可以根据Java中的类型限制(Java中的变量是有严格的类型限制的,可以通过指针分析找到某个变量的创建处的类型)来寻找和猜测满足条件的类。
- 比如,在上图中,在“使用点”175行,可以得知调用了反射对象的方法中,其参数为“parameters”变量,根据指针分析结果,可以得知该变量创建处(155行)表名了该变量类型即“this”(FrameworkCommandInterpreter类)或其子类,根据这个信息在所有方法中去寻找,能够找出符合条件的类,对这些类进行遍历即可完成反射类的分析。结果推断出了50个反射目标函数,48个为true。
方法3:较新的工作:Understanding and Analyzing Java Reflection (TOSEM 2019)
不仅求解的反射对象更准确更多,而且能知道哪里解的不准。
方法4:Taming reflection: Aiding static analysis in the presence of reflection and custom class loaders (ICSE 2011)
通过输入测试用例,执行动态执行得到相应的反射类。缺点是测试用例的覆盖路径有限,优点是解出的结果都为真。
三、Hard Language Feature: Native Code
Native Code
在Java中,一句简单的println
最后会调用与底层平台相关的C或C++代码,这些代码就称为Native Code。Java也借此实现了 Run anywhere
的目标。
Java Native Interface(JNI):是Java提供的本地代码的实现接口,使得JVM中的Java程序可以调用本地应用或库,也可以被本地程序调用(本地程序一般是C、C++)。
Why Native Code is Hard
- 用C/C++按照标准写一个库并编译生成*.so文件
- 在Java中加载Native Library
- 在Java中声明Native Method
- 调用Native Method
举一个例子,
在示例中,用c实现了一个类创建的功能,并通过传入参数和传出参数实现了Java和C之间的数据交互(*env指针支持在Native代码中创建对象、访问域、调用Java中的方法等)。
但是,这里面存在的困难有:
- 类似的JNI可提供的操作有230种
- 跨语言,Java分析器无法分析C语言的代码
同时,Native Code中包含一些关键操作,会影响静态分析过程,如果忽略会使分析结果不准确甚至错误,由此引出紫框中的问题:如何分析Native Call?
How to Analyzes Native Code
Current Practice & More
采用方法:对重要的Native Code手动建模,即使用Java代码实现相应的Native代码的功能,从而代替Native代码进行分析。
比如对于上图中Java常用的数组复制功能,采用Java直接实现该功能,从而完成Native代码的分析。
- 先将其副作用用Java代码表示出来(图中第一个方框)
- 然后用指针分析的方法进一步抽象其副作用(图中第二个方框)
Identifying Java Calls in Native Code via Binary Scanning(ISSTA 2020):通过对二进制进行扫描来识别Java中的Native调用
其他前沿研究