Skip to content

fix(parser): flat_byte_size — element-wise variant JOIN, not max#179

Open
avrabe wants to merge 1 commit into
mainfrom
fix/flat-byte-size-variant-join
Open

fix(parser): flat_byte_size — element-wise variant JOIN, not max#179
avrabe wants to merge 1 commit into
mainfrom
fix/flat-byte-size-variant-join

Conversation

@avrabe
Copy link
Copy Markdown
Contributor

@avrabe avrabe commented May 22, 2026

Summary

Fixes the flat_byte_size correctness defect the mythos-auto delta-pass surfaced on PR #178 — the auto-runner's first real finding.

flat_byte_size computed the payload width of result<T,E> / variant as max(flat_byte_size(arm)) instead of the Component Model's element-wise flatten_variant JOIN. max of arm byte totals underestimates whenever the arms flatten to a different number of core values:

result<u64, string> flat sequence bytes
ok arm u64 [i64] 8
err arm string [i32, i32] 8
old: 4 + max(8, 8) 12
joined [i32, i64, i32] 16

Fix

flat_byte_size is rewritten over a new private flat_width_list helper that materialises each type's flat core-value width list (one entry per value, 4 or 8 bytes) and JOINs variant/result arms element-wise — joined length = longest arm, each position the wider arm width. Non-variant types (record, tuple, primitive, flags, …) are byte-for-byte unchanged.

flat_width_list caps its length at FLAT_WIDTH_CAP = 256 (far past the canonical-ABI flat limit of 16). A nested fixed-length-list exceeding the cap yields Noneflat_byte_size returns u32::MAX. This preserves the LS-P-4 saturation contract (a saturated value fails downstream allocation safely rather than wrapping) and bounds the helper's Vec against the LS-P-4 OOM class. The LS-P-4 regression test still passes.

Finding disposition (validate.md)

The discover step also claimed an OOB-write hazard — "adapter code sizes a retptr buffer from this value." Rejected on validation: flat_byte_size has zero consumers in meld-core/src/ (only its own recursion + the LS-P-4 test); retptr return areas are sized by return_area_byte_sizecanonical_abi_size_unpadded, a different function. No reachable hazard → no possible PoC → not a confirmed finding, no LS-N entry.

This PR fixes the underlying arithmetic anyway — it's a correctness defect in a pub fn a future consumer would inherit. Hygiene, not a safety fix. (Full disposition: PR #178 thread.)

Tests

flat_byte_size_result_uses_element_wise_join_not_max pins result<u64,string>=16, an unequal-arity variant=16, equal-arms result<u32,u32>=8, and non-variant record/u64 unchanged. Full meld-core lib suite 248 → 249.

🤖 Generated with Claude Code

The mythos-auto delta-pass on PR #178 flagged that flat_byte_size
computes the payload width of result<T,E> and variant as
`max(flat_byte_size(arm))` rather than the Component Model's
element-wise flatten_variant JOIN.

`max` of arm byte totals underestimates whenever the arms flatten
to a different *number* of core values. result<u64, string>: the
ok arm u64 flattens to [i64] (8 B), the err arm string to [i32,i32]
(8 B). The old form gave 4 + max(8,8) = 12, but the joined payload
is [i64, i32] (12 B) and the true flat size is 4 + 12 = 16.

Fix: flat_byte_size is rewritten over a new private flat_width_list
helper that materialises each type's flat core-value width list
and JOINs variant/result arms element-wise. Non-variant types are
byte-for-byte unchanged. flat_width_list caps its length at
FLAT_WIDTH_CAP (256); a type whose flattening exceeds the cap
yields None and flat_byte_size returns u32::MAX, preserving the
LS-P-4 saturation contract and bounding the helper's Vec against
the LS-P-4 OOM class. The LS-P-4 regression test still passes.

Disposition of the mythos-auto finding: the discover step claimed
an OOB-write hazard. Rejected on validation — flat_byte_size has
zero consumers in meld-core/src/; retptr return areas are sized by
return_area_byte_size, a different function. No reachable hazard,
no possible PoC, NOT a confirmed finding, no LS-N entry. This
commit fixes the underlying arithmetic anyway, as correctness
hygiene on a pub fn a future consumer could inherit.

Regression test flat_byte_size_result_uses_element_wise_join_not_max
pins result<u64,string>=16, an unequal-arity variant=16, the
equal-arms result<u32,u32>=8, and non-variant record/u64 unchanged.

Refs: mythos-auto finding on PR #178.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

Mythos delta-pass required

This PR modifies one or more Tier-5 source files (per
scripts/mythos/rank.md):

meld-core/src/parser.rs

Before merge, run the Mythos discover protocol on the
modified Tier-5 files:

  1. Follow scripts/mythos/discover.md
    — one fresh agent session per touched Tier-5 file.
  2. For each finding, the agent must produce both a Kani
    harness and a failing PoC test (per the protocol's
    "if you cannot produce both, do not report" rule).
  3. Attach a comment on this PR with either the findings
    (formatted per discover.md's output schema) or
    NO FINDINGS.
  4. Add the mythos-pass-done label to this PR.

Why this gate exists: LS-A-10
(CABI alignment padding in async-lift retptr writeback) was
found by the v0.8.0 pre-release Mythos pass — but it had
lived in the callback emitter since #128, across six
releases. A PR-time gate would have caught it at review
time instead of at the release boundary.

The gate check on this PR will pass once the label is
applied.

@github-actions
Copy link
Copy Markdown

LS-N verification gate

19/19 approved LS entries verified

count
Passed (≥1 test, all green) 19
Failed (≥1 test failure) 0
Missing (no ls_*_NN_* test found) 0

Approved loss-scenarios.yaml entries are expected to have a
regression test named ls_<letter>_<num>_* (e.g. LS-A-11
ls_a_11_*). The gate runs each prefix via cargo test --lib --no-fail-fast and aggregates pass/fail/missing.

Failed LS entries

(none)

Missing regression tests

(none)

Updated automatically by tools/post_verification_comment.py.
Source of truth: safety/stpa/loss-scenarios.yaml.

@github-actions
Copy link
Copy Markdown

Mythos delta-pass (auto)

1 finding(s) across 1 Tier-5 file(s)

File Verdict Hypothesis
meld-core/src/parser.rs ❌ FINDING The size-accumulation loops at lines 1613 and 1584 use wrapping size += canonical_abi_size_unpadded(ty) instead of saturating_add, silently undoing the saturation guarantee that canonical_abi_size_unpadded itself documents ("Saturating to u32::MAX prevents wrap-to-0 … rather than under-allocating and writing OOB"). When a first param/result is FixedSizeList(f64, 2^29) — whose element-size product saturates to u32::MAX — and any second param/result follows, the accumulator wraps from u32::MAX to 0; the resolver stores params_area_byte_size = Some(0), and the adapter (fact.rs:1806) passes 0 to cabi_realloc, allocating a zero-byte buffer that it then fills with all parameters, producing an OOB write in callee linear memory.

Auto-run via anthropics/claude-code-action@v1
(SHA-pinned) on the touched Tier-5 files, using the
maintainer's Max-plan OAuth token. See
.github/workflows/mythos-auto.yml and
scripts/mythos/discover.md.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant