fix(napi): validate status before copying in ArrayBuffer ToNapiValue fallback#3079
fix(napi): validate status before copying in ArrayBuffer ToNapiValue fallback#3079graphite-app[bot] merged 1 commit intomainfrom
Conversation
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. 📝 WalkthroughWalkthroughChanges to ArrayBuffer creation methods in NAPI bindings improve error handling and refine buffer lifecycle management. Enhanced status checking is introduced in Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~40 minutes Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
📜 Recent review detailsConfiguration used: defaults Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🧰 Additional context used🧠 Learnings (2)📚 Learning: 2025-11-25T09:31:23.877ZApplied to files:
📚 Learning: 2025-11-25T09:31:23.877ZApplied to files:
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (40)
🔇 Additional comments (1)
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 |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
How to use the Graphite Merge QueueAdd either label to this PR to merge it via the merge queue:
You must have a Graphite account in order to use the merge queue. Sign up using this link. An organization admin has enabled the Graphite Merge Queue in this repository. Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue. This stack of pull requests is managed by Graphite. Learn more about stacking. |
There was a problem hiding this comment.
Pull request overview
This PR improves ArrayBuffer creation safety and WASM compatibility by adding defensive checks and platform-specific configuration.
- Adds
length > 0guards before memory copy operations to prevent undefined behavior - Restructures error handling to check buffer creation status before attempting copies
- Ensures finalize callbacks are always invoked for proper resource cleanup, even on errors
- Adds WASM target exclusion to thread-local buffer tracking
Reviewed changes
Copilot reviewed 1 out of 1 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
crates/napi/src/env.rs |
Enhanced error handling in ArrayBuffer creation methods with early status checks and length guards before copy operations; ensures finalize callbacks are called for proper cleanup |
crates/napi/src/bindgen_runtime/js_values/buffer.rs |
Added WASM exclusion to cfg attribute for thread-local BUFFER_DATA |
crates/napi/src/bindgen_runtime/js_values/arraybuffer.rs |
Applied consistent safety improvements across ArrayBuffer methods with length guards and status checks; added WASM exclusion to all buffer tracking cfg attributes |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
7c9aa9f to
4d6a5ac
Compare
13f1fe1 to
512153f
Compare
4d6a5ac to
ff47f3e
Compare
512153f to
72668eb
Compare
ff47f3e to
ac6a276
Compare
72668eb to
9945379
Compare
9945379 to
6752c3e
Compare
ac6a276 to
e55bc34
Compare
Merge activity
|
…fallback (#3079) <!-- CURSOR_SUMMARY --> > [!NOTE] > Improves ArrayBuffer creation fallbacks and error handling when external buffers aren’t allowed. > > - In `ArrayBuffer::from_data`, validate `napi_create_arraybuffer` status before copying and guard copies behind `len > 0` > - In `ArrayBuffer::from_external`, copy only on `napi_ok`, always call finalize, and check status in both branches; minor control-flow cleanup > - Similar status checks and guarded copies applied across typed array paths that construct ArrayBuffers > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 6752c3e. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
6752c3e to
c541fee
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (6)
crates/napi/src/bindgen_runtime/js_values/arraybuffer.rs (6)
257-285: Potential use-after-free:dataslice returned after finalize may have freed the source.In the fallback path (lines 257-269), after
finalize(*env, hint)is called (line 268), the function continues and returns anArrayBufferon line 283 with:data: unsafe { std::slice::from_raw_parts(data.cast(), len) }However,
finalizeis a user-provided callback that likely frees the originaldatapointer. The returnedArrayBufferwould then contain a dangling pointer.In the fallback path, the data was copied to
underlying_data(line 265), so the returned slice should referenceunderlying_data, not the originaldata.🔎 Proposed fix
The fix requires tracking whether we're in the fallback path and using the correct pointer:
if status == sys::Status::napi_no_external_buffers_allowed { let (hint, finalize) = *Box::from_raw(hint_ptr); let mut underlying_data = ptr::null_mut(); status = unsafe { sys::napi_create_arraybuffer(env.0, len, &mut underlying_data, &mut arraybuffer_value) }; // Copy data before calling finalize, since finalize may free the source data if status == sys::Status::napi_ok && len > 0 { unsafe { std::ptr::copy_nonoverlapping(data.cast(), underlying_data, len) }; } // Always call finalize to clean up caller's resources, even on error finalize(*env, hint); check_status!(status, "Failed to create arraybuffer from data")?; + return Ok(Self { + value: Value { + env: env.0, + value: arraybuffer_value, + value_type: ValueType::Object, + }, + data: if len == 0 { + &[] + } else { + unsafe { std::slice::from_raw_parts(underlying_data.cast(), len) } + }, + }); } else { check_status!(status, "Failed to create arraybuffer from data")?; } Ok(Self { value: Value { env: env.0, value: arraybuffer_value, value_type: ValueType::Object, }, data: if len == 0 { &[] } else { unsafe { std::slice::from_raw_parts(data.cast(), len) } }, })
767-782: Missing status validation and length guard inimpl_typed_array!macro fallback.The
ArrayBuffer::from_dataandArrayBuffer::from_externalmethods were updated to validate status before copying and guard copies withlen > 0. However, theimpl_typed_array!macro (used forUint8Array,Int32Array, etc.) still performs an unconditional copy at line 778:unsafe { std::ptr::copy_nonoverlapping(hint.data.cast(), underlying_data, length) };This doesn't check if
napi_create_arraybuffersucceeded before copying, and doesn't guard against zero-length buffers. Per the PR objective to "apply similar status checks and guarded copies across typed array paths", this should be consistent.🔎 Proposed fix
if status == napi_sys::Status::napi_no_external_buffers_allowed { let hint = unsafe { Box::from_raw(hint_ptr) }; let mut underlying_data = ptr::null_mut(); let status = unsafe { sys::napi_create_arraybuffer( env, length, &mut underlying_data, &mut arraybuffer_value, ) }; - unsafe { std::ptr::copy_nonoverlapping(hint.data.cast(), underlying_data, length) }; - status + if status == sys::Status::napi_ok && length > 0 { + unsafe { std::ptr::copy_nonoverlapping(hint.data.cast(), underlying_data, length) }; + } + status } else { status }
1053-1071: Missing status validation inimpl_from_slice!macro'sfrom_external.Similar to the
impl_typed_array!macro, theimpl_from_slice!macro'sfrom_externalmethod at lines 1064-1066 copies data unconditionally:let underlying_slice: &mut [u8] = unsafe { std::slice::from_raw_parts_mut(underlying_data.cast(), array_buffer_len) }; underlying_slice.copy_from_slice(unsafe { std::slice::from_raw_parts(data.cast(), array_buffer_len) });This doesn't validate that
napi_create_arraybuffersucceeded before dereferencingunderlying_data, which could be null or invalid if the call failed.🔎 Proposed fix
status = if status == sys::Status::napi_no_external_buffers_allowed { let (hint, finalize) = *Box::from_raw(hint_ptr); let mut underlying_data = ptr::null_mut(); let status = unsafe { sys::napi_create_arraybuffer( env.0, array_buffer_len, &mut underlying_data, &mut arraybuffer_value, ) }; - let underlying_slice: &mut [u8] = - unsafe { std::slice::from_raw_parts_mut(underlying_data.cast(), array_buffer_len) }; - underlying_slice.copy_from_slice(unsafe { std::slice::from_raw_parts(data.cast(), array_buffer_len) }); + if status == sys::Status::napi_ok && array_buffer_len > 0 { + let underlying_slice: &mut [u8] = + unsafe { std::slice::from_raw_parts_mut(underlying_data.cast(), array_buffer_len) }; + underlying_slice.copy_from_slice(unsafe { std::slice::from_raw_parts(data.cast(), array_buffer_len) }); + } finalize(*env, hint); status } else { status };
1667-1680: Missing status validation before copy inUint8ClampedSlice::from_data.At lines 1673-1675, data is copied unconditionally:
let underlying_slice: &mut [u8] = unsafe { std::slice::from_raw_parts_mut(underlying_data.cast(), len) }; underlying_slice.copy_from_slice(data.as_slice());The status check on line 1680 happens after this copy. If
napi_create_arraybufferfails,underlying_datamay be null or invalid, leading to undefined behavior.🔎 Proposed fix
let mut underlying_data = ptr::null_mut(); status = unsafe { sys::napi_create_arraybuffer(env.0, len, &mut underlying_data, &mut buf) }; - let underlying_slice: &mut [u8] = - unsafe { std::slice::from_raw_parts_mut(underlying_data.cast(), len) }; - underlying_slice.copy_from_slice(data.as_slice()); + if status == napi_sys::Status::napi_ok && len > 0 { + let underlying_slice: &mut [u8] = + unsafe { std::slice::from_raw_parts_mut(underlying_data.cast(), len) }; + underlying_slice.copy_from_slice(data.as_slice()); + } inner_ptr = underlying_data.cast(); } else { mem::forget(data); } check_status!(status, "Failed to create buffer slice from data")?;
1763-1777: Missing status validation before copy inUint8ClampedSlice::from_external.Same issue as
from_data- the copy at lines 1769-1771 occurs before the status check at line 1777.🔎 Proposed fix
status = if status == sys::Status::napi_no_external_buffers_allowed { let (hint, finalize) = *Box::from_raw(hint_ptr); let mut underlying_data = ptr::null_mut(); let status = unsafe { sys::napi_create_arraybuffer(env.0, len, &mut underlying_data, &mut arraybuffer_value) }; - let underlying_slice: &mut [u8] = - unsafe { std::slice::from_raw_parts_mut(underlying_data.cast(), len) }; - underlying_slice.copy_from_slice(unsafe { std::slice::from_raw_parts(data, len) }); + if status == sys::Status::napi_ok && len > 0 { + let underlying_slice: &mut [u8] = + unsafe { std::slice::from_raw_parts_mut(underlying_data.cast(), len) }; + underlying_slice.copy_from_slice(unsafe { std::slice::from_raw_parts(data, len) }); + } finalize(*env, hint); status } else { status };
949-969: Missing status validation before copy inimpl_from_slice!macro'sfrom_data.At lines 962-964, data is copied unconditionally after
napi_create_arraybuffer:let underlying_slice: &mut [u8] = unsafe { std::slice::from_raw_parts_mut(underlying_data.cast(), data.len()) }; underlying_slice.copy_from_slice(unsafe { core::slice::from_raw_parts(inner_ptr.cast(), array_buffer_len) });The status check on line 969 happens after this copy.
🔎 Proposed fix
if status == napi_sys::Status::napi_no_external_buffers_allowed { unsafe { let _ = Box::from_raw(finalize_hint); } let mut underlying_data = ptr::null_mut(); status = unsafe { sys::napi_create_arraybuffer( env.0, array_buffer_len, &mut underlying_data, &mut buf, ) }; - let underlying_slice: &mut [u8] = - unsafe { std::slice::from_raw_parts_mut(underlying_data.cast(), data.len()) }; - underlying_slice.copy_from_slice(unsafe { core::slice::from_raw_parts(inner_ptr.cast(), array_buffer_len) }); + if status == napi_sys::Status::napi_ok && array_buffer_len > 0 { + let underlying_slice: &mut [u8] = + unsafe { std::slice::from_raw_parts_mut(underlying_data.cast(), array_buffer_len) }; + underlying_slice.copy_from_slice(unsafe { core::slice::from_raw_parts(inner_ptr.cast(), array_buffer_len) }); + } inner_ptr = underlying_data.cast(); } else { mem::forget(data); }
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
crates/napi/src/bindgen_runtime/js_values/arraybuffer.rs
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-25T09:31:23.877Z
Learnt from: CR
Repo: napi-rs/napi-rs PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:31:23.877Z
Learning: Applies to /crates/**/*.rs : Use `#[napi]` attribute on impl blocks to define classes exported to JavaScript/TypeScript
Applied to files:
crates/napi/src/bindgen_runtime/js_values/arraybuffer.rs
📚 Learning: 2025-11-25T09:31:23.877Z
Learnt from: CR
Repo: napi-rs/napi-rs PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T09:31:23.877Z
Learning: Applies to /crates/**/*.rs : Use `#[napi(js_name = "...")]` attribute to rename Rust items in JavaScript/TypeScript
Applied to files:
crates/napi/src/bindgen_runtime/js_values/arraybuffer.rs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (40)
- GitHub Check: x86_64-unknown-linux-gnu - node@20 - toolchain@ stable
- GitHub Check: i686-pc-windows-msvc - node@22 - toolchain@ stable
- GitHub Check: aarch64-apple-darwin - node@22 - toolchain@ stable
- GitHub Check: aarch64-apple-darwin - node@24 - toolchain@ stable
- GitHub Check: x86_64-unknown-linux-gnu - node@24 - toolchain@ 1.88.0
- GitHub Check: x86_64-unknown-linux-gnu - node@24 - toolchain@ stable
- GitHub Check: Build only test - armv7-linux-androideabi
- GitHub Check: x86_64-pc-windows-msvc - node@20 - toolchain@ stable
- GitHub Check: x86_64-unknown-linux-gnu - node@22 - toolchain@ 1.88.0
- GitHub Check: aarch64-apple-darwin - node@20 - toolchain@ stable
- GitHub Check: aarch64-pc-windows-msvc - node@24 - toolchain@ stable
- GitHub Check: x86_64-unknown-linux-gnu - node@22 - toolchain@ stable
- GitHub Check: aarch64-pc-windows-msvc - node@22 - toolchain@ stable
- GitHub Check: x86_64-pc-windows-msvc - node@22 - toolchain@ stable
- GitHub Check: i686-pc-windows-msvc - node@24 - toolchain@ stable
- GitHub Check: x86_64-pc-windows-msvc - node@24 - toolchain@ stable
- GitHub Check: Build only test - aarch64-unknown-linux-ohos
- GitHub Check: Build only test - aarch64-linux-android
- GitHub Check: Build only test - riscv64gc-unknown-linux-gnu
- GitHub Check: Test node wasi target
- GitHub Check: build - s390x-unknown-linux-gnu
- GitHub Check: Build only test - loongarch64-unknown-linux-gnu
- GitHub Check: build - x86_64-unknown-linux-gnu
- GitHub Check: build - powerpc64le-unknown-linux-gnu
- GitHub Check: build - aarch64-unknown-linux-gnu
- GitHub Check: Test node wasi target (--cfg tokio_unstable)
- GitHub Check: ASAN - ubuntu-24.04
- GitHub Check: build - x86_64-unknown-linux-musl
- GitHub Check: build-and-test-msys2 (UCRT64)
- GitHub Check: stable - armv7-unknown-linux-gnueabihf - node@22
- GitHub Check: ASAN - windows-latest
- GitHub Check: Test freebsd target
- GitHub Check: build-and-test-msys2 (CLANG64)
- GitHub Check: build - aarch64-unknown-linux-musl
- GitHub Check: build-and-test-msys2 (MINGW64)
- GitHub Check: Memory leak detect job
- GitHub Check: Zig-Cross-Compile-On-Linux (aarch64-apple-darwin)
- GitHub Check: Zig-Cross-Compile-On-Linux (aarch64-unknown-linux-musl)
- GitHub Check: Zig-Cross-Compile-On-Linux (armv7-unknown-linux-musleabihf)
- GitHub Check: Zig-Cross-Compile-On-Linux (x86_64-unknown-linux-musl)
🔇 Additional comments (1)
crates/napi/src/bindgen_runtime/js_values/arraybuffer.rs (1)
181-191: LGTM! Proper status validation and guarded copy.The changes correctly:
- Validate the arraybuffer creation status before attempting to copy data (line 181)
- Guard the copy operation behind
len > 0to avoid creating slices from potentially null pointers (lines 182-186)- Check the status in the success path as well (line 189)
This prevents undefined behavior when
napi_create_arraybufferfails or returns a null data pointer for zero-length buffers.

Note
Improves ArrayBuffer creation fallbacks and error handling when external buffers aren’t allowed.
ArrayBuffer::from_data, validatenapi_create_arraybufferstatus before copying and guard copies behindlen > 0ArrayBuffer::from_external, copy only onnapi_ok, always call finalize, and check status in both branches; minor control-flow cleanupWritten by Cursor Bugbot for commit 6752c3e. This will update automatically on new commits. Configure here.
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.