REXML DoS vulnerability
Description
REXML is an XML toolkit for Ruby. The REXML gem 3.3.2 has a DoS vulnerability when it parses an XML that has many entity expansions with SAX2 or pull parser API. The REXML gem 3.3.3 or later include the patch to fix the vulnerability.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
REXML gem 3.3.2 has a denial-of-service vulnerability due to uncontrolled entity expansion when parsing XML with SAX2 or pull parser APIs.
Vulnerability
Description
CVE-2024-41946 is a denial-of-service (DoS) vulnerability in the REXML gem for Ruby, version 3.3.2. The root cause is a missing check on entity expansion count and total expansion text size when using the SAX2 or pull parser APIs. Without these limits, an attacker can craft an XML document with deeply nested or exponentially expanding entity references, causing the parser to consume excessive CPU and memory resources. The fix, introduced in commit 033d1909a8f259d5a7c53681bcaf14f13bcf0368 [1], adds counters (@entity_expansion_count and a byte-size sum) and raises an exception when the limits are exceeded.
Exploitation and
Attack Surface
The vulnerability is triggered by supplying a maliciously crafted XML document to any application that uses REXML's SAX2 or pull parser to parse user-controlled XML input. No authentication is required; the attack can be performed remotely by sending a single HTTP request (e.g., POST body) that includes the malformed XML. The classic "XML entity explosion" technique uses recursively nested entities to amplify a small input into an enormous expansion, as demonstrated in Ruby's 2008 security advisory [3]. The absence of expansion limits in the SAX2 and pull parser code paths leads to unbounded processing.
Impact
A successful attack causes a denial of service by exhausting the application's CPU and memory, potentially crashing the process or making it unresponsive. This is a high-severity issue (CVSS 7.5) [2] because it can disable services that rely on REXML for XML parsing, such as Ruby on Rails applications.
Mitigation
The vulnerability is patched in REXML gem version 3.3.3 and later [1]. Users should upgrade immediately. For applications that cannot upgrade, no direct workaround is provided; limiting XML input size or preprocessing requests may reduce risk but not eliminate it. The REXML project maintains a history of similar entity expansion protections [3], and this patch completes coverage for the SAX2 and pull parser APIs.
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
rexmlRubyGems | < 3.3.3 | 3.3.3 |
Affected products
126- osv-coords125 versionspkg:apk/chainguard/jruby-9.4pkg:apk/chainguard/jruby-9.4-default-rubypkg:apk/chainguard/kube-fluentd-operatorpkg:apk/chainguard/kube-fluentd-operator-compatpkg:apk/chainguard/kube-fluentd-operator-default-configpkg:apk/chainguard/kube-fluentd-operator-oci-entrypointpkg:apk/chainguard/logstashpkg:apk/chainguard/logstash-compatpkg:apk/chainguard/logstash-env2yamlpkg:apk/chainguard/logstash-jre-bcfipspkg:apk/chainguard/logstash-jre-bcfips-compatpkg:apk/chainguard/logstash-jre-bcfips-env2yamlpkg:apk/chainguard/logstash-jre-bcfips-with-output-opensearchpkg:apk/chainguard/logstash-with-output-opensearchpkg:apk/chainguard/ruby-3.1pkg:apk/chainguard/ruby-3.1-basepkg:apk/chainguard/ruby-3.1-base-devpkg:apk/chainguard/ruby-3.1-devpkg:apk/chainguard/ruby-3.1-docpkg:apk/chainguard/ruby3.1-fluentd-kubernetes-daemonset-1.16pkg:apk/chainguard/ruby3.1-fluentd-kubernetes-daemonset-1.16-kinesispkg:apk/chainguard/ruby3.1-fluentd-kubernetes-daemonset-1.17pkg:apk/chainguard/ruby3.1-fluentd-kubernetes-daemonset-1.17-kinesispkg:apk/chainguard/ruby-3.2pkg:apk/chainguard/ruby-3.2-basepkg:apk/chainguard/ruby-3.2-base-devpkg:apk/chainguard/ruby-3.2-devpkg:apk/chainguard/ruby-3.2-docpkg:apk/chainguard/ruby3.2-fluentd-kubernetes-daemonset-1.16pkg:apk/chainguard/ruby3.2-fluentd-kubernetes-daemonset-1.16-kinesispkg:apk/chainguard/ruby3.2-fluentd-kubernetes-daemonset-1.17pkg:apk/chainguard/ruby3.2-fluentd-kubernetes-daemonset-1.17-kinesispkg:apk/chainguard/ruby-3.3pkg:apk/chainguard/ruby-3.3-basepkg:apk/chainguard/ruby-3.3-base-devpkg:apk/chainguard/ruby-3.3-devpkg:apk/chainguard/ruby-3.3-docpkg:apk/chainguard/ruby3.3-fluentd-kubernetes-daemonset-1.16pkg:apk/chainguard/ruby3.3-fluentd-kubernetes-daemonset-1.16-kinesispkg:apk/chainguard/ruby3.3-fluentd-kubernetes-daemonset-1.17pkg:apk/chainguard/ruby3.3-fluentd-kubernetes-daemonset-1.17-kinesispkg:apk/chainguard/ruby3.4-fluentd-kubernetes-daemonset-1.16pkg:apk/chainguard/ruby3.4-fluentd-kubernetes-daemonset-1.16-kinesispkg:apk/chainguard/ruby3.4-fluentd-kubernetes-daemonset-1.17pkg:apk/chainguard/ruby3.4-fluentd-kubernetes-daemonset-1.17-kinesispkg:apk/wolfi/jruby-9.4pkg:apk/wolfi/jruby-9.4-default-rubypkg:apk/wolfi/kube-fluentd-operatorpkg:apk/wolfi/kube-fluentd-operator-compatpkg:apk/wolfi/kube-fluentd-operator-default-configpkg:apk/wolfi/kube-fluentd-operator-oci-entrypointpkg:apk/wolfi/logstashpkg:apk/wolfi/logstash-compatpkg:apk/wolfi/logstash-env2yamlpkg:apk/wolfi/logstash-with-output-opensearchpkg:apk/wolfi/ruby-3.1pkg:apk/wolfi/ruby-3.1-basepkg:apk/wolfi/ruby-3.1-base-devpkg:apk/wolfi/ruby-3.1-devpkg:apk/wolfi/ruby-3.1-docpkg:apk/wolfi/ruby3.1-fluentd-kubernetes-daemonset-1.17pkg:apk/wolfi/ruby3.1-fluentd-kubernetes-daemonset-1.17-kinesispkg:apk/wolfi/ruby-3.2pkg:apk/wolfi/ruby-3.2-basepkg:apk/wolfi/ruby-3.2-base-devpkg:apk/wolfi/ruby-3.2-devpkg:apk/wolfi/ruby-3.2-docpkg:apk/wolfi/ruby3.2-fluentd-kubernetes-daemonset-1.17pkg:apk/wolfi/ruby3.2-fluentd-kubernetes-daemonset-1.17-kinesispkg:apk/wolfi/ruby-3.3pkg:apk/wolfi/ruby-3.3-basepkg:apk/wolfi/ruby-3.3-base-devpkg:apk/wolfi/ruby-3.3-devpkg:apk/wolfi/ruby-3.3-docpkg:apk/wolfi/ruby3.3-fluentd-kubernetes-daemonset-1.17pkg:apk/wolfi/ruby3.3-fluentd-kubernetes-daemonset-1.17-kinesispkg:apk/wolfi/ruby3.4-fluentd-kubernetes-daemonset-1.17pkg:apk/wolfi/ruby3.4-fluentd-kubernetes-daemonset-1.17-kinesispkg:gem/rexmlpkg:rpm/almalinux/pcspkg:rpm/almalinux/pcs-snmppkg:rpm/almalinux/rubypkg:rpm/almalinux/ruby-bundled-gemspkg:rpm/almalinux/ruby-default-gemspkg:rpm/almalinux/ruby-develpkg:rpm/almalinux/ruby-docpkg:rpm/almalinux/rubygem-abrtpkg:rpm/almalinux/rubygem-abrt-docpkg:rpm/almalinux/rubygem-bigdecimalpkg:rpm/almalinux/rubygem-bundlerpkg:rpm/almalinux/rubygem-io-consolepkg:rpm/almalinux/rubygem-irbpkg:rpm/almalinux/rubygem-jsonpkg:rpm/almalinux/rubygem-minitestpkg:rpm/almalinux/rubygem-mysql2pkg:rpm/almalinux/rubygem-mysql2-docpkg:rpm/almalinux/rubygem-pgpkg:rpm/almalinux/rubygem-pg-docpkg:rpm/almalinux/rubygem-power_assertpkg:rpm/almalinux/rubygem-psychpkg:rpm/almalinux/rubygem-raccpkg:rpm/almalinux/rubygem-rakepkg:rpm/almalinux/rubygem-rbspkg:rpm/almalinux/rubygem-rdocpkg:rpm/almalinux/rubygem-rexmlpkg:rpm/almalinux/rubygem-rsspkg:rpm/almalinux/rubygemspkg:rpm/almalinux/rubygems-develpkg:rpm/almalinux/rubygem-test-unitpkg:rpm/almalinux/rubygem-typeprofpkg:rpm/almalinux/ruby-libspkg:rpm/opensuse/rubygem-rexml&distro=openSUSE%20Leap%2015.6pkg:rpm/suse/ruby2.5&distro=SUSE%20Enterprise%20Storage%207.1pkg:rpm/suse/ruby2.5&distro=SUSE%20Linux%20Enterprise%20High%20Performance%20Computing%2015%20SP2-LTSSpkg:rpm/suse/ruby2.5&distro=SUSE%20Linux%20Enterprise%20High%20Performance%20Computing%2015%20SP3-LTSSpkg:rpm/suse/ruby2.5&distro=SUSE%20Linux%20Enterprise%20High%20Performance%20Computing%2015%20SP4-LTSSpkg:rpm/suse/ruby2.5&distro=SUSE%20Linux%20Enterprise%20Module%20for%20Basesystem%2015%20SP5pkg:rpm/suse/ruby2.5&distro=SUSE%20Linux%20Enterprise%20Module%20for%20Basesystem%2015%20SP6pkg:rpm/suse/ruby2.5&distro=SUSE%20Linux%20Enterprise%20Server%2015%20SP2-LTSSpkg:rpm/suse/ruby2.5&distro=SUSE%20Linux%20Enterprise%20Server%2015%20SP3-LTSSpkg:rpm/suse/ruby2.5&distro=SUSE%20Linux%20Enterprise%20Server%2015%20SP4-LTSSpkg:rpm/suse/ruby2.5&distro=SUSE%20Linux%20Enterprise%20Server%20for%20SAP%20Applications%2015%20SP2pkg:rpm/suse/ruby2.5&distro=SUSE%20Linux%20Enterprise%20Server%20for%20SAP%20Applications%2015%20SP3pkg:rpm/suse/ruby2.5&distro=SUSE%20Manager%20Server%204.3pkg:rpm/suse/rubygem-rexml&distro=SUSE%20Package%20Hub%2015%20SP6
< 9.4.9.0-r0+ 124 more
- (no CPE)range: < 9.4.9.0-r0
- (no CPE)range: < 9.4.9.0-r0
- (no CPE)range: < 1.18.2-r14
- (no CPE)range: < 1.18.2-r14
- (no CPE)range: < 1.18.2-r14
- (no CPE)range: < 1.18.2-r14
- (no CPE)range: < 8.15.0-r2
- (no CPE)range: < 8.15.0-r2
- (no CPE)range: < 8.15.0-r2
- (no CPE)range: < 8.15.1-r0
- (no CPE)range: < 8.15.1-r0
- (no CPE)range: < 8.15.1-r0
- (no CPE)range: < 8.15.1-r0
- (no CPE)range: < 8.15.0-r2
- (no CPE)range: < 3.1.6-r2
- (no CPE)range: < 3.1.6-r2
- (no CPE)range: < 3.1.6-r2
- (no CPE)range: < 3.1.6-r2
- (no CPE)range: < 3.1.6-r2
- (no CPE)range: < 1.16.6.1.2-r1
- (no CPE)range: < 1.16.6.1.2-r1
- (no CPE)range: < 1.17.1.1.2-r2
- (no CPE)range: < 1.17.1.1.2-r2
- (no CPE)range: < 3.2.5-r1
- (no CPE)range: < 3.2.5-r1
- (no CPE)range: < 3.2.5-r1
- (no CPE)range: < 3.2.5-r1
- (no CPE)range: < 3.2.5-r1
- (no CPE)range: < 1.16.6.1.2-r2
- (no CPE)range: < 1.16.6.1.2-r2
- (no CPE)range: < 1.17.1.1.2-r1
- (no CPE)range: < 1.17.1.1.2-r1
- (no CPE)range: < 3.3.4-r2
- (no CPE)range: < 3.3.4-r2
- (no CPE)range: < 3.3.4-r2
- (no CPE)range: < 3.3.4-r2
- (no CPE)range: < 3.3.4-r2
- (no CPE)range: < 1.16.6.1.2-r2
- (no CPE)range: < 1.16.6.1.2-r2
- (no CPE)range: < 1.17.1.1.2-r2
- (no CPE)range: < 1.17.1.1.2-r2
- (no CPE)range: < 1.16.6.1.2-r2
- (no CPE)range: < 1.16.6.1.2-r2
- (no CPE)range: < 1.17.1.1.2-r2
- (no CPE)range: < 1.17.1.1.2-r2
- (no CPE)range: < 9.4.9.0-r0
- (no CPE)range: < 9.4.9.0-r0
- (no CPE)range: < 1.18.2-r14
- (no CPE)range: < 1.18.2-r14
- (no CPE)range: < 1.18.2-r14
- (no CPE)range: < 1.18.2-r14
- (no CPE)range: < 8.15.0-r2
- (no CPE)range: < 8.15.0-r2
- (no CPE)range: < 8.15.0-r2
- (no CPE)range: < 8.15.0-r2
- (no CPE)range: < 3.1.6-r2
- (no CPE)range: < 3.1.6-r2
- (no CPE)range: < 3.1.6-r2
- (no CPE)range: < 3.1.6-r2
- (no CPE)range: < 3.1.6-r2
- (no CPE)range: < 1.17.1.1.2-r2
- (no CPE)range: < 1.17.1.1.2-r2
- (no CPE)range: < 3.2.5-r1
- (no CPE)range: < 3.2.5-r1
- (no CPE)range: < 3.2.5-r1
- (no CPE)range: < 3.2.5-r1
- (no CPE)range: < 3.2.5-r1
- (no CPE)range: < 1.17.1.1.2-r1
- (no CPE)range: < 1.17.1.1.2-r1
- (no CPE)range: < 3.3.4-r2
- (no CPE)range: < 3.3.4-r2
- (no CPE)range: < 3.3.4-r2
- (no CPE)range: < 3.3.4-r2
- (no CPE)range: < 3.3.4-r2
- (no CPE)range: < 1.17.1.1.2-r1
- (no CPE)range: < 1.17.1.1.2-r1
- (no CPE)range: < 1.17.1.1.2-r2
- (no CPE)range: < 1.17.1.1.2-r2
- (no CPE)range: < 3.3.3
- (no CPE)range: < 0.10.18-2.el8_10.2.alma.1
- (no CPE)range: < 0.10.18-2.el8_10.2.alma.1
- (no CPE)range: < 3.3.5-3.module_el8.10.0+3894+6d587c81
- (no CPE)range: < 3.3.5-3.module_el8.10.0+3894+6d587c81
- (no CPE)range: < 3.3.5-3.module_el8.10.0+3894+6d587c81
- (no CPE)range: < 3.3.5-3.module_el8.10.0+3894+6d587c81
- (no CPE)range: < 3.3.5-3.module_el8.10.0+3894+6d587c81
- (no CPE)range: < 0.4.0-1.module_el8.10.0+3799+191214cc
- (no CPE)range: < 0.4.0-1.module_el8.10.0+3799+191214cc
- (no CPE)range: < 3.1.5-3.module_el8.10.0+3894+6d587c81
- (no CPE)range: < 2.5.16-3.module_el8.10.0+3894+6d587c81
- (no CPE)range: < 0.7.1-3.module_el8.10.0+3894+6d587c81
- (no CPE)range: < 1.13.1-3.module_el8.10.0+3894+6d587c81
- (no CPE)range: < 2.7.1-3.module_el8.10.0+3894+6d587c81
- (no CPE)range: < 5.20.0-3.module_el8.10.0+3894+6d587c81
- (no CPE)range: < 0.5.5-1.module_el8.10.0+3799+191214cc
- (no CPE)range: < 0.5.5-1.module_el8.10.0+3799+191214cc
- (no CPE)range: < 1.5.4-1.module_el8.10.0+3799+191214cc
- (no CPE)range: < 1.5.4-1.module_el8.10.0+3799+191214cc
- (no CPE)range: < 2.0.3-3.module_el8.10.0+3894+6d587c81
- (no CPE)range: < 5.1.2-3.module_el8.10.0+3894+6d587c81
- (no CPE)range: < 1.7.3-3.module_el8.10.0+3894+6d587c81
- (no CPE)range: < 13.1.0-3.module_el8.10.0+3894+6d587c81
- (no CPE)range: < 3.4.0-3.module_el8.10.0+3894+6d587c81
- (no CPE)range: < 6.6.3.1-3.module_el8.10.0+3894+6d587c81
- (no CPE)range: < 3.3.6-3.module_el8.10.0+3894+6d587c81
- (no CPE)range: < 0.3.1-3.module_el8.10.0+3894+6d587c81
- (no CPE)range: < 3.5.16-3.module_el8.10.0+3894+6d587c81
- (no CPE)range: < 3.5.16-3.module_el8.10.0+3894+6d587c81
- (no CPE)range: < 3.6.1-3.module_el8.10.0+3894+6d587c81
- (no CPE)range: < 0.21.9-3.module_el8.10.0+3894+6d587c81
- (no CPE)range: < 3.3.5-3.module_el8.10.0+3894+6d587c81
- (no CPE)range: < 3.3.9-bp156.4.3.1
- (no CPE)range: < 2.5.9-150000.4.32.1
- (no CPE)range: < 2.5.9-150000.4.32.1
- (no CPE)range: < 2.5.9-150000.4.32.1
- (no CPE)range: < 2.5.9-150000.4.32.1
- (no CPE)range: < 2.5.9-150000.4.32.1
- (no CPE)range: < 2.5.9-150000.4.32.1
- (no CPE)range: < 2.5.9-150000.4.32.1
- (no CPE)range: < 2.5.9-150000.4.32.1
- (no CPE)range: < 2.5.9-150000.4.32.1
- (no CPE)range: < 2.5.9-150000.4.32.1
- (no CPE)range: < 2.5.9-150000.4.32.1
- (no CPE)range: < 2.5.9-150000.4.32.1
- (no CPE)range: < 3.3.9-bp156.4.3.1
- ruby/rexmlv5Range: < 3.3.3
Patches
1033d1909a8f2Add support for XML entity expansion limitation in SAX and pull parsers (#187)
6 files changed · +222 −12
lib/rexml/parsers/baseparser.rb+18 −1 modified@@ -154,13 +154,15 @@ def initialize( source ) self.stream = source @listeners = [] @prefixes = Set.new + @entity_expansion_count = 0 end def add_listener( listener ) @listeners << listener end attr_reader :source + attr_reader :entity_expansion_count def stream=( source ) @source = SourceFactory.create_from( source ) @@ -513,7 +515,9 @@ def pull_event 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 @@ def unnormalize( string, entities=nil, filter=nil ) } 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 @@ def unnormalize( string, entities=nil, filter=nil ) 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
lib/rexml/parsers/pullparser.rb+4 −0 modified@@ -47,6 +47,10 @@ def add_listener( listener ) @listeners << listener end + def entity_expansion_count + @parser.entity_expansion_count + end + def each while has_next? yield self.pull
lib/rexml/parsers/sax2parser.rb+4 −0 modified@@ -22,6 +22,10 @@ def source @parser.source end + def entity_expansion_count + @parser.entity_expansion_count + end + def add_listener( listener ) @parser.add_listener( listener ) end
test/test_document.rb+14 −11 modified@@ -41,7 +41,7 @@ def teardown 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 @@ def test_have_value <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 @@ def test_empty_value <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 @@ def test_with_default_entity &a2; < </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
test/test_pullparser.rb+96 −0 modified@@ -155,5 +155,101 @@ def test_peek 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; +< +</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
test/test_sax.rb+86 −0 modified@@ -99,6 +99,92 @@ def test_sax2 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; +< +</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
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
9- github.com/advisories/GHSA-5866-49gr-22v4ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-41946ghsaADVISORY
- github.com/ruby/rexml/commit/033d1909a8f259d5a7c53681bcaf14f13bcf0368ghsax_refsource_MISCWEB
- github.com/ruby/rexml/security/advisories/GHSA-5866-49gr-22v4ghsax_refsource_CONFIRMWEB
- github.com/rubysec/ruby-advisory-db/blob/master/gems/rexml/CVE-2024-41946.ymlghsaWEB
- lists.debian.org/debian-lts-announce/2025/01/msg00011.htmlghsaWEB
- security.netapp.com/advisory/ntap-20250117-0007ghsaWEB
- www.ruby-lang.org/en/news/2008/08/23/dos-vulnerability-in-rexmlghsax_refsource_MISCWEB
- www.ruby-lang.org/en/news/2024/08/01/dos-rexml-cve-2024-41946ghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.