Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(PA-6507) Patch rexml gem for CVE-2024-35176 #868

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions configs/components/ruby-2.7.8.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
14 changes: 14 additions & 0 deletions configs/components/ruby-3.2.4.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
135 changes: 135 additions & 0 deletions resources/patches/ruby_27/rexml_for_CVE-2024-35176.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
commit e597f07718c27dc4414ad39f121376e53056475a
Author: Shubham Shinde <[email protected]>
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't expect conflicts. Did this occur because you cherry-picked the commit into the ruby 2.7 source tree? Is there a different patch we can use that applies cleanly?

Copy link
Contributor Author

@shubhamshinde360 shubhamshinde360 Jul 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @joshcooper,
Yes, I cherry-picked the commit ruby/rexml@4325835 into tag https://github.com/ruby/rexml/releases/tag/v3.2.5 and it conflicted.
Was not able to find other patches to apply cleanly either.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We had to resolve conflicts manually, is there any other path we can take in such situations?


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 [email protected]?
+ 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
123 changes: 123 additions & 0 deletions resources/patches/ruby_32/rexml_for_CVE-2024-35176.patch
Original file line number Diff line number Diff line change
@@ -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 [email protected]?
+ 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]
Loading