Skip to content

Add advisory for beamr: three classes of silent memory corruption (fixed in 0.16.3) - #3122

Open
tomWhiting wants to merge 1 commit into
rustsec:mainfrom
tomWhiting:beamr-memory-corruption-refile
Open

Add advisory for beamr: three classes of silent memory corruption (fixed in 0.16.3)#3122
tomWhiting wants to merge 1 commit into
rustsec:mainfrom
tomWhiting:beamr-memory-corruption-refile

Conversation

@tomWhiting

Copy link
Copy Markdown

Re-filing #3121, which did not use the pull request template. Same advisory, template-conformant, with two corrections found while preparing this one (the date field and a reference link that would have 404'd).

Affected crate(s)

  • beamr (3,135 recent downloads per crates.io)

Not separately filed: beamr-cli (94 recent) and beamr-wasm (161 recent) are
our own crates and are covered transitively by the beamr requirement.

Links to upstream issue(s) or PR(s)

We are the upstream maintainers, so this is filed under CONTRIBUTING's
maintainer exemption rather than reported inbound. Upstream record:

  • Public disclosure, classes 2 and 3 — CHANGELOG.md, 0.16.2 entry.
    Published to crates.io 2026-07-23T11:05:21Z (C1 GC refcount-release
    walk, C2 ETS borrowed caller-heap terms).
    https://github.com/ablative-io/beamr/blob/main/CHANGELOG.md
  • Public disclosure, class 1 — CHANGELOG.md, 0.16.3 entry.
    Published to crates.io 2026-07-28T15:09:08Z. (That entry heads itself
    2026-07-29; the changelog dates in the maintainers' local zone, crates.io
    in UTC. UTC is used throughout this advisory.)
  • Site-level audit of class 1, including its forward-only amendments.
    Pinned deliberately: the document is linked as of 4055cbe, and its own
    sweep base is f684d60 — that is where the audit's line coordinates
    resolve, not main, and f684d60 predates the document itself by twenty
    minutes, so it is not a valid path for it.
    https://github.com/ablative-io/beamr/blob/4055cbe/docs/design/beamr/briefs/evidence/aion-encode-gc-defect/asbytes-sweep/AUDIT.md
  • Fix commits (all red-first; the wall commit precedes each fix and is
    committed RED by design):
    • 499c414 — lane 1, own the bytes before the allocating call at all nine
      mechanical crossings (walls: af8440b)
    • 9a19961 — lane 2, binary_to_term/1,2 own the source bytes up front,
      before the decode recursion (walls: 8f693c9)
    • c7609b6 — lane 3, jit_bs_get_binary owns its bytes, allocates
      uniformly, advances position pre-alloc (walls: 91d772d, f7d0372)
    • 2672fa0 — lane 4, mutation-proven tripwire walls
    • Release tag v0.16.3 = 1a77d5c480a7d75e87dff13756431d2fac744592

Severity

Silent memory corruption — no panic, no crash, no error return.

beamr is a BEAM virtual machine with a per-process generational copying
collector. A minor collection relocates live young-heap objects and then
zero-fills the vacated region, so a stale borrow of moved data reads zeros
presented as valid data
rather than faulting. A passing test suite is not
evidence of non-exposure.

Three classes, all fixed as of 0.16.3:

  1. as_bytes borrow-across-allocation. A helper returned
    &'static [u8] borrowed from garbage-collected process-heap memory;
    consumers held it across a call that could collect and then read through
    it. Eleven real crossings, nine of them BIF sites reachable from ordinary
    Erlang/Gleam string and binary operations (string:trim/2, split/3,
    find/2, pad/4, slice/3, binary:part/3, uri_string:parse/1,
    erlang:'++'/2), plus binary_to_term/1,2 and a JIT binary-match helper.
  2. GC refcount-release walk (fixed 0.16.2) — inferred object type from
    word[0] and could call Arc::from_raw on a heap-cons payload: arbitrary
    free plus heap corruption at the next minor GC.
  3. ETS stored borrowed caller-heap terms (fixed 0.16.2) that outlived
    the heap they pointed into.

Reaching class 1 requires no unsafe code and no unusual configuration
the affected operations are ordinary string and binary BIFs invoked by
compiled bytecode. Observed under forced collection geometry:
binary:part(<<1..40>>, 10, 20) returned twenty zero bytes.

On the affected range

patched = [">= 0.16.3"] with no unaffected bound, deliberately.

Our own changelog previously said "if you are on 0.16.0 or 0.16.1" — 56
versions are published and it named two. Preparing this advisory is what
caught it. Measured: the file carrying the borrow helper and all five
affected string BIFs is byte-identical — one blob hash,
d4054622d770886a7d91e1748cfd078d172206e3 — across all 29 tags from 0.4.4
through 0.15.2, and at the 0.16.2 release commit 67f89c4, which no
tag sweep could have reached because the 0.16.x line before 0.16.3 was
never tagged. The helper's signature is present from 0.2.0
(string_bifs.rs:324). The introduction points of the two 0.16.2-fixed
classes have not been measured, and the advisory says so rather than
implying a narrow range.

Scope note

The advisory text also mentions a separate JIT-reachable rooting class that
is still open and is publicly disclosed in our changelog. It is
deliberately not the subject of this advisory: it has no fix to point at, so
there is no patched range to state. It is named only so that upgrading to
0.16.3 is not read as a clean bill of health. We expect to file it
separately once the fix lands.

Happy to adjust categories, wording, or split this into per-class advisories.

Checklist

  • Advisory filename(s) starts with RUSTSEC-0000-0000 as the ID
  • date field is set to the public disclosure date — 2026-07-23, the
    0.16.2 release that first publicly described classes 2 and 3. Class 1
    followed on 2026-07-28 with 0.16.3; because one field cannot carry
    two dates, the advisory body has a Disclosure timeline section
    naming both. The earlier date is used so the advisory never understates
    how long any covered class has been public.
  • Contains a concise and descriptive title after advisory metadata
  • Asked maintainer(s) if publishing an advisory is appropriate — we are
    the maintainers
    ; filed by us under CONTRIBUTING's maintainer exemption

@djc

djc commented Aug 10, 2026

Copy link
Copy Markdown
Member

Please edit down your LLM-generated drivel both in the PR description and in the advisory. It should contain just a few sentences/paragraphs explaining the root cause of the issue, making it easy for downstream crate maintainers to understand if they're affected. If there are different issues fixed in different releases, those probably merit different advisories. An issue that is not yet fixed in any release definitely merits a different advisory.

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.

2 participants