Skip to content

fix(link,crypto): ELF archive grouping and scrypt cost parameters (from #9311) - #9314

Merged
proggeramlug merged 3 commits into
mainfrom
fix/9311-separable
Aug 31, 2026
Merged

fix(link,crypto): ELF archive grouping and scrypt cost parameters (from #9311)#9314
proggeramlug merged 3 commits into
mainfrom
fix/9311-separable

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Lands the two fixes from #9311 that apply cleanly to current main, keeping the original commits and authorship.

#8930 — group the ELF archive block. The wrappers are already emitted twice, so on paper every reference has somewhere to go; the ld map is what settles it (364 stdlib members pulled, then 92 ext members, with the first ext listing pulling nothing). --start-group/--end-group, gated to ELF targets, gives fixed-point semantics rather than one-step-cycle coverage — which matters because the graph is LTO-partitioned across hundreds of CGUs in both directions. The author verified it is a byte-identical no-op where linking already worked.

#9289 — honour scrypt cost parameters. crypto.scrypt(password, salt, keylen, options) discarded options entirely and always computed node's N=2¹⁴/r=8/p=1 defaults, returning one constant digest for every requested cost. This is the security-relevant one: a password hasher records its parameters next to the digest, so the stored record claimed a work factor that was never paid, and a needsRehash check keyed on those parameters never fired. A caller asking for the OWASP N=2¹⁷ baseline silently got an 8× weaker one. The fix honours N/r/p/keylen/maxmem with node's aliases, enforces OpenSSL's 128 * r * (N + p + 2) boundary, and throws rather than substituting defaults.

Also carries a small refactor of mine: build_and_run.rs was at 1994 lines, and #8930's 8-line archive-group call takes it past the 2000-line gate. The HarmonyOS native-object block needs only cmd/target/format, so it moves to a sibling module unchanged.

Verification. I ran the scrypt parity fixture under both the pinned Node 26.5.1 oracle and the compiled binary — all four sections (sync vectors, async vectors, direct-callback options, invalid-parameter errors) produce identical output, including the maxmem RangeError. That test is also non-vacuous by construction: it pins six distinct digests across N=2¹²…2¹⁷, and the pre-fix implementation returned a single constant, so it could not have matched more than one row.

perry-stdlib + perry-codegen (40 suites) green under RUST_TEST_THREADS=1; release build clean with no new warnings; file-size, raw-handle, thread-local, root-holder and fmt gates all pass.

Summary by CodeRabbit

  • New Features

    • Improved ELF linking for applications with mutually dependent libraries, reducing unresolved-symbol failures.
    • Added automatic inclusion of HarmonyOS native build objects during linking.
  • Bug Fixes

    • Updated crypto.scrypt and scryptSync to honor cost, block size, parallelization, and memory-limit options.
    • Added validation for invalid parameters, conflicting aliases, key lengths, and insufficient memory.
    • Improved compatibility with Node.js scrypt behavior across synchronous and asynchronous APIs.
  • Tests

    • Added coverage for scrypt parameter combinations, exact results, aliases, and validation errors.

Claude and others added 3 commits August 31, 2026 20:17
…dlib (#8930)

A `bundled-streams` build died with `undefined reference to
<futures_channel::mpsc::SenderTask>::notify` out of `libperry_ext_http.a`,
even though the `libperry_stdlib.a` on the same command line exports that
symbol from its own `futures_channel` member.

The mechanism is archive order, but not the obvious one. The wrapper archives
already appear twice — once before perry-stdlib, once after — so at a glance
every reference has somewhere to go. What the #8930 link map shows is that the
FIRST listing pulls nothing at all: the user objects reference only `js_*`
symbols that perry-stdlib provides, so nothing in the wrapper is undefined yet
when `ld` walks it. All 92 wrapper members that end up in the executable are
pulled from the SECOND listing, on references that stdlib's own members had
just opened (`js_node_http_*`, …) — and the references those members carry
back INTO stdlib have nowhere to go, because GNU `ld` scans each archive once
and never revisits one it has passed.

That was harmless while each wrapper bundled its own copy of everything it
closed over. It becomes a hard error the moment
`strip_bundled_shared_deps_from_well_known_lib` drops a bundled member because
stdlib provides it — a correct decision by the archive index, but one that
only holds if `ld` can still get back to stdlib. That is what `bundled-streams`
changes: it is the feature (enabled by a `fs/promises` or `stream/web` import,
per `stdlib_features::module_to_features`) that pulls futures_channel into
perry-stdlib's own graph, so stdlib starts bundling a name-matching
`futures_channel-*.rcgu.o`, so the wrapper's copy becomes eligible to drop. A
stdlib built without it carries no futures_channel member at all, the wrapper
keeps its copy, and the link stays self-contained. #8939 tightened the pruning
rule to the name-matched stdlib member's own exports; here that member does
export the symbol, so the rule fires correctly and the link still fails. The
bug is on the link line, not in the pruning.

Wrap the perry archive block in `-Wl,--start-group` / `-Wl,--end-group` on ELF
targets so `ld` re-scans it to a fixed point — the guarantee a mutually
recursive archive set needs. Repeating one archive (the codebase's usual
"archive twice" trick) only covers a one-step cycle, and this graph is
LTO-partitioned across hundreds of codegen units on both sides. Members are
still pulled left to right, so the wrappers-before-stdlib and
localized-runtime-last first-definition-wins ordering is unaffected; Mach-O
`ld64` resolves archives to a fixed point already (and rejects the flag) and
`lld-link` / MSVC have no group concept, so both are left alone.

Verified against both reported repros in mb24 — `apps/landing` (hono plus a
compiled `@skelpo/cms-client`) and `apps/api` (hono + ws + mysql2, four
wrapper archives, no `perry.compilePackages` at all). Both fail before and
link after; replaying either final link line with only the two group flags
removed reproduces the exact undefined reference. A case that already linked
(`packages/db/src/migrate.ts`) produces a byte-identical executable with and
without the flags. The link/strip-dedup unit tests pass (68), as do the two
archive-ordering integration tests —
`issue_5920_wrapper_bundled_runtime_async_starvation` (the two-runtime-copies
hazard) and `issue_6715_native_wrapper_precedence`. `native_link_cache` fails
identically on the unpatched parent commit.
…nd_run.rs

build_and_run.rs was at 1994 lines; #8930's archive-group call takes it over
the 2000-line gate. The HarmonyOS block needs only cmd/target/format, so it
moves to a sibling module unchanged.
@proggeramlug
proggeramlug merged commit 9c8cdfc into main Aug 31, 2026
19 of 20 checks passed
@proggeramlug
proggeramlug deleted the fix/9311-separable branch August 31, 2026 18:18
@coderabbitai

coderabbitai Bot commented Aug 31, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0cabf444-6e0b-4b96-be58-f6ca70b76713

📥 Commits

Reviewing files that changed from the base of the PR and between 69b068a and 369e56a.

📒 Files selected for processing (11)
  • changelog.d/8930-elf-archive-group.md
  • changelog.d/9289-scrypt-params.md
  • crates/perry-codegen/src/expr/calls/crypto_kdf.rs
  • crates/perry-codegen/src/runtime_decls/stdlib_ffi/data_stores.rs
  • crates/perry-codegen/src/runtime_decls/strings_part2.rs
  • crates/perry-stdlib/src/crypto/kdf.rs
  • crates/perry-stdlib/src/crypto/random.rs
  • crates/perry/src/commands/compile/link/build_and_run.rs
  • crates/perry/src/commands/compile/link/harmonyos_objects.rs
  • crates/perry/src/commands/compile/link/mod.rs
  • test-parity/node-suite/crypto/scrypt/options.ts

📝 Walkthrough

Walkthrough

The change forwards scrypt options through code generation and runtime validation, adds Node parity tests, extracts HarmonyOS object discovery, and wraps eligible ELF archive links with GNU linker groups.

Changes

Scrypt option handling

Layer / File(s) Summary
Scrypt option ABI and dispatch wiring
crates/perry-codegen/src/expr/calls/crypto_kdf.rs, crates/perry-codegen/src/runtime_decls/..., crates/perry-stdlib/src/crypto/kdf.rs, crates/perry-stdlib/src/crypto/random.rs
Scrypt options now pass as boxed DOUBLE values through synchronous and asynchronous calls.
Scrypt parameter validation and native tests
crates/perry-stdlib/src/crypto/kdf.rs
The runtime validates aliases, numeric ranges, memory limits, and key lengths. Native tests verify Node digests and memory boundaries.
Node scrypt parity coverage
test-parity/node-suite/crypto/scrypt/options.ts, changelog.d/9289-scrypt-params.md
Parity tests cover all supported APIs, aliases, key lengths, invalid parameters, and insufficient memory. The changelog records the behavior.

ELF linking and HarmonyOS object collection

Layer / File(s) Summary
HarmonyOS native-object collection
crates/perry/src/commands/compile/link/harmonyos_objects.rs, crates/perry/src/commands/compile/link/mod.rs, crates/perry/src/commands/compile/link/build_and_run.rs
HarmonyOS object discovery moved to a helper that recursively finds emitted .o files and appends them to the link command.
ELF archive grouping and coverage
crates/perry/src/commands/compile/link/mod.rs, crates/perry/src/commands/compile/link/build_and_run.rs, changelog.d/8930-elf-archive-group.md
Eligible ELF archive links now use --start-group and --end-group. Tests cover ELF, non-ELF, and empty archive cases.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant NativeDispatch
  participant Codegen
  participant ScryptRuntime
  participant OpenSSL
  NativeDispatch->>Codegen: Pass boxed scrypt options
  Codegen->>ScryptRuntime: Call scrypt with options_bits
  ScryptRuntime->>ScryptRuntime: Validate aliases, ranges, and maxmem
  ScryptRuntime->>OpenSSL: Derive key with validated parameters
  OpenSSL-->>ScryptRuntime: Return derived bytes
Loading
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/9311-separable

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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