CVE-2024-35176 (published 2024-05-16T16:15Z)

 REXML is an XML toolkit for Ruby. The REXML gem before 3.2.6 has a denial of service vulnerability when it parses an XML that has many `<`s in an attribute value. Those who need to parse untrusted XMLs may be impacted to this vulnerability. The REXML gem 3.2.7 or later include the patch to fix this vulnerability. As a workaround, don't parse untrusted XMLs.


Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/ruby/rexml
commit_details: repo_url: https://github.com/ruby/rexml
author: Nobuyoshi Nakada
author_date: 2024-05-16 11:26:51+09:00
msg: Read quoted attributes in chunks (#126)
repository: repo_url: https://github.com/ruby/rexml
repo_name: ruby/rexml
description: REXML is an XML toolkit for Ruby
repo_language: Ruby
owner: ruby
file_changes: filename: Gemfile
change_type: ModificationType.MODIFY
diff: @@ -10,4 +10,5 @@ group :development do
   gem "bundler"
   gem "rake"
   gem "test-unit"
+  gem "test-unit-ruby-core"
 end

programming_language: Ruby
filename: baseparser.rb
change_type: ModificationType.MODIFY
diff: @@ -628,17 +628,17 @@ module REXML
               message = "Missing attribute equal: <#{name}>"
               raise REXML::ParseException.new(message, @source)
             end
-            unless match = @source.match(/(['"])(.*?)\1\s*/um, true)
-              if match = @source.match(/(['"])/, true)
-                message =
-                  "Missing attribute value end quote: <#{name}>: <#{match[1]}>"
-                raise REXML::ParseException.new(message, @source)
-              else
-                message = "Missing attribute value start quote: <#{name}>"
-                raise REXML::ParseException.new(message, @source)
-              end
+            unless match = @source.match(/(['"])/, true)
+              message = "Missing attribute value start quote: <#{name}>"
+              raise REXML::ParseException.new(message, @source)
+            end
+            quote = match[1]
+            value = @source.read_until(quote)
+            unless value.chomp!(quote)
+              message = "Missing attribute value end quote: <#{name}>: <#{quote}>"
+              raise REXML::ParseException.new(message, @source)
             end
-            value = match[2]
+            @source.match(/\s*/um, true)
             if prefix == "xmlns"
               if local_part == "xml"
                 if value != "http://www.w3.org/XML/1998/namespace"

programming_language: Ruby
filename: source.rb
change_type: ModificationType.MODIFY
diff: @@ -65,7 +65,11 @@ module REXML
       encoding_updated
     end
 
-    def read
+    def read(term = nil)
+    end
+
+    def read_until(term)
+      @scanner.scan_until(Regexp.union(term)) or @scanner.rest
     end
 
     def ensure_buffer
@@ -158,9 +162,9 @@ module REXML
       end
     end
 
-    def read
+    def read(term = nil)
       begin
-        @scanner << readline
+        @scanner << readline(term)
         true
       rescue Exception, NameError
         @source = nil
@@ -168,6 +172,21 @@ module REXML
       end
     end
 
+    def read_until(term)
+      pattern = Regexp.union(term)
+      data = []
+      begin
+        until str = @scanner.scan_until(pattern)
+          @scanner << readline(term)
+        end
+      rescue EOFError
+        @scanner.rest
+      else
+        read if @scanner.eos? and !@source.eof?
+        str
+      end
+    end
+
     def ensure_buffer
       read if @scanner.eos? && @source
     end
@@ -218,8 +237,8 @@ module REXML
     end
 
     private
-    def readline
-      str = @source.readline(@line_break)
+    def readline(term = nil)
+      str = @source.readline(term || @line_break)
       if @pending_buffer
         if str.nil?
           str = @pending_buffer

programming_language: Ruby
filename: test_document.rb
change_type: ModificationType.MODIFY
diff: @@ -1,8 +1,12 @@
 # -*- coding: utf-8 -*-
 # frozen_string_literal: false
 
+require 'core_assertions'
+
 module REXMLTests
   class TestDocument < Test::Unit::TestCase
+    include Test::Unit::CoreAssertions
+
     def test_version_attributes_to_s
       doc = REXML::Document.new(<<~eoxml)
         <?xml version="1.0" encoding="UTF-8" standalone="no"?>
@@ -198,6 +202,13 @@ EOX
       assert_equal('no', doc.stand_alone?, bug2539)
     end
 
+    def test_gt_linear_performance
+      seq = [10000, 50000, 100000, 150000, 200000]
+      assert_linear_performance(seq) do |n|
+        REXML::Document.new('<test testing="' + ">" * n + '"></test>')
+      end
+    end
+
     class WriteTest < Test::Unit::TestCase
       def setup
         @document = REXML::Document.new(<<-EOX)

programming_language: Ruby