Title: [231197] trunk
Revision
231197
Author
[email protected]
Date
2018-05-01 09:01:25 -0700 (Tue, 01 May 2018)

Log Message

Correctly detect string overflow when using the 'Function' constructor
https://bugs.webkit.org/show_bug.cgi?id=184883
<rdar://problem/36320331>

Reviewed by Filip Pizlo.

JSTests:

I put this regression test in the 'slowMicrobenchmarks' directory because it takes nearly 30s to run, and I am not sure where else to put it.

* slowMicrobenchmarks/function-constructor-with-huge-strings.js: Added.
(catch):

Source/_javascript_Core:

The 'Function' constructor creates a string containing the source code of the new function through repeated string concatenation.
Because there was no way for the string concatenation routines in WTF to return an error, they just crashed in that case.

I added new tryAppend methods alongside the old append methods, that return a boolean (true means success, false means an overflow happened).
In this way, it becomes possible for the Function constructor to just throw a proper JS exception when asked to create a string > 4GB.
I made new methods instead of just adapting the existing ones (and reverted such a change on appendQuotedJSONString) so that callers that rely on the old behaviour (a hard CRASH() on overflow) don't silently start failing.

* runtime/FunctionConstructor.cpp:
(JSC::constructFunctionSkippingEvalEnabledCheck):
* runtime/JSONObject.cpp:
(JSC::Stringifier::appendStringifiedValue):

Source/WTF:

I added new tryAppend methods alongside the old append methods in StringBuilder, that return a boolean (true means success, false means an overflow happened).
I made new methods instead of just adapting the existing ones (and reverted such a change on appendQuotedJSONString) so that callers that rely on the old behaviour (a hard CRASH() on overflow) don't silently start failing.

* wtf/text/StringBuilder.cpp:
(WTF::StringBuilder::allocateBufferUpConvert):
(WTF::StringBuilder::tryAllocateBufferUpConvert):
(WTF::StringBuilder::appendUninitialized):
(WTF::StringBuilder::append):
(WTF::StringBuilder::tryAppend):
* wtf/text/StringBuilder.h:
(WTF::StringBuilder::tryAppend):
(WTF::StringBuilder::append):
(WTF::StringBuilder::tryAppendLiteral):
* wtf/text/StringBuilderJSON.cpp:
(WTF::StringBuilder::appendQuotedJSONString):
(WTF::StringBuilder::tryAppendQuotedJSONString):

Modified Paths

Added Paths

Diff

Modified: trunk/JSTests/ChangeLog (231196 => 231197)


--- trunk/JSTests/ChangeLog	2018-05-01 15:47:29 UTC (rev 231196)
+++ trunk/JSTests/ChangeLog	2018-05-01 16:01:25 UTC (rev 231197)
@@ -1,5 +1,18 @@
 2018-05-01  Robin Morisset  <[email protected]>
 
+        Correctly detect string overflow when using the 'Function' constructor
+        https://bugs.webkit.org/show_bug.cgi?id=184883
+        <rdar://problem/36320331>
+
+        Reviewed by Filip Pizlo.
+
+        I put this regression test in the 'slowMicrobenchmarks' directory because it takes nearly 30s to run, and I am not sure where else to put it.
+
+        * slowMicrobenchmarks/function-constructor-with-huge-strings.js: Added.
+        (catch):
+
+2018-05-01  Robin Morisset  <[email protected]>
+
         IntlObject.cpp::removeUnicodeLocaleExtension() should not touch locales that end in '-u'
         https://bugs.webkit.org/show_bug.cgi?id=185162
 

Added: trunk/JSTests/slowMicrobenchmarks/function-constructor-with-huge-strings.js (0 => 231197)


--- trunk/JSTests/slowMicrobenchmarks/function-constructor-with-huge-strings.js	                        (rev 0)
+++ trunk/JSTests/slowMicrobenchmarks/function-constructor-with-huge-strings.js	2018-05-01 16:01:25 UTC (rev 231197)
@@ -0,0 +1,13 @@
+var hugeString = "x";
+for (i = 0; i < 25; ++i) {
+    hugeString += hugeString;
+}
+
+var weird = '';
+try {
+    var f = new Function(hugeString, hugeString, hugeString, hugeString, hugeString, hugeString, hugeString,
+      hugeString, hugeString, hugeString, hugeString, hugeString, hugeString, hugeString,
+      hugeString, hugeString, hugeString, hugeString, hugeString, hugeString, hugeString,
+      () => 42,
+      "return 42;");
+} catch (e) {}

Modified: trunk/Source/_javascript_Core/ChangeLog (231196 => 231197)


--- trunk/Source/_javascript_Core/ChangeLog	2018-05-01 15:47:29 UTC (rev 231196)
+++ trunk/Source/_javascript_Core/ChangeLog	2018-05-01 16:01:25 UTC (rev 231197)
@@ -1,5 +1,25 @@
 2018-05-01  Robin Morisset  <[email protected]>
 
+        Correctly detect string overflow when using the 'Function' constructor
+        https://bugs.webkit.org/show_bug.cgi?id=184883
+        <rdar://problem/36320331>
+
+        Reviewed by Filip Pizlo.
+
+        The 'Function' constructor creates a string containing the source code of the new function through repeated string concatenation.
+        Because there was no way for the string concatenation routines in WTF to return an error, they just crashed in that case.
+
+        I added new tryAppend methods alongside the old append methods, that return a boolean (true means success, false means an overflow happened).
+        In this way, it becomes possible for the Function constructor to just throw a proper JS exception when asked to create a string > 4GB.
+        I made new methods instead of just adapting the existing ones (and reverted such a change on appendQuotedJSONString) so that callers that rely on the old behaviour (a hard CRASH() on overflow) don't silently start failing.
+
+        * runtime/FunctionConstructor.cpp:
+        (JSC::constructFunctionSkippingEvalEnabledCheck):
+        * runtime/JSONObject.cpp:
+        (JSC::Stringifier::appendStringifiedValue):
+
+2018-05-01  Robin Morisset  <[email protected]>
+
         IntlObject.cpp::removeUnicodeLocaleExtension() should not touch locales that end in '-u'
         https://bugs.webkit.org/show_bug.cgi?id=185162
 

Modified: trunk/Source/_javascript_Core/runtime/FunctionConstructor.cpp (231196 => 231197)


--- trunk/Source/_javascript_Core/runtime/FunctionConstructor.cpp	2018-05-01 15:47:29 UTC (rev 231196)
+++ trunk/Source/_javascript_Core/runtime/FunctionConstructor.cpp	2018-05-01 16:01:25 UTC (rev 231197)
@@ -125,20 +125,25 @@
         program = makeString("{", prefix, functionName.string(), "() {\n", body, "\n}}");
     } else {
         StringBuilder builder;
-        builder.append('{');
-        builder.append(prefix);
-        builder.append(functionName.string());
-        builder.append('(');
+        bool success = true;
+        success &= builder.tryAppend('{');
+        success &= builder.tryAppend(prefix);
+        success &= builder.tryAppend(functionName.string());
+        success &= builder.tryAppend('(');
         StringBuilder parameterBuilder;
         auto viewWithString = args.at(0).toString(exec)->viewWithUnderlyingString(exec);
         RETURN_IF_EXCEPTION(scope, nullptr);
-        parameterBuilder.append(viewWithString.view);
+        success &= parameterBuilder.tryAppend(viewWithString.view);
         for (size_t i = 1; i < args.size() - 1; i++) {
-            parameterBuilder.appendLiteral(", ");
+            success &= parameterBuilder.tryAppendLiteral(", ");
             auto viewWithString = args.at(i).toString(exec)->viewWithUnderlyingString(exec);
             RETURN_IF_EXCEPTION(scope, nullptr);
-            parameterBuilder.append(viewWithString.view);
+            success &= parameterBuilder.tryAppend(viewWithString.view);
         }
+        if (!success) {
+            throwOutOfMemoryError(exec, scope);
+            return nullptr;
+        }
 
         {
             // The spec mandates that the parameters parse as a valid parameter list
@@ -153,14 +158,18 @@
             }
         }
 
-        builder.append(parameterBuilder);
-        builder.appendLiteral(") {\n");
+        success &= builder.tryAppend(parameterBuilder);
+        success &= builder.tryAppendLiteral(") {\n");
         auto body = args.at(args.size() - 1).toWTFString(exec);
         RETURN_IF_EXCEPTION(scope, nullptr);
         checkBody(body);
         RETURN_IF_EXCEPTION(scope, nullptr);
-        builder.append(body);
-        builder.appendLiteral("\n}}");
+        success &= builder.tryAppend(body);
+        success &= builder.tryAppendLiteral("\n}}");
+        if (!success) {
+            throwOutOfMemoryError(exec, scope);
+            return nullptr;
+        }
         program = builder.toString();
     }
 

Modified: trunk/Source/_javascript_Core/runtime/JSONObject.cpp (231196 => 231197)


--- trunk/Source/_javascript_Core/runtime/JSONObject.cpp	2018-05-01 15:47:29 UTC (rev 231196)
+++ trunk/Source/_javascript_Core/runtime/JSONObject.cpp	2018-05-01 16:01:25 UTC (rev 231197)
@@ -357,7 +357,7 @@
     if (value.isString()) {
         const String& string = asString(value)->value(m_exec);
         RETURN_IF_EXCEPTION(scope, StringifyFailed);
-        if (builder.appendQuotedJSONString(string))
+        if (builder.tryAppendQuotedJSONString(string))
             return StringifySucceeded;
         throwOutOfMemoryError(m_exec, scope);
         return StringifyFailed;

Modified: trunk/Source/WTF/ChangeLog (231196 => 231197)


--- trunk/Source/WTF/ChangeLog	2018-05-01 15:47:29 UTC (rev 231196)
+++ trunk/Source/WTF/ChangeLog	2018-05-01 16:01:25 UTC (rev 231197)
@@ -1,3 +1,28 @@
+2018-05-01  Robin Morisset  <[email protected]>
+
+        Correctly detect string overflow when using the 'Function' constructor
+        https://bugs.webkit.org/show_bug.cgi?id=184883
+        <rdar://problem/36320331>
+
+        Reviewed by Filip Pizlo.
+
+        I added new tryAppend methods alongside the old append methods in StringBuilder, that return a boolean (true means success, false means an overflow happened).
+        I made new methods instead of just adapting the existing ones (and reverted such a change on appendQuotedJSONString) so that callers that rely on the old behaviour (a hard CRASH() on overflow) don't silently start failing.
+
+        * wtf/text/StringBuilder.cpp:
+        (WTF::StringBuilder::allocateBufferUpConvert):
+        (WTF::StringBuilder::tryAllocateBufferUpConvert):
+        (WTF::StringBuilder::appendUninitialized):
+        (WTF::StringBuilder::append):
+        (WTF::StringBuilder::tryAppend):
+        * wtf/text/StringBuilder.h:
+        (WTF::StringBuilder::tryAppend):
+        (WTF::StringBuilder::append):
+        (WTF::StringBuilder::tryAppendLiteral):
+        * wtf/text/StringBuilderJSON.cpp:
+        (WTF::StringBuilder::appendQuotedJSONString):
+        (WTF::StringBuilder::tryAppendQuotedJSONString):
+
 2018-04-30  Mark Lam  <[email protected]>
 
         Apply PtrTags to the MetaAllocator and friends.

Modified: trunk/Source/WTF/wtf/text/StringBuilder.cpp (231196 => 231197)


--- trunk/Source/WTF/wtf/text/StringBuilder.cpp	2018-05-01 15:47:29 UTC (rev 231196)
+++ trunk/Source/WTF/wtf/text/StringBuilder.cpp	2018-05-01 16:01:25 UTC (rev 231197)
@@ -126,10 +126,17 @@
 // from either m_string or m_buffer, neither will be reassigned until the copy has completed).
 void StringBuilder::allocateBufferUpConvert(const LChar* currentCharacters, unsigned requiredLength)
 {
+    if (!tryAllocateBufferUpConvert(currentCharacters, requiredLength))
+        CRASH();
+}
+bool StringBuilder::tryAllocateBufferUpConvert(const LChar* currentCharacters, unsigned requiredLength)
+{
     ASSERT(m_is8Bit);
     ASSERT(requiredLength >= m_length);
     // Copy the existing data into a new buffer, set result to point to the end of the existing data.
-    auto buffer = StringImpl::createUninitialized(requiredLength, m_bufferCharacters16);
+    auto buffer = StringImpl::tryCreateUninitialized(requiredLength, m_bufferCharacters16);
+    if (!buffer)
+        return false;
     for (unsigned i = 0; i < m_length; ++i)
         m_bufferCharacters16[i] = currentCharacters[i];
     
@@ -139,6 +146,7 @@
     m_buffer = WTFMove(buffer);
     m_string = String();
     ASSERT(m_buffer->length() == requiredLength);
+    return true;
 }
 
 template <>
@@ -201,6 +209,7 @@
 
 // Make 'length' additional capacity be available in m_buffer, update m_string & m_length,
 // return a pointer to the newly allocated storage.
+// Returns nullptr if the size of the new builder would have overflowed
 template <typename CharType>
 ALWAYS_INLINE CharType* StringBuilder::appendUninitialized(unsigned length)
 {
@@ -209,7 +218,7 @@
     // Calculate the new size of the builder after appending.
     unsigned requiredLength = length + m_length;
     if (requiredLength < length)
-        CRASH();
+        return nullptr;
 
     if ((m_buffer) && (requiredLength <= m_buffer->length())) {
         // If the buffer is valid it must be at least as long as the current builder contents!
@@ -248,8 +257,14 @@
 
 void StringBuilder::append(const UChar* characters, unsigned length)
 {
+    if (!tryAppend(characters, length))
+        CRASH();
+}
+
+bool StringBuilder::tryAppend(const UChar* characters, unsigned length)
+{
     if (!length)
-        return;
+        return true;
 
     ASSERT(characters);
 
@@ -257,40 +272,54 @@
         if (length == 1 && !(*characters & ~0xff)) {
             // Append as 8 bit character
             LChar lChar = static_cast<LChar>(*characters);
-            append(&lChar, 1);
-            return;
+            return tryAppend(&lChar, 1);
         }
 
         // Calculate the new size of the builder after appending.
         unsigned requiredLength = length + m_length;
         if (requiredLength < length)
-            CRASH();
+            return false;
         
         if (m_buffer) {
             // If the buffer is valid it must be at least as long as the current builder contents!
             ASSERT(m_buffer->length() >= m_length);
-            
-            allocateBufferUpConvert(m_buffer->characters8(), expandedCapacity(capacity(), requiredLength));
+            if (!tryAllocateBufferUpConvert(m_buffer->characters8(), expandedCapacity(capacity(), requiredLength)))
+                return false;
         } else {
             ASSERT(m_string.length() == m_length);
-            allocateBufferUpConvert(m_string.isNull() ? 0 : m_string.characters8(), expandedCapacity(capacity(), requiredLength));
+            if (!tryAllocateBufferUpConvert(m_string.isNull() ? 0 : m_string.characters8(), expandedCapacity(capacity(), requiredLength)))
+                return false;
         }
 
         memcpy(m_bufferCharacters16 + m_length, characters, static_cast<size_t>(length) * sizeof(UChar));
         m_length = requiredLength;
-    } else
-        memcpy(appendUninitialized<UChar>(length), characters, static_cast<size_t>(length) * sizeof(UChar));
+    } else {
+        UChar* dest = appendUninitialized<UChar>(length);
+        if (!dest)
+            return false;
+        memcpy(dest, characters, static_cast<size_t>(length) * sizeof(UChar));
+    }
     ASSERT(m_buffer->length() >= m_length);
+    return true;
 }
 
 void StringBuilder::append(const LChar* characters, unsigned length)
 {
+    if (!tryAppend(characters, length))
+        CRASH();
+}
+
+bool StringBuilder::tryAppend(const LChar* characters, unsigned length)
+{
     if (!length)
-        return;
+        return true;
+
     ASSERT(characters);
 
     if (m_is8Bit) {
         LChar* dest = appendUninitialized<LChar>(length);
+        if (!dest)
+            return false;
         if (length > 8)
             memcpy(dest, characters, static_cast<size_t>(length) * sizeof(LChar));
         else {
@@ -300,10 +329,13 @@
         }
     } else {
         UChar* dest = appendUninitialized<UChar>(length);
+        if (!dest)
+            return false;
         const LChar* end = characters + length;
         while (characters < end)
             *(dest++) = *(characters++);
     }
+    return true;
 }
 
 #if USE(CF)

Modified: trunk/Source/WTF/wtf/text/StringBuilder.h (231196 => 231197)


--- trunk/Source/WTF/wtf/text/StringBuilder.h	2018-05-01 15:47:29 UTC (rev 231196)
+++ trunk/Source/WTF/wtf/text/StringBuilder.h	2018-05-01 16:01:25 UTC (rev 231197)
@@ -49,9 +49,12 @@
     StringBuilder& operator=(StringBuilder&&) = default;
 
     WTF_EXPORT_PRIVATE void append(const UChar*, unsigned);
+    WTF_EXPORT_PRIVATE bool tryAppend(const UChar*, unsigned);
     WTF_EXPORT_PRIVATE void append(const LChar*, unsigned);
+    WTF_EXPORT_PRIVATE bool tryAppend(const LChar*, unsigned);
 
     ALWAYS_INLINE void append(const char* characters, unsigned length) { append(reinterpret_cast<const LChar*>(characters), length); }
+    ALWAYS_INLINE bool tryAppend(const char* characters, unsigned length) { return tryAppend(reinterpret_cast<const LChar*>(characters), length); }
 
     void append(const AtomicString& atomicString)
     {
@@ -60,8 +63,13 @@
 
     void append(const String& string)
     {
+        if (!tryAppend(string))
+            CRASH();
+    }
+    bool tryAppend(const String& string)
+    {
         if (!string.length())
-            return;
+            return true;
 
         // If we're appending to an empty string, and there is not a buffer (reserveCapacity has not been called)
         // then just retain the string.
@@ -69,19 +77,24 @@
             m_string = string;
             m_length = string.length();
             m_is8Bit = m_string.is8Bit();
-            return;
+            return true;
         }
 
         if (string.is8Bit())
-            append(string.characters8(), string.length());
+            return tryAppend(string.characters8(), string.length());
         else
-            append(string.characters16(), string.length());
+            return tryAppend(string.characters16(), string.length());
     }
 
     void append(const StringBuilder& other)
     {
+        if (!tryAppend(other))
+            CRASH();
+    }
+    bool tryAppend(const StringBuilder& other)
+    {
         if (!other.m_length)
-            return;
+            return true;
 
         // If we're appending to an empty string, and there is not a buffer (reserveCapacity has not been called)
         // then just retain the string.
@@ -88,21 +101,26 @@
         if (!m_length && !m_buffer && !other.m_string.isNull()) {
             m_string = other.m_string;
             m_length = other.m_length;
-            return;
+            return true;
         }
 
         if (other.is8Bit())
-            append(other.characters8(), other.m_length);
+            return tryAppend(other.characters8(), other.m_length);
         else
-            append(other.characters16(), other.m_length);
+            return tryAppend(other.characters16(), other.m_length);
     }
 
     void append(StringView stringView)
     {
+        if (!tryAppend(stringView))
+            CRASH();
+    }
+    bool tryAppend(StringView stringView)
+    {
         if (stringView.is8Bit())
-            append(stringView.characters8(), stringView.length());
+            return tryAppend(stringView.characters8(), stringView.length());
         else
-            append(stringView.characters16(), stringView.length());
+            return tryAppend(stringView.characters16(), stringView.length());
     }
 
 #if USE(CF)
@@ -131,6 +149,12 @@
         if (characters)
             append(characters, strlen(characters));
     }
+    bool tryAppend(const char* characters)
+    {
+        if (characters)
+            return tryAppend(characters, strlen(characters));
+        return true;
+    }
 
     void append(UChar c)
     {
@@ -150,19 +174,23 @@
 
     void append(LChar c)
     {
+        if (!tryAppend(c))
+            CRASH();
+    }
+    bool tryAppend(LChar c)
+    {
         if (m_buffer && m_length < m_buffer->length() && m_string.isNull()) {
             if (m_is8Bit)
                 m_bufferCharacters8[m_length++] = c;
             else
                 m_bufferCharacters16[m_length++] = c;
+            return true;
         } else
-            append(&c, 1);
+            return tryAppend(&c, 1);
     }
 
-    void append(char c)
-    {
-        append(static_cast<LChar>(c));
-    }
+    void append(char c) { append(static_cast<LChar>(c)); }
+    bool tryAppend(char c) { return tryAppend(static_cast<LChar>(c)); }
 
     void append(UChar32 c)
     {
@@ -174,10 +202,13 @@
         append(U16_TRAIL(c));
     }
 
-    WTF_EXPORT_PRIVATE bool appendQuotedJSONString(const String&);
+    WTF_EXPORT_PRIVATE void appendQuotedJSONString(const String&);
+    WTF_EXPORT_PRIVATE bool tryAppendQuotedJSONString(const String&);
 
     template<unsigned characterCount>
     ALWAYS_INLINE void appendLiteral(const char (&characters)[characterCount]) { append(characters, characterCount - 1); }
+    template<unsigned characterCount>
+    ALWAYS_INLINE bool tryAppendLiteral(const char (&characters)[characterCount]) { return tryAppend(characters, characterCount - 1); }
 
     WTF_EXPORT_PRIVATE void appendNumber(int);
     WTF_EXPORT_PRIVATE void appendNumber(unsigned int);
@@ -298,6 +329,7 @@
     void allocateBuffer(const LChar* currentCharacters, unsigned requiredLength);
     void allocateBuffer(const UChar* currentCharacters, unsigned requiredLength);
     void allocateBufferUpConvert(const LChar* currentCharacters, unsigned requiredLength);
+    bool tryAllocateBufferUpConvert(const LChar* currentCharacters, unsigned requiredLength);
     template <typename CharType>
     void reallocateBuffer(unsigned requiredLength);
     template <typename CharType>

Modified: trunk/Source/WTF/wtf/text/StringBuilderJSON.cpp (231196 => 231197)


--- trunk/Source/WTF/wtf/text/StringBuilderJSON.cpp	2018-05-01 15:47:29 UTC (rev 231196)
+++ trunk/Source/WTF/wtf/text/StringBuilderJSON.cpp	2018-05-01 16:01:25 UTC (rev 231197)
@@ -74,8 +74,14 @@
     }
 }
 
-bool StringBuilder::appendQuotedJSONString(const String& string)
+void StringBuilder::appendQuotedJSONString(const String& string)
 {
+    if (!tryAppendQuotedJSONString(string))
+        CRASH();
+}
+
+bool StringBuilder::tryAppendQuotedJSONString(const String& string)
+{
     // Make sure we have enough buffer space to append this string without having
     // to worry about reallocating in the middle.
     // The 2 is for the '"' quotes on each end.
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to