Fix integer overflow in Chakra napi_create_dataview bounds check#181
Fix integer overflow in Chakra napi_create_dataview bounds check#181bkaradzic-microsoft wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Pull request overview
This pull request hardens the Chakra Node-API implementation of napi_create_dataview against a size_t addition overflow in its bounds check, preventing creation of a DataView whose stored (reported) offset/length could be out of range for the underlying buffer (potential OOB access in native addons). It also adds a targeted native regression test and wires up a build define so the test only compiles/runs for the Chakra configuration.
Changes:
- Fix overflow-prone range validation in
napi_create_dataviewby checkingbyte_offsetandbyte_lengthagainst the buffer size without adding them. - Add a Chakra + 64-bit guarded gtest regression that reproduces the wraparound scenario and asserts the request is rejected (or at minimum never reports raw huge extents).
- Add a CMake compile definition (
JSRUNTIMEHOST_NAPI_ENGINE_CHAKRA) for UnitTests when building with the Chakra backend.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| Core/Node-API/Source/js_native_api_chakra.cc | Replaces the overflow-prone byte_length + byte_offset bounds check with a subtraction-based check that cannot overflow and keeps stored DataViewInfo extents in range. |
| Tests/UnitTests/Shared/Shared.cpp | Adds a Chakra/_WIN64 regression test that constructs an overflowing (offset, length) pair and verifies it is rejected (and does not leak huge extents via napi_get_dataview_info). |
| Tests/UnitTests/CMakeLists.txt | Defines JSRUNTIMEHOST_NAPI_ENGINE_CHAKRA for UnitTests when NAPI_JAVASCRIPT_ENGINE is Chakra, enabling the guarded regression test. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
e448fbf to
3025fb8
Compare
| testThread.join(); | ||
| } | ||
|
|
||
| #ifdef JSRUNTIMEHOST_NAPI_ENGINE_CHAKRA |
There was a problem hiding this comment.
Drop this guard. The test's assertions are backend-agnostic (overflow rejected, or reported extents in range), so it should run on V8/JSC too and verify they reject the overflow instead of assuming it. The guard also only exists by adding the JSRUNTIMEHOST_NAPI_ENGINE_CHAKRA compile-def in CMakeLists, which is otherwise unused — so this forces a CMake change for no real reason.
There was a problem hiding this comment.
Done — removed the JSRUNTIMEHOST_NAPI_ENGINE_CHAKRA guard. The test is backend-agnostic (it asserts the overflow is either rejected or reported in range), so it now runs on V8/JSC too and verifies they reject it. Confirmed it still passes on Chakra locally.
| target_compile_definitions(UnitTests PRIVATE JSRUNTIMEHOST_PLATFORM="${JSRUNTIMEHOST_PLATFORM}") | ||
|
|
||
| if(NAPI_JAVASCRIPT_ENGINE STREQUAL "Chakra") | ||
| target_compile_definitions(UnitTests PRIVATE JSRUNTIMEHOST_NAPI_ENGINE_CHAKRA) |
There was a problem hiding this comment.
This compile-def exists only to drive the Chakra #ifdef around the new test. With that guard removed, nothing else uses it — drop this block too.
There was a problem hiding this comment.
Done — dropped this block; with the #ifdef removed, nothing else used JSRUNTIMEHOST_NAPI_ENGINE_CHAKRA.
| } | ||
|
|
||
| #ifdef JSRUNTIMEHOST_NAPI_ENGINE_CHAKRA | ||
| #ifdef _WIN64 |
There was a problem hiding this comment.
_WIN64 is MSVC/Windows-only, not a portable 64-bit check. The precondition is a 64-bit size_t, and <cstdint> is already included:
| #ifdef _WIN64 | |
| #if SIZE_MAX > 0xFFFFFFFFu |
Leave this as the sole guard — it also works on the non-Windows backends the test should now run on.
There was a problem hiding this comment.
Done — replaced _WIN64 with #if SIZE_MAX > 0xFFFFFFFFu as the sole guard (<cstdint> is already included), so it's a portable 64-bit size_t check that also covers the non-Windows backends.
napi_create_dataview validated the requested view with an unchecked �yte_length + byte_offset > bufferLength comparison. byte_length and byte_offset are caller-supplied size_t values, so on 64-bit builds their sum can overflow and wrap past the limit. When that happens the values are then truncated to 32-bit for JsCreateDataView (creating a small, valid view) while the ORIGINAL 64-bit values are stored in DataViewInfo and later returned by napi_get_dataview_info alongside the small backing buffer, giving a calling addon an out-of-bounds read/write primitive. Validate byte_offset and byte_length against the buffer size individually without adding them, so neither overflow nor the subsequent 32-bit truncation can slip an out-of-range view past the check. After the check both values are <= bufferLength (a 32-bit quantity), so the truncation and the stored values are guaranteed in range. The JavaScriptCore backend delegates to the JS DataView constructor and is unaffected. The V8 backend carries the same upstream pattern but is vendored verbatim from Node and left untouched. Add a native regression test (the path is not reachable from JS ew DataView) that crafts an offset/length whose low 32 bits are valid but whose 64-bit sum wraps, and asserts the view is rejected (or at least never reports the raw 64-bit extents). Guarded to the Chakra engine and 64-bit builds. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
3025fb8 to
7c4bd47
Compare
Summary
napi_create_dataview(Chakra backend) validated the requested view with an uncheckedbyte_length + byte_offset > bufferLengthcomparison. Both operands are caller-suppliedsize_tvalues, so on 64-bit builds their sum can overflow and wrap past the limit. When that happens:JsCreateDataView(yielding a small, valid view), butbyte_offset/byte_lengthare stored inDataViewInfoand later returned bynapi_get_dataview_infoalongside the small backing buffer…handing a calling native addon an out-of-bounds read/write primitive (CWE-190 → CWE-125/787, also CWE-681).
Fix
Validate
byte_offsetandbyte_lengthagainst the buffer size individually, without adding them:cpp if (byte_offset > bufferLength || byte_length > bufferLength - byte_offset) { /* range error */ }After this check both values are
<= bufferLength(a 32-bit quantity), so the later 32-bit truncation and the values stored inDataViewInfoare guaranteed in range.DataViewconstructor → unaffected.Test
This path is not reachable from JS (
new DataViewuses the engine directly, not this N-API), so coverage is a native gtest. It crafts an offset/length whose low 32 bits are valid for a 16-byte buffer but whose 64-bit sum wraps, and asserts the view is rejected (and never reports the raw 64-bit extents). Guarded to the Chakra engine and 64-bit builds (JSRUNTIMEHOST_NAPI_ENGINE_CHAKRA+_WIN64).Verified the test fails on the pre-fix code and passes after the fix; full suite green (5 native tests + 151 JS tests) on Win32-x64 Chakra Debug.