Skip to content

Fix integer underflow in Chakra napi_get_value_string_* zero-bufsize handling#197

Open
bkaradzic-microsoft wants to merge 1 commit into
BabylonJS:mainfrom
bkaradzic-microsoft:fix/string-utf16-bufsize-underflow
Open

Fix integer underflow in Chakra napi_get_value_string_* zero-bufsize handling#197
bkaradzic-microsoft wants to merge 1 commit into
BabylonJS:mainfrom
bkaradzic-microsoft:fix/string-utf16-bufsize-underflow

Conversation

@bkaradzic-microsoft

Copy link
Copy Markdown
Member

Summary

Fixes an integer underflow in the Chakra Node-API string getters when a caller passes a non-null buffer with bufsize == 0.

napi_get_value_string_utf16 forwarded bufsize - 1 to JsCopyStringUtf16 as the destination capacity and stored the null terminator at buf[bufsize - 1]. With bufsize == 0, bufsize - 1 underflows to SIZE_MAX, so:

  • the entire JS string is copied into the zero-length buffer, and
  • the terminator is written at buf[SIZE_MAX].

That's an attacker-sized out-of-bounds write reachable from any native caller that passes (buf != nullptr, bufsize == 0) (CWE-191 → CWE-787/CWE-120).

Fix

  • napi_get_value_string_utf16: gate the copy on bufsize != 0 (mirroring the upstream Node implementation). A non-null buffer with bufsize == 0 now reports zero copied and writes nothing.
  • napi_get_value_string_latin1 / napi_get_value_string_utf8: these shared a related, milder bug — with bufsize == 0 they took the slow path (a needless allocation) and stored the terminator at buf[0], one byte past a zero-length buffer. Both now short-circuit bufsize == 0 identically.

Test

Adds a native regression test (NodeApi.GetValueStringUtf16HandlesZeroBufsize) — the path isn't reachable from JS, so it's exercised through the C API. It passes a sentinel-filled buffer with bufsize == 0 and asserts nothing is written and the reported length is zero, while a normally-sized buffer still copies and null-terminates. Guarded off the V8JSI backend (which doesn't expose napi_get_value_string_utf16), same as the napi_create_dataview regression test from #181.

Verified (Win32/Chakra): builds clean; the new test and the full UnitTests suite pass.

Copilot AI review requested due to automatic review settings June 12, 2026 00:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the Chakra Node-API string getter implementations against bufsize == 0 with a non-null buffer to prevent size_t underflow and out-of-bounds writes, and adds a native regression test to validate the behavior.

Changes:

  • Add bufsize == 0 short-circuit handling in Chakra napi_get_value_string_latin1 / utf8 to avoid an OOB terminator write and avoid the slow-path allocation.
  • Gate Chakra napi_get_value_string_utf16 copy/terminator logic on bufsize != 0 to prevent bufsize - 1 underflow.
  • Add a native unit test covering the napi_get_value_string_utf16(buf != nullptr, bufsize == 0) regression scenario.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
Tests/UnitTests/Shared/Shared.cpp Adds a native regression test for napi_get_value_string_utf16 zero-bufsize behavior (currently built on all non-JSI backends).
Core/Node-API/Source/js_native_api_chakra.cc Fixes Chakra string getters’ handling of (buf != nullptr, bufsize == 0) to prevent underflow/OOB writes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Tests/UnitTests/Shared/Shared.cpp
napi_get_value_string_utf16 computed bufsize - 1 as the destination capacity
and stored the null terminator at buf[bufsize - 1]. With a non-null buffer and
bufsize == 0, bufsize - 1 underflows to SIZE_MAX, so the entire JS string is
copied into the zero-length buffer and the terminator is written at
buf[SIZE_MAX] -- an attacker-sized out-of-bounds write reachable from any native
caller passing (buf != nullptr, bufsize == 0) (CWE-191 -> CWE-787).

This affected both the Chakra and JavaScriptCore backends:
- Chakra (js_native_api_chakra.cc): gate the copy on bufsize != 0 (mirroring the
  upstream Node implementation). The latin1/utf8 siblings shared a milder bug --
  with bufsize == 0 they took the slow path (a needless allocation) and stored
  the terminator at buf[0], one byte past a zero-length buffer; both now
  short-circuit bufsize == 0 too.
- JavaScriptCore (js_native_api_javascriptcore.cc): the utf16/latin1/utf8 getters
  delegate to JSString::CopyTo*, which underflow bufsize - 1 and always write
  buf[size] = 0. Short-circuit bufsize == 0 at the getter before calling the copy
  helper.

V8 already handles bufsize == 0 correctly and is unchanged.

Add a native regression test (this path is not reachable from JS) that calls
napi_get_value_string_utf16 with a sentinel buffer and bufsize == 0, asserting
nothing is written and the reported length is zero, while a normally-sized
buffer still copies and null-terminates. Runs on Chakra, V8 and JavaScriptCore;
guarded off the V8JSI backend, which does not expose napi_get_value_string_utf16.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@bkaradzic-microsoft bkaradzic-microsoft force-pushed the fix/string-utf16-bufsize-underflow branch from 063b9db to e7682b1 Compare June 12, 2026 00:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants