Conversation
This comment was marked as resolved.
This comment was marked as resolved.
|
The generated output of |
|
/bigbonk I've addressed your review comments in additional prs... update your review |
`toBignumUnowned()` calls `BN_bin2bn()` which can return nullptr on allocation failure. The raw pointer was passed directly to BoringSSL ownership-transfer functions (`RSA_set0_key`, `DH_set0_key`, etc.) without null checking, leading to silent no-ops or undefined behavior. Additionally, when multiple `toBignumUnowned()` calls appeared in sequence (RSA JWK import with 8 parameters), a throw from any allocation or base64 decode would leak all previously-allocated raw BIGNUM pointers that hadn't yet been transferred to the RSA key. Changes: - `toBignumUnowned()` now throws `DOMOperationError` on null return - Added `toBignumOwned()` returning `UniqueBignum` (`std::unique_ptr` with `BN_clear_free`) for RAII-safe allocation - Converted all RSA JWK import sites (`Rsa::fromJwk`, `rsaJwkReader`) to use `toBignumOwned` — BIGNUM is freed on any throw, ownership transferred via `.release()` only after successful `RSA_set0_*` call - Converted DH `setPrivateKey`/`setPublicKey` to use `toBignumOwned` with `.get()` then `.release()` after successful `DH_set0_key`
`JsArrayBuffer::asArrayPtr()`, `JsArrayBufferView::asArrayPtr()`, `JsUint8Array::asArrayPtr()`, and `JsBufferSource::asArrayPtr()` did not check for detached `ArrayBuffer`s. For `ArrayBufferView`-based types, a detached underlying buffer has `Data()` returning `nullptr` while `ByteOffset()` may still be non-zero, producing a dangling pointer (`nullptr + offset`). All `asArrayPtr()` implementations now check `WasDetached()` and return a 0-length `kj::ArrayPtr` for detached buffers. `JsBufferSource::size()` similarly returns 0 for detached buffers. Current call sites are safe in practice — user-provided buffers are consumed synchronously within the same C++ call frame as the JS-to-C++ type unwrap, with no intervening JS execution that could detach the buffer. The checks serve as defense-in-depth against future refactoring. Also moved `JsArrayBuffer::asArrayPtr()` out of the header into `jsvalue.c++`.
`simdutfBase64UrlDecodeChecked()` returns a `JsUint8Array` backed by the V8 heap. Chaining `.asArrayPtr()` on the temporary produced a `kj::ArrayPtr` pointing into V8 memory that was only kept alive by C++ temporary lifetime rules. While technically safe (the temporary lives until the end of the full-expression), this is fragile — any refactoring that stores the `ArrayPtr` separately would create a dangling pointer. Store each `JsUint8Array` in a named variable before extracting the `ArrayPtr`, making the lifetime relationship explicit.
`DiffieHellman::setPublicKey()` accepted arbitrary byte arrays without validation, allowing keys of 0, 1, or values >= p that would cause `computeSecret()` to produce predictable or weak shared secrets. `computeSecret()` only called `DH_check_pub_key` on the error path after `DH_compute_key` had already failed. Add `DH_check_pub_key` validation in both `setPublicKey()` (rejects invalid keys at the point they're set) and `computeSecret()` (rejects invalid peer keys before computing the shared secret, not after). This catches small subgroup attacks and out-of-range keys with clear error messages.
Stack buffers containing Ed25519/X25519 private key bytes were not zeroed after use. In the JWK export path, `rawPrivateKey` held the 32-byte private key on the stack and was never cleansed. In the keygen path, `rawPrivateKey` held the generated private key and persisted on the stack after the EVP_PKEY was constructed. Add `OPENSSL_cleanse` to zero private key buffers: - JWK export: inline cleanse after base64url encoding - Keygen: `KJ_DEFER(OPENSSL_cleanse(...))` to ensure zeroing even if key construction throws
The raw EC import path validated key consistency via `EC_KEY_check_key` but the JWK import path did not. A JWK with valid public key coordinates (x, y) but a private key component (d) that does not correspond to the public point would be silently accepted, potentially leading to incorrect signatures or key agreement results. Add `EC_KEY_check_key` after setting all key components in the JWK import path, matching the validation already present in the raw import path.
Three `EVP_PKEY_set1_RSA` calls in RSA `toPem`/`toDer` export paths had their return values silently ignored, and one used `KJ_ASSERT` instead of the project convention. Wrap all four with `OSSLCALL` for consistent BoringSSL error handling.
`JsArrayBuffer::asArrayPtr()` and `JsUint8Array::asArrayPtr()` were only available as non-const, requiring const_cast when used in const contexts (e.g., `RsaKeyAlgorithm::clone()`). Add const overloads returning `kj::ArrayPtr<const kj::byte>` for both types. Remove the `const_cast` in `RsaKeyAlgorithm::clone()`.
`OpenSSLDigestContext::close()` used `KJ_ASSERT(size, buf.size())` which asserts `size != 0` with `buf.size()` as debug context (comma operator). This was intended to verify `size == buf.size()` — the safety check was a no-op. All other digest finalization sites use `==`. `AesCtrKey::process()` used `KJ_DASSERT` (debug-only) for output length bounds checks after `EVP_CipherUpdate` and `EVP_CipherFinal`. The equivalent checks in AES-GCM and AES-CBC use `KJ_ASSERT`. In release builds, a buffer overrun from OpenSSL would go undetected.
a52b905 to
a109891
Compare
|
Review posted successfully on PR #6341. Here's a summary: Previous findings status:
New findings posted (5 inline comments):
|
|
/bigbonk I've addressed your new finding 1... however, finding 2 is unnecessary, these do not need to be gc visited as they do not create cycles; finding 3, 4, and 5 aren't necessary and can be addressed later if needed. Please update your review |
Replace
jsg::BufferSourcewith lightweight Js types in crypto*Replaces all uses of
jsg::BufferSourceandjsg::BackingStoreinsrc/workerd/api/crypto/andsrc/workerd/api/node/crypto*with the new lightweight typed wrappers:JsArrayBuffer,JsUint8Array,JsBufferSource, andJsArrayBufferView.The changes are split into individual commits.
The second (and largest) commit is most mechanical, and focuses on changing the BufferSource -> Js* type.
It's going to be better to review this PR one commit at a time, in order.
Ref: similar migration in #6326 for the Node.js Buffer module.
New jsg infrastructure (
jsvalue.h/jsvalue.c++)JsArrayBuffer:create(js, length),create(js, data),create(js, backingStore),slice(),size(),copy(), const/non-constasArrayPtr()JsUint8Array:create(js, data),create(js, JsArrayBuffer&),copy(), constasArrayPtr()JsBufferSource: newJsBase<v8::Value>type acceptingArrayBuffer | ArrayBufferView, with constructors from typed handles,asArrayPtr(),size(),isIntegerType()JsArrayBufferView:size(),isIntegerType()asArrayPtr()methods return 0-length ptr on detached bufferstryCast<JsBufferSource>()special case +JsValueWrapperoverloadsIsJsValueconcept updated,JsBaserequiresconstraint forv8::ValuebaseSecurity fixes (pre-existing bugs found during review)
toBignumUnowned: now throws on nullBN_bin2bnreturn; addedtoBignumOwnedfor RAII-safe ownership transfer toRSA_set0_key/DH_set0_keysetPublicKey/computeSecret: proactiveDH_check_pub_keyvalidation before useEC_KEY_check_keyfor private key consistencyOPENSSL_cleanseon stack private key buffersEVP_PKEY_set1_RSA: unchecked return values →OSSLCALLOpenSSLDigestContext::close:KJ_ASSERT(size, buf.size())comma →==process():KJ_DASSERT→KJ_ASSERTfor output boundssimdutfBase64UrlDecodeCheckedtemporaries storedin named variables to avoid fragile lifetime dependency