Skip to content

Commit 011cf3f

Browse files
motiz88facebook-github-bot
authored andcommitted
JSStringToSTLString: truncate string on conversion failure
Summary: [A recent change to JSStringToSTLString](#26955) causes a crash when the function is invoked with invalid UTF-16 data. The old behaviour, restored here, was to truncate the string before the first invalid character. Here's how [the original code](https://github.com/facebook/react-native/blob/aee88b6843cea63d6aa0b5879ad6ef9da4701846/ReactCommon/jsi/JSCRuntime.cpp#L287) handled this case: ``` std::string JSStringToSTLString(JSStringRef str) { size_t maxBytes = JSStringGetMaximumUTF8CStringSize(str); // ^ maxBytes >= 1 regardless of str's contents std::vector<char> buffer(maxBytes); // ^ vector is zero initialised JSStringGetUTF8CString(str, buffer.data(), maxBytes); // ^ writes '\0' at the first invalid character and returns early (see JSC source code) return std::string(buffer.data()); // ^ copies the string up to the first '\0' } ``` See the JSC implementations of [`JSStringGetUTF8CString`](https://opensource.apple.com/source/JavaScriptCore/JavaScriptCore-7600.8.7/API/JSStringRef.cpp.auto.html) and [`convertUTF16ToUTF8`](https://opensource.apple.com/source/WTF/WTF-7600.7.2/wtf/unicode/UTF8.cpp.auto.html). Based on the fact that `JSStringGetUTF8CString` *always* null-terminates the buffer - even when it bails out of converting an invalid string - here we're able to both 1. keep the fast path (not zero-initialising, not scanning for the null terminator) for the common case when the data is valid and JSStringGetUTF8CString returns a nonzero length; and 2. return the truncated string when JSStringGetUTF8CString returns an error code of 0, by scanning for the null terminator. Changelog: [General] [Fixed] - Fix crash when passing invalid UTF-16 data from JSC into native code Differential Revision: D19902751 fbshipit-source-id: 06bace2719800e921ec115ad6a29251eafd473f6
1 parent 3611170 commit 011cf3f

File tree

1 file changed

+13
-1
lines changed

1 file changed

+13
-1
lines changed

ReactCommon/jsi/JSCRuntime.cpp

+13-1
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ std::string JSStringToSTLString(JSStringRef str) {
294294
std::array<char, 20> stackBuffer;
295295
std::unique_ptr<char[]> heapBuffer;
296296
char *buffer;
297+
// NOTE: By definition, maxBytes >= 1 since the null terminator is included.
297298
size_t maxBytes = JSStringGetMaximumUTF8CStringSize(str);
298299
if (maxBytes <= stackBuffer.size()) {
299300
buffer = stackBuffer.data();
@@ -302,7 +303,18 @@ std::string JSStringToSTLString(JSStringRef str) {
302303
buffer = heapBuffer.get();
303304
}
304305
size_t actualBytes = JSStringGetUTF8CString(str, buffer, maxBytes);
305-
// NOTE: By definition, maxBytes >= actualBytes >= 1.
306+
if (!actualBytes) {
307+
// Happens if maxBytes == 0 (never the case here) or if str contains
308+
// invalid UTF-16 data, since JSStringGetUTF8CString attempts a strict
309+
// conversion.
310+
// When converting an invalid string, JSStringGetUTF8CString writes a null
311+
// terminator before returning. So we can reliably treat our buffer as a C
312+
// string and return the truncated data to our caller. This is slightly
313+
// slower than if we knew the length (like below) but better than crashing.
314+
// TODO(T62295565): Perform a non-strict, best effort conversion of the
315+
// full string instead, like we did before the JSI migration.
316+
return std::string(buffer);
317+
}
306318
return std::string(buffer, actualBytes - 1);
307319
}
308320

0 commit comments

Comments
 (0)