From e34edaa74575ee13efcebdb7672b949a743ab32a Mon Sep 17 00:00:00 2001
From: Michael Saboff <msaboff@apple.com>
Date: Mon, 3 Apr 2023 20:25:08 -0700
Subject: [PATCH] [JSC] RegExpGlobalData::performMatch issue leading to OOB
 read https://bugs.webkit.org/show_bug.cgi?id=254930 rdar://107436732

Reviewed by Alexey Shvayka.

Fixed two issues:
1) In YarrInterpreter.cpp::matchAssertionBOL() we were advancing the string position for non-BMP
   characters.  Since it is an assertion, we shouldn't advance the character position.
   Made the same fix to matchAssertionEOL().
2) In StringPrototype.cpp::replaceUsingRegExpSearch(), we need to advance past both elements of
   a non-BMP character for the case where the RegExp match is empty.

* JSTests/stress/string-replace-regexp-matchBOL-correct-advancing.js: New test.
* Source/JavaScriptCore/runtime/StringPrototype.cpp:
(JSC::replaceUsingRegExpSearch):
* Source/JavaScriptCore/yarr/YarrInterpreter.cpp:
(JSC::Yarr::Interpreter::InputStream::readCheckedDontAdvance):
(JSC::Yarr::Interpreter::matchAssertionBOL):
(JSC::Yarr::Interpreter::matchAssertionEOL):

Canonical link: https://commits.webkit.org/259548.551@safari-7615-branch
---
 .../runtime/StringPrototype.cpp               | 10 ++++++++++
 .../JavaScriptCore/yarr/YarrInterpreter.cpp   | 19 +++++++++++++++++--
 2 files changed, 27 insertions(+), 2 deletions(-)

diff --git a/Source/JavaScriptCore/runtime/StringPrototype.cpp b/Source/JavaScriptCore/runtime/StringPrototype.cpp
index 08104b1d..459295f7 100644
--- a/Source/JavaScriptCore/runtime/StringPrototype.cpp
+++ b/Source/JavaScriptCore/runtime/StringPrototype.cpp
@@ -603,6 +603,11 @@ static ALWAYS_INLINE JSString* replaceUsingRegExpSearch(
                 startPosition++;
                 if (startPosition > sourceLen)
                     break;
+                if (U16_IS_LEAD(source[startPosition - 1]) && U16_IS_TRAIL(source[startPosition])) {
+                    startPosition++;
+                    if (startPosition > sourceLen)
+                        break;
+                }
             }
         }
     } else {
@@ -682,6 +687,11 @@ static ALWAYS_INLINE JSString* replaceUsingRegExpSearch(
                 startPosition++;
                 if (startPosition > sourceLen)
                     break;
+                if (U16_IS_LEAD(source[startPosition - 1]) && U16_IS_TRAIL(source[startPosition])) {
+                    startPosition++;
+                    if (startPosition > sourceLen)
+                        break;
+                }
             }
         } while (global);
     }
diff --git a/Source/JavaScriptCore/yarr/YarrInterpreter.cpp b/Source/JavaScriptCore/yarr/YarrInterpreter.cpp
index 95a848a1..d222e620 100644
--- a/Source/JavaScriptCore/yarr/YarrInterpreter.cpp
+++ b/Source/JavaScriptCore/yarr/YarrInterpreter.cpp
@@ -209,6 +209,21 @@ public:
             }
             return result;
         }
+
+	int readCheckedDontAdvance(unsigned negativePositionOffest)
+        {
+            RELEASE_ASSERT(pos >= negativePositionOffest);
+            unsigned p = pos - negativePositionOffest;
+            ASSERT(p < length);
+            int result = input[p];
+            if (U16_IS_LEAD(result) && decodeSurrogatePairs && p + 1 < length && U16_IS_TRAIL(input[p + 1])) {
+                if (atEnd())
+                    return -1;
+
+                result = U16_GET_SUPPLEMENTARY(result, input[p + 1]);
+            }
+            return result;
+        }
         
         int readSurrogatePairChecked(unsigned negativePositionOffset)
         {
@@ -482,13 +497,13 @@ public:
 
     bool matchAssertionBOL(ByteTerm& term)
     {
-        return (input.atStart(term.inputPosition)) || (pattern->multiline() && testCharacterClass(pattern->newlineCharacterClass, input.readChecked(term.inputPosition + 1)));
+        return (input.atStart(term.inputPosition)) || (pattern->multiline() && testCharacterClass(pattern->newlineCharacterClass, input.readCheckedDontAdvance(term.inputPosition + 1)));
     }
 
     bool matchAssertionEOL(ByteTerm& term)
     {
         if (term.inputPosition)
-            return (input.atEnd(term.inputPosition)) || (pattern->multiline() && testCharacterClass(pattern->newlineCharacterClass, input.readChecked(term.inputPosition)));
+            return (input.atEnd(term.inputPosition)) || (pattern->multiline() && testCharacterClass(pattern->newlineCharacterClass, input.readCheckedDontAdvance(term.inputPosition)));
 
         return (input.atEnd()) || (pattern->multiline() && testCharacterClass(pattern->newlineCharacterClass, input.read()));
     }
-- 
2.33.0

