• No results found

Relational Symbolic Execution in WebAssembly

N/A
N/A
Protected

Academic year: 2021

Share "Relational Symbolic Execution in WebAssembly"

Copied!
92
0
0

Loading.... (view fulltext now)

Full text

(1)

Relational Symbolic

Execution in WebAssembly

JOHAN SJÖLÉN

KTH ROYAL INSTITUTE OF TECHNOLOGY

SCHOOL OF ELECTRICAL ENGINEERING AND COMPUTER SCIENCE

(2)
(3)

JOHAN SJÖLÉN

Master in Computer Science Date: 2020-08-29

Supervisor: Musard Balliu Examiner: Roberto Guanciale

School of Electrical Engineering and Computer Science Swedish title: Relationssymbolisk körning av WebAssembly

(4)
(5)

Abstract

WebAssembly is a new low-level language used as a compilation target which runs in web browsers. As more code is run on the client side of a web appli- cation the issue of security of that code become more important.

Our work is based in the approach of using formal verification in order to prove that for a program one or more security properties hold. In this thesis we have explored the usage of relational symbolic execution in order to perform formal verification of security properties for WebAssembly programs.

We described a formal semantics of relational symbolic execution for We- bAssembly, implemented it in the Redex framework, extended the implemen- tation for verification of constant-time security, and used the implementation to formally verify multiple sample programs including Salsa20.

Our work shows that relational verification of standard security properties such as non-interference and constant-time security by using relational sym- bolic execution for WebAssembly is a viable approach.

(6)

Sammanfattning

WebAssembly är ett nytt lågnivåspråk vilket fungerar som kompileringsmål och körs i webbläsare. Allt eftersom att mer programkod körs på klientsidan av webbprogram har säkerheten av dessa program blivit viktigare.

Vårt arbete nyttjar formella verifikationsmetoder för att bevisa att ett pro- gram uppfyller en eller flera säkerhetsegenskaper. Mer specifikt har vi i detta examensarbete utforskat användandet av relationssymbolisk exekvering i syf- te att utföra formell verifiering av säkerhetsegenskaper för WebAssemblypro- gram.

Vi har beskrivit en formell semantik för relationssymbolisk exekvering för WebAssembly, implementerat det i Redexramverket, utökat implementationen för verifiering av konstanttidssäkerhet samt använt implementationen för att formellt verifiera flera urval av program, inkluderande Salsa20.

Vårt arbete visar att relationell verifiering av standardsäkerhetsegenska- per såsom icke-interferens samt konstanttidssäkerhet är genomförbart via re- lationssymbolisk exekvering för WebAssembly.

(7)

Acknowledgement

I would like to thank my supervisor Musard Balliu for the time and effort he invested into discussing theory with me and advising me on the thesis report.

(8)

1 Introduction 1

1.1 Problem and Motivation . . . 3

1.2 Related works . . . 4

1.2.1 Self-compositon approach . . . 4

1.2.2 Type system approach . . . 4

1.2.3 Logics approach . . . 5

1.2.4 Relational symbolic execution approach . . . 6

1.3 Research Questions . . . 7

1.4 Ethics and sustainability . . . 8

1.5 Contributions and outline . . . 8

2 Preliminaries 9 2.1 Theory - Logic and semantics . . . 9

2.1.1 While language . . . 9

2.1.2 Hoare logic . . . 12

2.1.3 Relational properties . . . 13

2.1.4 Relational execution . . . 13

2.1.5 Symbolic execution . . . 16

2.2 WebAssembly . . . 19

2.2.1 Type safety . . . 19

2.2.2 Control flow . . . 20

3 Relational Symbolic Execution for WebAssembly 22 3.1 A useful fragment of WebAssembly . . . 22

3.1.1 Semantics . . . 24

3.2 Symbolic WebAssembly . . . 26

3.2.1 Abstracting the heap . . . 27

3.2.2 Symbolic semantics . . . 29

3.2.3 Discharging to SMT solver . . . 31

vi

(9)

3.2.4 Controlling state explosion . . . 32

3.3 Relational symbolic WebAssembly . . . 32

3.3.1 Semantics . . . 33

3.3.2 Discharge to SMT solver re-visited . . . 34

3.3.3 Stepping and collecting configurations . . . 35

4 Results 36 4.1 Implementation and runtime environment . . . 36

4.2 Case studies . . . 36

4.2.1 Program equivalence . . . 37

4.2.2 Non-interference . . . 39

4.2.3 Statistics . . . 44

4.2.4 Constant-time WASM . . . 44

5 Discussion 49 5.1 Results and their limitations . . . 49

5.2 Future work . . . 50

5.2.1 Reimplementation for performance . . . 50

5.2.2 Addition of features . . . 50

6 Conclusions 52

Bibliography 53

A Full WebAssembly grammar 58

B Case studies generated counter examples 60

C Leakage model 65

D Salsa20 WebAssembly translation 67

E Leaked observations 79

(10)
(11)

Introduction

Today it is not uncommon to do your shopping, banking, and private commu- nication over the internet. Since many of these tasks involve some level of secrecy the security of the applications where these tasks are performed is im- portant. As such, much effort has gone into ensuring the security of systems by using technologies such as HTTPS in order to ensure that what the client sends to the server is kept a secret. Websites have grown over the past 25 years from delivery of static HTML to full-blown applications with HTML, CSS, and JavaScript ranging in the megabytes being delivered to each user. The current trend is for client side computation to be favored as opposed to server side computation, and as such the limitations of JavaScript performance wise has become more noticeable and the need for a performant alternative has been established. Development for such an alternative started in the early 2010s with efforts such as asm.js [1] which defined a subset of JavaScript that could be compiled into efficient native code by webbrowsers’ just-in-time compilers.

Emscripten, a C/C++ to asm.js compiler [2], showed that such a runtime could be useful through porting large programs and running them in the web browser.

In 2015 WebAssembly was introduced, superseding asm.js. WebAssembly is a concerted effort between W3C, Mozilla, Microsoft, Google, and Apple to introduce a low-level language with near native execution speed while being safer than traditional low-level languages. With WebAssembly boasting fea- tures such as structured control flow, formally defined stack machine seman- tics, and a static type system we believe that this is a language which is up to the task of providing a more secure web. Others have already published re- search concerning static analysis and formal methods which strengthens this belief. Some of these is the type system CT-Wasm by Watt et al. [3] and program logics such as the one by Watt et al [4] .

1

(12)

In this thesis we will contribute further to this line of work as we present a tool which can formally verify that several desirable security properties holds for arbitrary programs written in a practical fragment of the full WebAssembly specification.

(13)

1.1 Problem and Motivation

Often formal verification is concerned with proving a functional correctness property regarding execution of one program, for example that given an appro- priate input it will produce appropriate an output. However, many interesting properties are naturally expressed by relating a program with two (or more) inputs or two (or more) programs with one input in some manner.

Let us consider a simple example and then move on to some properties which are important to real world software. We will consider an example con- sisting of voting in a general election. We would like it to hold that the result of a vote counting algorithm (P ), taking a sequence of votes as input, tallies those votes identically regardless of the ordering of those votes. This is read- ily expressible by a relational property and would be stated by saying “For all possible pairs of sequences of votes s1, s2, it holds that P (s1) = P (s2) if s2 = σ(s1)” (where σ(x) means “a permutation of x”).

As it turns out many desired security and safety properties may be ex- pressed as relational properties of exactly two programs and one input or one program and two inputs. Two examples of such properties are program equiv- alence and non-interference. Program equivalence expresses that two pro- grams produces the same output when given the same input. As compilers are programs which given a program as input produces another program with equivalent semantics this is an important property for them to hold. Non- interference expresses that given a set of secret variables and a set of public variables the value of the secret variables doesn’t interfere with the values of the public variables. A form of non-interference is constant-time security, which we have seen is quite important with attacks such as Spectre and Melt- down being widely publicized [5]. Constant-time security, in essence, means to not branch on or access memory by values which are secret.

A traditional tool for allowing developers to prove properties regarding a program’s behaviour is Hoare logic named after C.A.R. Hoare who introduced the notion in [6] and many computer systems can verify Hoare logic assertions efficiently. However Hoare logic was designed with non-relational properties in mind, and so much work has gone into verifying relational properties using such off the shelf systems and many different approaches has been taken. What follows is a summary of some of them, including the technique which we have utilised in the work presented in this thesis.

(14)

1.2 Related works

Several methods for formal verification of relational properties of programs have been developed. We have considered four approaches: self-composition, the statically-typed, logics and symbolic execution approaches. Our research is part of the symbolic execution approach. We now give a short overview of each of these approaches.

1.2.1 Self-compositon approach

By a method belonging to the self-composition approach we mean one which operates by manipulation of the abstract syntax trees of a program. That is, there is no interpretation of the meaning of a program that guides the method.

The goal is instead to transform the program such that another off-the-shelf unary verification tool can be utilized to perform the relational verfication.

An example of such a method is self-composition and an early example for use in relational verification was presented in [7] from 2011 by Barthe et al.

Self-composition means to reduce an information flow policy of a program P to a safety property about a single program P ; P0 where P0 is a renaming of P .

This idea of utilizing a syntactic transformation of a program is also con- sidered in the concept of product programs. In Barthe et al. [8] we can again find P and its renamed P0, but this time each statement is intermingled in a specific manner to produce a unary program.

Both of these tools utilize other off-the-shelf tools such as symbolic inter- preters to do the verification of the relational properties.

1.2.2 Type system approach

Type systems are tools which most programmers are familiar with. A type sys- tem attaches some semantic meaning of programs and conducts a check of the program which is equivalent to proof checking (as noted by Curry [9]) while ensuring that the check itself will terminate. Type system solutions inherently overapproximate the semantics of the language.

One such solution for WebAssembly is a type system called CT-Wasm pre- sented by Watt et al [3]. CT-Wasm introduces secret and public types for WebAssembly values along with two new operations classify and declassify.

These operations are only in effect during typechecking where classify makes

(15)

a public value secret and vice versa for declassify. They annotate whole heaps as secret or public1.

This approach ensures that the type system can check that a program satis- fies what is called the constant-time security property. In short, the constant- time security property says that certain operations are leaky and reveals data to an attacker. This data should never be able to be observed by the attacker in order to infer something about some secret piece of data (such as a users’

password). Typically what is considered a leaky operation is branching on a secret-dependent condition (revealing the condition data) or performing a heap access or store with a secret-dependent index (revealing where data was stored to or accessed in the heap).

The development of type systems for low-level assembly languages has a long history, with an early example being TAL developed by Morrisett et al.

in [10] which allows for expressing high-level language abstractions such as user-defined records and closures for low-level RISC-like assembly languages.

Note that the type system based approaches are limited in that they can only check a few specific properties which they were designed for, and that their semantic meaning may be very limited. Both of these are issues which the next method does not have.

1.2.3 Logics approach

Defining a logic means to define a formal system with a set of logical rules with which to formally verify programs. We have identified two works which are of particular relevance to our works. These are work by Watt et al. in [4], which defines a first-order logic for encapsulated WebAssembly, and seminal work by Benton in [11], which defines a relational logic, allowing for the verification of relational properties.

Both of these are based in traditional Hoare logic, as first described by Hoare in [6]. That is, they are based in describing a program along with a con- tract consisting of a pre- and post-condition as a hoare triple. A hoare triple is of the form {P }C{Q} where P is the post-condition, C is the program, and Q is the post-condition. These contracts utilise an assertion language al- lowing for some specific expressivity. For example the assertion language in Watt et al.’s work include separation logic to describe the state of the heap. Fi- nally these triples may be automatically verified by using the set of structural rules which define the logic. Typically logics are only concerned with partial correctness, that is termination needs to be proven separately.

1WebAssembly supports multiple heaps through its module system

(16)

1.2.4 Relational symbolic execution approach

Relational symbolic execution discards the idea of syntactic transformation of some program and instead replaces it with an operational view where relational commands and symbolic values are added to the language. For example values may be unary (v) or paired ((vl, vr)) and are seen as purely symbolic. There may also exist a pair(l, r) command which is executed in a relational manner.

This may allow for more efficient execution, since only a small part of the ex- ecution is required to be relational, as opposed to the purely syntactic product programs. Proving a relational property may be done by discharging condi- tions to a SMT solver. A SMT solver is essentially a tool that given some logi- cal constraints returns if they are satisfiable or not. As opposed to for example CT-Wasm any relational property which is expressible to a SMT solver may be verified. A relational symbolic interpreter might not terminate, this may occur because the program which it is interpreting is itself non-terminating.

It may also have issues recognizing that a while-loop’s test condition is false (since such a test condition might depend on a symbolic value) and therefore may not terminate. This is typically solved by requiring loop invariants to be provided by the programmer or by setting a bound on the number of itera- tions per loop. A lot of work have been developed for this approach, starting with the seminal work of Benton mentioned in the logics approach which not only shows a relational hoare logic but which also shows a type system which captures a form of secure information flow. Other works include but are not limited to ENCoVer developed by Balliu et al. [12] which allows for model checking of non-interference like security policies utilizing epistemic logic, and the work by Farina et al. [13] which defines symbolic relational execution for an imperative language.

This thesis explores the idea of relational symbolic execution for verifying relational properties of WebAssembly programs.

(17)

1.3 Research Questions

With the goal in mind being to explore the feasibility of verifying relational properties in WebAssembly we will now articulate two research questions and one goal which set out a proper scope for this work.

Can relational symbolic execution be formalized to a fragment of WebAssembly?

We will answer this question by presenting a fragment of WebAssembly and formalizing both symbolic and relational symbolic execution for this frag- ment.

If such a formalization is possible we will implement a relational symbolic interpreter and evaluate relational symbolic execution for a substantial

fragment of WebAssembly.

In order to do this we will extend the fragment previously presented and im- plement the interpreter in a programming language. We will then construct several case studies where multiple desirable relational properties are verified for example programs.

How does our relational symbolic interpreter compare to existing methods for WebAssembly?

We hypothesize that other, static, solutions such as specialized type sys- tems are insufficient in verifying more complex properties because of their lack of dynamic runtime information. In order to test this we will compare our solution to that of CT-wasm[3] which is a type-based solution for verifi- cation of constant time safety properties.

(18)

1.4 Ethics and sustainability

The work in this thesis only required the usage of consumer electronics. We did not collect data from any human test subjects, therefore there is no ethical issue with the handling of their data. Our tool allows the formal verification of absence of certain vulnerabilities, this means that it may be used as a tool to discover security flaws. Clearly such a tool is useful to both attackers as well as those developing secure software. The content of this report is focused on software security, as such no sustainability issues have been identified.

1.5 Contributions and outline

The remainder of this report is separated into multiple chapters. First comes the preliminaries, which will introduce key concepts and terminology. If the reader is inexperienced with the problem area then reading this chapter in depth is recommended, otherwise a quick skim ought to be sufficient.

The contributions are developed in chapters 3 and 4. The former presents the formal semantics of a relational symbolic interpreter for a small fragment of WebAssembly. The latter then utilizes the interpreter on a series of simple case studies, specifically case studies verifying program equivalence and non- interference properties. Finally we develop a solution for verifying a constant- time property and utilize this solution in order to verify an implementation of the Salsa20 cipher (as described by Bernstein [14]).

To summarize, our contributions are the following:

• A relational symbolic interpreter for a practical fragment of WebAssem- bly (source code available at: https://github.com/jsjolen/

relsym-wasm)

• A formal semantics presented of the interpreter (chapter 3)

• A formalization of constant-time properties using that interpreter (chap- ter 4)

• Some case studies illustrating the practicality of our approach (chapter 4)

(19)

Preliminaries

This chapter contains introductory material for the concepts which we build upon in chapter 3.

We typeset inline comments usingthe following style.

2.1 Theory - Logic and semantics

In this section we will explore the central theoretical concepts which our work is based upon. We will start by presenting a toy imperative language called While. We will use this language in order to put the rest of the concepts into context. After that we will describe Hoare logic, relational properties, rela- tional execution, and symbolic execution.

2.1.1 While language

The While language is composed of commands (c ∈ hCi), expressions over the integers (e ∈ hExpri), values (z ∈ Z), and variables (v ∈ hVari). We express booleans by letting 1 being true and 0 being false.

In Figure 2.1 the definition of the grammar of commands is presented in Backus-Naur form.

We define a store for While to be a mapping from variables to values, that is a store is s ∈ hVari → Z. Finally we define a configuration for the While language to be a pair hs, ci. A configuration encodes the entire state representing a program and its execution, so for While we include both some program c to be executed and some state s where assignments can be done. A final configuration is one where the program has finished executing, we encode such a configuration in While as hs, i.

9

(20)

hCi ::= hCi;hCiComposition

| hVari := hExpriAssignment

| if hExpri then hCi else hCi endifConditional

| while hExpri hCiendwhileLoop

| skipDo nothing

| End of program

hExpri ::= ZValues

| hExpri + hExpri

| hExpri - hExpri

| hExpri = hExpriEquality comparison

Figure 2.1: Grammar of the While-language.

An experienced programmer may infer the meaning of a While program,

1, nonetheless we will formalize part of While’s semantics by presenting op- erational semantics (also called small step semantics) in the style of Plotkin [15] and evaluation context reduction rules in the style of Felleisen-Hieb [16].

In the rest of the thesis we will only use Felleisen-Hieb style reduction rules however Plotkin’s style with its inspiration from logic is more familiar to the average computer scientist and is therefore useful as an introduction.

Defining semantics

Semantics may be defined using reduction rules. These are similar to inference rules as used in conventional logic. Such rules utilize structural recursion.

We will now describe two rules which define part of the semantics of the While language (see Figure 2.2). The first rule is the [If-true] rule. To read this rule, first start by the (1)and read everything before the ⇒. This is the state that the configuration is currently in. Then read(2), this is the state that the configuration will reduce to if this rule applies. Finally read everything above the line in the middle (by(3)). This is the set of conditions which must hold if the rule can be applied. The ⇑ is similar in function to the ⇒ but only applies to expressions.

In plain English this can be read as the following:

1The meaning of a program if you will

(21)

(1)A configuration whose code is if e then cthnelsecelsendif reduces to(2)cthnif(3)e reduces to 1.

The second rule is for while loops. As an exercise, you may do the same translation into plain English as was done for the [If-true] rule.

[If-true]

(3)hs, ei ⇑ 1

hs,(1)if e then cthnelse cels endifi ⇒ hs,(2)cthni

[While-loop]

hs, ei ⇑ 1

hs, while e c endwhilei ⇒ hs, c;while e c endwhilei

Figure 2.2: [If-true] and [While-loop] semantics in Plotkin-style.

Now we shall consider Felleisen-Hieb style reduction rules. Such reduc- tion rules depends on defining an evaluation context which specifies specific places where holes can occur which can then be plugged with a value. To do so we must first define an evaluation context as seen in Figure 2.3

The hole is [] and can be seen as a place where the next computational step can take place. Given a program hCi we can find some hole hEi and subprogram hCi’ such that hCi = hEi[hCi0]. The term hEi[hCi0] is read as

“in the hole hEi the next computation step to be done will evaluate hCi0”.

Computations are done by replacing hCi0 with some appropriate expressions.

The equivalent reduction rules which were previously presented in Plotkin- style along with the rest of the semantics is as in Figure 2.4.

Note that there is no need to evaluate the test condition explicitly, this is because the definition of the evaluation context specifies that the test-condition must be evaluated first. This style allows for succinct rules where the order of evaluation becomes irrelevant to single rules, heavily simplifying the defini- tion of evaluation for larger languages.

hEi ::= []

| hEi ; hCiFirst evaluate left

| hVari := hEi

| if hEi then hCi else hCiTest-condition first

Figure 2.3: Evaluation context for While.

(22)

[If-true] hs, hEi[if 1 then cthn else cels]i → hs, hEi[cthn]i

[While-loop] hs, hEi[while e c]i → hs, hEi[if e then c; while e c else skip]i [If-false] hs, hEi[if 0 then cthn else cels]i → hs, hEi[cels]i

[Ass] hs, hEi[v := z]i → hs[v 7→ z], hEi[]i [Skip] hs, hEi[skip]i → hs, hEi[]i

Figure 2.4: While expressed as Felleisen-Hieb style reduction relation

2.1.2 Hoare logic

Hoare logic, introduced by Tony Hoare in [6], is a formal verification system based on Hoare triples. A Hoare triple, notated as { P } C { Q } where P and Q are assertions in some assertion language (a logic) and C is a command in our While-language. Such an assertion language is great for stating properties on single programs, an example would be Figure 2.5 which states that the program computes the factorial of n. This Hoare triple can then be verified through some proof system.

1 { x =x0 ∧ n =0}

2 w h i l e ( x > 0 )

3 n : = n * x

4 x : = x -1

5 e n d w h i l e

6 { n = x0!}

Figure 2.5: Hoare triple and factorial program.

However such triples prove insufficient when attempting to express prop- erties that formalize how two programs relate to each other. This leads to a separate definition of relational properties which we will define next.

(23)

Ry−independent= ((s1, s01), (s2, s20))|s1(y) = s2(y) ⇒ s01(y) = s02(y)

Figure 2.6: Relational property expressing y being independent.

2.1.3 Relational properties

Relational properties relate two or more initial and final state pairs to each other. We will focus on relational properties which talk about exactly 2 initial and final states.

Formally a relational property is defined in Definition 1, this definition is taken from Beckert and Ulbrich’s work [17].

Definition 1. Relational property. A relational property R is a set of state pairs, that is R ⊂ (S × S)(S × S). Two programs P1, P2 satisfy R iff

∀s1, s01, s2, s02 ∈ S.s1 −→ sP1 01∧ s2 −→ sP2 02 ⇒ ((s1, s01), (s2, s02)) ∈ R

One example of a relational property would be one which says that y is the same in the final state regardless of the value of the other variables. Later on in this thesis a more general property will be presented based on this one. The property may be expressed directly in the above notation as in Figure 2.6.

As we relate two initial states and two final states it becomes relevant to be able to reduce two initial programs concurrently. We will now define a relational execution which allows for precisely that.

2.1.4 Relational execution

We allow for relational execution of the While-language by extending it in three ways:

• We extend stores from being a mapping V ar → Z to being one V ar → (Z ∪ ⊥ × Z ∪ ⊥) ∪ Z where ⊥ signifies that a value is missing.

• The grammar is extended with a new non-terminal hRCi ::= pairhCihCi where rc ∈ hRCi

• The configuration is changed from hs, ci to hs, rci

With these additions two concrete executions can now be executed in a step-wise fashion. This is not very useful in itself but when combined with

(24)

symbolic execution we will have relational executions representing all possible pairwise execution paths.

Stores and pair commands can be projected either to the left or the right, for example bpair clef t crightclef t ≡ clef t for the pair command and b{x → (1, 2)}clef t ≡ {x → 1} for a store. Stores can also be merged, for example merge-store({x → 1}, {x → 2}) ≡ {x → (1, 2)}.

All that is needed from a relational execution is to define how each differ- ent pairing of instruction from hCi ought to reduce. In essence, unless either the left or right projection is a branching command (while or if in While) then just perform stepwise reduction, otherwise wait until the opposite projection configuration either also reaches a branching command or is final. The re- ductions of projected configurations are done by another reduction relation, in this case it is the concrete unary semantics we presented earlier. This reduc- tion relation is referred to as →, as opposed to the relational−→. We omitrel the [Then/Else],[Else/Then], and [Else/Else] rules for brevity’s sake. These rules are equivalent to the [Then/Then] rule, but with different branches taken depending on the values of the branching condition.

(25)

[If/e]hs, hEi[pair hEilef t[if zl then cthn else cels] cright]i−→rel hs0hEi[pair hEilef t[if zl then cthn else cels ]c0right]i where cright 6≡ hEiright[if zr then cthn−right else cels−right]

and hs, crighti → hs0, c0righti

[e/If]hs, hEi[pair clef t hEiright[if zr then cthn else cels]]i−→rel hs0hEi[pair c0lef t hEiright[if zr then cthn else cels]]i where clef t 6≡ hEilef t[if zl then cthn−lef t else cels−lef t]

and hs, clef ti → hs0, c0lef ti

[Then/Then]hs, hEi[pair hEilef t[if 1 then cthn−l else cels−l] hEiright[if 1 then cthn−r else cels−r ]]i

−→ hs, hEi[pair hEirel lef t[cthn−l]hEiright[cthn−r]]

[Pair-step]hs, hEi[pair clef t cright]i

−→ hmerge-store(srel 0lef t, s0right), hEi[pair c0lef t c0right]i where cright 6≡ hEiright[if z then cthn−right else cels−right] where clef t 6≡ hEilef t[if z then cthn−lef t else cels−lef t] where hs, clef ti → hs0lef t, c0lef ti

and hs, crighti → hs0right, c0righti

Figure 2.7: Reduction rules for relational execution of While.

The relevance of this ordering becomes apparent when execution is ex- tended to a symbolic context. This is because depending on how the semantics are defined an interpreter implementing them may yield different efficiency in execution.

The example in Figure 2.8 illustrates how the store splits into two during reduction and is an application of the [Pair-step] rule.

(26)

h{}, x := 5 | y := 5i−→rel h{x → (5, ⊥), y → (⊥, 5)}, i

Figure 2.8: Example assignment for relational execution.

2.1.5 Symbolic execution

Symbolic execution first appeared in the 1970s, with early work presented by Boyer et al. [18] and by King [19]. There are many use cases for symbolic execution, but our goal is to allow us to verify Hoare-style properties.

Several changes has to be made to the interpreter in order to make it sym- bolic. The first is that some set of operations will operate on a purely syntactic level. In the While-language this will occur for operations performed with ⇑.

Consider the assignment in 2.9.

h{x → x0}, y := x + 5i−−→sym h{x → x0, y → x0+ 5}, i

Figure 2.9: Example of symbolic addition.

In the initial store x is assigned to the symbolic variable x0 and the store after the reduction shows that y is assigned to the syntactic object x0+ 5.

The second change to the interpreter is the addition of a path condition, which we append to configuration. That is, configurations are now a triple s, c, pc. A path condition is a set of logical assertions generated through ex- ecution of the program. Since the test condition of an if or while command may depend on a symbolic value the introduction of non-determinism becomes necessary. We model non-determinism by defining several rules which may be applicable at the same time.

For example we can define non-deterministic reduction rules for if as in Figure 2.10. Note that this also illustrates the usage of the path condition in order to record what the asserted value of the symbolic variable is.

(27)

[If-true]hs, hEi[if e then cthn else cels], pci−−→ hs, csym thn, pc ∧ e = 1i [If-false]hs, hEi[if e then cthn else cels], pci−−→ hs, csym els, pc ∧ e = 0i

Figure 2.10: Symbolic reduction rules for the if command.

Loops, branching and state explosion

There are a few limiting issues with regards to symbolic execution. We have al- ready mentioned the source of one of these limiting issues, and that is the expo- nential state explosion which occurs because of the non-determinism present when branching. There are no solutions to this, the most we can do is prove that a certain configuration is impossible because of some contradiction in the path condition and therefore safely discard such a configuration.

Another issue is with regards to loops. Because of the introduction of symbolic values it is possible that a loop condition can never be proven to be false, and therefore an interpreter cannot soundly exit from a loop. This can be solved by introducing loop invariants, indeed that is what Farina et al. does in [13]. Our work does not include loop invariants however, and so care must be taken when performing case studies to ensure that loops exit condition al- ways depend on concrete values (in essence, the lops must be iterate a constant number of times).

Discharging the path condition Consider the program in Figure 2.11.

x := 5;

if x = 5 then skip

else skip unreachable

Figure 2.11: Unreachable branch example.

Clearly the else branch will never be taken. Let us now do a pen-and-paper run of our symbolic interpreter over this code.

(28)

h{}, x := 5; if x = 5 then skip else skip, truei−−→sym h{x → 5}, if x = 5 then skip else skip, truei−−→sym h{x → 5}, skip, true ∧ x = 5i, h{x → 5}, skip, true ∧ ¬x = 5i−−→sym h{x → 5}, , true ∧ x = 5i, h{x → 5}, , true ∧ ¬x = 5i

Figure 2.12: Unreachable branch execution.

Here the two final configurations differ only in their path condition. The latter configuration contains a contradiction, in that it says that x is both 5 (from the store) and not 5 (from the path condition). If a configuration contains contradictions then there cannot exist a concrete assignment of the variables in the store and in the path condition which satisfies the path condition.

To know if there is a satisfiable concrete assignment to a configuration we can utilize SMT solvers. These solvers were first introduced by Davis et al.

[20] in the 1960s and has seen major development over the decades with mod- ern ones such as Z3 being introduced in 2008 and in active development as late as 2020 by Microsoft[21][22]. The specifics of how these solvers work, while interesting, is not relevant to the rest of the thesis. Instead think of these as black boxes which, given some state, tells us if there is a satisfiable assign- ment (and an example of such an assignment) or that there is no satisfiable assignment (that is, it is unsatisfiable). Finding satisfiable assignments for a propositional logic formula is NP-complete (see Cook [23]), and as such as such a SMT solver’s runtime for a problem may be quite large.

Strongest post-condition

Symbolic execution is a functioning formal verification approach because it computes what is called the strongest post-condition. That is, it computes a set of path conditions, P C, for a program C such that given a contract (P, Q) the following holds if and only if the property which is being proven is valid for P :

∀s ∈ Store.s |= P ⇒

pc

^

P C

pc ⇒ Q

In terms of Hoare logic this means that for some Hoare contract, (P, Q), and for all initial configurations for which P holds, hs, ci |= P ,.

(29)

2.2 WebAssembly

WebAssembly is a new low-level language with the high-level goals of being efficient to decode on the fly and execute and is mainly targetting the current web platform [24]. It is a statically typed stack-machine architecture, including support for heaps, global variables, constants and local variables for function calls. WebAssembly also supports modules and indirect function calls through a function table, however both of these features are not considered in our work and so will not be discussed in any detail.

The semantics of our WebAssembly fragment is based upon the work in [25] by Rossberg et al., therefore small digressions from the official specifica- tion may occur. A WebAssembly configuration is a triple hs, F, ei where s is the current store containing the heap and global variable vector , F is the current frame which is the local variable vector of the current function call and e is the stack of data and instructions to be executed.

The heap is a vector of octets and the global variables and frame are 0- indexed lookup tables with slots of any primitive WebAssembly type.

2.2.1 Type safety

WebAssembly is a statically typed programming language. Type systems are a means of ensuring that certain properties hold for a program, typically with an end goal being that a well typed program does not crash. Traditionally two properties are ensured by a type system (as specified by Felleisen et al.

in [26]), these properties are called progress and preservation. Progress says that a well-typed program will either reduce to another reducible expression or it will be a value. Preservation says that a well-typed program will only reduce to other well-typed programs. Rossberg et al. defines these properties for WebAssembly in [25].

The progress property says that a program will either reduce or it will be a value. In WebAssembly this means that the stack only consists of values, which are encoded as (t.const v) instructions. From this we get a def- inition for final configurations, which says that a final configuration in We- bAssembly is one where the stack only consists of values.

(30)

WebAssembly type annotations

Almost all instructions2in WebAssembly are annotated with a type, for exam- ple the t.const instruction just seen is annotated with the type t, for example i32.const 5. Type annotated instructions are either annotated with a sin- gle type (like t.const) or with an arrow type which describes the effect that the instruction has on the stack. One such instruction is the instruction for looping, which is defined as follows:

loop (tn → tm) e end

The arrow type (also formatted as tf ) says that there must be n poppable values on the stack ’on-top’ of the loop instruction and m instructions will be pushed on the stack by the loop instruction when finished. A consequence of typing the stack in this manner is that a well-typed WebAssembly program always has a valid stack, meaning that the progress and preservation properties from above are valid.

2.2.2 Control flow

WebAssembly’s control flow is more structured compared to other low-level languages. WebAssembly takes an approach inspired by structured program- ming, instead of exposing an arbitrary dynamically computed goto as shown in [27] by Bohm et al. More specifically, WebAssembly supports a block instruction, an if-then-else instruction, a loop instruction along with (mutual) recursion through the call.

Each of these instructions are reduced into (label n {e*}) where n is its argument arity and e* is a continuation. The continuation is only used by loop, where the continuation is the full loop-instruction again.

All constructs can be unwound using the (br i) construct, which un- winds to the ith label continuation, with 0 being the closest. That is, control flow can only move from an inner label to an outer one using (br i).

Let us consider two examples in Figure 2.13 and Figure 2.14 showing how control flow works in WebAssembly where the nop instruction shows where the break jumps to. Note that breaking to a block is forwards jumping, while breaking to a loop is backwards jumping.

2Exclusions to the rule are instructions such as trap which is there only to signify for- bidden code paths.

(31)

1 ( b l o c k ( [ ] → []) b

2 ( b l o c k ( [ ] → [])

3 ( l o o p ( [ ] → [])

4 ( br 2) ) )

5 n o p )

6

Figure 2.13: Break 2 steps, jump forwards.

1 ( l o o p ( [ ] → [])

2 n o p

3 ( br 0) )

4

Figure 2.14: Break 0 steps, jump backwards.

(32)

Relational Symbolic Execution for WebAssembly

With the preliminaries explained we can now go ahead and first describe our implementation of WebAssembly in greater detail (Section A useful fragment of WebAssembly), then move on to symbolic WebAssembly (Section Sym- bolic WebAssembly), and finally develop relational symbolic WebAssembly (Section Relational symbolic WebAssembly).

3.1 A useful fragment of WebAssembly

In order to make the work feasible we decided to reduce the WebAssembly specification presented by Rossberg et al. [25] to a useful core. The simplifi- cations listed in 3.1 were made.

22

(33)

• Modules are removed

• The set of datatypes is reduced to 32-bit integers only (32-bit was chosen since the heap is at most 231− 1 bytes large)

• Dynamic function calls are removed. In other words, it is statically de- ducible which function is being called. This is important when values are raised into the symbolic domain.

• Only a small set of operations are kept, to lower the burden of imple- mentation.

• Only 4-byte aligned stores and loads are allowed. In other words heap[x] is allowed iff x(mod4) ≡ 0.

Figure 3.1: WebAssembly simplifications.

Control flow, local frame variables, global variables and heap access is kept intact.

In this chapter we discuss an even smaller fragment of this language. This is sufficient to illustrate the contributions of our work. The grammar of this language is presented in Figure 3.2.

(34)

t ::= i32

tf ::= ([t ...] → [t ...])

e-no-v ::= (block tf e*) | (loop tf e*) | (if tf then e* else e*) | (br i) | (i32.load oset) | (i32.store oset)

| op

| (label n {e*} e*) v ::= (i32.const c) e ::= e-no-v | v mem ::= Vector<u8>

op ::= binop-i | relop-i relop-i ::= i32.eq

binop-i ::= i32.add | i32.sub | i32.mul | i32.and | i32.or e* ::= ϵ

| (e e*) v* ::= ϵ

| (v v*) c ::= integer oset ::= integer Cong ::= {mem ; e*}

FinalCong ::= {mem ; v*}

E ::= []

| (v E)

| ((label n {e*} E) e*)

Figure 3.2: Grammar of minimal WebAssembly

A configuration Conf ig is defined as consisting of the pair mem; e with mem being the current heap (a vector of octets), and ebeing the current stack.

E, the evaluation context, picks the left-most non-value instruction as the next to be executed. It also describes how to reduce expression stacks contained within a label instruction. Label instructions is the generic control-flow in- struction which all others reduce into. The supported control flow instructions are block, loop, and if, as defined by e-v. A final configuration, defined by F inalConf ig, is one which consists solely of values.

3.1.1 Semantics

We will present the semantics piecewise, excluding some administratory tasks delegated to metafunctions.

(35)

{mem ; E[((i32.const c0) (i32.const c1) op e*)]} [Operation]

{mem ; E[((i32.const c2) e*)]}

where c2 = do-op⟦op, c0, c1 {mem ; E[((i32.const c) (i32.load o set) e*)]} [Load]

{mem ; E[((i32.const c1) e*)]}

where c1 = load-i32⟦mem, c + o set⟧

{mem0 ; E[((i32.const c0) (i32.const c1) (i32.store o set) e*)]} [Store]

{mem1 ; E[e*]}

where mem1 = store-i32⟦mem0, c0 + o set, c1

Figure 3.3: Heap and operation rules for core WebAssembly.

In the [Operation] rule the function do-op performs the computation of the operation op with the operands c0,c1 and reduces by replacing c0,c1 with the result of do-op, c2, on the stack. The function store-i32 produces a new heap with the specified index changed, and the [Store] rule reduces into a configura- tion where this new heap is used. The [Load] rule reduces by loading an index from the heap and replacing the index with the loaded element on the stack.

Let us now consider the main control flow instructions for WebAssembly.

{mem ; E[((i32.const 0) (if tf then e*thn else e*els) e*)]} [If-false]

{mem ; E[((block tf e*els) e*)]}

{mem ; E[((i32.const k) (if tf then e*thnelsee*els) e*)]} [If-true]

{mem ; E[((block tf e*thn) e*)]}

where k0

{mem ; E[(eloop e*1)]} [Loop]

{mem ; Eouter[(elbl e*1)]}

where l = [tm ...], k = [tn ...], (Eouter Ev) = v-split⟦E, k⟧, elbl = (label l {eloop} Ev[e*0]), eloop = (loop ([tn ...] → [tm ...]) e*0)

{mem ; E[((block ([tn ...] → [tm ...]) e*body) e*cont)]} [Block]

{mem ; Eouter[((label l {ϵ} Ev[e*body])e*cont)]}

where l = [tn ...], k = [tm ...], (Eouter Ev) = v-split⟦E, k⟧

Figure 3.4: Control flow rules for core WebAssembly.

As can be seen in Figure 3.4 [If-true] and [If-false] inspect the stack in order to infer which rule ought to be applied. One complexity in these rules is the usage of v-split in the [Block] and [Loop] rule. v-split splits an evaluation context into two. An outer one surrounding the inner context which has nested values around a hole. This is used in order to extract values on the stack into some other stack, as required by operations with a function type signature.

Now the way that forward and respectively backwards breaks are imple-

(36)

mented becomes clear. Block (see [Block]) and loop (see [Loop]) instructions are both turned into label instructions, with loop and its body is saved as a continuation in the label.

Finally let us consider the specific rules regarding the label instruction.

{mem ; E[((label n {e*cont}v*) e*outer)]} [Label-value]

{mem ; E[(v* e*outer)]}

{mem ; E[((label n {e*cont}Einner[((br j)e*1)])e*2)]} [Label-br]

{mem ; E[(Ev[e*cont] e*2)]}

where j = label-depth⟦Einner⟧, (EouterEv) = v-split⟦Einner, n⟧

Figure 3.5: Label rules for core WebAssembly.

If a label is br:d to the [Label-br] rule is invoked and its continuation is placed on the stack, this is how looping is achieved. The label-depth meta- function computes the number of labels within an evaluation context, allowing us to pattern match on the correct label to branch to.

Note that the evaluation context in the grammar defines how to reduce within a label instruction.

3.2 Symbolic WebAssembly

To write a symbolic interpreter for WebAssembly we first extend the grammar of the concrete WebAssembly. Values are raised into the symbolic domain by extending c such that c ∈ i32 ∪ S where S is the set of symbolic variables.

In our grammar it is defined by the non-terminal sym-var which says that all syntactic objects with a prefix of sym-var is considered as part of the symbolic variables (for example sym-var-x is a symbolic variable, x is not). Along with this the representation of the heap is changed to a sequence of store and select expressions. We will expand on this in the next subsection.

A path condition is defined as a sequence of logical assertions, which are implicitly ∧:ed together. Constraint expressions are arithmetic operations over Z∪S. An implication of replacing i32 with Z is that there is a loss of precision in the logical assertions. This loss of precision would be made apparent when overflow, underflow or shift operators occur.

The definition of Conf ig is extended to include a path condition. Se- quences of Conf igs are defined by Conf ig along with another evaluation context EE which captures an evaluation order for sequences of configura- tions. This ordering is arbitrary, any evaluation order is sufficient as long as all

(37)

configurations will be reduced into their final state (if such a state exists). Se- quences of configurations are used in order to capture non-determinism while keeping all of the rules deterministic. We will come back to this concept later when the semantics are introduced in Subsection 3.2.2 at which point the use- fulness will become clear.

sym-var ::= (variable-prefix sym-var) c ::= .... | sym-var | constr-expr constr-expr ::= ArithmeticExpr

constr ::= false

| (= constr-expr constr-expr) | (< constr-expr constr-expr) | (> constr-expr constr-expr) | (select constr-expr constr-expr) | (store constr-expr constr-expr)

pc ::= ϵ | (constr pc) meminst ::= (select c c) | (store c c)

mem ::= ϵ

| (meminst mem) Cong ::= (mem ; e* ; pc) Cong* ::= ϵ | (Cong Cong*)

EE ::= []

| ((mem ; v* ; pc) EE)

Figure 3.6: Extended grammar for symbolic WebAssembly.

Before the re-defined semantics for our symbolic interpreter is presented we will first expand on the choice of switching representation of mem.

3.2.1 Abstracting the heap

Representation of the heap must be changed. To see why, consider the code snippet (const i32 sym-var) (store i32 0 0). No longer can the interpreter know which sequence of bytes are referenced in the heap. To solve this issue we must abstract the heap.

We will abstract the heap by utilizing a lightly modified version of Mc- Carthy’s “Theory of Arrays” from his seminal paper [28]. McCarthy’s orig- inal theory consists of two functions, select and store, operating on a vector, V , of non-specific size. A SMT solver may implement these functions as a way of expressing the contents of vectors and access to the contents of that vector. They are presented alongside our updated versions along with the axiomatization of the theory.

Two things are changed from McCarthy’s definition: The vector argument is moved furthest to the right and is curried. The function select takes an

(38)

select_M: V × N → Z store_M: V × N × Z → V

Figure 3.7: McCarthy’s definition of store and select.

select: N × S → (V → V ) store: N × Z → (V → V )

Figure 3.8: Updated definition of store and select.

∀v ∈ V, i, j ∈ N, e ∈ Z.

i = j ⇒ select_M(store_M(v, j, e), j) = e ∧

i 6= j ⇒ select_M(store_M(v, j, e), j) = select_M(v, j)

Figure 3.9: The axiomatization of McCarthy’s Theory of Arrays.

additional symbolic variable. The call select(0,Var,H) now expresses that “Var is equal to the 0th element in the vector H”. This allows a heap to be expressed as a series of partially applied stores and selects.

The heap compilation procedure compiles from our new definition into a set of selects and one vector with McCarthy’s original functions as described in Algorithm 1.

((select 0 X) ((store 0 5) EmptyHeap))

Figure 3.10: Example store expressed using s-expressions.

This representation comes at the cost of allocating a new symbolic variable for each select done, which may increase the pressure on the SMT solver, we have not investigated any potential performance downsides related to this.

The advantage comes from being very simple to compile to SMT-code for use in discharging to an SMT solver.

(39)

1: function CompileHeap(heap, stmts = ∅, lastHeap = emptyHeap)

2: match heap

3: case store(from,to)(innerHeap)

4: CompileHeap(innerHeap, stmts, store_M(latestHeap,from,to))

5: end case

6: case select(from)(innerHeap)

7: CompileHeap(innerHeap ,stmts ∪ select_M(latestHeap), latestHeap)

8: end case

9: case emptyHeap

10: return stmts, latestHeap

11: end case

12: end match

13: end function

Algorithm 1: Compilation of heap into McCarthy’s definition.

3.2.2 Symbolic semantics

With the basics explained we may now consider the symbolic semantics of WebAssembly. The type of the symbolic reduction relation is changed from Conf ig → Conf ig into Conf ig → Conf ig, this is in order to support non-determinism while keeping the reduction rules deterministic.

The rules [Block], [Label-value], [Label-br], and [Loop] are identical to their current definitions, with the only change being that their reduction is done inside of an EE evaluation context, therefore these are not needed to be shown.

(40)

EE[({mem ; E[((i32.const c0) (i32.const c1) op0e*)] ; pc} Config*)] [Operation]

EE[({mem ; E[((i32.const sym-var) e*)]

(sym-var = c0 op c1 pc)} Config*)]

where op = translate-op⟦op0, sym-var fresh EE[({mem ; E[((i32.const c) (i32.load offset) e*)] ; pc} Config*)] [Load]

EE[({(meminst mem) ; E[((i32.const sym-var-loaded) e*)] ; (constrnooverlap pc)} Config*)]

where meminst = (select offset + c sym-var-loaded), constrnooverlap = (o + c)(mod 4) = 0, sym-var-loaded fresh EE[({mem ; E[((i32.const c0) (i32.const c1) (i32.store offset) e*)] ; pc} Config*)] [Store]

EE[({(meminst mem) ; E[e*] ; (constrnooverlap pc)} Config*)]

where meminst = (store offset + c0c1), constrnooverlap = (offset + c0)(mod 4) = 0 EE[({mem ; E[((const t c) (if then tf e*thn else e*els) e*)] ; pc} Config*)] [If]

EE[({mem ; E[((block tf e*els) e*)] ; (c = 0 pc)}

{mem ; E[((block tf e*thn) e*)] ; (c0 pc)}

Config*)]

Figure 3.11: Symbolic semantics of WebAssembly.

The first two rules, [Operation] and [Load], are now symbolic in that they allocate fresh variables.

For [Operation] a syntactic object representing the operation to be per- formed (where translate-op : op → ArithmOp) of each operation is recorded as an equality constraint on the fresh symbolic variable which is added to the path condition. For [Load] it is recorded in the store, and the syntactic objects are only select or store expressions.

An additional constraint is added to the path condition in both [Load] and [Store] to ensure that memory accesses are only made on 4-byte boundaries.

Without such a constraint the complexity of encoding a path condition for a SMT solver would increase. This is because otherwise a 32-bit integer might be changed on its 16th bit and upwards, forcing us to represent integers as fixed-width bit vectors in the SMT solver instead as members of Z.

The [If] rule is the sole producer of non-determinism in symbolic We- bAssembly. It reduces to two configurations, one where the branch condition is constrained to be exactly 0 in the path condition, and one where it is strictly greater than 0. This is where the introduction of Conf ig becomes relevant.

Now two configurations have been produced, and both will have to be reduced until they are final.

In some cases it can be deduced that exactly one of these branches will be taken, and if so it must be safe to discard the other configuration. This, and the need for stating logical pre- and post-conditions of a program which can be verified, leads to the introduction of a SMT solver which may verify such

(41)

assertions.

3.2.3 Discharging to SMT solver

We discharge to the SMT solver by computing a source-translation from the store and path condition assertions of a configuration to the SMT-LIB language as defined by Barrett et al. [29].

This translation exposes a set of variables which allow for access to the initial and final heap and the individual stack elements after a small-step re- duction has occurred (if any are available). These variables are used by the pre- and post-condition assertions during verification.

Variable name Meaning Optional

heap The heap No

stack-n The nth stack value, 0 being the top, post-reduction Yes

Table 3.1: Variables available for usage in SMT-assertions.

The final-heap is expressed in terms of heap by asserting that it is equal to a set of stores performed in series on heap.

The stack-n variables are only gathered until a non-value is found on the stack.

Each configuration can be checked if it has a satisfying assignment. If no such assignment exists, then there cannot possibly exist a concrete configura- tion which the symbolic configuration represents and therefore that configura- tion may be discarded.

Verification of an initial set of symbolic configurations {C} given a pre- condition P and a post-condition Q is done by:

1. Setting P as the initial path condition

2. Reducing the collection of configurations until only final states remain 3. Checking that Q is valid in each final configuration by searching for a

satisfying assignment to ¬Q.

If a satisfying assignment to ¬Q is found then verification has failed. If it is shown to be unsatisfiable for all final configurations then verification has succeeded.

(42)

3.2.4 Controlling state explosion

Non-determinism leads to a growth of the number of configurations which must be reduced. As this increases, verification time also increases and as such we would like to keep the increase of configurations as low as possible.

The advantage of WebAssembly’s control flow semantics become clear with this in mind. WebAssembly has only a static branching operator, this is in contrast to for example the b instruction for the ARM instruction set. Such an instruction takes a relative address as input which it unconditionally jumps to (see the ARM instruction set manual [30]). The relative address might not be concrete in a symbolic execution, and therefore the symbolic interpreter will have to determine which values such an address can be (typically through an SMT solver) and create exactly one new configuration for each of these possible values. This leads to extreme state explosion. Compare this to We- bAssembly, where each br instruction encodes which label depth to jump to and therefore does not lead to a growth in configurations.

3.3 Relational symbolic WebAssembly

As in the preliminaries we will now define a relational interpreter for We- bAssembly. The major addition which we did not see in the preliminaries is that this final interpreter will also be symbolic, yielding a relational symbolic interpreter. These are the final semantics needed in order to perform verifica- tion of relational properties in WebAssembly.

We will now extend our symbolic WebAssembly grammar such that it is capable of expressing relational programs. This will be done in two ways:

1. The stack is altered such that it consists of a pair of stacks, the left and the right stack, encoding the two programs which relate to each other.

2. The heap is split into a left and right heap, correlating to the respective heap of each program.

The path condition is kept as one, the soundness of this decision is ex- panded upon in a later section.

The paired heap allows for projecting and merging stores in the same man- ner that was seen in the preliminaries.

References

Related documents

This considerably com- plicates the generalization of symbolic bisimulation from pi to spi: (1) we must keep track of when an attacker has learned some piece of information so that

The figure plots a total of 103003 dt static values for each representation, two values for each variant pair comparison (including original) for the 239 program. The value on

Byggstarten i maj 2020 av Lalandia och 440 nya fritidshus i Søndervig är således resultatet av 14 års ansträngningar från en lång rad lokala och nationella aktörer och ett

Omvendt er projektet ikke blevet forsinket af klager mv., som det potentielt kunne have været, fordi det danske plan- og reguleringssystem er indrettet til at afværge

Performance comparison of the Neo4j graph database and the Oracle relational database can also be done through time complexity analy- sis and execution plan analysis for both

From a call to prio, a 4-tuple is returned where the first element is the priority one formula and second element denotes whether the priority one formula is the left direct

The JavaScript implementation of the array sorting algorithms proved to have a significantly better performance in the Chrome browser compared to WebAssembly.. For the rest of

This thesis compared WebAssembly to the technologies ActiveX, Java applets, Asm.js, and Portable Native Client (PNaCl) in terms of their performance, security, and browser support..