wasm_snapshot_file (wasm v0.3.0)
View SourceThe on-disk form of an initialized runtime snapshot.
Encoding and decoding only: this module opens no file, resolves no module and
calls nothing that could reach the facade, so it stays outside the cycle
wasm_architecture_SUITE asserts on. What it takes and answers is a plain map
of an image's contents, which wasm_snapshot knows how to take apart and put
back together.
Why not term_to_binary/1
For the reason the reaper's journal records: binary_to_term/1 on a planted
file materialises atoms before any structural check can reject the record,
and the atom table is node-wide and never reclaimed. A snapshot lives in a
directory an embedder names, which is exactly where a planted file would go.
So the format is explicit, every field is validated on the way in, and an atom
is decoded through binary_to_existing_atom/2, which cannot create one.
The shape
"WASMIMG\\0" | u16 format | u32 image ABI | u32 payload length
sha256 of the payload, checked before a byte of it is used
payload: u8 codec | u32 uncompressed length | body
body: sections, each u8 tag, u32 length, contentsThe digest covers the payload as stored, so a corrupt file is refused
before it is decompressed rather than after. The uncompressed length is checked
against a caller-supplied ceiling first, because zlib:uncompress/1 on a
crafted three-megabyte stream is otherwise a multi-gigabyte allocation.
What a reader must still not assume
A digest proves the bytes are the bytes that were written. It proves nothing
about who wrote them, and a snapshot is not code but guest state injected
into a live runtime, which is worse rather than better: a table slot holds a
function index, so a planted image is a call to a function the module never
exposed. wasm_snapshot checks every index against the module's own function
count on the way back in, and the directory is documented as being as trusted
as the release, in the same words wasm_code_cache uses.
Summary
Functions
Read an image, or say why it is not one.
The format this build writes. An older one is a miss, never an error.
The value-representation version, bumped by hand.
The atoms the runtime's own values are made of, listed so that loading this module interns them.
Types
Functions
-spec decode(binary(), non_neg_integer()) -> {ok, parts()} | {error, wasm_error:error()}.
Read an image, or say why it is not one.
Max bounds the decompressed size and must come from the module, not from
the file: a length a planted file supplies is not a bound on anything.
Every failure is the same shape, and a caller is expected to treat all of them
as a miss rather than an error, which is what wasm_code_cache promises for
its own reads and does not deliver.
-spec format_version() -> pos_integer().
The format this build writes. An older one is a miss, never an error.
-spec image_abi() -> pos_integer().
The value-representation version, bumped by hand.
-spec own_atoms() -> [atom()].
The atoms the runtime's own values are made of, listed so that loading this module interns them.
unterm/1 decodes a name through binary_to_existing_atom/2, which is right:
nothing in a file may mint an atom. But "existing" is a property of the
emulator at that moment, and Erlang loads modules lazily, so without this the
answer depends on whether some unrelated module carrying the same literal
happened to have been loaded first.
That is not hypothetical. A CPython image holds funcref in its tables, and on
a node that had only started the application binary_to_existing_atom( <<"funcref">>, utf8) raised badarg: the image was refused, lookup/2 turned
the refusal into a miss as it must, and the worker spent 104 seconds capturing
a snapshot it already had on disk. test/audit/ATTEMPTS.md has that run.
Every name here is a literal in this module's source, so it is in this module's
atom table and exists from the moment the module is loaded -- which is before
it can decode anything. The set is exactly what wasm_snapshot's admissible
admits, and the guarantee it restores is only about these names: an atom a
hook kept is still subject to existing already, because that one really does
come from outside.