215 lines · plain
1.. SPDX-License-Identifier: GPL-2.02 3================================================================================4binfmt_wasm: userspace WebAssembly binary container for arch/wasm325================================================================================6 7:Author: Linux-in-the-Browser project8:Status: STABLE for ABI version 19:Implementation: ``arch/wasm32/kernel/binfmt_wasm.c`` and10 ``hardwarejs/src/binfmt.ts``11 12Scope13=====14 15This document specifies the userspace binary container format that16``arch/wasm32`` understands. It is the **only** userspace binary17format supported by the wasm32 port. There is no ELF on wasm32; there18is no thin-ELF wrapper around the wasm payload either. The container19is a *raw WebAssembly module*, distinguished from a generic wasm20module by the presence of a single project-owned custom section.21 22This format is part of the public ABI of the project. The custom23section name and the payload layout described below are NOT permitted24to change in a non-breaking way. Breaking changes require bumping the25``abi_version`` field; the kernel rejects unknown ``abi_version`` with26``-ENOEXEC``.27 28Rationale29=========30 31We chose raw wasm + custom section over a thin-ELF wrapper for four32reasons:33 341. WebAssembly is the ISA. A thin-ELF wrapper would be ELF cosplay to35 make ``file(1)`` happy; the "code" is not machine code for any36 ``e_machine``.372. HardwareJS, the userspace loader, reads our metadata via stock38 ``WebAssembly.Module.customSections()``. No custom parser.393. Standard wasm tooling (``wasm2wat``, ``wasm-objdump``, ``wabt``,40 ``binaryen``) Just Works on the artifacts.414. Custom sections are a first-class wasm construct and MUST be42 ignored by runtimes that don't recognise them. Our artifacts are43 therefore also valid plain wasm modules that can be inspected in44 any wasm runtime.45 46Custom section name47===================48 49::50 51 wasm.linux.exec52 53The name is part of the ABI. The kernel matches it exactly,54case-sensitive, as a UTF-8 byte sequence.55 56(Rationale for this particular name: reverse-DNS-ish and unlikely to57collide with names other ecosystems already use — Emscripten uses58``emscripten_metadata``, the wasm spec defines ``name``, etc.)59 60Custom section payload format61=============================62 63Binary, little-endian, fixed-prefix header followed by length-prefixed64extensions. Total payload MUST be ≤ 256 bytes for ABI version 1;65larger payloads are reserved for future versions and the kernel66rejects them on ABI version 1 with ``-ENOEXEC``.67 68Header (24 bytes, fixed)::69 70 offset size field notes71 ------ ---- --------------------- -----------------------------------72 0 4 magic ASCII "WLEX" (0x57 0x4c 0x45 0x58)73 4 4 abi_version u32 LE; MUST be 174 8 4 flags u32 LE; reserved, MUST be 075 12 4 required_stack_size u32 LE; bytes, 0 = use loader default76 16 4 required_heap_init u32 LE; wasm pages (64 KiB each), 0 = loader default77 20 4 required_heap_max u32 LE; wasm pages, 0 = loader default78 79After the header, a length-prefixed string (entry symbol name):80 81::82 83 24 1 entry_symbol_len u8; 0..127. See below.84 25 N entry_symbol N bytes of ASCII, NOT null-terminated85 86Where ``N == entry_symbol_len``. For ABI version 1, ``entry_symbol``87MUST be exactly ``"_start"`` and ``entry_symbol_len`` MUST be exactly88``6``. The field is reserved for future versions but encoded now so89that older kernels reject newer entry conventions explicitly.90 91After the entry symbol, an optional syscall capability declaration:92 93::94 95 25+N 1 cap_len u8; 0 means "no declaration, all caps allowed"96 26+N M cap_bitmap ceil((max_syscall_nr+1)/8) bytes; bit i set => syscall i required97 M == cap_len; if cap_len == 0, the field is omitted entirely98 99For ABI version 1, the kernel ignores ``cap_bitmap`` (treats every100binary as if it required every syscall). The field is reserved so101that adding capability enforcement in a later version is a non-102breaking change for binaries that set ``cap_len == 0``.103 104Any bytes after the capability bitmap (up to the 256-byte payload105cap) MUST be zero in ABI version 1. The kernel does NOT inspect106them but reserves the right to in future versions.107 108Field semantics109---------------110 111* ``magic``: 4-byte literal ``"WLEX"`` (Wasm Linux EXecutable). Distinguishes112 this section from accidental name reuse by other tooling.113* ``abi_version``: monotonically increasing. ABI version 1 covers the114 layout above. Breaking layout changes bump this. Backwards-compatible115 additions (e.g. consuming previously-reserved fields) MAY land116 without a bump only if a clean default exists for older binaries.117* ``flags``: reserved; the kernel rejects nonzero flags in ABI 1.118* ``required_stack_size``: hint to the loader. The loader is allowed119 to round up, never down. Zero means "use loader default" (currently120 1 MiB).121* ``required_heap_init`` / ``required_heap_max``: hints to the122 loader for the wasm memory's ``initial`` / ``maximum`` page counts.123 Zero means "use loader default" (currently 32 init / 16384 max,124 i.e. 2 MiB / 1 GiB). The loader is allowed to round up the125 initial size if the wasm module's own ``memory`` import declares126 a larger minimum.127* ``entry_symbol``: fixed at ``"_start"`` in ABI 1. The named export128 is called with no arguments after ``__wasm_call_ctors`` (if exported)129 runs.130* ``cap_bitmap``: reserved enforcement; bit i corresponds to syscall131 number i; the syscall table is the upstream x86_64 syscall table132 (mirrored at ``arch/wasm32/include/asm/unistd_64.h``).133 134Detection sequence135==================136 137``binfmt_wasm.c::load_wasm_binary()`` performs, in this order:138 1391. ``bprm->buf`` (which holds the first ``BINPRM_BUF_SIZE`` bytes of140 the file) must contain the wasm magic at offset 0:141 ``\x00 \x61 \x73 \x6d \x01 \x00 \x00 \x00``. If not, return142 ``-ENOEXEC``.1432. Walk the WebAssembly section table starting at offset 8. For each144 section: read the 1-byte section id, the LEB128 section length.145 If id == 0 (custom section), read the LEB128 name length, then146 the name. If the name is exactly ``"wasm.linux.exec"``, found.147 Otherwise skip ``length`` bytes and continue. If end-of-file is148 reached without finding the section, return ``-ENOEXEC``.1493. Within the custom section, verify ``magic == "WLEX"``. If not,150 return ``-ENOEXEC`` (another tool happened to use the same custom151 section name — extremely unlikely, but explicit).1524. Verify ``abi_version == 1``. If not, return ``-ENOEXEC``.1535. Verify ``flags == 0``. If not, return ``-ENOEXEC``.1546. Verify ``entry_symbol == "_start"`` (length 6, byte equality). If155 not, return ``-ENOEXEC``.1567. Hand off to the HardwareJS-spawn-Worker pathway with the parsed157 metadata (stack size, heap pages). The kernel does NOT itself158 load the wasm bytes into memory; HardwareJS does.159 160Step 1's check for the wasm magic prefix at file offset 0 makes161``binfmt_wasm`` the only handler that ever fires for our binaries,162even if other binfmts are registered. There is no fallback ELF163binfmt on wasm32; ABI version 1 reserves no second-chance behaviour.164 165Hand-off to HardwareJS166======================167 168After successful detection, ``binfmt_wasm.c``:169 1701. Calls the upstream helpers it needs to install the new process:171 ``begin_new_exec(bprm)``, ``setup_new_exec(bprm)``,172 ``setup_arg_pages(bprm, ...)``, ``copy_string_kernel(...)`` for173 argv/envp.1742. Builds a small argv/envp blob in the kernel's shared memory.1753. Issues the host call176 ``linux.spawn_worker(pid, blob_ptr, blob_len, argv_ptr, envp_ptr,177 required_stack_size, required_heap_init, required_heap_max)``.1784. The host call does not return on success — control transfers to179 the new Worker. The calling Worker is terminate-and-respawn'd by180 HardwareJS (the PID is preserved; the same ``task_struct`` is181 reused on the kernel side).182 183On any failure between step 1 and step 3, ``binfmt_wasm.c`` does the184upstream cleanup (``send_sig(SIGSEGV, current, 0)``) and returns the185appropriate errno.186 187Generation188==========189 190The wasm binary is produced by ``wackywasm-tools/bin/wasmld``, which191appends the ``wasm.linux.exec`` custom section as a post-link step192(simplest: a separate ``wasm-objcopy``-equivalent run, or a tiny193``wasm-meta`` helper script shipped in ``wackywasm-tools/bin/``). The194defaults are: ``abi_version = 1``, ``flags = 0``,195``required_stack_size = 0`` (use loader default), ``cap_len = 0``.196 197For ABI version 1, the entry symbol is hard-coded to ``"_start"``;198``wasmld`` always emits it as such.199 200Backward / forward compatibility201================================202 203* The kernel ABI version is bumped on any layout change. Older204 kernels reject newer ``abi_version`` values explicitly with205 ``-ENOEXEC``.206* The HardwareJS loader follows the same versioning; the kernel and207 HardwareJS MUST agree on the ABI version they implement. A version208 mismatch between the kernel and HardwareJS is treated as a fatal209 configuration error at boot, not a per-exec error.210 211History212=======213 214* ABI version 1 (this document): initial format. Spec'd 2026-05-22.215