This PoC demonstrates a soundness bug in Deno's N-API implementation where SendPtr<T> implements Send for all types T, even when T is not Send.
In ext/napi/util.rs:
#[repr(transparent)]
pub(crate) struct SendPtr<T>(pub *const T);
unsafe impl<T> Send for SendPtr<T> {} // ❌ UNSOUND
unsafe impl<T> Sync for SendPtr<T> {} // ❌ UNSOUNDThis allows sending pointers to non-Send types across threads, violating Rust's safety guarantees.
The bug is triggered through N-API's threadsafe function mechanism:
- Native addon creates a threadsafe function via
napi_create_threadsafe_function - Worker thread calls
napi_call_threadsafe_functionwith arbitrary data pointer - Deno wraps the pointer in
SendPtr<T>(node_api.rs:972) - Data is sent to V8 thread via
sender.spawn()(node_api.rs:980) - V8 thread accesses the data (node_api.rs:995, 1019)
If the data pointer points to non-Send data, this causes undefined behavior.
- Node.js (for node-gyp)
- Deno 2.8+
- GCC/Clang
cd /tmp/deno_sendptr_poc
node-gyp configure builddeno run -A --unstable-detect-cjs test.js=== Deno SendPtr PoC ===
This demonstrates that SendPtr<T> allows sending non-Send data across threads
[Main Thread] Created threadsafe function
[Main Thread] Spawned worker thread
[JavaScript] Waiting for async callback...
[Worker Thread] Starting...
[Worker Thread] Created data: value=42, ptr=0x7fe2240008a0
[Worker Thread] Successfully queued callback
[V8 Thread] Accessing data: value=42, ptr=0x7fe2240008a0
[V8 Thread] Dereferencing shared_ptr: 100
[JavaScript] Done
- Worker Thread allocates data and sends it via
napi_call_threadsafe_function - SendPtr wrapping happens inside Deno's Rust code (transparent to C code)
- V8 Thread receives and accesses the data
The C code simulates non-thread-safe data (shared pointers). In a real exploit, this could be:
Rc<T>orCell<T>in Rust native addons- Thread-local storage
- Any data structure that assumes single-threaded access
- Severity: Medium-High
- Exploitability: Low (requires native addon)
- Impact: Undefined behavior, potential memory corruption
- Scope: N-API threadsafe functions only
// Option 1: Add proper bounds (but breaks because *const T is never Send)
unsafe impl<T: Send> Send for SendPtr<T> {}
unsafe impl<T: Sync> Sync for SendPtr<T> {}
// Option 2: Document the safety requirement
/// SAFETY: Caller must ensure T is Send
unsafe impl<T> Send for SendPtr<T> {}
// Option 3: Redesign to use Arc instead of raw pointersaddon.c- N-API native addon that triggers the bugtest.js- JavaScript test that loads and calls the addonbinding.gyp- Build configurationpackage.json- Node.js package metadata
- Deno 2.8.1
- Node.js v26.2.0
- Linux x86_64
- Deno source:
ext/napi/util.rs:16 - Usage:
ext/napi/node_api.rs:972 - N-API docs: https://nodejs.org/api/n-api.html#napi_threadsafe_function