From 6caad90557373d93739d4bbcb84e6814e850c0c7 Mon Sep 17 00:00:00 2001
From: Aaron Patterson <aaron.patterson () gmail com>
Date: Tue, 9 Jun 2015 11:24:25 -0700
Subject: [PATCH] enforce a depth limit on XML documents

XML documents that are too deep can cause an stack overflow, which in
turn will cause a potential DoS attack.

CVE-2015-3227

Conflicts:
	activesupport/lib/active_support/xml_mini.rb
---
 activesupport/lib/active_support/xml_mini.rb       |  3 +++
 activesupport/lib/active_support/xml_mini/jdom.rb  | 11 ++++++-----
 activesupport/lib/active_support/xml_mini/rexml.rb | 11 ++++++-----
 3 files changed, 15 insertions(+), 10 deletions(-)

diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb
index a4ac1d7..afe9c4a 100644
--- a/activesupport/lib/active_support/xml_mini.rb
+++ b/activesupport/lib/active_support/xml_mini.rb
@@ -77,6 +77,9 @@ module ActiveSupport
     end
 
     attr_reader :backend
+    attr_accessor :depth
+    self.depth = 100
+
     delegate :parse, :to => :backend
 
     def backend=(name)
diff --git a/activesupport/lib/active_support/xml_mini/jdom.rb b/activesupport/lib/active_support/xml_mini/jdom.rb
index 8d23ce4..8d64bc2 100644
--- a/activesupport/lib/active_support/xml_mini/jdom.rb
+++ b/activesupport/lib/active_support/xml_mini/jdom.rb
@@ -47,7 +47,7 @@ module ActiveSupport
         xml_string_reader = StringReader.new(data)
         xml_input_source = InputSource.new(xml_string_reader)
         doc = @dbf.new_document_builder.parse(xml_input_source)
-        merge_element!({CONTENT_KEY => ''}, doc.document_element)
+        merge_element!({CONTENT_KEY => ''}, doc.document_element, XmlMini.depth)
       end
     end
 
@@ -59,9 +59,10 @@ module ActiveSupport
     #   Hash to merge the converted element into.
     # element::
     #   XML element to merge into hash
-    def merge_element!(hash, element)
+    def merge_element!(hash, element, depth)
+      raise 'Document too deep!' if depth == 0
       delete_empty(hash)
-      merge!(hash, element.tag_name, collapse(element))
+      merge!(hash, element.tag_name, collapse(element, depth))
     end
 
     def delete_empty(hash)
@@ -72,14 +73,14 @@ module ActiveSupport
     #
     # element::
     #   The document element to be collapsed.
-    def collapse(element)
+    def collapse(element, depth)
       hash = get_attributes(element)
 
       child_nodes = element.child_nodes
       if child_nodes.length > 0
         for i in 0...child_nodes.length
           child = child_nodes.item(i)
-          merge_element!(hash, child) unless child.node_type == Node.TEXT_NODE
+          merge_element!(hash, child, depth - 1) unless child.node_type == Node.TEXT_NODE
         end
         merge_texts!(hash, element) unless empty_content?(element)
         hash
diff --git a/activesupport/lib/active_support/xml_mini/rexml.rb b/activesupport/lib/active_support/xml_mini/rexml.rb
index a13ad10..cb2a4f4 100644
--- a/activesupport/lib/active_support/xml_mini/rexml.rb
+++ b/activesupport/lib/active_support/xml_mini/rexml.rb
@@ -30,7 +30,7 @@ module ActiveSupport
         doc = REXML::Document.new(data)
 
         if doc.root
-          merge_element!({}, doc.root)
+          merge_element!({}, doc.root, XmlMini.depth)
         else
           raise REXML::ParseException,
             "The document #{doc.to_s.inspect} does not have a valid root"
@@ -45,19 +45,20 @@ module ActiveSupport
       #   Hash to merge the converted element into.
       # element::
       #   XML element to merge into hash
-      def merge_element!(hash, element)
-        merge!(hash, element.name, collapse(element))
+      def merge_element!(hash, element, depth)
+        raise REXML::ParseException, "The document is too deep" if depth == 0
+        merge!(hash, element.name, collapse(element, depth))
       end
 
       # Actually converts an XML document element into a data structure.
       #
       # element::
       #   The document element to be collapsed.
-      def collapse(element)
+      def collapse(element, depth)
         hash = get_attributes(element)
 
         if element.has_elements?
-          element.each_element {|child| merge_element!(hash, child) }
+          element.each_element {|child| merge_element!(hash, child, depth - 1) }
           merge_texts!(hash, element) unless empty_content?(element)
           hash
         else
-- 
2.2.1

