fix(crypto): decode inline SSO string args instead of dereferencing them (#11481) - #11513
proggeramlug wants to merge 2 commits into
Conversation
… StringHeader pointers (#11481) A runtime-built string of <= 5 bytes is an inline SSO value (SHORT_STRING_TAG); its low 48 bits are length + payload, not an address. Crypto read string/bytes arguments by masking to 48 bits and handing the result to `bytes_from_ptr`, which dereferenced the payload as a `StringHeader*` and segfaulted. Two layers did this: - stdlib, NaN-boxed f64 args (hash/hmac `update` data and input encoding, `digest` encoding, cipher `update`/`setAuthTag`/`setAAD`, sign/verify `update`, ECDH args, PEM key inputs): new `bytes_from_value(f64)` decodes the SSO bytes directly and otherwise keeps the masked-pointer path. `arg_bytes`, `decode_hash_update_value`, `decode_crypto_value`, `decode_ecdh_input` and the direct masks route through it; the now-unused `arg_ptr` is removed. - codegen, i64 FFI args (`createHash`/`createHmac` algorithm and key, pbkdf2/scrypt/hkdf/argon2 inputs, `createCipheriv`, `createSign`, `createECDH`, `createSecretKey`, `generateKey*`, one-shot sign/verify and RSA encrypt/decrypt data): unbox via `unbox_str_handle` (`js_get_string_pointer_unified`, which materializes SSO to a heap header) instead of the raw `unbox_to_i64` mask. The generateKeyPairSync options object keeps the plain mask. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LdEUAyNTC8iuUa5RDkLyny
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LdEUAyNTC8iuUa5RDkLyny
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (12)
Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 3 remain after this review. 📝 WalkthroughWalkthroughCrypto code generation now unboxes string-valued arguments with ChangesCrypto SSO string handling
Priority: ➖ Normal Estimated code review effort: 3 (Moderate) | ~20 minutes Change: Bug fix · Severity of issue fixed: Medium Merge Risk: ⚪ Minimal · up to The changed crypto argument paths have no identified merge-blocking issue. Merge after normal checks. Security Architecture ReviewSecurity architecture risk: 🔵 Low · up to Short string inputs can now reach cryptographic operations without being mistaken for pointers. The reviewed paths retain their key validation and authentication checks. No new security weakness was established, but execution was not verified. Retained concerns Security review detailsSecurity Blast Radius
Trust Boundaries and Controls
Resilience and Maintainability Implications
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 66.07% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 56 functions across 11 files. (1 skipped: 1 unsupported.)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
⚔️ Resolve merge conflicts 💡
🧪 Generate unit tests (beta)
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. Comment |
Summary
cryptoread string/bytes arguments by masking the NaN-box to 48 bits and passing the result tobytes_from_ptr. A runtime-built string of 5 bytes or fewer is an inline SSO value (SHORT_STRING_TAG). Its low 48 bits hold the length and payload, not an address, sobytes_from_ptrread those bytes as aStringHeader*and segfaulted. The issue repro (createHash("sha1").update("x" + (i & 7))) is one case of this. The same mask appears in two layers, and onmainboth of them crash.Changes
bytes_from_value(f64)incrypto/util.rs. It decodes SSO bytes directly and otherwise uses the existing masked-pointer path (Buffer, heap string or raw pointer), so only SSO values behave differently. These now go through it:arg_bytes/arg_string,decode_hash_update_value,decode_crypto_valueanddecode_ecdh_input(which now takes the f64)digest(enc), cipherupdate/setAuthTag/setAAD, sign/verifyupdate, ECDHconvertKey,crypto_value_bytes, and PEM key inputsarg_ptrhad no remaining callers, so it is removed.crypto_{hash,kdf,keys,misc}.rsnow unbox string/bytes args withunbox_str_handle(js_get_string_pointer_unified, which materializes SSO into a heap header) instead ofunbox_to_i64. The fast paths incrypto_hash.rsalready did this. It covers:createHash/createHmacalgorithm and keycreateCipheriv,createSign/Verify,createECDH,createSecretKey,generateKey*sign/verifyand RSApublicEncrypt-family datagenerateKeyPairSyncoptions object keeps the plain mask.Related issue
Fixes #11481
Test plan
#[test]s incrypto/hash_handles.rs(sso_arg_tests) drivedispatch_hash/dispatch_hmacwith SSOupdatedata, input encoding and digest encoding. Each fixture asserts it really isis_short_string(), so a pass can't come from the heap-string path. Result:RUST_TEST_THREADS=1 cargo test -p perry-stdlib --lib crypto::gives 30 passed.test-files/test_gap_11481_crypto_sso_string_args.tsmatches Node byte for byte.perry-devbuild withPERRY_NO_AUTO_OPTIMIZE=1and pinnedPERRY_RUNTIME_DIR, with the static.as rebuilt each time:mainthe issue repro and the gap test both segfault (exit 139). So does each of these 9 shapes run on its own: SSOupdatedata, digest encoding, algorithm, input encoding, HMAC key, HMAC algorithm, pbkdf2 password, pbkdf2 digest, scrypt password. The codegen half is a real crash, not only the stdlib half.test-files/gives the same output as Node, excepttest_crypto.ts(a non-module script that Node itself rejects) andtest_parity_crypto(already inknown_failures.jsonfor inventory lengths). Both differ onmaintoo.cargo test -p perry-codegen --lib cryptopasses.check_file_size.sh,addr_class_inventory.pyandunrooted_local_shape.py --checkpass.rustfmtis clean.test-files/and a#[test]in the affected crateChecklist
fix:prefix convention🤖 Generated with Claude Code
https://claude.ai/code/session_01LdEUAyNTC8iuUa5RDkLyny
Generated by Claude Code
Summary by CodeRabbit