<<

Finding and Preventing Bugs in JavaScript Bindings Fraser Brown⋆ Shravan Narayan† Riad S. Wahby⋆ Dawson Engler⋆ Ranjit Jhala† Deian Stefan† ⋆Stanford University †UC San Diego

Abstract—JavaScript, like many high-level languages, relies on run- secure foundational operations [40, 68, 106]. This paper focuses time systems written in low-level and C++. For example, the on detecting and exploiting flaws in two pervasive JavaScript Node.js gives JavaScript code access to the under- runtime systems—Node.js and Chrome—since binding bugs in lying filesystem, networking, and I/O by implementing utility func- tions in C++. Since C++’s , memory model, and execution these systems endanger hundreds of millions of people (e.g., all model differ significantly from JavaScript’s, JavaScript code must users of the Chrome browser). call these runtime functions via intermediate binding layer code that JavaScript’s variables are dynamically typed. Therefore, translates type, state, and failure between the two languages. Unfor- when JavaScript code calls a binding layer function, that C++ tunately, binding code is both hard to avoid and hard to get right. This paper describes several types of exploitable errors that bind- binding function should first determine the underlying type of ing code creates, and develops both a suite of easily-to-build static each incoming parameter. Then, the function should translate checkers to detect such errors and a backwards-compatible, low- each parameter’s current value to its equivalent statically-typed overhead API to prevent them. We show that binding flaws are a representation in C++. The binding code should also determine serious security problem by using our checkers to craft 81 proof-of- if values are legal (e.g., whether an index is within the bounds concept exploits for security flaws in the binding layers of the Node.js and Chrome, runtime systems that support hundreds of millions of of an array); if not, the binding should propagate an error back users. As one practical measure of binding bug severity, we were to the JavaScript layer. Finally, before the function completes, awarded $6,000 in bounties for just two Chrome bug reports. it should store any result in the memory and type representation that JavaScript expects. 1 Introduction In practice, writing binding code is complicated: it can fail at Many web services and other attacker-facing code bases are many points, and bindings should detect failure and correctly written in high-level scripting languages like JavaScript, Python, communicate any errors back to JavaScript. Too often, binding and Ruby. By construction, these languages prevent developers code simply crashes, leading to denial-of-service or covert- from introducing entire classes of bugs that plague low-level channel attacks (§2). If a binding function does not crash, it languages—e.g., buffer overflows, use-after-frees, and memory might still skip domain checking (e.g., checking that an array leaks. On the other hand, high-level languages introduce new index is in bounds)—or even ignore type checking, therefore classes of severe, exploitable flaws that are often less obvious allowing attackers to use nonsensical values as legal ones. (e.g., than low-level code bugs. by invoking a number as a function). One especially insidious High-level languages push significant functionality to their source of errors is the fact that binding code may invoke new runtime systems, which are written in low-level, unsafe lan- JavaScript routines during type and domain checking. For ex- guages (mainly C and C++). Runtime systems provide function- ample, in translating to a C++ uint32_t, bindings may use the ality not possible in the base scripting language (e.g., network Uint32Value method, which could invoke a JavaScript “upcall” and file system access) or expose fast versions of routines that (i.e., a call back into the JavaScript layer). JavaScript gives users would otherwise be too slow (e.g., sorting routines). Since the extreme flexibility in redefining fundamental language methods, high-level, dynamically-typed scripting language and low-level which makes it hard to know all methods that an upcall can language have different approaches to typing, memory man- transitively invoke, and makes it easy for attackers to circum- agement, and failure handling, the scripting code cannot call vent security and correctness checks. For example: bindings runtime routines directly. Instead, it invokes intermediary bind- may check that a start index is within the bounds of an array ing code that translates between value types, changes value before calling Uint32Value to get the value of an end index. representations, and propagates failure between the languages. The Uint32Value call, however, may be hijacked by a mali- Binding code has the dangerous distinction of being both cious client to change the value of the start index, invalidating hard to avoid and hard to get right. This paper demonstrates the all previous bounds checking. severity of the problem by demonstrating 81 proof-of-concept These bugs are neither hypothetical nor easily avoidable. exploits for bugs in multiple widely-used runtimes for the Our checkers find numerous exploitable security holes in both JavaScript language. We picked JavaScript because of its ubiq- Node.js and Chrome, heavily-used and actively developed code uity: it is both the most popular language on GitHub and the bases. Furthermore, security holes in binding code may be sig- language with the largest growth factor [11, 110]. And though nificantly more dangerous than holes in script code. First, these it was originally confined to web pages, JavaScript now appears bugs render attacks more generic: given an exploitable binding in desktop applications, server-side applications, browser ex- bug, attackers need only trigger a path to that bug, rather than tensions, and IoT infrastructure. Organizations like PayPal and craft an entire application-specific attack. Second, binding flaws Walmart use JavaScript to process critical financial information, do not appear in scripts themselves: a script implementor can and as a result implicitly rely on runtimes and binding code for write correct, flawless code and still introduce security errors Violation type Possible consequence Application code JavaScript Crash-safety DOS attacks, including poison-pill attacks [45]; breaking language-level security abstractions, including [41, 42, 94, 109], by introducing a new covert channel. Binding code Type-safety Above + type confusion attacks which, for example, can be used to carry out remote code execution attacks. V8 C++ Memory-safety Above + memory disclosure and memory corruption runtime system attacks which, for example, can be used to leak TLS keys [28] or turn off the same-origin policy [82]. Figure 1—The Blink runtime system uses the V8 JavaScript engine to execute JavaScript application code. Blink also uses V8’s to Table 1—The three types of binding bugs that we describe extend the base JavaScript environment with new functionality and APIs, such as the DOM. This code—which bridges the JavaScript if their code calls flawed runtime routines. As a result, writing application code and Blink’s C++ runtime—is binding code. secure scripts requires not only understanding the language (already a high bar for many), but also knowing all of the bugs or otherwise impersonate other [website] origins” [84]. Table 1 in all of the versions of all of the runtime systems on which the summarizes the security consequences of these classes of bugs. code might run. In §4 we will discuss the precise security implications of safety To address this threat, this paper makes two contributions: violations with respect to the systems that we analyze. 1. A series of effective checkers that find bugs in widely-used We start with an overview of how binding code works in JavaScript runtime systems: Node.js, Chrome’s rendering runtime systems and how untrusted JavaScript application code engine Blink, the Chrome extension system, and PDFium. can call into the trusted C++ runtime system to exploit binding We show how bugs lead to exploitable errors by manually bugs. We find that these bugs often arise because JavaScript en- writing 81 exploits, including multiple out-of-bounds mem- gines like V8 make it easy for developers to violate JavaScript’s ory accesses in Node.js and use-after-frees in Chrome’s crash-, type-, and memory-safety; even V8’s “hello world” code PDFium (two of which resulted in $6,000 in bug bounties). examples depend on hard-crashing functions [105]. We con- clude with a detailed overview of V8-based binding functions. 2. A backwards-compatible binding-code library that wraps Runtime system binding bugs. Runtime systems use the V8 JavaScript engine’s API, preventing bugs without JavaScript engines to execute application code written in imposing significant overhead. Our library does not break JavaScript. For example, the Chrome rendering engine, Blink, any of Node.js’s over 1,000 tests or the test suites of 74 relies on the V8 engine to interpret and run JavaScript code external Node.js-dependent modules. By design, the migra- embedded in web pages as

Web Analytics