深入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. */
|