计算机研究与发展 ISSN1000-1239/CN11-1777/TP Journal of Computer Research and Development 50(5):1044-1054,2013 一个程序验证器的设计和实现 张志天李兆鹏陈意云刘刚 (中国科学技术大学计算机科学技术学院合肥230026) (中国科学技术大学苏州研究院软件安全实验室江苏苏州215123) (zpli@ustc.edu.cn) An Automatic Program Verifier for PointerC:Design and Implementation Zhang Zhitian,Li Zhaopeng,Chen Yiyun,and Liu Gang (School of Computer Science and Technology.University of Science and Technology of China.Hefei 230026) (So ftware Security Laboratory.Suzhou Institute for Advanced Study.University of Science and Technology of China, Suzhou.Jiangsu 215123) Abstract Formal verification is a major method to improve the dependability of software.One of the hot research areas is automatic program verification based on logical inference.So far there is no product which can be used directly in the industries,and the root of this problem lies in the slow development and difficulties of automated theorem proving.Our approach is based on the observation that program analysis methods can be used in collecting global information to support program verification.We build shape graphs at each program point in the program analysis phase.A method is proposed,which uses regular Hoare logic rules to reason about assignment not dealing with pointers in a C-like programming language.Such rules will be applied after aliasing is eliminated using the information of shape graphs.The soundness of the logic system has been proved.Furthermore,an approach is presented to verify data constraints on mutable data structures without using user-defined predicates.A prototype of our program verifier has been implemented for the PointerC programming language.We have used it in the verification of programs manipulating recursive data structures,such as lists and trees,and programs dealing with one-dimension arrays. Key words program verification;Hoare logic;shape graph logic;program analysis;separation logic 摘要形式验证是提高软件可信程度的重要方法,基于逻辑推理对程序性质进行严格的自动证明是当 前的研究热点,但尚无可供工业界使用的产品,其根源在于自动定理证明方面的困难.介绍在通过程序 分析建立起各程序点的形状图的基础上,如何利用形状图提供的信息来支持程序验证的方法,提出一种 利用形状图信息来消除访问路径别名,使得指针程序中非指针部分的性质仍然可以用Hore逻辑来进 行验证的方法,并证明了该方法的可靠性.还提出一种在不使用自定义谓词的情况下,易变数据结构上 数据性质的描述和验证方法,另外,介绍所设计并实现的基于上述方法的PointerC语言的程序验证器 的原型.它不仅能自动验证操作易变数据结构程序的性质,也能自动验证使用一雏数组的程序的性质. 关键词程序验证;Hoare逻辑;形状图逻辑;程序分析;分离逻辑 中图法分类号TP301 收稿日期:2010-11-29:修回日期:2012-12-10 基金项目:国家自然科学基金项日(61170018,61003043) 通信作者:李兆鹏(zpli@ustc.edu.cm)
书 ! " # $ % & ' ( !""#$%%%&$'()!*#$$&$+++!,- ./01234/5*/670891:9;931?9@94/76928 A%!A"#$%BB$%AB$'%$( !"#$#'%$%C$$C')%%&#$#'%$'C$'C$% '()*#)*+,-./012!D$$+%%$E$D$%%(%B(" +,-.#345!F74G"0;80H$%:?@ABCDE F==> '$A$'(" !F74G"0;80HKG0N32J !!"#$$%$&'$()*+,-!".,/",0/12,"#/$%$34$5/.6,-7.+4$&!".,/",0/12,"#/$%$34$&'#./0$8,&,.'(%%'D" !!$&+90-,!,"*-.+4:0;$-0+$-4$!*160/",1!+*14$5/.6,-7.+4$&!".,/",0/12,"#/$%$34$&'#./0$ !*8/G671/@98=9>9792>3QG4G8M/5;/58R319HS29/58=9 =/819;931/24/JG0>G190;81G9;$32>8=91//8/58=G;71/Q4964G9;G28=9;4/R >9@94/7692832>>G55G8=9/19671/@G2JHS013771/3/28=9/Q;91@38G/2 8=3871/J136 3234M;G; 698=/>;G2;=379J137=;3893G; 71/7/;9>$R=G934G2JRG8=7/G2891; G23*&4GW971/J1366G2J432J03J9H"03589134G3;G2JG;94G6G2389>0;G2J8=9 G25/1638G/2/5;=379J137=;H,=9;/02>29;;/58=94/JGHO018=916/19$32 3771/38/@91G5M>383383;81095G29> 719>G5/18=9-/G2891*71/J1366G2J 432J03J9HT9=3@90;9>G8G28=9@91G5G383;8108199;$32>71/J136;>934G2JRG8=/29&>G692;G/23113M;H 8.9:%+32 71/J136@91G5G?@.AB$C%D E.FGHI!JKL*MNOPQR.ST!UVWX4ABY8$C1Z.[\H]^X_`,: abcdef,:I.!gh.3ij!klmR!gh&M.+nopq,:#$.12H&rst mR!gh+nouvwxyz{|!Q}~,:~a.;#$.12!$C12.*;H&rstXQRAY.!j ;?@ ,:#$"V/31956"!gh56",:ab"a¯56 ABCDEF ,-(%$
张志天等:一个程序验证器的设计和实现 1045 随着国家、社会和日常生活对软件系统的依赖 在下面的三元式中: 程度日益增长,复杂软件系统的正确、安全(包括可 {Q}*p=5{p==qΛ*p+*q==10}, 靠安全性(safety)和保密安全性(security))和可靠 访问路径¥p和*g互为别名.若按Hoare逻辑的赋 等对安全攸关的基础设施和应用是至关重要的,安 值公理,最弱前条件Q是p==q∧*q==5,而正确 全攸关软件的高可信成了保障国家安全、保持经济 的最弱前条件是p==q. 可持续发展和维护社会稳定的必要条件。 对于访问路径的别名,一种针对性的解决办法 形式验证是提高软件可信程度的重要方法,粗 是引入存储模型,把存储器M看成地址到被存储值 略地说,软件的形式验证有两种途径.第1种途径是 的映射,并且有下面的存储器公理,其中E和E 模型检测],它通过遍历系统所有状态空间,能够对 是地址表达式,E2是值表达式: 有穷状态系统进行自动验证,并自动构造不满足验 select(update(M,E1,E2),Es)=E2,if E== 证性质的反例.这种方法在工业界比较流行,其优点 E3; 是需要最小的用户交互,并可用于大规模复杂系统, select(update (M,E1,E2),Es)=select (M, 近年来广泛用于清扫现有代码的错误上.第2种途 Es),ifE1!=E 径是逻辑推理,它采用程序逻辑,比较有影响的是 然后,将Hoare逻辑的赋值公理修改为 Hoare逻辑),对程序的性质进行严格地推理,并且 (Q[update(u,E1,E2)/u]*E1=E2 (Q), 在推理过程中通常需要使用像Isabellets]或Cog) 其中,牡是存储器当前状态.利用该赋值公理和存储 这样的定理证明器.在这种途径中,大部分研究围绕 器公理,对先前的那个例子进行演算,可以得到最弱 采用某种演算来产生验证条件,然后用某个定理证 前条件p==q.这种办法的缺点是需要引入语义模 明器来证明验证条件,如Ynot),Spec#[)和ESC 型 Java[们.有些研究依靠符号计算及其过程中的定理 另一种解决办法是采用分离逻辑门.分离逻辑 证明来避免验证条件生成步骤,如Smalllfoots]和 通过把动态分配的各堆块的断言分离地表示,使得 jStar).还有的研究采用经严格证明的变换,从抽象 别名仅可能出现在同一个堆块的断言中.再通过对 规范逐步求精得到具体程序,如Perfect Developer. 赋值语句的推理规则的特别设计,使得必须先消除 虽然这些工具已在实验室研发出来,但是尚无可供 堆断言中出现的别名,然后才可以对修改该堆块单 工业界使用的产品问世.究其原因,根源在于自动定 元的赋值语句进行推理.使用分离逻辑的一个问题 理证明方面的困难.因为不管是访问路径的别名判 是,通常的自动定理证明器都不能证明带分离合取 断、循环不变式的推断、断言语言的表达能力和领域 连接词(*,separating conjunction)的验证条件,必 专用逻辑的设计等,最终都受到自动定理证明的能 须为分离逻辑设计专用的自动定理证明工具.而使 力的影响. 用这种自动定理器,规范的表达性受限于分离逻辑 在研究自动定理证明技术的同时,也应该考虑 的一个可判定片段(fragment)a 怎样降低对自动定理证明器的能力的要求.例如,设 在对操作易变数据结构的程序进行验证时,在 计新的编程语言机制来提高合法程序的门槛,以排 各程序点形状图的支持下,本文方法仍然基于普通 除部分有逻辑错误的程序.还有采用其他程序分析 的Hoare逻辑,既不需要从语义角度对它扩展,也 方法来收集程序信息,用这些信息来支持程序验证· 不需要引入新的逻辑连接词,既方便了程序员也减 这些都有可能减轻程序验证的负担,本文介绍在通 轻了自动定理证明器的负担. 过程序分析建立起各程序点形状图(shape graph) 其次,本文提出一种在不使用自定义谓词的情 的基础上,如何利用形状图提供的信息来支持程序 况下,易变数据结构上数据性质的描述和验证方法. 验证的方法,介绍基于该方法开发的一个程序验证 主要是指断言中允许使用带上角标的访问路径(即 器原型。 p(->ext)代表p->next->…->next,其中 本文的主要贡献是提出一种利用形状图信息来 ->next重复i次),以便用带全称量词的断言描述 消除访问路径别名,使得指针程序仍然可以用Hoare 链表的有序性等性质.在产生验证条件时,通过引入 逻辑来进行验证的方法,并证明了该方法的可靠性. 辅助函数来把带上角标的访问路径变换成可满足性 Hoare逻辑的一个重要限制是程序中不同的名 模理论(satisfiability modulo theories)求解器Z3ls町 字代表不同的程序对象,即不允许出现别名.例如, 能接受的形式」
GH)*&IJKLMNOP?@QRSTU VWLXYZ$[\?@QRS]^&AB!_`a bABc!;3598M"KdeABc!;9?K@A BmÎÏSj!f$ºCDEü+¤~ÑÝS ?SÔÕH ¯$%+¤~ÑÝ78SFG$HlIJK LÜMNP+¤~ÑÝÞS?SqùHè$j !OSPV$F' n=># 7,%,"+!*)10+,!E$F$$F'"$F("ZF'$G5F$ZZ F(% 7,%,"+!*)10+,!E$F$$F' "$F( "Z7,%,"+!E$ F("$G5F$+ ZF(D ,æ$ V/319ÎÏSÑ1 )@'*)10+,!!$F$$F'""!(*"F$ZF')@*$ ¶6$!nÞDkmIÑK ÞÑ$PS碣ä"$aTûüº @)ZZBD®S¸n¹qcEu¿àÎÏ Smça7~¯°!513J6928"'$'( H ¯P±²³ò´µ¶¦SVÓ¢£DG$¯ hVÓ¸iS\wz$ab t,/¿· S V/319ÎÏ$¸¨¹qô04/8=9/1G9;"ù ÞI('$(( ESH ËÌÍf#mçVÓDÞSj!KCÈ $%BA
1046 计算机研究与发展2013,50(5) 最后,本文介绍我们所设计并实现的基于上述 是按形状分析的需求所作的语言扩展,所允许的形 方法的PointerC语言的程序验证器的原型.它不仅 状有单链表、循环单链表、双向链表、循环双向链表 能自动验证操作易变数据结构程序的性质,也能自 和二叉树等.在图1的文法中,有关表达式的部分产 动验证使用一维数组程序的性质 生式被略去,assertion的语法在用到时再解释。 在PointerC中,指针类型的变量只能用于赋 1 PointerC语言和形状图逻辑简介 值、相等比较、存取指向对象等运算以及作为函数的 参数:指针算术和取地址运算(&)被禁止,并且没有 本节介绍验证器面向的编程语言PointerC和 指向指针类型的指针类型.malloc和free被看成是 验证器所依赖的形状图逻辑(shape graph logic)). PointerC预定义的函数,并且满足安全程序的最基 1.1 PointerC语言 本要求,例如malloc任何一次调用都能成功并且所 PointerC是一种强调指针类型并增加形状声明 分配空间同尚未释放空间无任何重叠。 的类C小语言,语法的主要产生式如图1所示.在 1.2形状图和形状图逻辑 结构体声明中,通过指针域指向形状的声明来确定 程序验证之前需要先基于形状图逻辑对程序进 这种结构体用来构造什么形状的数据结构.这同时 行形状分析为每个程序点构建形状图,这些形状图 也限定了该结构体类型的指针所能指向的形状.这 构成了程序验证所需要的指针信息.在此通过举例 program::=struct_def_list var_dec_list fun_def_list 来介绍形状图,形状图的严格定义请见文献[14幻. struct_def_list::=struct_def_list struct_defle 图2(a)是用形状图给出的单链表的归纳定义 struct_def::=typedef struct id field_dec_list)type_name; list(s).圆节点称为声明节点,代表指针型的声明变 var_dec_list::=var_dec_list var_decle var_dec::=type id_list: 量,唯一的出边上的标记是该变量的名字.框中没有 field_dec_list::=field_dec_list field_declfield_dec 标记的实线矩形称为结构节点,代表用malloc生成 field_dec::=field_type id; 的结构体变量,其出边代表它的指针域,边上的标记 id_list::=id_list,idid 是域名.框中有标记P的实线矩形称为谓词节点, fun_def_list::=fun_def_list fun_def\fun_def fun_def::=type id(param_list)bodyltype id()body 框的下方是该谓词的名字,谓词节点没有出边.框中 Param_list::=param_type id param_list.param_type id 有标记W的虚线矩形称为NULL节点,表示指向 body::=(entry_assertion var_dec_list stmt_list exit_assertion 它的边代表NULL指针.类似地,有标记的D虚线 type::=simple_typelelement_type [number]type_nameIvoid type_name::=id 矩形称为悬空节点,表示指向它的边代表悬空指针. element_type::=simple_type 图2(a)的谓词定义list(s)表示单链表分成空表(s field_type::=simple_typeltype_name":shape 是NULL指针)和非空(s指向一个结构节点,该节 param_type::=simple_typeltype_name" simple_type::=boollint GG9G巴 shape:=LISTIDLISTIC_LISTIC_DLISTITREEI.. list list entry_assertion::=assertion assertion:e (a)Definition of list (s) exit_assertion::=assertion assertion:e stmt_list:=stmt_list stmtlstmt stmt::=lval=exp:lval=malloc(type_name):if(exp)block lif(exp)block else block while(exp)loop_invariant block list,n.a n,a Ilval=id(exp_list):lid(exp_list):lal=id():lid(): (b)Definition of list (s,n,a) return:Ireturn exp:free(lval): block::=stmtlstmt_list) loop_invariant::=loop_invariant assertion:le exp::=numberl lval|NULLItruelfalse c dlist.n,a l-expl!explexp op expl(exp) (a→(n==0) (a→(n≥1) lval::=id\lval->id id Lexp] (c)Definition of c dlist (s,n,a) Fig.I Representative syntax of PointerC language. Fig.2 Three definitions of linked list. 图1 PointerC语言的语法(部分) 图2链表的几种定义
ºæ$abcdÎÏj!¥CÈS/¿Í S -/G2891*95;810 +4),-/0(,%Z.1 ,%,(,/+-+4),%Z7.()%,-+4), &.,%1-+4),%Z7.()%,-+4),&+4),-/0(," #7#0), )0-0(-+4),%Z7.()%,-+4),&+4),-/0(," 7.()%,-+4),%ZQ//4&G28 7#0),%Z:=!2&H:=!2&'-:=!2&'-H:=!2&2IFF&, ,/+-4-077,-+.$/%Z3;;918G/2077,-+.$/%&# ,G.+-077,-+.$/%Z3;;918G/2077,-+.$/%&# 7+(+-%.7+%Z7+(+-%.7+7+(+&7+(+ 7+(+%Z%60%Z,G)%&%60%Z(0%%$"!+4),-/0(,"%&G5!,G)";%$"J &G5!,G)";%$"J94;9;%$"J&R=G49!,G)"%$$)-./60-.0/+;%$"J &%60%Z.1!,G)-%.7+"%&.1!,G)-%.7+"%&%60%Z.1!"%&.1!"% &198012%&198012,G)%&&-,,!%60%"% ;%$"J%Z7+(+&)7+(+-%.7+* %$$)-./60-.0/+%Z4//7-G2@31G328077,-+.$/%&# ,G)%Z/*(;,-&%60%&#[KK&8109&534;9 &C,G)&+,G)&,G)$),G)&!,G)" %60%%Z.1&%60%C$.1&.1 ',G)( OGJH$ :9719;92838G@9;M283\/5-/G2891*432J03J9H i$ -/G2891*Sßàå NÞ$077,-+.$/S95G2G8G/2;/54G2W9>4G;8H i' Æ=Só~ i'!3"nmiô"S¨Æ=Sõö~ %.7+!7"D÷ѸÄ1×ÝѸ$É=rsS×Ýò Å$ømS"ùÍSÃúnIòÅS6vDû6æ ÃúSCüýÄ1¶¦Ñ¸$É=m (0%%$"Nt S¶¦þòÅ$¶"ùÉ=SrsA$ùÍSÃú nA6Dû6Ãú ! SCüýÄ1¾®Ñ¸$ ûSz nI¾®S6v$¾®Ñ¸æ"ùDû6 Ãú " SþüýÄ1 #[KK Ѹ$= rÒ SùÉ= #[KKrsDÕÿ$ÃúS # þü ýÄ1!Ѹ$= rÒSùÉ=!rsD i'!3"S¾®~%.7+!7"= ¨Æ=àt=!7 n #[KKrs"K"!7rÒm綦Ѹ$IÑ $%BD !"#$%&'( '%$($A%!A"