wasm_decode (wasm v0.1.0)

View Source

WebAssembly 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

annotated()

-type annotated() ::
          {Height :: non_neg_integer(), instr()} |
          {Height :: non_neg_integer(), Base :: non_neg_integer(), instr()}.

externtype()

-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()}}.

funcidx()

-type funcidx() :: non_neg_integer().

heaptype()

-type heaptype() ::
          func | extern | exn | any | eq | i31 | struct | array | nofunc | noextern | noexn | none |
          {type, typeidx()}.

instr()

-type instr() :: atom() | tuple().

memidx()

-type memidx() :: non_neg_integer().

mut()

-type mut() :: const | var.

numtype()

-type numtype() :: i32 | i64 | f32 | f64.

reftype()

-type reftype() :: {ref, null | nonull, heaptype()}.

tableidx()

-type tableidx() :: non_neg_integer().

typeidx()

-type typeidx() :: non_neg_integer().

valtype()

-type valtype() :: numtype() | vectype() | reftype().

vectype()

-type vectype() :: v128.

Functions

heaptype(Bin)

-spec heaptype(binary()) -> {heaptype(), binary()}.

imports/1

-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.

limits/1

-spec limits(binary()) ->
                {#limits{min :: non_neg_integer(),
                         max :: undefined | non_neg_integer(),
                         shared :: boolean(),
                         index_type :: i32 | i64},
                 binary()}.

module/1

-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.

module_unchecked/1

-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.

name(Bin0)

-spec name(binary()) -> {binary(), binary()}.

A length-prefixed UTF-8 name. Stays a binary: see the module note on why module-controlled data must never become an atom.

reftype/1

-spec reftype(binary()) -> {reftype(), binary()}.

valtype/1

-spec valtype(binary()) -> {valtype(), binary()}.