From 16d488be940f8802a502c919c7b7dd79f61631da Mon Sep 17 00:00:00 2001 From: Shubham Shinde Date: Wed, 10 Jul 2024 22:26:06 +0530 Subject: [PATCH] (PA-6507) Patch rexml for CVE-2024-35176 - Ruby 3.2.4 has rexml as its bundled gem, so the oatch was applied after the install step since bundled gems are available in the build after the install step in the .bundle folder. - Ruby 2.7.8 has rexml as its default gem, so we can go with the usual way to patch it. - Note that in the patch files for ruby_32, the file paths are prefixed with .bundle/gems/rexml-3.2.5 because that is where the bundled gem rexml is located with respect to the working directory of patching. - The upstream fix commit: https://github.com/ruby/rexml/commit/4325835f92f3f142ebd91a3fdba4e1f1ab7f1cfb --- configs/components/ruby-2.7.8.rb | 3 + configs/components/ruby-3.2.4.rb | 14 ++ .../ruby_27/rexml_for_CVE-2024-35176.patch | 135 ++++++++++++++++++ .../ruby_32/rexml_for_CVE-2024-35176.patch | 123 ++++++++++++++++ 4 files changed, 275 insertions(+) create mode 100644 resources/patches/ruby_27/rexml_for_CVE-2024-35176.patch create mode 100644 resources/patches/ruby_32/rexml_for_CVE-2024-35176.patch diff --git a/configs/components/ruby-2.7.8.rb b/configs/components/ruby-2.7.8.rb index cf1df8999..e1f396753 100644 --- a/configs/components/ruby-2.7.8.rb +++ b/configs/components/ruby-2.7.8.rb @@ -42,6 +42,9 @@ pkg.apply_patch "#{base}/uri-redos-cve-2023-36617.patch" + # This patch is not required for ruby >= 3.3.3 + pkg.apply_patch "#{base}/rexml_for_CVE-2024-35176.patch" + if platform.is_cross_compiled? unless platform.is_macos? pkg.apply_patch "#{base}/uri_generic_remove_safe_nav_operator_r2.5.patch" diff --git a/configs/components/ruby-3.2.4.rb b/configs/components/ruby-3.2.4.rb index b5e808a1b..027d5587f 100644 --- a/configs/components/ruby-3.2.4.rb +++ b/configs/components/ruby-3.2.4.rb @@ -300,4 +300,18 @@ ] end end + + ######### + # BUILD + ######### + + pkg.add_source("file://resources/patches/ruby_32/rexml_for_CVE-2024-35176.patch") + + pkg.build do + # This patch is applied after the install step because rexml gem is the bundled gem hence build + # cannot find the path of the files to be patched prior to configuring and installing. + # This patch is not required for ruby >= 3.3.3 + steps = ["#{platform.patch} --strip=1 --fuzz=0 --ignore-whitespace --no-backup-if-mismatch < ../rexml_for_CVE-2024-35176.patch"] + end + end diff --git a/resources/patches/ruby_27/rexml_for_CVE-2024-35176.patch b/resources/patches/ruby_27/rexml_for_CVE-2024-35176.patch new file mode 100644 index 000000000..f53f6bae3 --- /dev/null +++ b/resources/patches/ruby_27/rexml_for_CVE-2024-35176.patch @@ -0,0 +1,135 @@ +commit e597f07718c27dc4414ad39f121376e53056475a +Author: Shubham Shinde +Date: Tue Jul 9 19:40:43 2024 +0530 + + Read quoted attributes in chunks (#126) + + + # Conflicts: + # lib/rexml/parsers/baseparser.rb + # lib/rexml/source.rb + +diff --git a/lib/rexml/parsers/baseparser.rb b/lib/rexml/parsers/baseparser.rb +index f76aed0..f0b365d 100644 +--- a/lib/rexml/parsers/baseparser.rb ++++ b/lib/rexml/parsers/baseparser.rb +@@ -518,25 +518,43 @@ module REXML + message = "Missing attribute equal: <#{name}>" + raise REXML::ParseException.new(message, @source) + end +- quote = scanner.scan(/['"]/) +- unless quote ++ unless match = @source.match(/(['"])/, true) + message = "Missing attribute value start quote: <#{name}>" + raise REXML::ParseException.new(message, @source) + end +- unless scanner.scan(/.*#{Regexp.escape(quote)}/um) +- match_data = @source.match(/^(.*?)(\/)?>/um, true) +- if match_data +- scanner << "/" if closed +- scanner << ">" +- scanner << match_data[1] +- scanner.pos = pos +- closed = !match_data[2].nil? +- next +- end +- message = +- "Missing attribute value end quote: <#{name}>: <#{quote}>" ++ 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 ++ @source.match(/\s*/um, true) ++ if prefix == "xmlns" ++ if local_part == "xml" ++ if value != "http://www.w3.org/XML/1998/namespace" ++ msg = "The 'xml' prefix must not be bound to any other namespace "+ ++ "(http://www.w3.org/TR/REC-xml-names/#ns-decl)" ++ raise REXML::ParseException.new( msg, @source, self ) ++ end ++ elsif local_part == "xmlns" ++ msg = "The 'xmlns' prefix must not be declared "+ ++ "(http://www.w3.org/TR/REC-xml-names/#ns-decl)" ++ raise REXML::ParseException.new( msg, @source, self) ++ end ++ curr_ns << local_part ++ elsif prefix ++ prefixes << prefix unless prefix == "xml" ++ end ++ ++ if attributes[name] ++ msg = "Duplicate attribute #{name.inspect}" ++ raise REXML::ParseException.new(msg, @source, self) ++ end ++ ++ attributes[name] = value ++ else ++ message = "Invalid attribute name: <#{@source.buffer.split(%r{[/>\s]}).first}>" ++ raise REXML::ParseException.new(message, @source) + end + name = scanner[1] + prefix = scanner[2] +diff --git a/lib/rexml/source.rb b/lib/rexml/source.rb +index 770aefc..86bf6cf 100644 +--- a/lib/rexml/source.rb ++++ b/lib/rexml/source.rb +@@ -81,7 +81,11 @@ module REXML + rv + end + +- def read ++ def read(term = nil) ++ end ++ ++ def read_until(term) ++ @scanner.scan_until(Regexp.union(term)) or @scanner.rest + end + + def consume( pattern ) +@@ -204,9 +208,9 @@ module REXML + rv + end + +- def read ++ def read(term = nil) + begin +- @buffer << readline ++ @buffer << readline(term) + rescue Exception, NameError + @source = nil + end +@@ -216,6 +220,21 @@ module REXML + match( pattern, true ) + 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 match( pattern, cons=false ) + rv = pattern.match(@buffer) + @buffer = $' if cons and rv +@@ -263,8 +282,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 diff --git a/resources/patches/ruby_32/rexml_for_CVE-2024-35176.patch b/resources/patches/ruby_32/rexml_for_CVE-2024-35176.patch new file mode 100644 index 000000000..ef79a19b4 --- /dev/null +++ b/resources/patches/ruby_32/rexml_for_CVE-2024-35176.patch @@ -0,0 +1,123 @@ + +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 90b370b..373abcf 100644 +--- a/.bundle/gems/rexml-3.2.5/lib/rexml/source.rb ++++ b/.bundle/gems/rexml-3.2.5/lib/rexml/source.rb +@@ -81,7 +81,11 @@ module REXML + rv + end + +- def read ++ def read(term = nil) ++ end ++ ++ def read_until(term) ++ @scanner.scan_until(Regexp.union(term)) or @scanner.rest + end + + def consume( pattern ) +@@ -204,14 +208,29 @@ module REXML + rv + end + +- def read ++ def read(term = nil) + begin +- @buffer << readline ++ @buffer << readline(term) + rescue Exception, NameError + @source = nil + 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 consume( pattern ) + match( pattern, true ) + end +@@ -263,8 +282,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 +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 305b120..7001bb2 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 +@@ -618,25 +618,43 @@ module REXML + message = "Missing attribute equal: <#{name}>" + raise REXML::ParseException.new(message, @source) + end +- quote = scanner.scan(/['"]/) +- unless quote ++ unless match = @source.match(/(['"])/, true) + message = "Missing attribute value start quote: <#{name}>" + raise REXML::ParseException.new(message, @source) + end +- unless scanner.scan(/.*#{Regexp.escape(quote)}/um) +- match_data = @source.match(/^(.*?)(\/)?>/um, true) +- if match_data +- scanner << "/" if closed +- scanner << ">" +- scanner << match_data[1] +- scanner.pos = pos +- closed = !match_data[2].nil? +- next +- end +- message = +- "Missing attribute value end quote: <#{name}>: <#{quote}>" ++ 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 ++ @source.match(/\s*/um, true) ++ if prefix == "xmlns" ++ if local_part == "xml" ++ if value != "http://www.w3.org/XML/1998/namespace" ++ msg = "The 'xml' prefix must not be bound to any other namespace "+ ++ "(http://www.w3.org/TR/REC-xml-names/#ns-decl)" ++ raise REXML::ParseException.new( msg, @source, self ) ++ end ++ elsif local_part == "xmlns" ++ msg = "The 'xmlns' prefix must not be declared "+ ++ "(http://www.w3.org/TR/REC-xml-names/#ns-decl)" ++ raise REXML::ParseException.new( msg, @source, self) ++ end ++ curr_ns << local_part ++ elsif prefix ++ prefixes << prefix unless prefix == "xml" ++ end ++ ++ if attributes[name] ++ msg = "Duplicate attribute #{name.inspect}" ++ raise REXML::ParseException.new(msg, @source, self) ++ end ++ ++ attributes[name] = value ++ else ++ message = "Invalid attribute name: <#{@source.buffer.split(%r{[/>\s]}).first}>" ++ raise REXML::ParseException.new(message, @source) + end + name = scanner[1] + prefix = scanner[2]