ruby/backport-CVE-2024-41946.patch
2024-08-09 11:47:06 +08:00

408 lines
12 KiB
Diff

From 033d1909a8f259d5a7c53681bcaf14f13bcf0368 Mon Sep 17 00:00:00 2001
From: NAITOH Jun <naitoh@gmail.com>
Date: Thu, 1 Aug 2024 09:20:31 +0900
Subject: [PATCH] Add support for XML entity expansion limitation in SAX and
pull parsers (#187)
https://github.com/ruby/rexml/commit/033d1909a8f259d5a7c53681bcaf14f13bcf0368
- Supported `REXML::Security.entity_expansion_limit=` in SAX and pull parsers
- Supported `REXML::Security.entity_expansion_text_limit=` in SAX and pull parsers
---
lib/rexml/parsers/baseparser.rb | 19 ++++++-
lib/rexml/parsers/pullparser.rb | 4 ++
lib/rexml/parsers/sax2parser.rb | 4 ++
test/rexml/test_document.rb | 25 +++++----
test/rexml/test_pullparser.rb | 96 +++++++++++++++++++++++++++++++++
test/rexml/test_sax.rb | 86 +++++++++++++++++++++++++++++
6 files changed, 222 insertions(+), 12 deletions(-)
diff --git a/lib/rexml/parsers/baseparser.rb b/lib/rexml/parsers/baseparser.rb
index 54014e5..c4ddee3 100644
--- a/lib/rexml/parsers/baseparser.rb
+++ b/lib/rexml/parsers/baseparser.rb
@@ -154,6 +154,7 @@ module REXML
self.stream = source
@listeners = []
@prefixes = Set.new
+ @entity_expansion_count = 0
end
def add_listener( listener )
@@ -161,6 +162,7 @@ module REXML
end
attr_reader :source
+ attr_reader :entity_expansion_count
def stream=( source )
@source = SourceFactory.create_from( source )
@@ -513,7 +515,9 @@ module REXML
def entity( reference, entities )
value = nil
value = entities[ reference ] if entities
- if not value
+ if value
+ record_entity_expansion
+ else
value = DEFAULT_ENTITIES[ reference ]
value = value[2] if value
end
@@ -552,12 +556,17 @@ module REXML
}
matches.collect!{|x|x[0]}.compact!
if matches.size > 0
+ sum = 0
matches.each do |entity_reference|
unless filter and filter.include?(entity_reference)
entity_value = entity( entity_reference, entities )
if entity_value
re = Private::DEFAULT_ENTITIES_PATTERNS[entity_reference] || /&#{entity_reference};/
rv.gsub!( re, entity_value )
+ sum += rv.bytesize
+ if sum > Security.entity_expansion_text_limit
+ raise "entity expansion has grown too large"
+ end
else
er = DEFAULT_ENTITIES[entity_reference]
rv.gsub!( er[0], er[2] ) if er
@@ -570,6 +579,14 @@ module REXML
end
private
+
+ def record_entity_expansion
+ @entity_expansion_count += 1
+ if @entity_expansion_count > Security.entity_expansion_limit
+ raise "number of entity expansions exceeded, processing aborted."
+ end
+ end
+
def need_source_encoding_update?(xml_declaration_encoding)
return false if xml_declaration_encoding.nil?
return false if /\AUTF-16\z/i =~ xml_declaration_encoding
diff --git a/lib/rexml/parsers/pullparser.rb b/lib/rexml/parsers/pullparser.rb
index f8b232a..36b4595 100644
--- a/lib/rexml/parsers/pullparser.rb
+++ b/lib/rexml/parsers/pullparser.rb
@@ -47,6 +47,10 @@ module REXML
@listeners << listener
end
+ def entity_expansion_count
+ @parser.entity_expansion_count
+ end
+
def each
while has_next?
yield self.pull
diff --git a/lib/rexml/parsers/sax2parser.rb b/lib/rexml/parsers/sax2parser.rb
index 36f98c2..cec9d2f 100644
--- a/lib/rexml/parsers/sax2parser.rb
+++ b/lib/rexml/parsers/sax2parser.rb
@@ -22,6 +22,10 @@ module REXML
@parser.source
end
+ def entity_expansion_count
+ @parser.entity_expansion_count
+ end
+
def add_listener( listener )
@parser.add_listener( listener )
end
diff --git a/test/rexml/test_document.rb b/test/rexml/test_document.rb
index 33cf400..0764631 100644
--- a/test/rexml/test_document.rb
+++ b/test/rexml/test_document.rb
@@ -41,7 +41,7 @@ EOF
class GeneralEntityTest < self
def test_have_value
- xml = <<EOF
+ xml = <<XML
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE member [
<!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;">
@@ -55,23 +55,24 @@ EOF
<member>
&a;
</member>
-EOF
+XML
doc = REXML::Document.new(xml)
- assert_raise(RuntimeError) do
+ assert_raise(RuntimeError.new("entity expansion has grown too large")) do
doc.root.children.first.value
end
+
REXML::Security.entity_expansion_limit = 100
assert_equal(100, REXML::Security.entity_expansion_limit)
doc = REXML::Document.new(xml)
- assert_raise(RuntimeError) do
+ assert_raise(RuntimeError.new("number of entity expansions exceeded, processing aborted.")) do
doc.root.children.first.value
end
assert_equal(101, doc.entity_expansion_count)
end
def test_empty_value
- xml = <<EOF
+ xml = <<XML
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE member [
<!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;">
@@ -85,23 +86,24 @@ EOF
<member>
&a;
</member>
-EOF
+XML
doc = REXML::Document.new(xml)
- assert_raise(RuntimeError) do
+ assert_raise(RuntimeError.new("number of entity expansions exceeded, processing aborted.")) do
doc.root.children.first.value
end
+
REXML::Security.entity_expansion_limit = 100
assert_equal(100, REXML::Security.entity_expansion_limit)
doc = REXML::Document.new(xml)
- assert_raise(RuntimeError) do
+ assert_raise(RuntimeError.new("number of entity expansions exceeded, processing aborted.")) do
doc.root.children.first.value
end
assert_equal(101, doc.entity_expansion_count)
end
def test_with_default_entity
- xml = <<EOF
+ xml = <<XML
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE member [
<!ENTITY a "a">
@@ -112,14 +114,15 @@ EOF
&a2;
&lt;
</member>
-EOF
+XML
REXML::Security.entity_expansion_limit = 4
doc = REXML::Document.new(xml)
assert_equal("\na\na a\n<\n", doc.root.children.first.value)
+
REXML::Security.entity_expansion_limit = 3
doc = REXML::Document.new(xml)
- assert_raise(RuntimeError) do
+ assert_raise(RuntimeError.new("number of entity expansions exceeded, processing aborted.")) do
doc.root.children.first.value
end
end
diff --git a/test/rexml/test_pullparser.rb b/test/rexml/test_pullparser.rb
index 096e8b7..55205af 100644
--- a/test/rexml/test_pullparser.rb
+++ b/test/rexml/test_pullparser.rb
@@ -155,5 +155,101 @@ module REXMLTests
end
assert_equal( 0, names.length )
end
+
+ class EntityExpansionLimitTest < Test::Unit::TestCase
+ def setup
+ @default_entity_expansion_limit = REXML::Security.entity_expansion_limit
+ end
+
+ def teardown
+ REXML::Security.entity_expansion_limit = @default_entity_expansion_limit
+ end
+
+ class GeneralEntityTest < self
+ def test_have_value
+ source = <<-XML
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE member [
+ <!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;">
+ <!ENTITY b "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;">
+ <!ENTITY c "&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;">
+ <!ENTITY d "&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;">
+ <!ENTITY e "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">
+]>
+<member>
+&a;
+</member>
+ XML
+
+ parser = REXML::Parsers::PullParser.new(source)
+ assert_raise(RuntimeError.new("entity expansion has grown too large")) do
+ while parser.has_next?
+ parser.pull
+ end
+ end
+ end
+
+ def test_empty_value
+ source = <<-XML
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE member [
+ <!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;">
+ <!ENTITY b "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;">
+ <!ENTITY c "&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;">
+ <!ENTITY d "&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;">
+ <!ENTITY e "">
+]>
+<member>
+&a;
+</member>
+ XML
+
+ parser = REXML::Parsers::PullParser.new(source)
+ assert_raise(RuntimeError.new("number of entity expansions exceeded, processing aborted.")) do
+ while parser.has_next?
+ parser.pull
+ end
+ end
+
+ REXML::Security.entity_expansion_limit = 100
+ parser = REXML::Parsers::PullParser.new(source)
+ assert_raise(RuntimeError.new("number of entity expansions exceeded, processing aborted.")) do
+ while parser.has_next?
+ parser.pull
+ end
+ end
+ assert_equal(101, parser.entity_expansion_count)
+ end
+
+ def test_with_default_entity
+ source = <<-XML
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE member [
+ <!ENTITY a "a">
+ <!ENTITY a2 "&a; &a;">
+]>
+<member>
+&a;
+&a2;
+&lt;
+</member>
+ XML
+
+ REXML::Security.entity_expansion_limit = 4
+ parser = REXML::Parsers::PullParser.new(source)
+ while parser.has_next?
+ parser.pull
+ end
+
+ REXML::Security.entity_expansion_limit = 3
+ parser = REXML::Parsers::PullParser.new(source)
+ assert_raise(RuntimeError.new("number of entity expansions exceeded, processing aborted.")) do
+ while parser.has_next?
+ parser.pull
+ end
+ end
+ end
+ end
+ end
end
end
diff --git a/test/rexml/test_sax.rb b/test/rexml/test_sax.rb
index 5a3f5e4..5e3ad75 100644
--- a/test/rexml/test_sax.rb
+++ b/test/rexml/test_sax.rb
@@ -99,6 +99,92 @@ module REXMLTests
end
end
+ class EntityExpansionLimitTest < Test::Unit::TestCase
+ def setup
+ @default_entity_expansion_limit = REXML::Security.entity_expansion_limit
+ end
+
+ def teardown
+ REXML::Security.entity_expansion_limit = @default_entity_expansion_limit
+ end
+
+ class GeneralEntityTest < self
+ def test_have_value
+ source = <<-XML
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE member [
+ <!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;">
+ <!ENTITY b "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;">
+ <!ENTITY c "&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;">
+ <!ENTITY d "&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;">
+ <!ENTITY e "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx">
+]>
+<member>
+&a;
+</member>
+ XML
+
+ sax = REXML::Parsers::SAX2Parser.new(source)
+ assert_raise(RuntimeError.new("entity expansion has grown too large")) do
+ sax.parse
+ end
+ end
+
+ def test_empty_value
+ source = <<-XML
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE member [
+ <!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;">
+ <!ENTITY b "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;">
+ <!ENTITY c "&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;">
+ <!ENTITY d "&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;">
+ <!ENTITY e "">
+]>
+<member>
+&a;
+</member>
+ XML
+
+ sax = REXML::Parsers::SAX2Parser.new(source)
+ assert_raise(RuntimeError.new("number of entity expansions exceeded, processing aborted.")) do
+ sax.parse
+ end
+
+ REXML::Security.entity_expansion_limit = 100
+ sax = REXML::Parsers::SAX2Parser.new(source)
+ assert_raise(RuntimeError.new("number of entity expansions exceeded, processing aborted.")) do
+ sax.parse
+ end
+ assert_equal(101, sax.entity_expansion_count)
+ end
+
+ def test_with_default_entity
+ source = <<-XML
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE member [
+ <!ENTITY a "a">
+ <!ENTITY a2 "&a; &a;">
+]>
+<member>
+&a;
+&a2;
+&lt;
+</member>
+ XML
+
+ REXML::Security.entity_expansion_limit = 4
+ sax = REXML::Parsers::SAX2Parser.new(source)
+ sax.parse
+
+ REXML::Security.entity_expansion_limit = 3
+ sax = REXML::Parsers::SAX2Parser.new(source)
+ assert_raise(RuntimeError.new("number of entity expansions exceeded, processing aborted.")) do
+ sax.parse
+ end
+ end
+ end
+ end
+
# used by test_simple_doctype_listener
# submitted by Jeff Barczewski
class SimpleDoctypeListener
--
2.20.1