软件分析笔记

软件分析笔记

50实验,50理论考试(英文考试,作答中英都可以)

class-1

网课(b站)听过了就可以不用听了

主讲的是静态分析,static program analysis

Programming Language,简称PL,在liyue看来可以分为三个部分

  • 理论:语法,语义,…
  • 环境:编译器,运行环境,…
  • 应用:程序分析,程序验证,程序合成,…

虽然语言很多,但是从2000年开始,语言的核心,就没有变过了(这意味着理论和环境变化很少,变化多的是应用)

语言分为三种范式

  • 命令式语言:有时序,和状态修改
  • 函数式编程语言:弱化了时序和状态修改。函数作为变量,表达式求值,lambda演算
    • 由于弱化了时序,很难出现并发错误,多用于金融系统
  • 逻辑式编程语言:mysql,datalog等,基于谓词演算逻辑,主要用于处理大数据

静态分析能干哪些事?

  • 程序可靠性:在运行前(称为静态期,编译期)检测出空指针引用,内存泄漏等
  • 程序安全:隐私保护,注入攻击等
  • 编译优化:死代码消除,代码转移等
  • 程序理解:IDE的类调用图,类型提示

不要浮躁

认识自己

重拾自信


Rice’s Theorem:recursively&enumerable的语言(re语言,是图灵机可识别的语言)的non-trival properties(理解为运行时行为)不存在完美的(sound&complete)程序分析

解释一下,程序有5个bug,sound就是我说这程序有10个bug,保证5个都包含,即误报。complete就是我说这程序有2个bug,这俩都在那5个里,即漏报。

image-20210902194052147

现实世界里,大部分程序分析都在妥协sound,即允许误报,不允许漏报。

image-20210902194924758

1和2的分析都是sound的,正确的。

静态分析:在保证(接近)soundness时,在精度和开销大之间做平衡

静态分析的大框架:分为两步走

  1. 抽象 abstraction
  2. 过近似 over-approximation
    • transfer function
    • control flows

以“预测一个程序中所有变量的正负性”这个目标展开的静态分析为例:

abstraction: 给每个变量定义正,负,0,不确定,未定义这5种类型

transfer function:规定正+正=正,正+负=不确定等

control flow:将控制流图里的汇聚点找出,确定每个汇聚点的变量是什么符号


image-20210902201500591

实验任务安排如上


本节课的重点

image-20210902202402069

TODO:回答上述问题

  1. 运行前vs运行时
  2. 那个圈圈图
  3. 误报是允许的,但漏报就会导致错误
  4. +-0,确定符号的例子回顾

class-2

讲中间表示 intermediate representation

4种文法分类(由强到弱,开销也由大到小):图灵机,上下文相关文法,上下文无关文法,正则表达式

image-20210909184655494

AST vs. 3AC

为什么静态分析不用AST,而是用三地址码IR呢?

AST

  • 比较high-level,近似语法结构
  • 和语言相关
  • 对type检查很快
  • 缺乏控制流信息

3-Address Code(3AC)

  • 比较low-level,接近机器码

  • 和语言无关

  • 缺乏类型信息,简单

  • 看得出来控制流信息

对java中的一些解释

image-20210909192405357

SSA

Static Single Assignment(SSA)静态单赋值

  • 一个变量只会有一个定义

为了解决分支造成的变量值不确定,引入了phi-function

SSA好处:流的信息进入变量名、def-use清晰

SSA坏处:引入很多变量和phi函数,生成机器码时候不高效

Control Flow Analysis

为了看的更清楚,把3AC画成CFG

CGF中的node都都是Basic Block(BB)

Basic Block定义

  • 最大的,满足如下条件的连续语句
    1. 只有一个入口
    2. 只有一个出口

Basic Block

BB切分算法:

入口(leader):

  1. 第一条指令
  2. 跳转语句的target
  3. 跳转语句的下一条指令

出口:不用管了,leader之间夹着就足以确定BB了

如何添加边(edge):

  1. A到B有跳转关系
  2. B在A下面顺序执行,且A的最后一句不是无条件goto

goto i号语句,可以改成goto Bi基本块,这样清晰且不用考虑语句i的编号增加

给CFG添加entry和exit

今日重点

image-20210909201216382

image-20210909201431894

需要掌握

  1. 为什么IR比AST更常用?IR更简单,语言无关,有控制流信息
  2. BB的定义,划分算法,如何添边
  3. CFG(并不是非要由BB组成)是怎么画出来的
  4. 开篇的那张关于词法分析,语法分析,语义分析,IR。。。的图

class-3

may analysis:分析的结果可能是对的(over-approximation),漏报会出问题

must analysis:分析的结果一定是对的(under-approximation)漏报没关系,顶多是少优化一点

image-20210916185913134

前向分析和后向分析

image-20210916190410368

前向分析时,OUT[B]是算出来的,IN[B]是B的前驱的OUT们简单meet出来的

后向分析时,反的

数据流分析不考虑:函数调用,别名

Reaching Definition

是forward analysis和may analysis

definition定义:

image-20211009175611191

用途:检测undefined variables(在程序entry处给所有变量一个undefine的定义,然后检测是否reaching到变量被使用的地方,如果reaching,就说明未定义就使用啦)

求reaching definition的迭代算法

image-20210916192909319

Q:为什么要区分entry和B\entry?因为这种迭代算法是一个模板,虽然在这个例子里都是初始化为空,但别的算法里未必。

Q:为什么初始化为空?想象在程序entry的地方,什么definition都没有。

Q:为什么迭代算法能停?

观察发现,gen和kill是不变的如果IN不变,那么OUT也不会变

OUT[S] = gen U (IN[S] - kill)

当更多的fact进入IN时,多出来的1有可能被kill,也可能保留下来

所以OUT永远不会shrink,只有0->1,1->1

而fact的次数是有限的,所以能停

Q:为什么OUT不再改变,算法就可以停了,结果就是精准的最后的结果呢?

这个和程序的单调性有关,达到不动点(后续讲)

class-4

Live Variables Analysis

image-20210923183616652

v在给定的程序点P之后一直到exit,是不是还有被用到,且路上没有再次被定义

他是一个backward analysis(后向更加简洁直接,不代表前向无法做到哈)

考虑定义,一个变量将来是否可能被用到,假如有多条路径,即使有的路我不跑过去,我也是全部考虑,所以是may analysis

给定OUT求IN,OUT很好做,OUT=U(In of successors),如何定义transfer function呢?

image-20210923185827091

注意情况4,看似v被改了,但是首先v先被v-1用到了,所以我们先去除redefine的,然后加上use的

。下面看算法

image-20210923190224858

以后讲为什么是初始化为空0000000(一般,may analysis都是空,must analysis都是1111111)

Available Expressions Analysis

是一个forward,must analysis

一个表达式x op y在给定程序点P是可用的,如果满足1,从entry到P的所有路径都要求值x op y,在最后一次x op y算完之后,不能对x和y进行重定义

有几个表达式就有几个0/1

怎么考虑gen和kill呢?

image-20210923193714904

kill的是IN里含有被redefine的表达式

gen的是BB里的x op y

image-20210923194332904

这是一个反直觉的例子,看起来x被改了,但是根据定义,e^16 * x确实在两次last计算后都没有改x,所以在c这一句之前的程序点,表达式是可用的

有人可能会问:哎,那我使用这个表达式的时候x的值在ab处不一样呀。如下,就行了

image-20210923194238722

merge怎么考虑呢?注意是一个must analysis,要用操作

image-20210923194357503

算法如下

image-20210923195108605

U代表1111111(TOP)

理论课会讲这和不动点有关,直观的想就是,因为用了“与”,如果初始化为0000,那没机会变1了

analysis comparison of the 3

image-20210923201830729

transfer function中,available exp之所以也可以这么写,是因为所谓的IN和OUT是可以反一反的

本课重点

image-20210923202036349

class-5

讲数据流分析的数学基础

迭代算法的遍历是在格上爬升?抵达不动点?单调性?可分配性?

事后复习可看ppt第8部分,一图流

换一种视角看迭代算法

image-20210930185604144

Xi = F(Xi),说明Xi是F的一个不动点

3个general的问题:

  • 迭代算法模板一定能停吗?

  • 不动点是唯一不动点吗?如果不是的话,我们的不动点是最好的结果吗?

  • 什么时候能抵达不动点?

回顾一下

离散背景

偏序集(<=代表一种二元关系,自定义)

  • 自反性:x <= x
  • 反对称性:x<=y ^ y<=x => x=y
  • 传递性:x <=y, y<=z => x<=z
image-20210930190823625

image-20210930191051791

偏序偏在哪?两个元素之间不一定可比

上界和下界

image-20210930191202099

注意,P是幂集,上界不一定要在S中,在大的P中即可,如下图

image-20210930191400711

下面定义最小上界,least upper bound,简称lub,或者join

最大下界,greatest lower bound,glb,或者meet

image-20210930191541385

image-20210930191706578

注意,当S只有两个元素是,join就像交,meet就像并

image-20210930191810665

一些重要性质:

不是每个偏序集都有lub或者glb

如果偏序集有lub和glb,那么一定是唯一的

格,lattice

如果偏序集poset,中的任意两个元素同时有lub和glb,那么就是格

example1,整数,<=,是的

example2,一些字符串,子串关系,不一定嗷

example3,abc的幂集,子集关系,是的

半格,semilattice

相当于格的一半,如果偏序集poset,中的任意两个元素

如果只有lub存在,就是join semilattice

如果只有glb存在,就是meet semilattice

全格,complete lattice

现在要求格的任意子集都存在lub和glb

image-20210930193224520

example1,整数,<=,不是,如果一个子集包含了所有正整数,它没有upper bound,但如果把正无穷符号引入,它就是了

example2,幂集,是的

下面引入全格的top和bottom概念,且一定存在

  • top就是P最大的上界

  • bottom是P最小的下界

一个性质

格如果是有限的,那他一定是全格

所有全格一定是有限格吗?不一定是的,例如0~1里的实数,无穷多个,但有top和bottom

product lattice

一堆格,假如每个格都有上下界

image-20210930194032125

image-20210930194103532

数据流分析和格

迭代式算法就是在格上面跑transfer function,meet,join

image-20210930194656140

但是呢,需要满足一些条件才能保证后面能停。

单调性

函数f: L->L,如果x<=y,那么f(x)<=f(y)

image-20210930195317451

不动点理论

假如f: L->L是单调的,全格L是有限的

那么最小不动点是可以从bottom一路ffff上去的

最大不动点是可以从top一路ffff上去的

证明分两步,一步是证明可以获得不动点,第二步证明是最小不动点

第一步:

核心:因为L是有限的,所以可以通过有限步(L的高度)的f应用于bottom

第二步如下:

image-20210930200739121

class-6 Data Flow Analysis - Foundations II

join meet monotonic证明

image-20211009173230567

由上节课的内容,我们知道了单调函数F可以让bottom达到最小不动点,top达到最大不动点,那么我们接下来要证明meet和join是单调的

image-20211009171839165

就是要证明当x<=y时,xuz <= yuz

证:

x <= y

y <= yuz

所以x <= yuz

又 z <= yuz

所以 yuz是x和z的upper bound

而xuz是x和z的the least upper bound

所以xuz <= yuz

由此证明了join函数是单调的

迭代算法何时收敛

接下来只剩一个问题,算法何时收敛?

定义格的高度h,假设CFG里节点一共有k个,最坏情况就是:每次迭代只有1个节点的1个位变化了,总共需要h*k次迭代

image-20211009173801057

may must analyses from lattice view

image-20211009180522909

这张图很直观

另一种角度解释达到的是最大和最小不动点:u和^操作都是最小上界/最大下界,所以迭代的每一个step都是最小的,所以能一个个step达到最小不动点/最大不动点

精度有多高

MOP概念:把每条路径的f组合起来

image-20211009192546501

迭代算法vsMOP

image-20211009192900963

先u再f —vs— 先f再u

证明F(xuy) <= F(x) u F(y) 熟悉的感觉

image-20211009193354604

当F是可分配的时候,我们的分析和MOP一样准

哪些是可分配的呢?例如前面提到的reaching definition, available expression, live variable,同时满足

  • bit-vector:问题转换成集合的交和并
  • gen/kill problem

哪些是不可分配的呢?

下面这个例子就是不可分配

constant propagation

image-20211009200657242

image-20211009201404648

这次的meet不是一个简单交并了

image-20211009202241303

otherwise其实只剩val(y),val(z)中有UNDEF的情况了

identity function:恒等函数,等于啥也不做

比如if,goto之类的语句

非分配的例子

image-20211009202745580

worklist algorithm

对迭代算法的一种优化:迭代算法如果一个节点变了一点就要重新算一遍所有的,存在冗余,所以我们改一改,只计算那些OUT变化了的节点(对前向分析来说)

OUT变化意味着后继节点的IN会改,然后后继节点的OUT也会改,所以需要把后继节点加入worklist

image-20211009203409123

重点

image-20211009203827282

class-7 Inteprocedual Analysis

过程内(intraprocedural)分析是按最保守的假设来的,不够准确

image-20211014194212217

在这个例子里,x=42,y=43是更精确滴

Call Graph

调用图的概念

image-20211014194349988

OOPLs的call graph有哪些应用

image-20211014194606638

Java的call有三种类型

  • static call
  • special call
  • virtual call

image-20211014195016141

SOOT中采用的格式,绿的是我们课内简写

image-20211014195313068

起作用的是方法名,参数类型

image-20211014195507872

相当于找实际调用的是哪个类的方法

Classs Hierarchy Analysis (CHA)

假设A a可以指向A类和它的子类,CHA就是找目标method

image-20211014200104973

怎么求解?分情况讨论

static call就是自己的foo就行了

注意special call不是只找父类就完事了,因为父类B里不一定有foo,可能是父类的父类

image-20211014200510811

virtual call需要去找c的声明类型C的所有子类(包括子类的子类)

image-20211014200801820

一个例子,注意B,B需要Dispatch(B), DIspatch(C), Dispatch(B),B本身没有foo,所以Dispatch(B)会找到父类A的foo

image-20211014201506737

即使B b = new B(),依然是ACD,因为CHA只看b的声明类型!不看实际指向。

可以发现这也导致了一个问题,C.foo()和D.foo()在下图的例子里是假的

image-20211014201716224

CHA的特点

pros:快,只看声明类型

cons:导致引入假方法

用途:IDE方法提示

call graph construction

从main开始

image-20211014202604005

T是一个目标方法的集合(之所以是集合,因为virtual call的存在,一对多)

image-20211014203144030

Interprocedural Control-Flow Graph (ICFG)

ICFG是所有method的CFG加上call edge和return edge

image-20211014205411790

image-20211014205434270

一个例子,里面黄色部分标注的边其实没有也可以呀,但是还是要有

image-20211014224154286

Interprocedural Data-Flow Analysis

主要处理多出来的两种边

image-20211014210546045

之所以要有b = addOne(a)到c=b-3的边(这样的边称为call-to-return edge),因为a=6这样的local dataflow如果跟着过程调用跑一遍会低效,如下图

image-20211014213717355

注意要kill b,这样才能和返回值merge成常量,不然就是NAC了,丢失精度

kill b的位置不是call site,而是call-to-return edge的transfer edge时kill。call site只做恒等变换

-插播TT的话

现在对于调用语句(如x = m(…);)的node transfer改为identity function(见87页),把kill LHS variable值的操作挪到了call-to-return edge的edge transfer当中(见100–103页对b = ten()的处理)。此外作为参考,105页总结了interprocedural constant propagation的node/edge transfer。

做出上述改动是为了可以更一致地处理不同类型的ICFG edges,同时简化interprocedural data-flow analysis solver的算法与实现。相关作业文档也会讲解改动后的算法,届时请同学们以最新课件以及作业文档为准。

image-20211014224445529

一个小总结

image-20211014224435597

重点

  • 怎么建立class hierarchy analysis?从main开始一个类似BFS
  • 过程间分析要处理哪些关键?与过程内分析有哪些区别?–过程间要处理call edge和return edge。比起过程内,过程间的结果更精确
  • 过程间的常量传播是怎么处理edge的?

image-20211014215007255

后续预告

image-20211014215157202

class-8 Pointer Analysis

五节课介绍他

image-20211021183247914

这个例子里CHA只考虑了class hierarchy,所以不精确

image-20211021183751766

指针分析的话,看n具体指向哪个对象

image-20211021183925280

Pointer Analysis

指针分析研究有一个pointer可以指向什么值

指针分析是may analysis,做保守的分析

即使过了40年之久,指针分析还是一个活跃的问题

image-20211021184522912

输出一个表格

variable和field都是指针,都要考虑

image-20211021185146606

当程序规模扩大的时候就问题来了,如何做的又快又准

指针分析和别名分析(alias analysis)

别名分析回答的是:两个指针是否指向同一个object

可以看出别名分析可以从指针分析中得到,指针分析更难

image-20211021185551451

指针分析应用

image-20211021185852463

德国的dagstuhl seminar每年举办研讨会,大佬都说指针分析最重要:)

哪些因素会影响指针分析的精度和效率

下面展开说

image-20211021190413127

heap abstraction

heap里可能new出无限个对象,循环停不下来,如果静态分析也跟着while跑,那分析也停不下来,所以需要做抽象。

抽象成有限个对象

image-20211021190637856

咋做抽象的呢?

一类是store based model,另一类是storeless

调用点抽象是最常用的

image-20211021190752797

allocation-site abstraction

循环里new三次,我们就用一个o2来抽象

程序里有几句new,就有几个o2,所以是有限的

image-20211021191039909

context sensitivity

一个method被不同地方调用时有不同的上下文

上下文敏感就是区分上下文,精度更高。不敏感就是凡是上下文全放一起,不区分,更快。

image-20211021191537505

上下文的概念在interaprocedural中都会用到,比如你跨函数的constant propagation,分开就是不同c1 c2,合一起就是NAC

flow sensitivity

流敏感:对于每个程序点(语句之类)都记录了这个点上的信息

流不敏感:不关心语句执行顺序,一锅端,要考虑所有顺序

流敏感要存的信息量很大,对于OO语言来说开销太大,比如java里,栈内存会自动销毁,堆内存只能靠垃圾回收,静态分析时候为了sound,就得全考虑进去,程序跑到后面就越来越大。1亿条的表格没法存了都

所以还是流不敏感能用

image-20211021194312939

analysis scope

全程序分析提供全部分析

需求驱动的只关心需求里的那些pointer,以及会影响这些pointer的东西(拔出萝卜带出泥,对复杂程序开销也不小,且只满足一种需求,万一多个客户,有冗余)

image-20211021194726188

哪些对象我们关心

程序里有很多复杂语句,但我们不关心,只关心影响pointer的语句

关心的对象有这些东西

static field类似全局变量,和local variable一起处理。

数组有很多下标,但在静态分析里我们只认为有一个array.arr,和instance field一样处理

image-20211021195437562

哪些语句我们关心

影响指针的语句

x.f.g.h这种可以打散成左边的五种

image-20211021200002506

focus on最难的virtual call

image-20211021200043002

重点

image-20211021200222820

别着急,还有一部分。。

class-8-2 Pointer Analysis Foundations

指针分析的domain

variable + instance field = Pointers

image-20211021200830016

rule是推导式,横线上面是前提,横线下面是结论

image-20211021200951206

image-20211021201418578

image-20211021201441970

image-20211021201638832

class-9

实现Pointer Analysis

课上的算法易于理解,重新设计过了,而且比较完备

指针分析是指向关系在指针之间传播

有的研究人员(andersen,安徒生)认为指针分析是在求解约束关系(Andersen-style analysis)

实现的关键是处理变化,传播这种变化

用图来帮助描述分析

Pointer Flow Graph(PFG)

node就是pointer

edge指的是流向关系,x->y说明x指向的对象may流到y去

为什么是may?pointer analysis是一个may analysis,多包含了一些信息

哪些边是may flow to呢?后面说

image-20211028184409571

例如

image-20211028185229994

例子

image-20211028185701761

如果新加一句b=new T(),pt(b) = {oj}这件事会流到e

image-20211028185730665

实现PA难得地方在于,建立PFG,和传播指针信息是相互依赖的

所以PA不是单纯的传递闭包问题

1依赖2体现在,首先得知道c指向oi,然后才能得出a->oi.f这些边

image-20211028190255073

PA Algorithm

算法如下,solve是main函数

image-20211028190703475

为啥需要第3条呢?假如s->t这条边已经建立了,那么当s的指针集变化时,s的指针集会传给t,没有问题,但假如s->t还没有建立,没有第3条的话,t指向的对象就少了

image-20211028192039251

从WL里取出一个指针和指针集

差集delta,减少冗余计算,这样做不会漏传,因为pt(n)中含有的东西已经/在之后肯定会传播出去

image-20211028193914335

传播

image-20211028194204186

下面处理field的问题

为啥是may引入新边呢?

比如有个x2.f = y先让y指向oi.f了,那就不用有新边了

image-20211028195824894

一个例子,看ppt走一遍

重点

image-20211028201529968

class-10

Pointer Analysis with Method Calls

过程间分析的时候,需要知道参数a指向了哪些对象,就是需要建立Call Graph,但是这个call graph的建立也依赖a

所以,指针分析和call graph的建立是互相依赖的

image-20211104184059991

call的rule:

this看成一个特殊的参数,他决定了callee是谁

image-20211104185416934

为啥x不能新建一条指向m_this的边呢?

如果x指向 new A,那么dispatch的m结果是A.foo,A.this指向new A;如果x指向new B,那么dispatchm结果是B.foo,B.this指向new B,C也是一样。

如果x指向 {new A, new B, new C},但是A.this只能指向单一对象new A,把ABC都传过去就不对了

image-20211104190546640

我们只分从main开始的reachable的mothods,这样的好处是精度和时间复杂度都好

精度为什么好?如果我特地额外分析了unreachable的方法(如m9),假如这个m9指向了m4,会导致m4里的指针新增了“m9里的指针指向的那些玩意”,那就不准嘞

image-20211104192009651

algorithm

算法的主体部分和上节课的不涉及过程调用的指针分析差不多

image-20211104194836829

问题1:为什么addReachable只处理x = new T()和x = y这两种语句呢

因为其他的x.f = y,在新方法被分析时,x的指针集是空的,没法分析啊

但是x = new T() 和 x=y是确定的

问题2:processCall中为什么要判断l->m不属于CG?

m是dispatch的结果,可能之前有其他变量也dispatch出m了

一个例子,自己看ppt,需要注意的是x = new T()其实分两步:

  1. x = new T
  2. x.<init>()

重点

image-20211104202047234

class-11 Pointer Analysis Context Sensitivity

按之前学的,x和y的值来源于n,n来源于n1和n2,那么pt(x) = {new One(), new Two()}

但实际上程序执行的时候,x只指向 new One(),产生了精度损失

image-20211111183952505

如果我们把id分开处理,就可以提升精度

image-20211111184237249

Context Insensitivity为什么不准

因为他把method call的上下文混在一起,这些混合的上下文通过method的返回值和副作用传播了出去,造成假的数据流

Context Sensitivity

一种好理解的:java抛异常时候的调用栈

image-20211111190310520

堆也需要上下文,细分对象

image-20211111191241381

一个例子

在没有heap context的时候,虽然x1,x2都分开了,但是第八行的new X,o8.f同时指向o1 o2,就丢精度了

image-20211111193935044

另一个例子,没有变量context,有heap context的话,也不对

image-20211111193747076

Rules

把乱七八糟的东西前面都加上c

image-20211111194619784

new,assign,store,load

image-20211112110325621

invoke

image-20211112112255565

需要注意的是Select,需要继承(?)一下caller的上下文,所以需要用到call site的信息,和call site的上下文信息

本讲重点

image-20211112112418039

class-12 Static Analysis for Security

Information Flow Security

主要关注injection和sensitive data exposure?

访问和控制对于一个系统来说都是需要的

image-20211125185318640

信息流

长得很像指针分析

image-20211125185450633

高低安全性之间有偏序的关系

image-20211125185841961

信息流policy

高级的变量H不能流向低级的变量L,这样才无法通过L推理出H

image-20211125190833918

Confidentiality and Integrity

image-20211125193813950

Explicit Flows and Covert Channels

隐私流:L受H影响,造成leak

image-20211125193854779

最后一种例子,越界会抛异常

image-20211125194443993

Covert/Hidden Channels

定义covert channels:一个变量流到另一个变量,是一个channel,程序中那些不是为了传递信息而实际上传了信息的channel是covert channel(无意中的泄露)

image-20211125195017806

显式流比隐式流更容易发现,而且泄露的信息更多(下图32bit,1bit)

image-20211125195422088

Taint Analysis

污点分析

数据

  • 将我们感兴趣的数据,给他打上标记,成为污点数据
  • 其他是非污点数据

source:获得污点数据的地方,getPassword,getInput等

sink:关键函数,会把你的数据广播出去,泄露出去

image-20211125195829561

污点分析类似于同位素标记,同时可以解决保密性和完整性问题

image-20211125200002807

将指针分析和污点分析结合起来

把污点数据当成object

把source当成allocation site

这里提到的指针分析不带上下文,但是可以带,更准确

image-20211125200744872

输入是source和sinks

image-20211125201332856

rules

call有新意

image-20211202182050304

同指针分析

image-20211125201536299

Example

image-20211202182140205

注意,按rules找污点可能找不到,比如cmd = … + x,cmd的指针不会指向污点。sb.append(s),sb也不会带上污点数据。

本讲重点

image-20211125202456236

class-13 Datalog Based Program Analysis

datalog是一种编程语言,有额外好的特性

image-20211202183520794

Motivation

命令式语言做程序分析的时候比较繁琐,大量细节需要考虑

image-20211202184346835

datalog语言,逻辑语言,比较精炼,可读性强

image-20211202184523118

Introduction to Datalog

诞生于1980s中期,起初用来写数据库,后来被发现做程序分析,网络协议,大数据,云计算都适合

image-20211202184804479

谓词(predicate),就是一张表,表里的一个项代表一个事实(fact)

image-20211202185134139

term可以是变量也可以是常量

image-20211202185304883

relational atom关系型原子,值是true/false

arithmetic atom算术型原子

image-20211202185511707

image-20211202185838674

逗号看成逻辑and

image-20211202185956953

一个例子,可以推导出Adult

image-20211202190606190

image-20211202190631958

问题是data是哪来的

EDB是预设的表,不可变

IDB是推导出来的表

image-20211202190840728

表示逻辑或的两种方法,与优先级高于或image-20211202191138340

取反

image-20211202191339982

递归,推导式可以递归

例如求图可达性的传递闭包

image-20211202191557225

如果没有递归,datalog的表达能力就和sql差不多

Rule Safety

这俩例子都会造成无穷多的A(x)

所以一个safe的规则需要每个变量都在非取反的关系型原子中出现至少一次

image-20211202193224555

这个例子不收敛,递归和非要分开

image-20211202193603930

datalog的不同实现都有些功能的增减,比如logicblox支持修改一下EDB

image-20211202194113103

Pointer Analysis via Datalog

非call语句

EDB和IDB的设置

image-20211202194621634

对应指针分析的基本输入

image-20211202194912161

严格的11对应关系

image-20211202195402491

call 语句

call比较复杂,分三步

第一步推导callGraph

image-20211202200554669

第二步推导参数

image-20211202200918549

第三步处理返回值

image-20211202201005263

whole program

需要新增一个entry method的reachable

需要对new做小小的改动,只添加reachableMethod的new,避免不可达方法的分析

image-20211202201131138

Taint Analysis via Datalog

污点分析

image-20211204115916316

图改了

image-20211204115932910

pros & cons

缺点就是优点

简洁:逻辑不好表示

引擎高度优化:黑盒,性能不可控

image-20211202202515260

本节重点

  • datalog语法
  • pointer analysis
  • taint analysis

class-14 CFL-Reachablility and IFDS

image-20211209183514903

Feasible and Relizable Paths

函数可达好判断,但是路径可不可达不好判断,这样引入了不精确

image-20211209183905043

motvation

x = -1传回到x是不可避免地,但是x = 30是可以避免的,我们需要改进掉x = 30

image-20211209184452531

realiazable path:call和return匹配的path(范围更大)

executable path:动态执行时可能执行的path(包含于realiazable之中)

两者关系:

realiazable不一定executable,但不realiazable一定不excutable

image-20211209184843161

如何寻找realiazable path?类似括号匹配,callsite带着信息作为左括号,return作为右括号要和callsite的信息匹配。

括号匹配的CFG(这里的CFG是context-free grammar)

image-20211209190116168

image-20211209190339287

比如下面图里,1 call foo(18)是realizable的,但是2 call foo(30)返回到1 call foo(18)是non-realizable的

image-20211209203744556

IFDS

目的就是求出realizable path

image-20211209191951199

回忆一下MOP的概念,把从start到end的所有path都meet起来(path为单位,而不是某个节点meet)

MRP比MOP更小

image-20211209193221249

image-20211209193921834

image-20211209194137283

image-20211209194235854

lambda表达式

flow function:决定每个节点抵达前未被初始化的变量

image-20211209194518320

x/a表示用a替换x

P当中的P(a)就是a替换a,等于没动

retp的地方要-{g},是因为callee可能会影响全局变量g,所以要删掉

image-20211209195638255

return edge处的-{a}是已经超出p的范围了,所以要把本地的a kill掉

image-20211209200702118

这啥。。把flow function变成图

image-20211209201536906

image-20211209201629059

用0->0把所有的flow-function串起来

image-20211209201936721

一个例子,把flow-function改成exploded graph,自己看ppt

image-20211209202508727

还有没讲完的内容,如何把空心点改成实心点

Tabulation算法

复杂度O(E D^3)

E是icfg的边个数

D是点的个数

image-20211216183505422

IFDS的分配性

IFDS不能用来解常量传播和指针分析

why?

首先,infinite domain,cp不满足,domain = int的个数

其次,IFDS在点亮点的时候,一次只能点一个点,(1个fact)

image-20211216185011821

但是常量传播时候,一个二元表达式,需要同时考虑2个fact

如果只要求2种表达式,第一种是直接赋值,第二种是线性的赋值,那可以用IFDS

image-20211216185358991

指针分析不行的原因

建立的图如下,发现0到z不可达

原因是x.f = x只连了x到x.f这条边,没有连x到y.f。而x.f和y.f是alias

image-20211216185749345

要考虑到x和y是alias的话,又需要同时用两个fact

image-20211216190013574

本讲重点

image-20211216190041514

class-15 Soundiness

社会险恶

image-20211216190304423

soundiness

几乎所有的产品都是unsound的

因为语言有难以分析的成分,对静态分析不友好

image-20211216190742108

sound:完美的分析

soundy:允许对hard的那部分内容不处理,但要求说明具体边界

unsound:也不是完全没用,平衡的好的话,对于关注的那一部分很有用

Java Reflection

反射的基本例子

image-20211216193611007

不分析反射会出问题

image-20211216194031509

尝试解决反射

05年的paper,分析字符串常量

但是,如果是动态信息没法在编译器知道

image-20211216194415183

14年tt和ly:从使用者的信息来推测那些method

image-20211216195129076

19年进化了:目前最好的分析反射的系统

image-20211216195233860

另一种方法,用动态运行时,记录下object信息等,然后帮助静态分析

但是这些信息依赖于程序输入

image-20211216195612444

Native Code

System.out.println一层层下去会调用JNI,用C实现的

image-20211216195707888

native code难分析的原因

  • 你得写java和c的分析器
  • heap model得互通
  • 解决c里类似反射的东西

image-20211216200932261

现在的方法是手动给native code建模

比如这个例子,分析native arraycopy的时候

  • 改写成等同语义的java
  • 在指针分析时候,由于不考虑数组下标,所以写成array之间的赋值

image-20211216201432018

本讲重点

image-20211216201637552