Skip to content

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

@avrabe

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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    questionFurther information is requested

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions