You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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).
Summary
meld fuse --memory shared --address-rebaserebases a module's data segment and its memargload/store offsets (
R_WASM_MEMORY_ADDR_LEB) but does not applyR_WASM_MEMORY_ADDR_SLEBrelocations — the ones that materialize a data address as an
i32.constin code. So when a rebasedmodule takes a pointer to its own data (returns
&buf[0], passes a buffer address to memcpy,returns a string/struct pointer), the
i32.conststays at the pre-rebase address, pointing into theother 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 droppedby 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 coremodule
dataBis rebased to0x30000(196608),dataAstays at0x10000:get-b(0)(i32.load offset=…— LEB, rebased)0xB0B0B0B00xB0B0B0B0✓ptr-b()(returns&dataB[0]—i32.const, SLEB)65536(0x10000, dataA's region)196608(0x30000)So
ptr-breturns a pointer 128 KB low — intodataA. A guest dereferencing it reads/writes theother component's buffer.
Root cause (source)
meld-core/src/reloc.rsrecognizesMemoryAddrSlebas a memory-address reloc(
RelocType::MemoryAddrSlebcode 4;is_memory_addrincludes it at ~:136-140), and the inputcarries it —
b_core.wasm'sreloc.CODE= 3 entriesMemoryAddrLeb@13, MemoryAddrLeb@33, MemoryAddrSleb@42. But the code-relocation rebase path applies the twoMemoryAddrLebsites andleaves the
MemoryAddrSlebsite unrewritten (thei32.constvalue is unchanged in the fusedoutput). The data-segment pointer rebasing (
rebase_data_segment_pointers, #326 Part C) and thememarg-offset rebasing are handled; the code
i32.constaddress-materialization (SLEB) is the gap.Impact
(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 toprovide, so it's a soundness hole in that feature.
wasm-tools validate→ VALID; no warning. Deterministic across re-fuses.Suggested fix
In the code-relocation rebase path, handle
MemoryAddrSleb(andMemoryAddrRelSleb) the same asMemoryAddrLeb— add the module's rebase delta to thei32.constvalue and re-encode the signedLEB128 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.constunchanged, wasmtime execution shows the stale pointer).