Soundness

The type system of WebAssembly is sound, implying both type safety and memory safety with respect to the WebAssembly semantics. For example:

  • All types declared and derived during validation are respected at run time; e.g., every local or global variable will only contain type-correct values, every instruction will only be applied to operands of the expected type, and every function invocation always evaluates to a result of the right type (if it does not trap or diverge).

  • No memory location will be read or written except those explicitly defined by the program, i.e., as a local, a global, an element in a table, or a location within a linear memory.

  • There is no undefined behavior, i.e., the execution rules cover all possible cases that can occur in a valid program, and the rules are mutually consistent.

Soundness also is instrumental in ensuring additional properties, most notably, encapsulation of function and module scopes: no locals can be accessed outside their own function and no module components can be accessed outside their own module unless they are explicitly exported or imported.

The typing rules defining WebAssembly validation only cover the static components of a WebAssembly program. In order to state and prove soundness precisely, the typing rules must be extended to the dynamic components of the abstract runtime, that is, the store, configurations, and administrative instructions. [1]

Results

Results can be classified by result types as follows.

Results val

  • For each value vali in val:

  • Let t be the concatenation of all ti.

  • Then the result is valid with result type [t].

(Sval:t)Sval:[t]

Results trap

Strap:[t]

Store Validity

The following typing rules specify when a runtime store S is valid. A valid store must consist of function, table, memory, global, and module instances that are themselves valid, relative to S.

To that end, each kind of instance is classified by a respective function, table, memory, or global type. Module instances are classified by module contexts, which are regular contexts repurposed as module types describing the index spaces defined by a module.

Store S

 (Sfuncinst:functype)(Stableinst:tabletype)(Smeminst:memtype)(Sglobalinst:globaltype)(Seleminst:reftype)(Sdatainstok)S={funcs funcinst,tables tableinst,mems meminst,globals globalinst,elems eleminst,datas datainst}Sok

Function Instances {type functype,module moduleinst,code func}

functypeokSmoduleinst:CCfunc:functypeS{type functype,module moduleinst,code func}:functype

Host Function Instances {type functype,hostcode hf}

[t1][t2]okS1,val, S1okSS1S1val:[t1]hf(S1;val)Rhf(S1;val), R=S2,result, S2okS1S2S2result:[t2]R=(S2;result)S{type [t1][t2],hostcode hf}:[t1][t2]

Note

This rule states that, if appropriate pre-conditions about store and arguments are satisfied, then executing the host function must satisfy appropriate post-conditions about store and results. The post-conditions match the ones in the execution rule for invoking host functions.

Any store under which the function is invoked is assumed to be an extension of the current store. That way, the function itself is able to make sufficient assumptions about future stores.

Table Instances {type (limits t),elem ref}

limits tokn=limits.min(Sref:t)nS{type (limits t),elem refn}:limits t

Memory Instances {type limits,data b}

limitsokn=limits.min64KiS{type limits,data bn}:limits

Global Instances {type (mut t),value val}

mut tokSval:tS{type (mut t),value val}:mut t

Element Instances {type t,elem ref}

(Sref:t)S{type t,elem ref}:t

Data Instances {data b}

  • The data instance is valid.

S{data b}ok

Export Instances {name name,value externval}

Sexternval:externtypeS{name name,value externval}ok

Module Instances moduleinst

  • Each function type functypei in moduleinst.types must be valid.

  • For each function address funcaddri in moduleinst.funcaddrs, the external value func funcaddri must be valid with some external type func functypei.

  • For each table address tableaddri in moduleinst.tableaddrs, the external value table tableaddri must be valid with some external type table tabletypei.

  • For each memory address memaddri in moduleinst.memaddrs, the external value mem memaddri must be valid with some external type mem memtypei.

  • For each global address globaladdri in moduleinst.globaladdrs, the external value global globaladdri must be valid with some external type global globaltypei.

  • For each element address elemaddri in moduleinst.elemaddrs, the element instance S.elems[elemaddri] must be valid with some reference type reftypei.

  • For each data address dataaddri in moduleinst.dataaddrs, the data instance S.datas[dataaddri] must be valid.

  • Each export instance exportinsti in moduleinst.exports must be valid.

  • For each export instance exportinsti in moduleinst.exports, the name exportinsti.name must be different from any other name occurring in moduleinst.exports.

  • Let functype be the concatenation of all functypei in order.

  • Let tabletype be the concatenation of all tabletypei in order.

  • Let memtype be the concatenation of all memtypei in order.

  • Let globaltype be the concatenation of all globaltypei in order.

  • Let reftype be the concatenation of all reftypei in order.

  • Let n be the length of moduleinst.dataaddrs.

  • Then the module instance is valid with context {types functype, funcs functype, tables tabletype, mems memtype, globals globaltype, elems reftype, datas okn}.

 (functypeok)(Sfunc funcaddr:func functype)(Stable tableaddr:table tabletype)(Smem memaddr:mem memtype)(Sglobal globaladdr:global globaltype)(SS.elems[elemaddr]:reftype)(SS.datas[dataaddr]ok)n(Sexportinstok)(exportinst.name) disjointS{typesfunctype,funcaddrsfuncaddr,tableaddrstableaddr,memaddrsmemaddr,globaladdrsglobaladdr,elemaddrselemaddr,dataaddrsdataaddrn,exportsexportinst }:{typesfunctype,funcsfunctype,tablestabletype,memsmemtype,globalsglobaltype,elemsreftype,datasokn }

Configuration Validity

To relate the WebAssembly type system to its execution semantics, the typing rules for instructions must be extended to configurations S;T, which relates the store to execution threads.

Configurations and threads are classified by their result type. In addition to the store S, threads are typed under a return type resulttype?, which controls whether and with which type a return instruction is allowed. This type is absent (ϵ) except for instruction sequences inside an administrative frame instruction.

Finally, frames are classified with frame contexts, which extend the module contexts of a frame’s associated module instance with the locals that the frame contains.

Configurations S;T

SokS;ϵT:[t]S;T:[t]

Threads F;instr

  • Let resulttype? be the current allowed return type.

  • The frame F must be valid with a context C.

  • Let C be the same context as C, but with return set to resulttype?.

  • Under context C, the instruction sequence instr must be valid with some type [][t].

  • Then the thread is valid with the result type [t].

SF:CS;C,return resulttype?instr:[][t]S;resulttype?F;instr:[t]

Frames {locals val,module moduleinst}

Smoduleinst:C(Sval:t)S{locals val,module moduleinst}:(C,locals t)

Administrative Instructions

Typing rules for administrative instructions are specified as follows. In addition to the context C, typing of these instructions is defined under a given store S. To that end, all previous typing judgements Cprop are generalized to include the store, as in S;Cprop, by implicitly adding S to all rules – S is never modified by the pre-existing rules, but it is accessed in the extra rules for administrative instructions given below.

trap

  • The instruction is valid with type [t1][t2], for any sequences of value types t1 and t2.

S;Ctrap:[t1][t2]

ref.extern externaddr

  • The instruction is valid with type [][externref].

S;Cref.extern externaddr:[][externref]

ref funcaddr

Sfunc funcaddr:func functypeS;Cref funcaddr:[][funcref]

invoke funcaddr

Sfunc funcaddr:func [t1][t2]S;Cinvoke funcaddr:[t1][t2]

labeln{instr0} instr end

  • The instruction sequence instr0 must be valid with some type [t1n][t2].

  • Let C be the same context as C, but with the result type [t1n] prepended to the labels vector.

  • Under context C, the instruction sequence instr must be valid with type [][t2].

  • Then the compound instruction is valid with type [][t2].

S;Cinstr0:[t1n][t2]S;C,labels[t1n]instr:[][t2]S;Clabeln{instr0} instr end:[][t2]

framen{F} instr end

  • Under the return type [tn], the thread F;instr must be valid with result type [tn].

  • Then the compound instruction is valid with type [][tn].

S;[tn]F;instr:[tn]S;Cframen{F} instr end:[][tn]

Store Extension

Programs can mutate the store and its contained instances. Any such modification must respect certain invariants, such as not removing allocated instances or changing immutable definitions. While these invariants are inherent to the execution semantics of WebAssembly instructions and modules, host functions do not automatically adhere to them. Consequently, the required invariants must be stated as explicit constraints on the invocation of host functions. Soundness only holds when the embedder ensures these constraints.

The necessary constraints are codified by the notion of store extension: a store state S extends state S, written SS, when the following rules hold.

Note

Extension does not imply that the new store is valid, which is defined separately above.

Store S

  • The length of S.funcs must not shrink.

  • The length of S.tables must not shrink.

  • The length of S.mems must not shrink.

  • The length of S.globals must not shrink.

  • The length of S.elems must not shrink.

  • The length of S.datas must not shrink.

  • For each function instance funcinsti in the original S.funcs, the new function instance must be an extension of the old.

  • For each table instance tableinsti in the original S.tables, the new table instance must be an extension of the old.

  • For each memory instance meminsti in the original S.mems, the new memory instance must be an extension of the old.

  • For each global instance globalinsti in the original S.globals, the new global instance must be an extension of the old.

  • For each element instance eleminsti in the original S.elems, the new element instance must be an extension of the old.

  • For each data instance datainsti in the original S.datas, the new data instance must be an extension of the old.

S1.funcs=funcinst1S2.funcs=funcinst1 funcinst2(funcinst1funcinst1)S1.tables=tableinst1S2.tables=tableinst1 tableinst2(tableinst1tableinst1)S1.mems=meminst1S2.mems=meminst1 meminst2(meminst1meminst1)S1.globals=globalinst1S2.globals=globalinst1 globalinst2(globalinst1globalinst1)S1.elems=eleminst1S2.elems=eleminst1 eleminst2(eleminst1eleminst1)S1.datas=datainst1S2.datas=datainst1 datainst2(datainst1datainst1)S1S2

Function Instance funcinst

  • A function instance must remain unchanged.

funcinstfuncinst

Table Instance tableinst

  • The table type tableinst.type must remain unchanged.

  • The length of tableinst.elem must not shrink.

n1n2{type tt,elem (fa1?)n1}{type tt,elem (fa2?)n2}

Memory Instance meminst

  • The memory type meminst.type must remain unchanged.

  • The length of meminst.data must not shrink.

n1n2{type mt,data b1n1}{type mt,data b2n2}

Global Instance globalinst

  • The global type globalinst.type must remain unchanged.

  • Let mut t be the structure of globalinst.type.

  • If mut is const, then the value globalinst.value must remain unchanged.

mut=varval1=val2{type (mut t),value val1}{type (mut t),value val2}

Element Instance eleminst

  • The vector eleminst.elem must either remain unchanged or shrink to length 0.

fa1=fa2fa2=ϵ{elem fa1}{elem fa2}

Data Instance datainst

  • The vector datainst.data must either remain unchanged or shrink to length 0.

b1=b2b2=ϵ{data b1}{data b2}

Theorems

Given the definition of valid configurations, the standard soundness theorems hold. [2] [3]

Theorem (Preservation). If a configuration S;T is valid with result type [t] (i.e., S;T:[t]), and steps to S;T (i.e., S;TS;T), then S;T is a valid configuration with the same result type (i.e., S;T:[t]). Furthermore, S is an extension of S (i.e., SS).

A terminal thread is one whose sequence of instructions is a result. A terminal configuration is a configuration whose thread is terminal.

Theorem (Progress). If a configuration S;T is valid (i.e., S;T:[t] for some result type [t]), then either it is terminal, or it can step to some configuration S;T (i.e., S;TS;T).

From Preservation and Progress the soundness of the WebAssembly type system follows directly.

Corollary (Soundness). If a configuration S;T is valid (i.e., S;T:[t] for some result type [t]), then it either diverges or takes a finite number of steps to reach a terminal configuration S;T (i.e., S;TS;T) that is valid with the same result type (i.e., S;T:[t]) and where S is an extension of S (i.e., SS).

In other words, every thread in a valid configuration either runs forever, traps, or terminates with a result that has the expected type. Consequently, given a valid store, no computation defined by instantiation or invocation of a valid module can “crash” or otherwise (mis)behave in ways not covered by the execution semantics given in this specification.