wasm_decode (wasm v0.1.0)
View SourceWebAssembly binary format decoder.
This is the layer that meets your input first, so read it when you are asking what a malformed module can do. It walks the input with sub-binary matching throughout: no section, name or data segment is copied out of the original binary unless it has to be.
Two invariants matter more than speed here, because this is the layer that faces hostile input directly:
- No atoms from module data. Import, export and custom section names stay binaries. The atom table is node-wide and never reclaimed,
so a module that could mint atoms would be a permanent memory leak and eventually a node kill. `wasm_decode_SUITE` asserts the atom count does not move across a decode. - Bounds before allocation. Every declared vector length is checked against the remaining input before any list is built. Each
vector element occupies at least one byte, so a count exceeding the remaining bytes is unsatisfiable and is rejected in constant time. Without this, `vec` of 4294967295 is a one-line denial of service.
Summary
Functions
The module and field names a decoded module imports.
Decode a module binary.
Decode, letting the internal throw escape. For use inside a capture.
A length-prefixed UTF-8 name. Stays a binary: see the module note on why module-controlled data must never become an atom.
Types
-type annotated() :: {Height :: non_neg_integer(), instr()} | {Height :: non_neg_integer(), Base :: non_neg_integer(), instr()}.
-type externtype() :: {func, typeidx()} | {table, #tabletype{limits :: #limits{min :: non_neg_integer(), max :: undefined | non_neg_integer(), shared :: boolean(), index_type :: i32 | i64}, elemtype :: reftype(), init :: undefined | [instr()]}} | {mem, #memtype{limits :: #limits{min :: non_neg_integer(), max :: undefined | non_neg_integer(), shared :: boolean(), index_type :: i32 | i64}}} | {global, #globaltype{valtype :: valtype(), mut :: mut()}} | {tag, #tagtype{type :: typeidx()}}.
-type funcidx() :: non_neg_integer().
-type heaptype() :: func | extern | exn | any | eq | i31 | struct | array | nofunc | noextern | noexn | none | {type, typeidx()}.
-type memidx() :: non_neg_integer().
-type mut() :: const | var.
-type numtype() :: i32 | i64 | f32 | f64.
-type reftype() :: {ref, null | nonull, heaptype()}.
-type tableidx() :: non_neg_integer().
-type typeidx() :: non_neg_integer().
-type vectype() :: v128.
Functions
-spec imports(#module{identity :: undefined | {sha256, binary()} | reference(), types :: [#subtype{final :: boolean(), supers :: [typeidx()], body :: #functype{params :: [valtype()], results :: [valtype()]} | #structtype{fields :: [#fieldtype{type :: valtype() | i8 | i16, mut :: mut()}]} | #arraytype{field :: #fieldtype{type :: valtype() | i8 | i16, mut :: mut()}}}], rec_groups :: [{non_neg_integer(), non_neg_integer()}], imports :: [#import{module :: binary(), name :: binary(), desc :: externtype()}], funcs :: [#func{type :: typeidx(), locals :: [valtype()], body :: [instr()] | {validated, [annotated()]}}], tables :: [#tabletype{limits :: #limits{min :: non_neg_integer(), max :: undefined | non_neg_integer(), shared :: boolean(), index_type :: i32 | i64}, elemtype :: reftype(), init :: undefined | [instr()]}], mems :: [#memtype{limits :: #limits{min :: non_neg_integer(), max :: undefined | non_neg_integer(), shared :: boolean(), index_type :: i32 | i64}}], tags :: [#tagtype{type :: typeidx()}], globals :: [#global{type :: #globaltype{valtype :: valtype(), mut :: mut()}, init :: [instr()]}], exports :: [#export{name :: binary(), desc :: {func | table | mem | global | tag, non_neg_integer()}}], start :: undefined | funcidx(), elems :: [#elem{type :: reftype(), init :: [[instr()]], mode :: passive | declarative | {active, tableidx(), [instr()]}}], datas :: [#data{init :: binary(), mode :: passive | {active, memidx(), [instr()]}}], data_count :: undefined | non_neg_integer(), customs :: [{binary(), binary()}]}) -> [{binary(), binary()}].
The module and field names a decoded module imports.
An accessor rather than a record field reached by position: the record grew a
field before imports when recursive type groups arrived, and the one caller
that indexed it positionally silently started reading the wrong thing.
-spec limits(binary()) -> {#limits{min :: non_neg_integer(), max :: undefined | non_neg_integer(), shared :: boolean(), index_type :: i32 | i64}, binary()}.
-spec module(binary()) -> {ok, #module{identity :: undefined | {sha256, binary()} | reference(), types :: [#subtype{final :: boolean(), supers :: [typeidx()], body :: #functype{params :: [valtype()], results :: [valtype()]} | #structtype{fields :: [#fieldtype{type :: valtype() | i8 | i16, mut :: mut()}]} | #arraytype{field :: #fieldtype{type :: valtype() | i8 | i16, mut :: mut()}}}], rec_groups :: [{non_neg_integer(), non_neg_integer()}], imports :: [#import{module :: binary(), name :: binary(), desc :: externtype()}], funcs :: [#func{type :: typeidx(), locals :: [valtype()], body :: [instr()] | {validated, [annotated()]}}], tables :: [#tabletype{limits :: #limits{min :: non_neg_integer(), max :: undefined | non_neg_integer(), shared :: boolean(), index_type :: i32 | i64}, elemtype :: reftype(), init :: undefined | [instr()]}], mems :: [#memtype{limits :: #limits{min :: non_neg_integer(), max :: undefined | non_neg_integer(), shared :: boolean(), index_type :: i32 | i64}}], tags :: [#tagtype{type :: typeidx()}], globals :: [#global{type :: #globaltype{valtype :: valtype(), mut :: mut()}, init :: [instr()]}], exports :: [#export{name :: binary(), desc :: {func | table | mem | global | tag, non_neg_integer()}}], start :: undefined | funcidx(), elems :: [#elem{type :: reftype(), init :: [[instr()]], mode :: passive | declarative | {active, tableidx(), [instr()]}}], datas :: [#data{init :: binary(), mode :: passive | {active, memidx(), [instr()]}}], data_count :: undefined | non_neg_integer(), customs :: [{binary(), binary()}]}} | {error, wasm_error:error()}.
Decode a module binary.
-spec module_unchecked(binary()) -> #module{identity :: undefined | {sha256, binary()} | reference(), types :: [#subtype{final :: boolean(), supers :: [typeidx()], body :: #functype{params :: [valtype()], results :: [valtype()]} | #structtype{fields :: [#fieldtype{type :: valtype() | i8 | i16, mut :: mut()}]} | #arraytype{field :: #fieldtype{type :: valtype() | i8 | i16, mut :: mut()}}}], rec_groups :: [{non_neg_integer(), non_neg_integer()}], imports :: [#import{module :: binary(), name :: binary(), desc :: externtype()}], funcs :: [#func{type :: typeidx(), locals :: [valtype()], body :: [instr()] | {validated, [annotated()]}}], tables :: [#tabletype{limits :: #limits{min :: non_neg_integer(), max :: undefined | non_neg_integer(), shared :: boolean(), index_type :: i32 | i64}, elemtype :: reftype(), init :: undefined | [instr()]}], mems :: [#memtype{limits :: #limits{min :: non_neg_integer(), max :: undefined | non_neg_integer(), shared :: boolean(), index_type :: i32 | i64}}], tags :: [#tagtype{type :: typeidx()}], globals :: [#global{type :: #globaltype{valtype :: valtype(), mut :: mut()}, init :: [instr()]}], exports :: [#export{name :: binary(), desc :: {func | table | mem | global | tag, non_neg_integer()}}], start :: undefined | funcidx(), elems :: [#elem{type :: reftype(), init :: [[instr()]], mode :: passive | declarative | {active, tableidx(), [instr()]}}], datas :: [#data{init :: binary(), mode :: passive | {active, memidx(), [instr()]}}], data_count :: undefined | non_neg_integer(), customs :: [{binary(), binary()}]}.
Decode, letting the internal throw escape. For use inside a capture.
A length-prefixed UTF-8 name. Stays a binary: see the module note on why module-controlled data must never become an atom.