-
Notifications
You must be signed in to change notification settings - Fork 0
meld --memory shared --address-rebase skips R_WASM_MEMORY_ADDR_SLEB relocations: a rebased module's i32.const data pointer stays stale -> silent cross-component aliasing (gap in #340) #351
Description
Summary
meld fuse --memory shared --address-rebase rebases a module's data segment and its memarg
load/store offsets (R_WASM_MEMORY_ADDR_LEB) but does not apply R_WASM_MEMORY_ADDR_SLEB
relocations — the ones that materialize a data address as an i32.const in code. So when a rebased
module takes a pointer to its own data (returns &buf[0], passes a buffer address to memcpy,
returns a string/struct pointer), the i32.const stays at the pre-rebase address, pointing into the
other fused component's memory region. Silent cross-component pointer aliasing; the fused module
validates cleanly.
Distinct from #326 (runtime-computed operands that carry no relocation) and #339 (absolute addresses
used as values with no reloc): here a relocation exists (MemoryAddrSleb) and is simply dropped
by the #340 reloc-driven rebasing. Verified on meld 0.41.1.
Reproduction (executed vs wasmtime)
Two C modules, each with a static array at wasm-ld's default base 0x10000, built with
-Wl,--emit-relocs, componentized, fused with --memory shared --address-rebase. In the fused core
module dataB is rebased to 0x30000 (196608), dataA stays at 0x10000:
| export | fused result | expected |
|---|---|---|
get-b(0) (i32.load offset=... — LEB, rebased) |
0xB0B0B0B0 |
0xB0B0B0B0 ✓ |
ptr-b() (returns &dataB[0] — i32.const, SLEB) |
65536 (0x10000, dataA's region) |
196608 (0x30000) |
So ptr-b returns a pointer 128 KB low — into dataA. A guest dereferencing it reads/writes the
other component's buffer.
Root cause (source)
meld-core/src/reloc.rs recognizes MemoryAddrSleb as a memory-address reloc
(RelocType::MemoryAddrSleb code 4; is_memory_addr includes it at ~:136-140), and the input
carries it — b_core.wasm's reloc.CODE = 3 entries MemoryAddrLeb@13, MemoryAddrLeb@33, MemoryAddrSleb@42. But the code-relocation rebase path applies the two MemoryAddrLeb sites and
leaves the MemoryAddrSleb site unrewritten (the i32.const value is unchanged in the fused
output). The data-segment pointer rebasing (rebase_data_segment_pointers, #326 Part C) and the
memarg-offset rebasing are handled; the code i32.const address-materialization (SLEB) is the gap.
Impact
- Any reloc-bearing component that materializes a pointer to its own static data — extremely common
(return&buf, pass a buffer to a host import, string/struct pointers across the canonical ABI) —
silently addresses the wrong component's memory after--address-rebase. This is the exact
"isolation" property--memory shared --address-rebase(fix(326): reloc-driven shared-memory address rebasing (sound --memory shared) #340 , "sound --memory shared") is meant to
provide, so it's a soundness hole in that feature. - Silent:
wasm-tools validate→ VALID; no warning. Deterministic across re-fuses.
Suggested fix
In the code-relocation rebase path, handle MemoryAddrSleb (and MemoryAddrRelSleb) the same as
MemoryAddrLeb — add the module's rebase delta to the i32.const value and re-encode the signed
LEB128 in place (SLEB is fixed-width-padded by wasm-ld, so length is preserved). Add a regression test
that fuses a module returning &data[0] and asserts the pointer equals the rebased base.
Reported by the pulseengine-challenge harness (research-agent lead; clean-room reverified — input SLEB
reloc present, meld output i32.const unchanged, wasmtime execution shows the stale pointer).