深入IonMonkey:根因分析CVE-2019-9810

本文详细分析了Firefox中IonMonkey JIT编译器的CVE-2019-9810漏洞,涉及别名分析优化过程中的错误依赖关系判断,导致全局值编号优化错误消除边界检查,最终引发越界写入。

深入IonMonkey:根因分析CVE-2019-9810

引言

五月期间,我本想研究BigInt并评估其在浏览器漏洞利用中的应用。我为blazefox编写的漏洞利用依赖于@5aelo开发的一个JavaScript库,该库允许操作64位整数。与此同时,ZDI发布了CVE-2019-9810的概念验证(PoC),这是IonMonkey(Mozilla的推测性JIT引擎)中的一个问题,由Richard Zhu和Amat Cama在Pwn2Own2019期间发现并用于攻破Mozilla的网页浏览器。

这是编写漏洞利用并在我的实用脚本中添加BigInt支持的绝佳机会。你可以在我的GitHub仓库中找到实际的漏洞利用代码:CVE-2019-9810。

完成后,我觉得这也是深入了解Ion并相互了解的好机会。最初的漏洞利用是在完全不理解问题根本原因的情况下编写的,解开这个谜团听起来是个不错的练习。本文基本上就是关于我探索Ion代码库并调查CVE-2019-9810根本原因的过程。

问题的标题“IonMonkey MArraySlice has incorrect alias information”暗示问题的根源涉及某些别名信息,问题的修复也指向Ion的AliasAnalysis优化阶段。

开始之前,如果大家想在家跟随源代码而不下载整个Spidermonkey/Firefox源代码,我在S3存储桶上设置了woboq代码浏览器:ff-woboq - 只需记住快照包含我们讨论的问题的修复。最后但同样重要的是,我注意到IonMonkey的代码变动较大,因此下面提到的一些函数在最新版本中可能名称略有不同。

好了,系好安全带,享受阅读吧!

目录

  • 推测性优化JIT编译器
  • 深入别名分析
  • 补丁分析
  • 全局值编号

推测性优化JIT编译器

这部分并不是要详细介绍推测性优化JIT引擎,而是让你了解它们试图解决的问题。此外,我们想介绍一些关于Ion的背景知识,以便能够理解后续内容。

对于从未听说过JIT(即时)引擎的人来说,这是一款能够在运行时将托管代码转换为本地代码的软件。历史上,解释型语言使用这种方法来生成更快的代码,因为运行汇编比软件CPU运行代码更快。考虑到这一点,这是Spidermonkey中Javascript字节码的样子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
js> function f(a, b) { return a+b; }
js> dis(f)
flags: CONSTRUCTOR
loc     op
-----   --
main:
00000:  getarg 0                        #
00003:  getarg 1                        #
00006:  add                             #
00007:  return                          #
00008:  retrval                         # !!! UNREACHABLE !!!

现在,生成汇编是一回事,但JIT引擎可以更先进,应用一堆程序分析来进一步优化代码。想象一个循环,对数组中的每个项求和,不做其他事情。嗯,JIT引擎可能能够证明不对索引进行任何边界检查是安全的,在这种情况下它可以移除检查。另一个容易推理的例子是在循环体中构造一个对象,但根本不依赖于循环本身。如果JIT引擎能够证明该语句实际上是一个不变量,那么为什么为每次循环运行都构造它呢?在这种情况下,优化器将语句移出循环以避免无用的构造是有意义的。这是Ion为上述相同函数生成的优化汇编:

1
2
3
4
5
6
7
8
9
0:000> u . l20
000003ad`d5d09231 cc              int     3
000003ad`d5d09232 8b442428        mov     eax,dword ptr [rsp+28h]
000003ad`d5d09236 8b4c2430        mov     ecx,dword ptr [rsp+30h]
000003ad`d5d0923a 03c1            add     eax,ecx
000003ad`d5d0923c 0f802f000000    jo      000003ad`d5d09271
000003ad`d5d09242 48b9000000000080f8ff mov rcx,0FFF8800000000000h
000003ad`d5d0924c 480bc8          or      rcx,rax
000003ad`d5d0924f c3              ret

好的,这是关于优化和JIT编译器的,但现在谈谈推测性?如果你思考一两分钟,为了完成我们上面讨论的优化,你还需要大量关于正在分析的代码的信息。例如,你需要知道正在处理的对象的类型,而在动态类型语言中很难获得这些信息,因为根据设计,变量的类型在程序执行过程中会变化。现在,引擎显然不能随机推测类型,相反,它们通常做的是在运行时内省程序并观察发生的情况。如果这个函数被调用多次并且每次只接收整数,那么引擎会做出有根据的猜测并推测该函数接收整数。因此,引擎将在此假设下优化该函数。除了优化函数之外,它还会插入一堆代码,仅用于确保参数是整数而不是其他东西(在这种情况下生成的代码无效)。例如,将两个整数相加与将两个字符串相加不同。因此,如果引擎遇到推测不再成立的情况,它可以丢弃生成的代码并回退到解释器中执行(称为反优化bailout),导致性能下降。

可以想象,分析程序以及运行完整的优化管道和生成本机代码的过程非常昂贵。因此,有时即使解释器较慢,JITing的成本可能不值得超过在解释器中执行某些东西。另一方面,如果你执行一个函数,比如说一千次,JITing的成本可能会随着时间的推移被优化本机代码的性能增益所抵消。为了处理这个问题,Ion使用所谓的预热计数器来识别热代码和冷代码(你可以通过传递给shell的–ion-warmup-threshold来调整)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// Force how many invocation or loop iterations are needed before compiling
// a function with the highest ionmonkey optimization level.
// (i.e. OptimizationLevel_Normal)
const char* forcedDefaultIonWarmUpThresholdEnv =
    "JIT_OPTION_forcedDefaultIonWarmUpThreshold";
if (const char* env = getenv(forcedDefaultIonWarmUpThresholdEnv)) {
  Maybe<int> value = ParseInt(env);
  if (value.isSome()) {
    forcedDefaultIonWarmUpThreshold.emplace(value.ref());
  } else {
    Warn(forcedDefaultIonWarmUpThresholdEnv, env);
  }
}

// From the Javascript shell source-code
int32_t warmUpThreshold = op.getIntOption("ion-warmup-threshold");
if (warmUpThreshold >= 0) {
  jit::JitOptions.setCompilerWarmUpThreshold(warmUpThreshold);
}

除了以上所有内容,Spidermonkey还使用另一种类型的JIT引擎,它生成优化程度较低的代码,但成本较低。因此,引擎根据用例有多个选项:它可以在解释模式下运行,可以执行更便宜但更慢的JITing,或者可以执行昂贵但快速的JITing。请注意,本文仅关注Ion,它是Spidermonkey中最快/最昂贵的JIT层级。

以下是整个管道的概述(图片取自Mozilla的wiki):

在Spidermonkey中,它的工作方式是将Javascript代码转换为解释器执行的中间语言。这个字节码进入Ion,Ion将其转换为另一种表示形式,即中级中间表示(后面缩写为MIR)代码。这是一个相当简单的IR,使用静态单赋值,并有大约~300条指令。MIR指令组织在基本块中,它们自己形成一个控制流图。

Ion的优化管道由29个步骤组成:某些步骤实际上通过移除或重新排列节点来修改MIR图,而其他步骤根本不修改它(它们只是分析它并产生由后续传递消耗的结果)。要调试Ion,我建议将以下内容添加到你的mozconfig文件中:

1
ac_add_options --enable-jitspew

这基本上打开了Spidermonkey代码库中的一堆宏,用于在标准输出上输出调试信息。调试基础设施不如Turbolizer好,但我们将使用我们拥有的工具。JIT子系统可以定义许多通道,它可以在这些通道上输出spew,用户可以打开/关闭任何通道。例如,如果你想调试单个优化传递,这非常有用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// New channels may be added below.
#define JITSPEW_CHANNEL_LIST(_)            \
  /* Information during sinking */         \
  _(Prune)                                 \
  /* Information during escape analysis */ \
  _(Escape)                                \
  /* Information during alias analysis */  \
  _(Alias)                                 \
  /* Information during alias analysis */  \
  _(AliasSummaries)                        \
  /* Information during GVN */             \
  _(GVN)                                   \
  /* Information during sincos */          \
  _(Sincos)                                \
  /* Information during sinking */         \
  _(Sink)                                  \
  /* Information during Range analysis */  \
  _(Range)                                 \
  /* Information during LICM */            \
  _(LICM)                                  \
  /* Info about fold linear constants */   \
  _(FLAC)                                  \
  /* Effective address analysis info */    \
  _(EAA)                                   \
  /* Information during regalloc */        \
  _(RegAlloc)                              \
  /* Information during inlining */        \
  _(Inlining)                              \
  /* Information during codegen */         \
  _(Codegen)                               \
  /* Debug info about safepoints */        \
  _(Safepoints)                            \
  /* Debug info about Pools*/              \
  _(Pools)                                 \
  /* Profiling-related information */      \
  _(Profiling)                             \
  /* Information of tracked opt strats */  \
  _(OptimizationTracking)                  \
  _(OptimizationTrackingExtended)          \
  /* Debug info about the I$ */            \
  _(CacheFlush)                            \
  /* Output a list of MIR expressions */   \
  _(MIRExpressions)                        \
  /* Print control flow graph */           \
  _(CFG)                                   \
                                           \
  /* BASELINE COMPILER SPEW */             \
                                           \
  /* Aborting Script Compilation. */       \
  _(BaselineAbort)                         \
  /* Script Compilation. */                \
  _(BaselineScripts)                       \
  /* Detailed op-specific spew. */         \
  _(BaselineOp)                            \
  /* Inline caches. */                     \
  _(BaselineIC)                            \
  /* Inline cache fallbacks. */            \
  _(BaselineICFallback)                    \
  /* OSR from Baseline => Ion. */          \
  _(BaselineOSR)                           \
  /* Bailouts. */         
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计