From cc56e2949d957abaf673e2231157044161a72944 Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Fri, 10 Jul 2026 17:57:59 +0200 Subject: [PATCH] =?UTF-8?q?chore(release):=20v0.38.0=20=E2=80=94=20memory-?= =?UTF-8?q?strategy=20soundness=20gate=20+=20reproducible=20output?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cut v0.38.0 (0.37.0 → 0.38.0). Scope: #326 shared+rebase soundness gate, #325 --reproducible, the #319 DWARF-verify trilogy, #297 kiln-seam tracking. Safety-doc reconciliation for #326 (the traceability gate for this release): - SR-37 corrected: it had REQUIRED Auto to prefer shared+rebasing for grow-free multi-memory inputs — an unsound requirement (it specified a corrupting transform). Now requires Auto → multi-memory (sound); shared+rebase gated to explicit opt-in. Scoped release: v0.38.0, status verified. - LS-M-11 added: shared-memory address rebasing does not rebase dynamic addresses → silent computed-pointer corruption; gate mitigation + pending rewriter fix. Pinned by ls_m_11_auto_gates_unsound_shared_rebase (renamed from auto_gates_shared_for_growfree_inputs_326 for the LS-N gate). - ADR-4 superseded-in-part: its "Auto is not flipped" decision assumed shared+rebase was a sound alternative isolation model; #326 proved it corrupts, so the rejection rationale is void and Auto now resolves to multi. Corrected the stale "log-only, no behavior change, resolution unchanged" note. - SWV-37 verification updated to the renamed test. `rivet release status v0.38.0` → cuttable (1 artifact, verified). `rivet validate` PASS. CHANGELOG [0.38.0] with falsification statements + the default-behaviour-change callout. Refs #326, #325, #319, #297, ADR-4, SR-37, LS-M-11. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 58 +++++++++++++++++ Cargo.lock | 4 +- Cargo.toml | 2 +- meld-core/tests/auto_memory.rs | 2 +- .../ADR-4-inter-component-isolation-model.md | 34 ++++++++-- safety/requirements/safety-requirements.yaml | 39 +++++++---- safety/requirements/sw-verifications.yaml | 2 +- safety/stpa/loss-scenarios.yaml | 64 +++++++++++++++++++ 8 files changed, 181 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eda685b..ec74e0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,64 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +## [0.38.0] - 2026-07-10 + +Correctness release: a **soundness fix** to the default memory strategy, a +**reproducible-build** flag, and the completion of the DWARF-verify trilogy. + +**⚠ Default-behaviour change (#326):** `meld fuse --memory auto` (the default) +**no longer selects shared memory + address rebasing** — it always resolves to +multi-memory. The shared+rebase path was silently corrupting memory for any +component that addresses memory via computed pointers (see below). This +**supersedes ADR-4's prior "`Auto` is not flipped" decision** (that decision +assumed shared+rebase was a sound alternative isolation model; #326 proved it +corrupts). Consequence: default fused output now needs `wasm-opt +--enable-multimemory` and loses single-address-space (MCU) lowering until the +correct-rebasing follow-up lands; use explicit `--memory shared --address-rebase` +(now warns loudly) if you knowingly want the old behaviour. + +**Falsification:** if `--memory auto` still selected shared+rebase for a +grow-free multi-memory input, `ls_m_11_auto_gates_unsound_shared_rebase` would +fail — it asserts multi-memory + two separate memories. If `--reproducible` +fusion were not byte-stable, `test_reproducible_attestation_is_byte_stable` +would fail (and its control asserts non-reproducible output *does* differ, so +the flag can't silently no-op). If the DWARF remap emitted a wrong length, +range, or location, `llvm-dwarfdump --verify` would report it — instead the +records+variants fixtures go from 1049 errors to 0. + +### Added + +- **`--reproducible` flag for byte-stable output (#325).** `meld fuse + --reproducible` derives the attestation id from the output content and takes + the timestamp from `SOURCE_DATE_EPOCH` (default epoch 0) instead of a random + UUID + wall clock, so identical input yields an identical sha256 — a + prerequisite for reproducible-build / supply-chain attestation. (Root cause of + the nondeterminism was the attestation section, not fusion; the fused module + was already deterministic.) + +### Fixed + +- **Shared-memory fusion silently corrupted memory (#326, LS-M-11) — GATED.** + Address rebasing only relocated the static memarg offset (and bulk-memory + ops), never the dynamic address operand of ordinary loads/stores, so + computed-pointer access collided across components with no trap or error. + `Auto` now gates this unsound path (→ multi-memory); explicit shared+rebase + warns. SR-37 corrected (it had *required* the unsound preference); ADR-4 + superseded; the correct runtime dynamic-address rebasing in `rewriter.rs` is a + tracked follow-up. +- **DWARF-verify trilogy complete (#319, #320/#321/#322).** `--dwarf remap` now + rewrites `DW_AT_high_pc` lengths, `DW_AT_ranges` lists, and `DW_AT_location` + lists to the fused layout — `llvm-dwarfdump --verify` goes from 1049 errors to + 0 on the records+variants fixtures. + +### Changed + +- **Tier-C meld→kiln execution seam (#297)** tracked to the current kiln-side + blocker (E0BBB on kiln#375; kiln#364 + kiln#382/E5DC2 resolved upstream); + meld's fused output is spec-valid throughout (runs on wasmtime 41). Test stays + `#[ignore]`d pending kiln. +- Adopted rivet v0.22.0 release-handling as the pre-tag readiness gate (#318). + ## [0.37.0] - 2026-06-26 Downstream-boundary release: meld now (a) emits machine-checkable **fusion diff --git a/Cargo.lock b/Cargo.lock index 0ab3a79..2565a5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1381,7 +1381,7 @@ checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4" [[package]] name = "meld-cli" -version = "0.37.0" +version = "0.38.0" dependencies = [ "anyhow", "clap", @@ -1396,7 +1396,7 @@ dependencies = [ [[package]] name = "meld-core" -version = "0.37.0" +version = "0.38.0" dependencies = [ "anyhow", "bitflags", diff --git a/Cargo.toml b/Cargo.toml index 94ec7c1..c2df652 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ exclude = [ ] [workspace.package] -version = "0.37.0" +version = "0.38.0" authors = ["PulseEngine "] edition = "2024" license = "Apache-2.0" diff --git a/meld-core/tests/auto_memory.rs b/meld-core/tests/auto_memory.rs index b4677e7..772d41e 100644 --- a/meld-core/tests/auto_memory.rs +++ b/meld-core/tests/auto_memory.rs @@ -185,7 +185,7 @@ fn validates_without_multimemory(wasm: &[u8]) -> bool { /// plausible-but-wrong one). Shared remains reachable only via explicit /// `--memory shared --address-rebase`, which warns loudly. #[test] -fn auto_gates_shared_for_growfree_inputs_326() { +fn ls_m_11_auto_gates_unsound_shared_rebase() { let component_a = build_component(build_module_a()); let component_b = build_component(build_module_b(false)); diff --git a/safety/adr/ADR-4-inter-component-isolation-model.md b/safety/adr/ADR-4-inter-component-isolation-model.md index be22252..7702fa9 100644 --- a/safety/adr/ADR-4-inter-component-isolation-model.md +++ b/safety/adr/ADR-4-inter-component-isolation-model.md @@ -116,9 +116,25 @@ shared mode. ## `Auto`-default policy (resolved — maintainer decision) -The `Auto` strategy (#172) prefers `SharedMemory` + rebasing when -provably sound (no `memory.grow`, ≥2 input memories), else `MultiMemory`, -with a runtime downgrade shared→multi if shared fusion refuses. +> **⚠ SUPERSEDED IN PART by #326 (v0.38.0).** The decision below rejected +> flipping `Auto` to prefer `MultiMemory` on the premise that shared+rebasing +> was a *sound* alternative isolation model with a size benefit. #326 proved +> that premise false: address rebasing does not relocate the dynamic address +> operand of ordinary loads/stores, so shared fusion silently corrupts any +> component using computed pointers (LS-M-11). With no valid benefit to +> preserve, `Auto` now resolves to `MultiMemory` (the sound strategy) and no +> longer selects shared+rebase; shared+rebase is reachable only by explicit +> `--memory shared --address-rebase`, which warns loudly (PR #329, SR-37 as +> corrected). Item 2's "flipping was rejected" is thus reversed — not to change +> an isolation *model*, but to stop defaulting to a *corrupting* transform. The +> single-address-space (MCU) benefit shared fusion was meant to give (#298/#299) +> returns only once correct dynamic-address rebasing lands in `rewriter.rs` +> (tracked follow-up). The rest of this section (explicit-is-safety, the +> attestation backstop) still holds. + +The `Auto` strategy (#172) originally preferred `SharedMemory` + rebasing when +believed provably sound (no `memory.grow`, ≥2 input memories), else `MultiMemory`. +Since #326 it always resolves to `MultiMemory`. **Decision: `Auto` is NOT flipped, and `Auto` is NOT the safety path.** Rationale (maintainer): *functional safety wants the isolation model to @@ -149,8 +165,16 @@ build, `Fuser::fuse` emits a `warn`-level note recommending an explicit safety builds. The warning reports the *final* strategy (after any shared→multi downgrade), not the initial pick, and fires only when attestation is enabled (the release/safety-build signal) to avoid noise -on casual fusion. Log-only — no change to fusion behavior; the existing -`auto_memory` tests confirm resolution is unchanged. +on casual fusion. (Since #326 this attested-build warning is in addition to +the always-on warning that `Auto` gated shared+rebase to multi-memory.) + +**Updated by #326 (v0.38.0):** the earlier claim here — "log-only, no change +to fusion behavior; the `auto_memory` tests confirm resolution is unchanged" — +no longer holds. `Auto` resolution DID change: it now always yields +`MultiMemory` (was shared+rebase for grow-free multi-memory inputs), because +shared+rebase is unsound (LS-M-11). The `auto_memory` tests were updated to +assert the new resolution (`ls_m_11_auto_gates_unsound_shared_rebase`, +`auto_keeps_multi_*`, `ls_m_7_*`). Not done (a stricter option, if wanted later): *erroring* instead of warning when `Auto` is used on an attested build, behind an opt-in diff --git a/safety/requirements/safety-requirements.yaml b/safety/requirements/safety-requirements.yaml index d42e4ce..c43bc91 100644 --- a/safety/requirements/safety-requirements.yaml +++ b/safety/requirements/safety-requirements.yaml @@ -1143,30 +1143,41 @@ artifacts: title: Automatic memory-strategy resolution (sound-by-construction default) description: > `MemoryStrategy::Auto` — the default for both `FuserConfig` and the - `meld fuse` CLI — shall resolve to shared memory + address rebasing - if and only if (a) no input core module contains a `memory.grow` - instruction (static probe, conservative on parse failure) and - (b) the inputs carry at least two linear memories. Otherwise it - shall resolve to multi-memory. If the shared-memory plan refuses - the input, fusion shall retry with multi-memory, so Auto accepts - every input multi-memory accepts. The resolved strategy shall be - reported in `FusionStats` and recorded in the attestation / - provenance metadata as the concrete strategy, never as "auto". - Rationale: out-of-the-box `meld fuse` output must flow through the - `wasm-opt → synth` chain with no extra flags (#172) without ever - selecting the shared form where `memory.grow` makes it unsound - (merger Bug #7, LS-M-7). + `meld fuse` CLI — shall resolve to **multi-memory**, the strategy + that is sound by construction for arbitrary inputs. Auto shall + NOT select shared memory + address rebasing: address rebasing does + not relocate the dynamic address operand of ordinary load/store + instructions (only the static memarg offset and bulk-memory ops), + so components that address memory via computed pointers — heap, + shadow stack, i.e. all real components — would silently collide + in a shared memory (#326, LS-M-11). Shared memory + rebasing shall + remain reachable only by explicit selection (`--memory shared + --address-rebase`), which shall emit a loud unsoundness warning. + The resolved strategy shall be reported in `FusionStats` and + recorded in the attestation / provenance metadata as the concrete + strategy, never as "auto". + History: the prior form of SR-37 (v0.22.0) required Auto to prefer + shared+rebasing for grow-free multi-memory inputs — that + requirement was UNSOUND (it specified a corrupting transform) and + is superseded here by the #326 gate. The single-address-space + (MCU) benefit that shared fusion was meant to provide (#172, #298, + #299) requires correct dynamic-address rebasing in `rewriter.rs` + first (tracked follow-up); until then Auto trades that benefit for + correctness and its output needs `--enable-multimemory`. status: verified - tags: [usability, memory-strategy, v0.22.0] + tags: [usability, memory-strategy, v0.22.0, v0.38.0] links: - type: derives-from target: SYS-6 - type: mitigates target: LS-M-7 + - type: mitigates + target: LS-M-11 cited-source: - uri: "https://github.com/pulseengine/meld/issues/172" kind: github last-checked: 2026-06-24 + release: v0.38.0 fields: implementation: - meld-core/src/memory_probe.rs diff --git a/safety/requirements/sw-verifications.yaml b/safety/requirements/sw-verifications.yaml index 5746972..f2f7a30 100644 --- a/safety/requirements/sw-verifications.yaml +++ b/safety/requirements/sw-verifications.yaml @@ -433,7 +433,7 @@ artifacts: type: sw-verification title: "Verification of SR-37: Automatic memory-strategy resolution (sound-by-construction default)" description: > - Verifies SR-37 via: tests `meld-core/tests/auto_memory.rs::auto_selects_shared_for_growfree_inputs`, `meld-core/tests/auto_memory.rs::ls_m_7_auto_keeps_multi_when_input_grows`, `meld-core/tests/auto_memory.rs::ls_m_7_auto_reprobes_after_add_component`, `meld-core/tests/auto_memory.rs::auto_keeps_multi_for_single_memory_input`, `meld-core/tests/auto_memory.rs::explicit_multi_is_not_overridden`, `meld-core/src/memory_probe.rs::tests::ls_m_7_malformed_input_counts_as_grow`. + Verifies SR-37 (post-#326: Auto resolves to multi-memory; shared+rebase is gated as unsound, LS-M-11) via: tests `meld-core/tests/auto_memory.rs::ls_m_11_auto_gates_unsound_shared_rebase`, `meld-core/tests/auto_memory.rs::ls_m_7_auto_keeps_multi_when_input_grows`, `meld-core/tests/auto_memory.rs::ls_m_7_auto_reprobes_after_add_component`, `meld-core/tests/auto_memory.rs::auto_keeps_multi_for_single_memory_input`, `meld-core/tests/auto_memory.rs::explicit_multi_is_not_overridden`, `meld-core/src/memory_probe.rs::tests::ls_m_7_malformed_input_counts_as_grow`. status: verified fields: method: automated-test diff --git a/safety/stpa/loss-scenarios.yaml b/safety/stpa/loss-scenarios.yaml index 35b9121..29deffe 100644 --- a/safety/stpa/loss-scenarios.yaml +++ b/safety/stpa/loss-scenarios.yaml @@ -3662,6 +3662,70 @@ loss-scenarios: slot (`seen` holds only the first sig) — a dedup-completeness gap, not a correctness issue (compute and emit stay in lockstep). + - id: LS-M-11 + title: shared-memory address rebasing does not rebase dynamic addresses, corrupting computed-pointer access + uca: UCA-M-11 + hazards: [H-2, H-3.2] + type: inadequate-control-algorithm + scenario: > + `--memory shared --address-rebase` (and, before the #326 gate, the + `MemoryStrategy::Auto` default for grow-free multi-memory inputs) + relocates each fused component's private memory into a shared + linear memory at a per-component base. But the rebasing is + INCOMPLETE: `rewriter.rs::convert_memarg` adds `memory_base_offset` + only to the STATIC memarg offset immediate, and `needs_rebase_locals` + injects runtime base-adjustment only for bulk-memory ops + (`MemoryCopy`/`MemoryFill`/`MemoryInit`). The DYNAMIC address operand + of ordinary `i32.load`/`i32.store` — a computed pointer pushed on the + stack (heap object, shadow-stack slot) — is never adjusted. Real + components address their heap/shadow-stack via computed pointers, so + two rebased components' private accesses land at the SAME physical + addresses: one component's store clobbers another's data, wrong + result, no trap, no error [H-2, H-3.2]. The output validates and + runs; corruption is silent. Distinct from LS-M-7 (shared unsound + under `memory.grow`) — this occurs with NO growth, purely from + unrebased computed addresses. Reproduced end-to-end: the wac-composed + consumer→provider fixture returns the provider's private sentinel to + the consumer under shared fusion, while `--memory multi` returns the + correct value. + causal-factors: + - >- + `convert_memarg` rebases only `MemArg.offset` (the static + immediate); the effective address is `dynamic_operand + offset`, + and the dynamic operand is left untouched + - >- + `needs_rebase_locals` is set true only when a function contains a + bulk-memory op, so plain loads/stores get no runtime base + adjustment at all + - >- + the auto-resolution and SR-37 both treated "no memory.grow" as + sufficient for sound shared fusion, but growth-freedom is + orthogonal to whether dynamic addresses are rebased + process-model-flaw: > + The rebasing model assumed that adding the component base to the + static memarg offset relocates every access. That holds only for + accesses whose address is a compile-time constant folded into the + memarg; for computed-pointer access (the norm) the runtime operand + dominates and is never relocated. Correct rebasing must inject + `dynamic_addr + memory_base` at runtime for every memory access in + a rebased component. + status: approved + priority: high + fix: > + Interim GATE (#326, PR #329, shipped v0.38.0): `resolve_auto_memory_ + strategy` no longer selects shared+rebase — Auto always resolves to + multi-memory (sound). Explicit `--memory shared --address-rebase` + remains an opt-in but emits a loud unsoundness warning. SR-37 was + corrected (it had required the unsound shared preference). Pinned by + `ls_m_11_auto_gates_unsound_shared_rebase` (Auto → multi + separate + memories) and the `test_304` shared path made explicit. PENDING + full fix (rewriter.rs dynamic-address rebasing, Tier-5): inject + `addr + memory_base` for every load/store in a rebased component, + which re-enables sound single-address-space (MCU) lowering for + #298/#299/gale. Until that lands, `--memory shared` stays unsound + for computed-pointer access and Auto's output needs + `--enable-multimemory`. + - id: LS-D-1 title: remapped DWARF emits a wrong code address for a fused function uca: UCA-M-9