=Paper=
{{Paper
|id=Vol-3731/paper30
|storemode=property
|title=Building call graph of WebAssembly programs via abstract semantics
|pdfUrl=https://ceur-ws.org/Vol-3731/paper30.pdf
|volume=Vol-3731
|authors=Mattia Paccamiccio,Franco Raimondi,Michele Loreti
|dblpUrl=https://dblp.org/rec/conf/itasec/PaccamiccioRL24
}}
==Building call graph of WebAssembly programs via abstract semantics==
Building Call Graph of WebAssembly Programs via Abstract Semantics Mattia Paccamiccio1,* , Franco Raimondi2 and Michele Loreti1 1 Università di Camerino, Via Andrea d’Accorso 16, 62032, Camerino, Italy 2 Gran Sasso Science Institute, Viale Francesco Crispi 7, 67100, L’Aquila, Italy Abstract WebAssembly is a binary format for code that is gaining popularity thanks to its focus on portability and performance. Currently, the most common use case for WebAssembly is execution in a browser. It is also being increasingly adopted as a stand-alone application due to its portability. The binary format of WebAssembly, however, makes it prone to being used as a vehicle for malicious software. For instance, one could embed a cryptocurrency miner in code executed by a browser. As a result, there is substantial interest in developing tools for WebAssembly security verification, information flow control, and, more generally, for verifying behavioral properties such as correct API usage. In this document, we address the issue of building call graphs for WebAssembly code. This is important because having or computing a call graph is a prerequisite for most inter-procedural verification tasks. In this paper, we propose a formal solution based on the theory of Abstract Interpretation. We compare our approach to the state-of-the-art by predicting how it would perform against a set of specifically crafted benchmark programs. Keywords WebAssembly, Call Graph, Abstract Interpretation, Semantics, Program Analysis 1. Introduction WebAssembly “is a binary instruction format for a stack-based virtual machine, designed as a portable compilation target for programming languages" 1 . WebAssembly aims to be portable and efficient: it is actively supported by most web browsers and it is employed for rendering streamed videos on devices such as smart televisions, set-top boxes, and other embedded devices [1, 2]. WebAssembly is primarily used in conjunction with JavaScript to optimize tasks that are typically resource-intensive and would benefit from more optimized, close-to-native perfor- mance [1], such as rendering images, streaming videos and generally heavy computational tasks for which interpreted languages might be too slow. In parallel with its adoption, researchers have investigated the issue of verifying WebAssembly code directly [3]. From a security point of view, this is motivated by the fact that WebAssembly, while executing in a relatively safe sandboxed environment, is still the source of security ITASEC 2024: The Italian Conference on CyberSecurity, April 08-12, 2024, Salerno, Italy * Corresponding author. $ mattia.paccamiccio@unicam.it (M. Paccamiccio); f.raimondi@gssi.it (F. Raimondi); michele.loreti@unicam.it (M. Loreti) 0000-0003-2096-0191 (M. Paccamiccio); 0000-0002-9508-7713 (F. Raimondi); 0000-0003-3061-863X (M. Loreti) © 2022 Copyright for this paper by its authors. Use permitted under Creative Commons License Attribution 4.0 International (CC BY 4.0). 1 https://webassembly.org/ CEUR ceur-ws.org Workshop ISSN 1613-0073 Proceedings concerns (information leakage, tampering, policy violation, unauthorized use of computational power, etc.). In terms of formal analysis, a call graph is a prerequisite for verifying several properties of interest, such as information flow analysis, call-ordering requirements, safety, and liveness. We define a call graph as a directed graph that encodes the possible calling relationships between functions or methods in a program, represented as nodes, and edges between two nodes (i.e. 𝑓 , 𝑔) representing that 𝑓 calls 𝑔. Constructing an exact call graph is an undecidable problem. The most adopted approach is soundly approximating all the possible calls, implying that an edge between 𝑓 and 𝑔, in a sound approximation, translates to 𝑓 may call 𝑔. The precision of a call graph depends on the inference rules used, which could include or ignore information about execution paths, context, and so on. 1.1. Contribution We investigate the problem of building a precise call graph for WebAssembly. The simple execution model of WebAssembly, briefly described in Section 2, implies the need to track values on top of the stack to resolve call sites relying on some form of indirection. Other sound approaches can be improved in precision [4, 5], while precise approaches are unsound (MetaDCE2 , WAVM3 , Manticore [6]). We propose an approach based on the theory of Abstract interpretation, aiming to improve precision while retaining soundness. We lay out the paper as follows: we introduce background details about WebAssembly and Abstract interpretation in Section 2; we describe our formal approach in Section 3; we present related work in Section 4 and we conclude in Section 5. 2. Background In this section, we start by giving an overview of WebAssembly. We then introduce basic notions of call graphs, what soundness entails when analyzing WebAssembly modules, and Abstract interpretation. 2.1. Basics of WebAssembly WebAssembly is a simple, typed, assembly-like language whose execution model is based on a stack machine, implementing a set of low-level instructions, less than 200 in total. It is designed to be a universal compilation target, mostly for the LLVM family of programming languages. The resulting binary is then run on an ad-hoc Virtual Machine (VM). The most common example of WebAssembly Virtual Machine as of today is a sandboxed environment in a browser, but stand-alone runtime environments for resource-constrained devices are available4 . Overall, the paradigm is very similar to the one that Java adopts: source code is compiled into bytecode and 2 https://github.com/WebAssembly/binaryen 3 https://wavm.github.io/ 4 https://github.com/bytecodealliance/wasm-micro-runtime then executed by a virtual machine. Such a paradigm allows the code to be executed, in theory, on every platform, as long as a VM is available. WebAssembly manages intra-procedural control flow predictably by using labeled jump-like instructions5 [7]. This allows for the production of a correct control flow graph of a function by simply analyzing the syntax, whereas in more traditional machine code, such as x86 Assembly, control flow graph recovery is an undecidable problem [8, 9]. Notice also that, in WebAssembly, function calls are performed by either the call operator or the call_indirect operator: the latter can be compared to calling a function pointer and, as we will see below, is the only source of lack of precision in call graph construction. In WebAssembly function “names" actually are indices, as such, both of call instructions work on numerical indices, either passed directly to the instruction as in call, or via the stack in call_indirect and referring to an index in a table. 2.2. Call graphs Formally, the call graph of a program is a directed graph 𝐺 = (𝑉, 𝐸) in which the set of nodes 𝑉 is the set of functions in the program, and there exists an edge (𝑓, 𝑔) ∈ 𝐸 ⊆ 𝑉 × 𝑉 if function 𝑓 calls function 𝑔 in the program. Apart from the verification of properties such as call ordering constraints, the availability of a call graph is a requirement in many other inter-procedural analyses that need to track the flow of data in a program. The construction of an exact call graph is an undecidable problem, and call graphs are usually computed by over-approximating the possible calls (i.e., the set 𝐸 contains edges containing all the actual calls, and additional spurious edges). Dynamic features such as function pointers in C/C++ further complicate the issues of computing a call graph. WebAssembly implements function pointers using tables containing references to functions. In object-oriented programming languages, the main approach to static call graph construction is Class Hierarchy Analysis [10] and its improvements. The main idea of Class Hierarchy Analysis is to employ the inheritance hierarchy to refine the set of methods that could be invoked at a specific program location. WebAssembly does not have a class hierarchy, so this method is not directly applicable. Instead, tools like Wassail [4] and Wasmati [11] over-approximate calls by looking at the type of the function appearing in a call_indirect instruction (i.e., the content of the stack is ignored). A more granular call graph can only be constructed by tracking the values that are on top of the stack when call_indirect is used. 2.3. Threats to soundness WebAssembly modules are designed with host-module interoperability in mind. One of the possible interactions they can have includes the host performing mutations of the running instance of the WebAssembly module. To be more specific, instances of shared memories and tables can be mutated, entailing that with no information available to the analysis about what the host code does, the analysis result may be unsound. For example, our property of interest is the call graph. If the target of a call_indirect instruction depends on a value from a memory block mutated by the host code, the analyzer cannot infer this behavior. As another example, 5 https://webassembly.github.io/spec/core/syntax/instructions.html#syntax-instr-control we could consider a table containing function references modified by the host at some point during the execution. For the sake of this work, we assume that the module under examination is closed and address this issue in Section 6. 2.4. Abstract interpretation Abstract interpretation [12] is a theory of sound approximation of the semantics of computer programs. It provides a methodology for defining rigorously sound static analyses. Its goal is to derive information about a program’s semantics without performing an exhausting exploration of the program under analysis. We obtain this by making the semantics decidable at the cost of precision (Rice’s theorem, halting problem) but in a way that the semantics remain sound compared to the concrete semantics. If a concrete interpretation is the evaluation of concrete semantics, an abstract interpretation is the evaluation of abstract semantics. How precise abstract semantics are when compared to concrete semantics is a matter of compromise between computability and tractability of the problem. Due to this factor, tailoring the abstract semantics to the program properties of interest can be an optimal design choice. Abstract interpretation sees application in proving software properties, such as the absence of critical errors, including detecting division-by-zero, NULL pointer dereferencing, etc. Abstract interpretation makes wide use of upper bound operators, denoted by ⊔ ̂︀ and used to compute control-flow joins, and widening, denoted by ∇ and used in the computation of loops ̂︀ and recursion in a way that ensures termination. These operators are sound, and they influence the precision of the result of the analysis. For WebAssembly, to-date the only known abstract interpreter capable of producing a call graph is Sturdy [5, 13]. It uses only non-relational abstractions, which can affect the precision of the analysis. Wassail [4], too, uses notions of abstract interpretation to perform taint analysis. 3. wassilly wassilly (WebAssembly Ain’t So Silly) is an abstract interpreter that we are actively developing. It is tailored to constructing WebAssembly call graphs but can be extended to verify more general properties, such as the absence of runtime errors. We introduce and briefly explain the architecture of the tool via the semantics we construct and some of the peculiarities we design for this implementation. We omit the abstract semantics of particular instructions if they are homomorphic to their concrete counterparts. 3.1. Semantics We introduce concrete and abstract denotational semantics for a simplified version of We- bAssembly. Semantics is the rigorous definition of the meaning of the syntactic constructs of a programming language. The form of semantics we use employs an explicit passing of the next function to compute and is known as continuation semantics. We denote continuations with the symbol 𝜒. In the context of our semantics, we define a memory 𝜎, holding: a store 𝑠𝑒 comprising of bindings of values to global variable declarations; an indexed table referencing functions; a reference to the topmost element (in-context) in the call (or context) stack, 𝜅. The global store and in-context value stack and local variables are available for reading and updating via specific accessors embedded in 𝜎, respectively: .𝑠𝑒, .𝑠𝑘, .𝑙𝑜𝑐. We use an environment 𝜖 to keep track of local and global variable declarations. The symbol 𝜑 is used as a timestamp to differentiate running function instances. 𝜌 is a map from label statements to a tuple consisting of a continuation 𝜒 and a transformation 𝜏 happening in case branching instructions such as br or br_if are interpreted with any given label 𝑙 as the argument. Our semantics take as inputs: 𝜌, 𝜒, 𝜎, 𝜖, 𝜑, 𝜅 and produce a function consisting of the continuation 𝜒 taking as input the new values for 𝜌, 𝜎, 𝜖, 𝜑, 𝜅. The “normal" continuation 𝜒 might be overridden with 𝜒′ in case of non-linear control flow. The semantics we defined cover what we deem as the main language concerns, keeping aside memory operations, which behave similarly to global variables, hence why they are excluded from this work. Typing is assumed always to be i32, 32-bit integer. Auxiliary functions and notations In order to ease the reading of the semantics we define a set of auxiliary functions. These include basic operations on the stack, boolean evaluation, variable allocation, and a timestamp to differentiate function instances. Some functions that will appear in the semantics will be self-explanatory (e.g: 𝑓 𝑖𝑙𝑡𝑒𝑟, 𝑏𝑖𝑛𝑑) and are not included in this table. 𝑙𝑒𝑡 𝑝𝑜𝑝 𝛼 = (𝛼′ , 𝑒) Pop an element (value, label) from the stack. 𝑙𝑒𝑡 𝑝𝑢𝑠ℎ 𝛼 ˜𝛿 = 𝛼′ Push an element (value, label) to the stack. 𝑙𝑒𝑡 𝑝𝑜𝑝_𝑡𝑤𝑖𝑐𝑒 𝜎 = 𝑙𝑒𝑡 𝜎 ′ , 𝑣𝑟𝑖𝑔ℎ𝑡 = 𝑝𝑜𝑝 𝜎 𝑖𝑛 𝑝𝑜𝑝 is performed twice. 𝑙𝑒𝑡 𝜎 ′′ , 𝑣𝑙𝑒𝑓 𝑡 = 𝑝𝑜𝑝 𝜎 ′ 𝑖𝑛 (𝜎 ′′ , 𝑣𝑙𝑒𝑓 𝑡 , 𝑣𝑟𝑖𝑔ℎ𝑡 ) 𝑙𝑒𝑡 𝑝𝑜𝑝_𝑛 𝜎 𝑛 = (𝜎 ′ , 𝑣˜) 𝑝𝑜𝑝 is performed 𝑛 times. 𝑙𝑒𝑡 𝑖𝑛𝑡𝑏𝑜𝑜𝑙 𝜎 = Concrete int-to-boolean evaluation: 𝑙𝑒𝑡 𝜎 ′ , 𝑣 = 𝑝𝑜𝑝 𝜎 𝑖𝑛 0 is 𝑓 𝑎𝑙𝑠𝑒, any non-zero value is 𝑡𝑟𝑢𝑒. 𝑣 = 0 ? (𝜎 ′ , 𝑓 𝑎𝑙𝑠𝑒) : (𝜎 ′ , 𝑡𝑟𝑢𝑒) ˆ︂ 𝜎 𝑙𝑒𝑡 𝑖𝑛𝑡𝑏𝑜𝑜𝑙 ̂︀ = Abstract int-to-boolean evaluation: 𝑙𝑒𝑡 𝜎̂︀′ , 𝑒̂︀ = 𝑝𝑜𝑝 𝜎 ̂︀′ , 𝑒̂︀ ̂︀ 𝑖𝑛 𝜎 the expression 𝑒̂︀ is evaluated later. 𝑙𝑒𝑡 𝑡𝑖𝑐𝑘 𝜑 = 𝜑 ′ Timestamp generator. 𝑙𝑒𝑡 𝑙𝑜𝑐𝑎𝑙 𝑖 = 𝑙𝑜𝑐𝑎𝑙 𝑙𝑜𝑐𝑎𝑡𝑖𝑜𝑛 Allocates a local variable. 𝑙𝑒𝑡 𝑔𝑙𝑜𝑏𝑎𝑙 𝑖 = 𝑔𝑙𝑜𝑏𝑎𝑙 𝑙𝑜𝑐𝑎𝑡𝑖𝑜𝑛 Allocates a global variable. Table 1: Auxiliary functions, briefly explained. 3.1.1. Concrete semantics In Table 2 we define the concrete semantics of the reduced version of the WebAssembly instruc- tion set we studied. To avoid ambiguities, we provide a brief description of the semantics. Concrete semantics Brief description J𝑐1 ; 𝑐2 K𝜌𝜒𝜎𝜖𝜑𝜅 = J𝑐1 K𝜌 (J𝑐2 K𝜒 )𝜎𝜖𝜑𝜅 Sequence of commands J𝑢𝑛𝑜𝑝K𝜌𝜒𝜎𝜖𝜑𝜅 = Class of operations consisting in: 𝑙𝑒𝑡 𝜎 ′ , 𝑣 = 𝑝𝑜𝑝 𝜎.𝑠𝑘 𝑖𝑛 pop once, perform a computation, 𝜒(𝑝𝑢𝑠ℎ 𝜎 ′ [𝑢𝑛𝑜𝑝 𝑣])𝜌𝜖𝜑𝜅 push the result to the stack. J𝑏𝑖𝑛𝑜𝑝K𝜌𝜒𝜎𝜖𝜑𝜅 = Class of operations consisting in: 𝑙𝑒𝑡 𝜎 ′ , 𝑣𝑙𝑒𝑓 𝑡 , 𝑣𝑟𝑖𝑔ℎ𝑡 = 𝑝𝑜𝑝_𝑡𝑤𝑖𝑐𝑒 𝜎.𝑠𝑘 𝑖𝑛 pop twice, perform a computation, 𝜒(𝑝𝑢𝑠ℎ 𝜎 ′ [𝑏𝑖𝑛𝑜𝑝 𝑣𝑙𝑒𝑓 𝑡 𝑣𝑟𝑖𝑔ℎ𝑡 ])𝜌𝜖𝜑𝜅 push the result to the stack. J𝑙𝑜𝑐𝑎𝑙 𝑖 𝑡K𝜌𝜒𝜎𝜖𝜑𝜅 = 𝑙𝑒𝑡 𝜖′ , 𝑛𝑒𝑤𝑣𝑎𝑟 = 𝑛𝑒𝑤𝜖 𝜖 𝜑 (𝑙𝑜𝑐𝑎𝑙 𝑖) 𝑖𝑛 Declaration of local variable 𝑙𝑒𝑡 𝜎 ′ = 𝑏𝑖𝑛𝑑 𝜎 𝜑 0 𝑛𝑒𝑤𝑣𝑎𝑟 𝑖𝑛 (typing 𝑡 is ignored) 𝜒𝜎 ′ 𝜌𝜖′ 𝜑𝜅 J𝑙𝑜𝑐𝑎𝑙.𝑔𝑒𝑡 𝑖K𝜌𝜒𝜎𝜖𝜑𝜅 = Read the value of a local variable, 𝜒(𝑝𝑢𝑠ℎ 𝜎.𝑠𝑘 [𝜎.𝑙𝑜𝑐(𝑖)])𝜌𝜖𝜑𝜅 then push it to the stack J𝑙𝑜𝑐𝑎𝑙.𝑠𝑒𝑡 𝑖K𝜌𝜒𝜎𝜖𝜑𝜅 = 𝑙𝑒𝑡 𝜎 ′ , 𝑣 = 𝑝𝑜𝑝 𝜎.𝑠𝑘 𝑖𝑛 Update a local variable 𝜒(𝜎 ′ .𝑙𝑜𝑐[𝑖 ← 𝑣])𝜌𝜖𝜑𝜅 J𝑔𝑙𝑜𝑏𝑎𝑙 𝑖 𝑡K𝜌𝜒𝜎𝜖𝜑𝜅 = 𝑙𝑒𝑡 𝜖′ , 𝑛𝑒𝑤𝑣𝑎𝑟 = 𝑛𝑒𝑤𝜖 𝜖 𝜑 (𝑔𝑙𝑜𝑏𝑎𝑙 𝑖) 𝑖𝑛 Declaration of global variable 𝑙𝑒𝑡 𝜎 ′ = 𝑏𝑖𝑛𝑑 𝜎 𝜑 0 𝑛𝑒𝑤𝑣𝑎𝑟 𝑖𝑛 (typing 𝑡 is ignored) 𝜒𝜎 ′ 𝜌𝜖′ 𝜑𝜅 J𝑔𝑙𝑜𝑏𝑎𝑙.𝑔𝑒𝑡 𝑖K𝜌𝜒𝜎𝜖𝜑𝜅 = Read the value of a global variable, 𝜒(𝑝𝑢𝑠ℎ 𝜎.𝑠𝑘 [𝜎.𝑠𝑒.𝑔𝑙𝑜𝑏(𝑖)])𝜌𝜖𝜑𝜅 then push it to the stack. J𝑔𝑙𝑜𝑏𝑎𝑙.𝑠𝑒𝑡 𝑖K𝜌𝜒𝜎𝜖𝜑𝜅 = 𝑙𝑒𝑡 𝜎 ′ , 𝑣 = 𝑝𝑜𝑝 𝜎.𝑠𝑘 𝑖𝑛 Update a global variable. 𝜒(𝜎 ′ .𝑠𝑒.𝑔𝑙𝑜𝑏[𝑖 ← 𝑣])𝜌𝜖𝜑𝜅 J𝑙𝑎𝑏𝑒𝑙 𝑙 (𝑐)K𝜌𝜒𝜎𝜖𝜑𝜅 = Evaluation of a labeled block, 𝑙𝑒𝑡 𝜎 ′ , 𝑣˜ = 𝑝𝑜𝑝_𝑛 𝜎.𝑠𝑘 (𝑎𝑟𝑖𝑡𝑦 𝑙) 𝑖𝑛 it can be either Block or Loop. 𝑙𝑒𝑡 𝜎 ′′ = 𝑝𝑢𝑠ℎ (𝑝𝑢𝑠ℎ 𝜎 ′ .𝑠𝑘 [𝑙]) 𝑣˜ 𝑖𝑛 A 𝑓 𝑖𝑥 operator is used to 𝑓 𝑖𝑥(𝜆Θ.[[𝑐]]𝜌[𝑙←𝜏,Θ] 𝜒𝜎′′′ 𝜖𝜑𝜅 ) ensure termination in Loops. J𝑏𝑟 𝑙K𝜌𝜒𝜎𝜖𝜑𝜅 = 𝑙𝑒𝑡 𝜏, 𝜒′ = 𝜌 𝑙 𝑖𝑛 Unconditional branching. 𝜒′ (𝜏 𝜎 ′ )𝜌𝜖𝜑𝜅 J𝑏𝑟_𝑖𝑓 𝑙K𝜌𝜒𝜎𝜖𝜑𝜅 = 𝑙𝑒𝑡 𝜏, 𝜒′ = 𝜌 𝑙 𝑖𝑛 𝑙𝑒𝑡 𝜎 ′ , 𝑡 = 𝑖𝑛𝑡𝑏𝑜𝑜𝑙 𝜎 𝑖𝑛 Conditional branching. 𝑡 ? 𝜒′ (𝜏 𝜎 ′ )𝜌𝜖𝜑𝜅 : 𝜒𝜎 ′ 𝜌𝜖𝜑𝜅 J𝑖𝑓 𝑡ℎ𝑒𝑛 𝑏𝑡 (𝑐𝑡 ) 𝑒𝑙𝑠𝑒 𝑏𝑓 (𝑐𝑓 )K𝜌𝜒𝜎𝜖𝜑𝜅 = Evaluation of if-then-else. 𝑙𝑒𝑡 𝜎 ′ , 𝑡 = 𝑖𝑛𝑡𝑏𝑜𝑜𝑙 𝜎 𝑖𝑛 𝑏𝑡 , 𝑏𝑓 are Block labels. 𝑡 ? J𝑙𝑎𝑏𝑒𝑙 𝑏𝑡 (𝑐𝑡 )K𝜌𝜒𝜎′ 𝜖𝜑𝜅 : J𝑙𝑎𝑏𝑒𝑙 𝑏𝑓 (𝑐𝑓 )K𝜌𝜒𝜎′ 𝜖𝜑𝜅 Concrete semantics Brief description J𝑓 𝑢𝑛 (𝑝 ˜) (𝑟˜) (𝑐)K𝜌𝜒𝜎𝜖𝜑𝜅 = 𝑙𝑒𝑡 𝜑′ = 𝑡𝑖𝑐𝑘 𝜑 𝑖𝑛 Evaluation of a function definition: 𝑙𝑒𝑡 𝜖′ , 𝑛𝑒𝑤𝑣𝑎𝑟𝑠 = 𝑛𝑒𝑤𝜖 𝜖 𝜑′ (𝑑𝑒𝑐𝑙 𝑝 ˜) 𝑖𝑛 as a definition can be recursive, ′ 𝑙𝑒𝑡 𝜎 = 𝑏𝑖𝑛𝑑 𝜎 𝜑 (𝑣𝑎𝑙 𝑝 ′ ˜) 𝑛𝑒𝑤𝑣𝑎𝑟𝑠 𝑖𝑛 we use the 𝑓 𝑖𝑥 operator. 𝑙𝑒𝑡 𝜏 = 𝑓 𝑢𝑛𝜏 ˜𝑟 𝑖𝑛 𝑙𝑒𝑡 𝜅′ = 𝑝𝑢𝑠ℎ 𝜅 (𝜒, 𝜖, 𝜏 ) 𝑖𝑛 𝑓 𝑖𝑥(𝜆Θ.J𝑐K𝜌𝜒(Θ→𝜎′ )𝜖′ 𝜑′ 𝜅′ ) J𝑐𝑎𝑙𝑙 𝑓𝑖 K𝜌𝜒𝜎𝜖𝜑𝜅 = 𝑙𝑒𝑡 𝑓𝑏𝑜𝑑𝑦 = 𝑏𝑜𝑑𝑦 𝑀.𝑓 𝑢𝑛𝑐𝑠[𝑖] 𝑖𝑛 Evaluation of a direct function call: 𝑙𝑒𝑡 𝜎 ′ , 𝑣˜ = 𝑝𝑜𝑝_𝑛 𝜎.𝑠𝑘 (𝑎𝑟𝑖𝑡𝑦 𝑀.𝑓 𝑢𝑛𝑐𝑠[𝑖].𝑡) 𝑖𝑛 the body is retrieved from 𝑀 , 𝑙𝑒𝑡 𝑝𝑎𝑟𝑎𝑚𝑠, ˜︂ ˜𝑟 = 𝑚𝑜𝑑.𝑓 𝑢𝑛𝑐𝑠[𝑖].𝑝𝑎𝑟𝑎𝑚𝑠, the function parameters are bound, 𝑚𝑜𝑑.𝑓 𝑢𝑛𝑐𝑠[𝑖].𝑟𝑒𝑡𝑢𝑟𝑛 𝑖𝑛 then the body is evaluated. 𝑙𝑒𝑡 𝑝 ˜ = 𝑚𝑎𝑝 (𝜆𝑓 𝑝, 𝑣.(𝑓 𝑝, 𝑣)) 𝑝𝑎𝑟𝑎𝑚𝑠,˜︂ 𝑣˜ 𝑖𝑛 J𝑓 𝑢𝑛 (𝑝 ˜) (𝑟˜) (𝑓𝑏𝑜𝑑𝑦 )K𝜌𝜒𝜎′ 𝜖𝜑𝜅 J𝑐𝑎𝑙𝑙_𝑖𝑛𝑑𝑖𝑟𝑒𝑐𝑡 𝑡𝑖 K𝜌𝜒𝜎𝜖𝜑𝜅 = Evaluation of function indirection. 𝑙𝑒𝑡 𝜎 ′ , 𝑓𝑖 = 𝑝𝑜𝑝 𝜎 𝑖𝑛 The reference is held on a table, 𝑙𝑒𝑡 𝑓𝑖𝑑𝑥 𝑖 = 𝜎.𝑠𝑒.𝑓 𝑢𝑛𝑡𝑎𝑏𝑙𝑒[𝑖] 𝑖𝑛 and it is retrieved by J𝑐𝑎𝑙𝑙 (𝑓𝑖𝑑𝑥 𝑖)K𝜌𝜒𝜎′ 𝜖𝜑𝜅 the function 𝑓𝑖𝑑𝑥 𝑖. J𝑟𝑒𝑡𝑢𝑟𝑛K𝜌𝜒𝜎𝜖𝜑𝜅 = 𝑙𝑒𝑡 𝜅′ , 𝜒′ , 𝜖′ , 𝜏 = 𝑝𝑜𝑝 𝜅 𝑖𝑛 Evaluation of return from function. 𝜒′ (𝜏 𝜎)𝜌𝜖𝜑𝜅′ Table 2: Concrete semantics for the subset of the WebAssembly language we studied. 3.1.2. Abstract semantics In Table 3 we introduce the abstract semantics functions. As already mentioned, we only introduce the semantics of instructions that are non-homomorphic to their concrete counterparts. The descriptions on the side provide an explanation of how the abstract semantic functions differ from their concrete version. Abstract semantics Brief description J𝑏𝑟 𝑙K𝜌𝜒̂︀𝜎̂︀𝜖𝜑̂︀𝜅 = 𝑙𝑒𝑡 𝜏, 𝜒′ = 𝜌 𝑙 𝑖𝑛 Abstract 𝑏𝑟, with respect to its ̂︀′ = 𝜏 𝜎 𝑙𝑒𝑡 𝜎 ̂︀ 𝑖𝑛 concrete counterpart, computes the 𝑙𝑒𝑡 𝜌′ , 𝜒′′ = 𝑐𝑎𝑠𝑒 𝑙 𝑜𝑓 : widening (∇) ̂︀ or the upper bound (⊔) ̂︀ 𝑙𝑜𝑜𝑝 → 𝑙𝑒𝑡 𝑟 = 𝜒′ ∇ ̂︀′ 𝑖𝑛 ̂︀ 𝜎 between the “old" computation 𝜒 , ′ 𝜌[𝑙 ← (𝑟), 𝜏 ], 𝑟 and the “new" one, 𝜎̂︀′ . 𝑏𝑙𝑜𝑐𝑘 → 𝑙𝑒𝑡 𝑟 = 𝜒′ ⊔ ̂︀′ 𝑖𝑛 ̂︀ 𝜎 With this mechanism we obtain 𝜒′′ . 𝜌[𝑙 ← (𝑟), 𝜏 ], 𝑟 The ∇ ̂︀ operator ensures termination. 𝑖𝑛 𝜒′′ 𝜎̂︀′ 𝜌′̂︀𝜖𝜑̂︀ 𝜅 Abstract semantics Brief description J𝑏𝑟_𝑖𝑓 𝑙K𝜌𝜒̂︀𝜎̂︀𝜖𝜑̂︀𝜅 = 𝑙𝑒𝑡 𝜏, 𝜒′ = 𝜌 𝑙 𝑖𝑛 A function 𝑓 𝑖𝑙𝑡𝑒𝑟 is applied. ̂︀′ , 𝑒̂︀ = 𝑖𝑛𝑡𝑏𝑜𝑜𝑙 𝑙𝑒𝑡 𝜎 ˆ︂ 𝜎 ̂︀ 𝑖𝑛 This improves the precision 𝑙𝑒𝑡 𝜎 ′ ̂︀𝑡 = 𝜏 (𝑓 𝑖𝑙𝑡𝑒𝑟 𝑒̂︀ 𝜎 ̂︀′ ) 𝑖𝑛 of the analysis. ′ ̂︀𝑓 = 𝑓 𝑖𝑙𝑡𝑒𝑟 ¬̂︀ 𝑙𝑒𝑡 𝜎 𝑒𝜎 ̂︀′ 𝑖𝑛 ̂︀𝑡′ , 𝜎 𝜎 ̂︀𝑓′ are the memories in which, 𝑙𝑒𝑡 𝜌′ , 𝜒′′ = 𝑐𝑎𝑠𝑒 𝑙 𝑜𝑓 : respectively, 𝑒̂︀ is true and false. 𝑙𝑜𝑜𝑝 → 𝑙𝑒𝑡 𝑟 = 𝜒′ ∇ ̂︀𝑡′ 𝑖𝑛 𝜌 [𝑙 ← (𝑟), 𝜏 ], 𝑟 ̂︀ 𝜎 𝑏𝑙𝑜𝑐𝑘 → 𝑙𝑒𝑡 𝑟 = 𝜒 ⊔ ′ ̂︀𝑡′ 𝑖𝑛 𝜌 [𝑙 ← (𝑟), 𝜏 ], 𝑟 ̂︀ 𝜎 𝑖𝑛 𝜒′′ 𝜎̂︀𝑡′ 𝜌′̂︀ 𝜖𝜑̂︀𝜅⊔ ̂︀ 𝜒̂︀𝜎𝑓′ 𝜌̂︀ 𝜖𝜑̂︀ 𝜅 J𝑖𝑓 𝑡ℎ𝑒𝑛 𝑏𝑡 (𝑐𝑡 ; 𝑒𝑛𝑑) 𝑒𝑙𝑠𝑒 𝑏𝑓 (𝑐𝑓 ; 𝑒𝑛𝑑)K𝜌𝜒̂︀𝜎̂︀𝜖𝜑̂︀𝜅 = Computes the upper bound between ̂︀′ , 𝑒̂︀ = 𝑖𝑛𝑡𝑏𝑜𝑜𝑙 𝑙𝑒𝑡 𝜎 ˆ︂ 𝜎 ̂︀ 𝑖𝑛 the computations in 𝑏𝑡 and 𝑏𝑓 . J𝑙𝑎𝑏𝑒𝑙 𝑏𝑡 (𝑐𝑡 ; 𝑒𝑛𝑑)K𝜌𝜒(𝑓 𝑖𝑙𝑡𝑒𝑟 𝑒̂︀ 𝜎̂︀′ )̂︀𝜖𝜑̂︀𝜅 ⊔ ̂︀ Similarly to 𝑏𝑟_𝑖𝑓 , 𝑓 𝑖𝑙𝑡𝑒𝑟 is applied. J𝑙𝑎𝑏𝑒𝑙 𝑏𝑓 (𝑐𝑓 ; 𝑒𝑛𝑑)K𝜌𝜒(𝑓 𝑖𝑙𝑡𝑒𝑟 ¬̂︀𝑒 𝜎̂︀′ )̂︀𝜖𝜑̂︀𝜅 J𝑐𝑎𝑙𝑙_𝑖𝑛𝑑𝑖𝑟𝑒𝑐𝑡 𝑡𝑖 K𝜌𝜒̂︀𝜎̂︀𝜖𝜑̂︀𝜅 = Computes the upper bound between ̂︀′ , 𝑓̂︀𝑖 = 𝑝𝑜𝑝 𝜎 𝑙𝑒𝑡 𝜎 ̂︀ 𝑖𝑛 the calls to the possible callees. 𝑙𝑒𝑡 𝑓𝑖𝑑𝑥 𝑖 = 𝜎.𝑠𝑒.𝑓 ̂︀ 𝑢𝑛𝑡𝑎𝑏𝑙𝑒[𝑖] 𝑖𝑛 Target functions are type filtered. ⊔({∀ ̂︀ 𝑖 ∈ 𝛾( ̂︀ 𝑓̂︀𝑖 )} | J𝑐𝑎𝑙𝑙 (𝑓𝑖𝑑𝑥 𝑖)K𝜌𝜒̂︀𝜎′ ̂︀𝜖𝜑̂︀𝜅 }) The notation omits this detail for triviality and readability. Table 3: Abstract semantics for the subset of the WebAssembly language we studied. In this semantic notation, we omit the specific semantics of the call graph for readability. Briefly explained: any instruction that is not call i or call_indirect t will produce an empty set of callees 𝜑. The call i instruction will produce a singleton {i}, and the call_indirect instruction will produce a set containing the function indexes obtained by resolving the indirections generated by concretizing the abstract value on top of the stack. As noted, we also filter possible callee functions that do not match the type t. 3.2. Architecture of wassilly As mentioned, wassilly is a specialized tool for constructing call graphs in a “reasonably" precise, yet sound, manner. We can achieve this by deducing what value(s) can be on top of the stack when a call_indirect instruction is met. This can be done with a varying degree of precision. For our work, we use a relational abstraction to represent values. We then build specialized data structures on top of it to represent the operand stack, the call stack, and the linear memory. As memory operations are outside the scope of this work, and the call stack does not present any particular novelty, we will not discuss those and will focus solely on the operand stack. One of the peculiarities of our implementation is its capability to analyze WebAssembly code directly as stack machines, relationally, and lazily. This is not the case for other analyzers for languages based on stack machines. For example, Sturdy [13, 5], to the best of our knowledge, is only capable of non-relational analyses when considering stack machines. Other tools, such as Soot [14], Wimpl [15], and cmm_of_wasm6 employ some kind of explicit language transformation step. The second peculiarity of our implementation of the operand stack consists of lazily building abstract expressions on the fly, without necessarily evaluating them (i.e. if we are adding two integer operands together and we are not interested in integer overflows/underflows, we do not evaluate the expression, we pop e1, e2 from the stack and push the resulting mathematical expression e1+e2 to the operand stack). This applies only to the cases in which it is possible to construct an expression representing the content of the stack lazily. For instance, bitwise operations are not performed this way. 3.3. Discussion As the tool is under development a real experimental evaluation cannot be performed yet. Ignoring performance issues, we demonstrate how the tool operates on the common language concerns in WebAssembly. The systematic literature review in [3] exhaustively showcases all of the language concerns when we are interested in building call graphs. We will also make some brief considerations about the language concerns outside the scope of this paper: mutations performed by the host and memory instructions. The work presented in [3] assesses that the main language concerns that are computed in an unsound manner are the reachability of functions in imported and exported tables. In WebAssembly the reachability of references in exported tables should be given for granted, as they are equivalently reachable as exported functions. It can be helpful to think of them as a mutable set of exported function references as opposed to a set of exported functions. Regarding imported functions, while we do not have any information about the behavior of such functions, unless some form of extra information is available, we can always asses that they are reachable and can give an unconstrained function output of the correct typing, as it is explicit in the function signature. Besides these points and concerns relative to unknown imported objects, we are potentially able to cover most of the rest of the concerns with at least the same degree of precision as the state of the art, while retaining soundness, as we use inference rules which consider the semantics of a given program instead of type inference only, and a relational abstraction that allows to derive stronger properties. Some specific language concerns, such as “10: Table init. offset is imported from host" would need some form of specification to provide a conclusive analysis. This means that with no additional information available we either produce a sound and very imprecise analysis or an unsound one, which is not a desirable result for the scope of this work. This need for additional information takes us to the next point: table mutability. The concern of table mutability performed by the host is, in a fundamental way, similar to the previous one: if we are to produce a sound analysis we should assume that calling a host function can mutate the tables, and without information available we are “forced" to be conservative and assume we do not know what function reference is held at any given table entry, making the analysis of some programs as imprecise as an analysis based on function types only. 6 https://github.com/SimonJF/cmm_of_wasm We can generalize these concerns as “Target callee is dependant on host behavior". This covers the concerns: “9: Table is mutated by host", “10: Table init. offset is imported from host", “11: Memory init. offset is imported from host". Further indications about this issue will be discussed in Section 6. 4. Related work WebAssembly was first released in 2017 [16] and can therefore be considered a relatively “young" language. The formal specification and a reference implementation in OCaml are available online [17]. An Isabelle specification is also available in [18]. The work presented in [19] shows how WebAssembly can be used as an attack vehicle to write to memory and cause unexpected behavior in the host environment. The tool Wasmati introduced in [11] employs a graph-based representation of WebAssembly code, called Code Property Graphs (CPG), and checks security properties on these using a query language. Wasmati requires a call graph but can generate one by considering function signatures only. Even in the absence of buffer overflows and similar attacks, it is still possible for WebAssembly to leak information in unintended ways; to address this issue, recent work has focused on information flow analysis, see for instance [7] and [20]. Tools that focus, more in general, on static analysis of WebAssembly include WASP [21], Manticore [6], and Wassail [4]. The first two tools are based, respectively, on symbolic and concolic execution. Wassail, instead, can be seen as a library to develop analyses. Wassail also supports program slicing [22] and the computation of control flow graphs and call graphs. In particular, similar to Wasmati, it can provide an over-approximation of the call graph for a specific WebAssembly module by relying on the types of functions invoked in indirect calls (see Section 2). Sturdy [5, 13] presents another framework to develop and perform static analysis. It is a modular, extensible framework based on abstract interpretation. It supports WebAssembly bytecode and out-of-the-box can perform analyses such as dead code analysis and taint analysis. The work presented in [15] uses a different approach to verifying WebAssembly code. The authors introduce a simplified C-like Intermediate Representation called Wimpl. The goal is to use existing tools for C instead of developing verification tools from scratch. The work in [3] presents a comparison between tools that are capable of constructing a call graph for WebAssembly dynamically or statically, introducing a set of micro-benchmarks that allow the assessment of the precision of a certain analyzer via specifically crafted edge cases. Such edge cases cover the language concerns regarding indirect calls. 5. Conclusion Given the increasing importance of WebAssembly and the importance of having static anal- ysis support, we have given concrete and abstract semantics for a simplified version of the WebAssembly language. We also highlighted some additional challenges which we do not cover in this work and propose means to tackle them. The abstract semantics we specified form the basis of an analyzer based on the theory of Abstract Interpretation that is currently being developed. We also described the approach we are pursuing in the implementation of the analyzer, which features a lazy operand stack and the ability to directly analyze WebAssembly bytecode relationally. Based on this, we can predict our approach to be at least as precise as the sound ones present in the state-of-the-art, whilst potentially being capable of tackling language concerns which, at the moment, are being computed in an unsound manner. 6. Future work The ability of WebAssembly to work as an external mechanism to provide high-performance computations is what makes it so popular. Unfortunately, we cannot assume in a definitive man- ner that all WebAssembly modules work in isolation and are closed. Some possible future work in improving the analysis of WebAssembly modules can and should include some mechanism for defining or inferring host-module(s) interactions. This includes analysis of imported host functions (and subsequently analysis of the functions depending on them) and production of function summaries or a form of specification in a custom Domain Specific Language. Trivially, a DSL would allow to manually set the rules we would instead infer, and would work as a sort of specification contract (i.e: the soundness of the analysis is guaranteed with respect to the contract). A similar approach has been explored in [23] for symbolic execution but, to the best of our knowledge, no approaches are available for abstract interpretation. Both of these approaches have pros and cons: the former keeps the analysis fully automated, but the analysis still has to maintain soundness at the cost of precision. The latter requires the handwriting of a specification and a degree of knowledge about the specific host program interacting with the WebAssembly module: it is potentially more precise, as false positives can be ruled out, but it is prone to be influenced by human error. References [1] A. Ene, How prime video updates its app for more than 8,000 device types, https://www.amazon.science/blog/ how-prime-video-updates-its-app-for-more-than-8-000-device-types, 2022. Accessed: 2022-10-11. [2] T. Schroeder, D. Jordan, S. Schulz, R. Cain, M. Hanley, M. Fay, Introducing the disney+ application development kit (adk), https://medium.com/disney-streaming/ introducing-the-disney-application-development-kit-adk-ad85ca139073, 2021. Accessed: 2022-10-10. [3] D. Lehmann, M. Thalakottur, F. Tip, M. Pradel, That’s a tough call: Studying the challenges of call graph construction for webassembly, in: R. Just, G. Fraser (Eds.), Proceedings of the 32nd ACM SIGSOFT International Symposium on Software Testing and Analysis, ISSTA 2023, Seattle, WA, USA, July 17-21, 2023, ACM, 2023, pp. 892–903. URL: https: //doi.org/10.1145/3597926.3598104. doi:10.1145/3597926.3598104. [4] Q. Stiévenart, C. De Roover, Wassail: a webassembly static analysis library, in: Fifth International Workshop on Programming Technology for the Future Web, 2021. [5] K. Brandl, S. Erdweg, S. Keidel, N. Hansen, Modular abstract definitional interpreters for webassembly, in: K. Ali, G. Salvaneschi (Eds.), 37th European Conference on Object- Oriented Programming, ECOOP 2023, July 17-21, 2023, Seattle, Washington, United States, volume 263 of LIPIcs, Schloss Dagstuhl - Leibniz-Zentrum für Informatik, 2023, pp. 5:1–5:28. URL: https://doi.org/10.4230/LIPIcs.ECOOP.2023.5. doi:10.4230/LIPIcs.ECOOP.2023. 5. [6] M. Mossberg, F. Manzano, E. Hennenfent, A. Groce, G. Grieco, J. Feist, T. Brunson, A. Dinaburg, Manticore: A user-friendly symbolic execution framework for binaries and smart contracts, in: 34th IEEE/ACM International Conference on Automated Software Engineering, ASE 2019, San Diego, CA, USA, November 11-15, 2019, IEEE, 2019, pp. 1186– 1189. URL: https://doi.org/10.1109/ASE.2019.00133. doi:10.1109/ASE.2019.00133. [7] Q. Stiévenart, C. De Roover, Compositional information flow analysis for webassembly programs, in: 2020 IEEE 20th International Working Conference on Source Code Analysis and Manipulation (SCAM), IEEE, 2020, pp. 13–24. [8] M. H. Nguyen, T. B. Nguyen, T. T. Quan, A hybrid aproach for control flow graph construction from binary code, in: 20th Asia-Pacific Software Engineering Conference, 2013. [9] A. Flores-Montoya, E. M. Schulte, Datalog disassembly, in: S. Capkun, F. Roesner (Eds.), 29th USENIX Security Symposium, USENIX Security 2020, August 12-14, 2020, USENIX As- sociation, 2020, pp. 1075–1092. URL: https://www.usenix.org/conference/usenixsecurity20/ presentation/flores-montoya. [10] J. Dean, D. Grove, C. Chambers, Optimization of object-oriented programs using static class hierarchy analysis, in: European Conference on Object-Oriented Programming, Springer, 1995, pp. 77–101. [11] T. Brito, P. Lopes, N. Santos, J. F. Santos, Wasmati: An efficient static vulnerability scanner for webassembly, Computers & Security 118 (2022) 102745. URL: https://www.sciencedirect. com/science/article/pii/S0167404822001407. doi:https://doi.org/10.1016/j.cose. 2022.102745. [12] P. Cousot, R. Cousot, Abstract interpretation: A unified lattice model for static analysis of programs by construction or approximation of fixpoints, in: R. M. Graham, M. A. Harrison, R. Sethi (Eds.), Conference Record of the Fourth ACM Symposium on Principles of Programming Languages, Los Angeles, California, USA, January 1977, ACM, 1977, pp. 238–252. URL: https://doi.org/10.1145/512950.512973. doi:10.1145/512950.512973. [13] S. Keidel, S. Erdweg, T. Hombücher, Combinator-based fixpoint algorithms for big-step abstract interpreters, Proc. ACM Program. Lang. 7 (2023) 955–981. URL: https://doi.org/10. 1145/3607863. doi:10.1145/3607863. [14] R. Vallée-Rai, P. Co, E. Gagnon, L. J. Hendren, P. Lam, V. Sundaresan, Soot - a java bytecode optimization framework, in: S. A. MacKay, J. H. Johnson (Eds.), Proceedings of the 1999 conference of the Centre for Advanced Studies on Collaborative Research, November 8-11, 1999, Mississauga, Ontario, Canada, IBM, 1999, p. 13. URL: https://dl.acm.org/citation.cfm? id=782008. [15] M. Thalakottur, F. Tip, D. Lehmann, M. Pradel, Wimpl: A simple ir for static analysis of webassembly binaries, in: Program Analysis for WebAssembly (PAW) 2022, 2022. [16] A. Haas, A. Rossberg, D. L. Schuff, B. L. Titzer, M. Holman, D. Gohman, L. Wagner, A. Zakai, J. Bastien, Bringing the web up to speed with webassembly, in: Proceedings of the 38th ACM SIGPLAN Conference on Programming Language Design and Implementation (PLDI), 2017, pp. 185–200. [17] A. Rossberg, WebAssembly Core Specification, 2019. URL: https://www.w3.org/TR/ wasm-core-1/. [18] C. Watt, Mechanising and verifying the webassembly specification, in: Proceedings of the 7th ACM SIGPLAN International Conference on certified programs and proofs, 2018, pp. 53–65. [19] D. Lehmann, J. Kinder, M. Pradel, Everything old is new again: Binary security of We- bAssembly, in: 29th USENIX Security Symposium (USENIX Security 20), USENIX As- sociation, 2020, pp. 217–234. URL: https://www.usenix.org/conference/usenixsecurity20/ presentation/lehmann. [20] I. Bastys, M. Algehed, A. Sjösten, A. Sabelfeld, Secwasm: Information flow control for webassembly, in: Program Analysis for WebAssembly (PAW), 2022. [21] F. Marques, J. Fragoso Santos, N. Santos, P. Adão, Concolic Execution for WebAssembly, in: K. Ali, J. Vitek (Eds.), 36th European Conference on Object-Oriented Programming (ECOOP 2022), volume 222 of Leibniz International Proceedings in Informatics (LIPIcs), Schloss Dagstuhl – Leibniz-Zentrum für Informatik, Dagstuhl, Germany, 2022, pp. 11:1– 11:29. URL: https://drops.dagstuhl.de/opus/volltexte/2022/16239. doi:10.4230/LIPIcs. ECOOP.2022.11. [22] Q. Stiévenart, D. W. Binkley, C. De Roover, Static stack-preserving intra-procedural slicing of webassembly binaries, in: Proceedings of the 44th International Conference on Software Engineering, 2022, pp. 2031–2042. [23] N. He, Z. Zhao, J. Wang, Y. Hu, S. Guo, H. Wang, G. Liang, D. Li, X. Chen, Y. Guo, Euno- mia: Enabling user-specified fine-grained search in symbolically executing webassembly binaries, in: R. Just, G. Fraser (Eds.), Proceedings of the 32nd ACM SIGSOFT Inter- national Symposium on Software Testing and Analysis, ISSTA 2023, Seattle, WA, USA, July 17-21, 2023, ACM, 2023, pp. 385–397. URL: https://doi.org/10.1145/3597926.3598064. doi:10.1145/3597926.3598064. Symbols 𝑀 WebAssembly module 𝑃 Program (instruction, function) 𝜖 Environment 𝜖 ̂︀ Abstract environment 𝜎 ̂︀ Abstract memory 𝜎 Memory 𝜒 Continuation 𝜏 Transformation ℐ Interpreter 𝑙 Label 𝜌 Map 𝑙 →− (𝜏, 𝜒) 𝜑 Timestamp 𝜅 Context 𝜅 ̂︀ Abstract context 𝛾 ̂︀ Concretization ⊔ ̂︀ Union ∇̂︀ Widening