首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >使用方舟编译器检查Fastjson OOM问题

使用方舟编译器检查Fastjson OOM问题

作者头像
安全乐观主义
发布2019-11-20 18:50:43
发布2019-11-20 18:50:43
87900
代码可运行
举报
文章被收录于专栏:安全乐观主义安全乐观主义
运行总次数:0
代码可运行

思路

通过介入编译期间进行安全检查是类似于Facebook infer类的产品,为什么要这么做呢?源代码安全检查工具粗略分为两个大的流派,一个是类似于coverity,需要编译,厂家集成实现了cov-build这样的编译工具;另一个是checkmarx直接分析语法树进行检查,再上层的例如p3c、pmd、sonarcube都是基于字节码、数据流的规范检查,执行编译有助于将代码规范起来,缓解路径不可达问题降低误报,SAST不能避免软件工程的莱斯定理(Rice’s Theorem)在图灵机的应用:我们可以把任意程序看成一个从输入到输出上的部分函数(Partial Function),该函数描述了程序的行为,关于程序行为的任何非平凡属性,都不存在可以检查该属性的通用算法,误报是允许在得不到精确值的时候,给出近似答案,这个答案就是一定比例的误报或者漏报。本文即尝试类似RoboVM、SVF使用LLVM的思路进行数据流和控制流的软件错误检测。扩展知识可以看下北大熊英飞教授的软件分析技术(Software Analysis)公开课件https://xiongyingfei.github.io/SA/2018/main.htm。

准备

目前方舟编译器项目已经开源的代码只有少部分,没有支持java语言和虚拟机特性,没有提供Runtime环境,有些关键组件是提供了静态库没有对应实现。可以跑通的有根据java字节码通过jbc2mpl转换出来的中间语言(IR),等待11月份会有完整代码。

看上图的架构设计,在外部的java代码经过方舟编译器处理ir,然后用编译优化,这一步可以嵌入代码安全检查逻辑,后端优化器编译器不链接语言依赖库,而是生成用于程序分析的中间件。参考了LLVM

我们不需要程序可以在平台运行,静态分析技术只需要分析“中间表示”(IR)即可进行检查,简单的说法是方舟编译器不是干掉了JDK,而是取代jvm,可以在方舟平台运行apk、jar、class,好处是支持多种语言,下面以Fastjson那“优美”的OOM为例。

过程

构建fastjson oom段的示例代码,去掉Java基本库(基本库有些native的写法,现在假设没有jvm),通过方舟编译器生成.mpl。然后分析IR结构。文章提供每一个步骤介绍通用的代码规范检查的实现步骤。方舟编译器安装的环境配置参考官方文档即可:https://www.openarkcompiler.cn/document/environment,编译器优化可以做很多事、,当然每一步都没有demo可以借鉴,全靠自己摸索。

编译方舟编译器

source ./build/envsetup.sh

make,编译方舟编译器,这里就粘贴大量的console内容了。

Fastjson代码

现在由于没有java-core包,不能跑通全量fastjson项目代码生成IR,也不能有main方法(因为入参是java.lang.String数组),生成IR的时候会报错,整理复现oom问题的核心代码。

代码语言:javascript
代码运行次数:0
运行
复制
public class OOM {    protected int sp = 2;    protected char[] sbuf = new char[]{'\u0000', '\u0000'};    public void method() {        for (; ; ) {            if (sp == sbuf.length) {                char[] newsbuf = new char[sbuf.length * 2];                //System.arraycopy(sbuf, 0, newsbuf, 0, sbuf.length);                sbuf = newsbuf;            }            sbuf[sp++] = 0x1A;        }    }}

Javac OOM.java 生成class文件。

javap -verbose OOM.class > OOM.jbc

nano@nano-VirtualBox:~/Desktop/java$ jbc2mpl -inclass OOM.class -o OOM.mpl

Warn 20: method Ljava_2Flang_2FObject_3B_7C_3Cinit_3E_7C_28_29V is undefined

至此,目录下有这些文件

OOM.class OOM.java OOM.bytecode OOM.mpl OOM.mplt

生成的mpl内容含义为:mplt是符号表,mpl是定义,mpl生成继续后端汇编代码。

分析

深入浅出虚拟机,javap查看一下字节码的内容。方舟编译器取代了这一套机制:

代码语言:javascript
代码运行次数:0
运行
复制
Classfile /home/nano/Desktop/oom/OOM.class  Last modified Sep 11, 2019; size 403 bytes  MD5 checksum 306e2c24dba71834894518074c078853  Compiled from "OOM.java"public class test.OOM  minor version: 0  major version: 52  flags: ACC_PUBLIC, ACC_SUPER  //类和方法的访问权限Constant pool: //常量池   #1 = Methodref          #5.#18         // java/lang/Object."<init>":()V   #2 = Fieldref           #4.#19         // test/OOM.sp:I  //字段   #3 = Fieldref           #4.#20         // test/OOM.sbuf:[C   #4 = Class              #21            // test/OOM   #5 = Class              #22            // java/lang/Object   #6 = Utf8               sp   #7 = Utf8               I   #8 = Utf8               sbuf   #9 = Utf8               [C  #10 = Utf8               <init>  #11 = Utf8               ()V  #12 = Utf8               Code  #13 = Utf8               LineNumberTable  #14 = Utf8               method  #15 = Utf8               StackMapTable  #16 = Utf8               SourceFile  #17 = Utf8               OOM.java  #18 = NameAndType        #10:#11        // "<init>":()V  #19 = NameAndType        #6:#7          // sp:I  #20 = NameAndType        #8:#9          // sbuf:[C  #21 = Utf8               test/OOM  #22 = Utf8               java/lang/Object{  protected int sp;//定义    descriptor: I    flags: ACC_PROTECTED
  protected char[] sbuf;    descriptor: [C    flags: ACC_PROTECTED
  public test.OOM();    descriptor: ()V    flags: ACC_PUBLIC    Code:      stack=5, locals=1, args_size=1         0: aload_0  //字节码指令,         1: invokespecial #1//invokespecial是方舟编译器需要实现的opcode ,                 //调用实例方法 Method java/lang/Object."<init>":()V         4: aload_0         5: iconst_2         6: putfield      #2                  // Field sp:I         9: aload_0        10: iconst_2        11: newarray       char  //初始创建数组        13: dup        14: iconst_0        15: iconst_0        16: castore        17: dup        18: iconst_1        19: iconst_0        20: castore        21: putfield      #3                  // Field sbuf:[C        24: return      LineNumberTable:        line 3: 0        line 4: 4        line 5: 9
  public void method();//具体方法    descriptor: ()V    flags: ACC_PUBLIC  //方法public    Code:      stack=5, locals=2, args_size=1         0: aload_0         1: getfield      #2                  // Field sp:I         4: aload_0         5: getfield      #3                  // Field sbuf:[C         8: arraylength         9: if_icmpne     27  //用if条件分支判断相等        12: aload_0        13: getfield      #3                  // Field sbuf:[C        16: arraylength        17: iconst_2        18: imul        19: newarray       char        21: astore_1        22: aload_0          23: aload_1        24: putfield      #3                  // Field sbuf:[C        27: aload_0        28: getfield      #3                  // Field sbuf:[C        31: aload_0        32: dup        33: getfield      #2                  // Field sp:I        36: dup_x1        37: iconst_1        38: iadd        39: putfield      #2                  // Field sp:I        42: bipush        26        44: castore             45: goto          0      LineNumberTable:        line 9: 0        line 10: 12        line 12: 22        line 14: 27      StackMapTable: number_of_entries = 2        frame_type = 0 /* same */        frame_type = 26 /* same */}SourceFile: "OOM.java"

打印出bytecode是方便我们查阅比对IR文件,生成IR这一步将.class文件转换为类似LLVM的前端,将程序通过中间层表示,各种语言表示为称为Maple IR的为芯片做过优化的语言。MAPLE IR是从不同编程语言编译的程序的通用表示,其中包括C,C ++和Java等通用语言,在MAPLE VM上消费执行。

OOM.mplt内容如下:

IR文件内容如下,可以看到是一种树状数据结构,代码简单,没有涉及到引用计数,IR的具体解读和设计文档参考https://gitee.com/harmonyos/OpenArkCompiler/blob/master/doc/MapleIRDesign.md java章节,解读时可以参考右侧java字节码注释。

代码语言:javascript
代码运行次数:0
运行
复制
flavor 1srclang 3id 65535numfuncs 2import "OOM.mplt"fileinfo {  @INFO_filename "OOM.class"}srcfileinfo {  1 "OOM.java"}javaclass $Ltest_2FOOM_3B <$Ltest_2FOOM_3B> publicfunc &Ltest_2FOOM_3B_7C_3Cinit_3E_7C_28_29V public constructor (var %_this <* <$Ltest_2FOOM_3B>>) voidfunc &Ltest_2FOOM_3B_7Cmethod_7C_28_29V public virtual (var %_this <* <$Ltest_2FOOM_3B>>) void  //虚函数func &Ljava_2Flang_2FObject_3B_7C_3Cinit_3E_7C_28_29V public virtual abstract (var %_this <* <$Ljava_2Flang_2FObject_3B>>) voidvar $__cinf_Ljava_2Flang_2FString_3B <$__class_meta__>func &MCC_GetOrInsertLiteral () <* <$Ljava_2Flang_2FString_3B>>func &Ltest_2FOOM_3B_7C_3Cinit_3E_7C_28_29V public constructor (var %_this <* <$Ltest_2FOOM_3B>>) void {  funcid 1  var %Reg5_R49 <* <$Ltest_2FOOM_3B>>  var %Reg5_R53 <* <$Ljava_2Flang_2FObject_3B>>  var %Reg0_I i32  var %Reg0_R48 <* <[] u16>>  var %Reg1_I i32  var %Reg2_I i32
  dassign %Reg5_R49 0 (dread ref %_this)  #INSTIDX : 0||0000:  aload_0  #INSTIDX : 1||0001:  invokespecial  dassign %Reg5_R53 0 (retype ref <* <$Ljava_2Flang_2FObject_3B>> (dread ref %Reg5_R49))  superclasscallassigned &Ljava_2Flang_2FObject_3B_7C_3Cinit_3E_7C_28_29V (dread ref %Reg5_R53) {}  #INSTIDX : 4||0004:  aload_0  #INSTIDX : 5||0005:  iconst_2  dassign %Reg0_I 0 (constval i32 2)  #INSTIDX : 6||0006:  putfield  iassign <* <$Ltest_2FOOM_3B>> 2 (dread ref %Reg5_R49, dread i32 %Reg0_I)  #INSTIDX : 9||0009:  aload_0  #INSTIDX : 10||000a:  iconst_2  dassign %Reg0_I 0 (constval i32 2)  #INSTIDX : 11||000b:  newarray  dassign %Reg0_R48 0 (gcmallocjarray ref <[] u16> (dread i32 %Reg0_I))  #INSTIDX : 13||000d:  dup  #INSTIDX : 14||000e:  iconst_0  dassign %Reg1_I 0 (constval i32 0)  #INSTIDX : 15||000f:  iconst_0  dassign %Reg2_I 0 (constval i32 0)  #INSTIDX : 16||0010:  castore  iassign <* u16> 0 (    array 1 ptr <* <[] u16>> (dread ref %Reg0_R48, dread i32 %Reg1_I),     cvt u16 i32 (dread i32 %Reg2_I))  #INSTIDX : 17||0011:  dup  #INSTIDX : 18||0012:  iconst_1  dassign %Reg1_I 0 (constval i32 1)  #INSTIDX : 19||0013:  iconst_0  dassign %Reg2_I 0 (constval i32 0)  #INSTIDX : 20||0014:  castore  iassign <* u16> 0 (    array 1 ptr <* <[] u16>> (dread ref %Reg0_R48, dread i32 %Reg1_I),     cvt u16 i32 (dread i32 %Reg2_I))  #INSTIDX : 21||0015:  putfield  iassign <* <$Ltest_2FOOM_3B>> 3 (dread ref %Reg5_R49, dread ref %Reg0_R48)  #INSTIDX : 24||0018:  return  return ()}func &Ltest_2FOOM_3B_7Cmethod_7C_28_29V public virtual (var %_this <* <$Ltest_2FOOM_3B>>) void {  funcid 2  var %Reg6_R49 <* <$Ltest_2FOOM_3B>>  var %Reg0_I i32  var %Reg1_R48 <* <[] u16>>  var %Reg1_I i32  var %Reg0_R48 <* <[] u16>>  var %Reg5_R48 <* <[] u16>>  var %Reg2_I i32
  dassign %Reg6_R49 0 (dread ref %_this)@label0   #INSTIDX : 0||0000:  aload_0  #INSTIDX : 1||0001:  getfield  dassign %Reg0_I 0 (iread i32 <* <$Ltest_2FOOM_3B>> 2 (dread ref %Reg6_R49))  #INSTIDX : 4||0004:  aload_0  #INSTIDX : 5||0005:  getfield  dassign %Reg1_R48 0 (iread ref <* <$Ltest_2FOOM_3B>> 3 (dread ref %Reg6_R49))  #INSTIDX : 8||0008:  arraylength  dassign %Reg1_I 0 (intrinsicop i32 JAVA_ARRAY_LENGTH (dread ref %Reg1_R48))  #INSTIDX : 9||0009:  if_icmpne  brtrue @label1 (ne i32 i32 (dread i32 %Reg0_I, dread i32 %Reg1_I))  #INSTIDX : 12||000c:  aload_0  #INSTIDX : 13||000d:  getfield  dassign %Reg0_R48 0 (iread ref <* <$Ltest_2FOOM_3B>> 3 (dread ref %Reg6_R49))  #INSTIDX : 16||0010:  arraylength  dassign %Reg0_I 0 (intrinsicop i32 JAVA_ARRAY_LENGTH (dread ref %Reg0_R48))  #INSTIDX : 17||0011:  iconst_2  dassign %Reg1_I 0 (constval i32 2)  #INSTIDX : 18||0012:  imul  dassign %Reg0_I 0 (mul i32 (dread i32 %Reg0_I, dread i32 %Reg1_I))  #INSTIDX : 19||0013:  newarray  dassign %Reg0_R48 0 (gcmallocjarray ref <[] u16> (dread i32 %Reg0_I))  #INSTIDX : 21||0015:  astore_1  dassign %Reg5_R48 0 (dread ref %Reg0_R48)  #INSTIDX : 22||0016:  aload_0  #INSTIDX : 23||0017:  aload_1  #INSTIDX : 24||0018:  putfield  iassign <* <$Ltest_2FOOM_3B>> 3 (dread ref %Reg6_R49, dread ref %Reg5_R48)@label1   #INSTIDX : 27||001b:  aload_0  #INSTIDX : 28||001c:  getfield  dassign %Reg0_R48 0 (iread ref <* <$Ltest_2FOOM_3B>> 3 (dread ref %Reg6_R49))  #INSTIDX : 31||001f:  aload_0  #INSTIDX : 32||0020:  dup  #INSTIDX : 33||0021:  getfield  dassign %Reg1_I 0 (iread i32 <* <$Ltest_2FOOM_3B>> 2 (dread ref %Reg6_R49))  #INSTIDX : 36||0024:  dup_x1  #INSTIDX : 37||0025:  iconst_1  dassign %Reg2_I 0 (constval i32 1)  #INSTIDX : 38||0026:  iadd  dassign %Reg2_I 0 (add i32 (dread i32 %Reg1_I, dread i32 %Reg2_I))  #INSTIDX : 39||0027:  putfield  iassign <* <$Ltest_2FOOM_3B>> 2 (dread ref %Reg6_R49, dread i32 %Reg2_I)  #INSTIDX : 42||002a:  bipush  dassign %Reg2_I 0 (constval i32 26)  #INSTIDX : 44||002c:  castore  iassign <* u16> 0 (    array 1 ptr <* <[] u16>> (dread ref %Reg0_R48, dread i32 %Reg1_I),     cvt u16 i32 (dread i32 %Reg2_I))  #INSTIDX : 45||002d:  goto  goto @label0}

阅读IR代码可知OOM问题的核心是for循环不断查询有没有char EOI = 0x1A,不合理代码引起sbuf和newsbuf在for循环里交替翻倍,不断向jvm申请倍增超大的char[]数组,直至程序崩溃。目前只需等待方舟编译器中期发布了控制流优化,数组越界检查功能之类的检查实现,就可以打通流程完成类似的OOM检测工具了,这比asm工具更贴合程序运行环境,有希望告别现在Fastjson多个漏洞出现,各种工具、扫描器哑然的情况。

展望

笔者检查认为方舟编译器是一些安全检查工具,包括jsp类webshell检查、rasp、国产白盒工具可以关注的对象,也可能挑战360的火线检查工具、各种移动应用平台的上线前检查工具的能力。谷歌为何可以保证Google play市场的安全性,提出GPSRP呢?因为具备对市场上的大量应用的快速分析能力,在方舟编译器推广后,华为应用市场会有类似的能力,用户只要提供apk,不管是否是加固过,都可以自动化进行审核,加强国内安卓市场的安全性。此外未来对JavaScript的支持或可解决对node框架的静态分析能力。阐幽探赜,曷其有极!在基础编译生态建立的进程上,国产工具还是有发展潜力的。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-09-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 安全乐观主义 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 准备
  • 过程
  • 编译方舟编译器
  • Fastjson代码
  • 分析
  • 展望
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档