From 6cac15d45864c8d70904baa5cbfcc97181000960 Mon Sep 17 00:00:00 2001
From: tomoya ishida <tomoyapenguin@gmail.com>
Date: Thu, 1 Aug 2024 09:21:19 +0900
Subject: [PATCH] Fix source.match performance without specifying term string
 (#186)

Performance problem of `source.match(regexp)` was recently fixed by
specifying terminator string. However, I think maintaining appropriate
terminator string for a regexp is hard.
I propose solving this performance issue by increasing bytes to read in
each iteration.

CVE: CVE-2024-41123

Upstream-Status: Backport [https://github.com/ruby/rexml/commit/6cac15d45864c8d70904baa5cbfcc97181000960]

Signed-off-by: Divya Chellam <divya.chellam@windriver.com>
---
 .../lib/rexml/parsers/baseparser.rb           | 22 ++++++------------
 .bundle/gems/rexml-3.2.5/lib/rexml/source.rb  | 23 +++++++++++++++----
 2 files changed, 25 insertions(+), 20 deletions(-)

diff --git a/.bundle/gems/rexml-3.2.5/lib/rexml/parsers/baseparser.rb b/.bundle/gems/rexml-3.2.5/lib/rexml/parsers/baseparser.rb
index 71fce99..c1a22b8 100644
--- a/.bundle/gems/rexml-3.2.5/lib/rexml/parsers/baseparser.rb
+++ b/.bundle/gems/rexml-3.2.5/lib/rexml/parsers/baseparser.rb
@@ -124,14 +124,6 @@ module REXML
       }
 
       module Private
-        # Terminal requires two or more letters.
-        INSTRUCTION_TERM = "?>"
-        COMMENT_TERM = "-->"
-        CDATA_TERM = "]]>"
-        DOCTYPE_TERM = "]>"
-        # Read to the end of DOCTYPE because there is no proper ENTITY termination
-        ENTITY_TERM = DOCTYPE_TERM
-
         INSTRUCTION_END = /#{NAME}(\s+.*?)?\?>/um
         TAG_PATTERN = /((?>#{QNAME_STR}))\s*/um
         CLOSE_PATTERN = /(#{QNAME_STR})\s*>/um
@@ -244,7 +236,7 @@ module REXML
             return process_instruction(start_position)
           elsif @source.match("<!", true)
             if @source.match("--", true)
-              md = @source.match(/(.*?)-->/um, true, term: Private::COMMENT_TERM)
+              md = @source.match(/(.*?)-->/um, true)
               if md.nil?
                 raise REXML::ParseException.new("Unclosed comment", @source)
               end
@@ -308,7 +300,7 @@ module REXML
               raise REXML::ParseException.new( "Bad ELEMENT declaration!", @source ) if md.nil?
               return [ :elementdecl, "<!ELEMENT" + md[1] ]
             elsif @source.match("ENTITY", true)
-              match_data = @source.match(Private::ENTITYDECL_PATTERN, true, term: Private::ENTITY_TERM)
+              match_data = @source.match(Private::ENTITYDECL_PATTERN, true)
               unless match_data
                 raise REXML::ParseException.new("Malformed entity declaration", @source)
               end
@@ -377,14 +369,14 @@ module REXML
                 raise REXML::ParseException.new(message, @source)
               end
               return [:notationdecl, name, *id]
-            elsif md = @source.match(/--(.*?)-->/um, true, term: Private::COMMENT_TERM)
+            elsif md = @source.match(/--(.*?)-->/um, true)
               case md[1]
               when /--/, /-\z/
                 raise REXML::ParseException.new("Malformed comment", @source)
               end
               return [ :comment, md[1] ] if md
             end
-          elsif match = @source.match(/(%.*?;)\s*/um, true, term: Private::DOCTYPE_TERM)
+          elsif match = @source.match(/(%.*?;)\s*/um, true)
             return [ :externalentity, match[1] ]
           elsif @source.match(/\]\s*>/um, true)
             @document_status = :after_doctype
@@ -417,7 +409,7 @@ module REXML
               #STDERR.puts "SOURCE BUFFER = #{source.buffer}, #{source.buffer.size}"
               raise REXML::ParseException.new("Malformed node", @source) unless md
               if md[0][0] == ?-
-                md = @source.match(/--(.*?)-->/um, true, term: Private::COMMENT_TERM)
+                md = @source.match(/--(.*?)-->/um, true)
 
                 case md[1]
                 when /--/, /-\z/
@@ -426,7 +418,7 @@ module REXML
 
                 return [ :comment, md[1] ] if md
               else
-                md = @source.match(/\[CDATA\[(.*?)\]\]>/um, true, term: Private::CDATA_TERM)
+                md = @source.match(/\[CDATA\[(.*?)\]\]>/um, true)
                 return [ :cdata, md[1] ] if md
               end
               raise REXML::ParseException.new( "Declarations can only occur "+
@@ -664,7 +656,7 @@ module REXML
       end
 
       def process_instruction(start_position)
-        match_data = @source.match(Private::INSTRUCTION_END, true, term: Private::INSTRUCTION_TERM)
+        match_data = @source.match(Private::INSTRUCTION_END, true)
         unless match_data
           message = "Invalid processing instruction node"
           @source.position = start_position
diff --git a/.bundle/gems/rexml-3.2.5/lib/rexml/source.rb b/.bundle/gems/rexml-3.2.5/lib/rexml/source.rb
index 7be430a..7c05cb5 100644
--- a/.bundle/gems/rexml-3.2.5/lib/rexml/source.rb
+++ b/.bundle/gems/rexml-3.2.5/lib/rexml/source.rb
@@ -72,7 +72,7 @@ module REXML
       @scanner.scan_until(Regexp.union(term)) or @scanner.rest
     end
 
-    def match(pattern, cons=false, term: nil)
+    def match(pattern, cons=false)
       if cons
         @scanner.scan(pattern).nil? ? nil : @scanner
       else
@@ -159,10 +159,20 @@ module REXML
       end
     end
 
-    def read(term = nil)
+    def read(term = nil, min_bytes = 1)
       term = encode(term) if term
       begin
-        @scanner << readline(term)
+        str = readline(term)
+        @scanner << str
+        read_bytes = str.bytesize
+        begin
+          while read_bytes < min_bytes
+            str = readline(term)
+            @scanner << str
+            read_bytes += str.bytesize
+          end
+        rescue IOError
+        end
         true
       rescue Exception, NameError
         @source = nil
@@ -186,7 +196,9 @@ module REXML
       end
     end
 
-    def match( pattern, cons=false, term: nil )
+    def match( pattern, cons=false )
+      # To avoid performance issue, we need to increase bytes to read per scan
+      min_bytes = 1
       read if @scanner.eos? && @source
       while true
         if cons
@@ -197,7 +209,8 @@ module REXML
         break if md
         return nil if pattern.is_a?(String) && pattern.bytesize <= @scanner.rest_size
         return nil if @source.nil?
-        return nil unless read(term)
+        return nil unless read(nil, min_bytes)
+        min_bytes *= 2
       end
 
       md.nil? ? nil : @scanner
-- 
2.40.0

