diff mbox series

[dunfell] ruby/cgi-gem: CVE-2021-33621 HTTP response splitting in CGI

Message ID 20230720061850.14599-1-hprajapati@mvista.com
State Accepted, archived
Headers show
Series [dunfell] ruby/cgi-gem: CVE-2021-33621 HTTP response splitting in CGI | expand

Commit Message

Hitendra Prajapati July 20, 2023, 6:18 a.m. UTC
Upstream-Status: Backport from https://github.com/ruby/cgi/commit/64c5045c0a6b84fdb938a8465a0890e5f7162708

Signed-off-by: Hitendra Prajapati <hprajapati@mvista.com>
---
 .../ruby/ruby/CVE-2021-33621.patch            | 139 ++++++++++++++++++
 meta/recipes-devtools/ruby/ruby_2.7.6.bb      |   1 +
 2 files changed, 140 insertions(+)
 create mode 100644 meta/recipes-devtools/ruby/ruby/CVE-2021-33621.patch
diff mbox series

Patch

diff --git a/meta/recipes-devtools/ruby/ruby/CVE-2021-33621.patch b/meta/recipes-devtools/ruby/ruby/CVE-2021-33621.patch
new file mode 100644
index 0000000000..cc2f9853db
--- /dev/null
+++ b/meta/recipes-devtools/ruby/ruby/CVE-2021-33621.patch
@@ -0,0 +1,139 @@ 
+From 64c5045c0a6b84fdb938a8465a0890e5f7162708 Mon Sep 17 00:00:00 2001
+From: Yusuke Endoh <mame@ruby-lang.org>
+Date: Tue, 22 Nov 2022 10:49:27 +0900
+Subject: [PATCH] Prevent CRLF injection
+
+Throw a RuntimeError if the HTTP response header contains CR or LF to
+prevent HTTP response splitting.
+
+https://hackerone.com/reports/1204695
+
+Upstream-Status: Backport [https://github.com/ruby/cgi/commit/64c5045c0a6b84fdb938a8465a0890e5f7162708]
+CVE: CVE-2021-33621
+Signed-off-by: Hitendra Prajapati <hprajapati@mvista.com>
+---
+ lib/cgi/core.rb             | 45 +++++++++++++++++++++++--------------
+ test/cgi/test_cgi_header.rb |  8 +++++++
+ 2 files changed, 36 insertions(+), 17 deletions(-)
+
+diff --git a/lib/cgi/core.rb b/lib/cgi/core.rb
+index bec76e0..62e6068 100644
+--- a/lib/cgi/core.rb
++++ b/lib/cgi/core.rb
+@@ -188,17 +188,28 @@ class CGI
+   # Using #header with the HTML5 tag maker will create a <header> element.
+   alias :header :http_header
+ 
++  def _no_crlf_check(str)
++    if str
++      str = str.to_s
++      raise "A HTTP status or header field must not include CR and LF" if str =~ /[\r\n]/
++      str
++    else
++      nil
++    end
++  end
++  private :_no_crlf_check
++
+   def _header_for_string(content_type) #:nodoc:
+     buf = ''.dup
+     if nph?()
+-      buf << "#{$CGI_ENV['SERVER_PROTOCOL'] || 'HTTP/1.0'} 200 OK#{EOL}"
++      buf << "#{_no_crlf_check($CGI_ENV['SERVER_PROTOCOL']) || 'HTTP/1.0'} 200 OK#{EOL}"
+       buf << "Date: #{CGI.rfc1123_date(Time.now)}#{EOL}"
+-      buf << "Server: #{$CGI_ENV['SERVER_SOFTWARE']}#{EOL}"
++      buf << "Server: #{_no_crlf_check($CGI_ENV['SERVER_SOFTWARE'])}#{EOL}"
+       buf << "Connection: close#{EOL}"
+     end
+-    buf << "Content-Type: #{content_type}#{EOL}"
++    buf << "Content-Type: #{_no_crlf_check(content_type)}#{EOL}"
+     if @output_cookies
+-      @output_cookies.each {|cookie| buf << "Set-Cookie: #{cookie}#{EOL}" }
++      @output_cookies.each {|cookie| buf << "Set-Cookie: #{_no_crlf_check(cookie)}#{EOL}" }
+     end
+     return buf
+   end # _header_for_string
+@@ -213,9 +224,9 @@ class CGI
+     ## NPH
+     options.delete('nph') if defined?(MOD_RUBY)
+     if options.delete('nph') || nph?()
+-      protocol = $CGI_ENV['SERVER_PROTOCOL'] || 'HTTP/1.0'
++      protocol = _no_crlf_check($CGI_ENV['SERVER_PROTOCOL']) || 'HTTP/1.0'
+       status = options.delete('status')
+-      status = HTTP_STATUS[status] || status || '200 OK'
++      status = HTTP_STATUS[status] || _no_crlf_check(status) || '200 OK'
+       buf << "#{protocol} #{status}#{EOL}"
+       buf << "Date: #{CGI.rfc1123_date(Time.now)}#{EOL}"
+       options['server'] ||= $CGI_ENV['SERVER_SOFTWARE'] || ''
+@@ -223,38 +234,38 @@ class CGI
+     end
+     ## common headers
+     status = options.delete('status')
+-    buf << "Status: #{HTTP_STATUS[status] || status}#{EOL}" if status
++    buf << "Status: #{HTTP_STATUS[status] || _no_crlf_check(status)}#{EOL}" if status
+     server = options.delete('server')
+-    buf << "Server: #{server}#{EOL}" if server
++    buf << "Server: #{_no_crlf_check(server)}#{EOL}" if server
+     connection = options.delete('connection')
+-    buf << "Connection: #{connection}#{EOL}" if connection
++    buf << "Connection: #{_no_crlf_check(connection)}#{EOL}" if connection
+     type = options.delete('type')
+-    buf << "Content-Type: #{type}#{EOL}" #if type
++    buf << "Content-Type: #{_no_crlf_check(type)}#{EOL}" #if type
+     length = options.delete('length')
+-    buf << "Content-Length: #{length}#{EOL}" if length
++    buf << "Content-Length: #{_no_crlf_check(length)}#{EOL}" if length
+     language = options.delete('language')
+-    buf << "Content-Language: #{language}#{EOL}" if language
++    buf << "Content-Language: #{_no_crlf_check(language)}#{EOL}" if language
+     expires = options.delete('expires')
+     buf << "Expires: #{CGI.rfc1123_date(expires)}#{EOL}" if expires
+     ## cookie
+     if cookie = options.delete('cookie')
+       case cookie
+       when String, Cookie
+-        buf << "Set-Cookie: #{cookie}#{EOL}"
++        buf << "Set-Cookie: #{_no_crlf_check(cookie)}#{EOL}"
+       when Array
+         arr = cookie
+-        arr.each {|c| buf << "Set-Cookie: #{c}#{EOL}" }
++        arr.each {|c| buf << "Set-Cookie: #{_no_crlf_check(c)}#{EOL}" }
+       when Hash
+         hash = cookie
+-        hash.each_value {|c| buf << "Set-Cookie: #{c}#{EOL}" }
++        hash.each_value {|c| buf << "Set-Cookie: #{_no_crlf_check(c)}#{EOL}" }
+       end
+     end
+     if @output_cookies
+-      @output_cookies.each {|c| buf << "Set-Cookie: #{c}#{EOL}" }
++      @output_cookies.each {|c| buf << "Set-Cookie: #{_no_crlf_check(c)}#{EOL}" }
+     end
+     ## other headers
+     options.each do |key, value|
+-      buf << "#{key}: #{value}#{EOL}"
++      buf << "#{_no_crlf_check(key)}: #{_no_crlf_check(value)}#{EOL}"
+     end
+     return buf
+   end # _header_for_hash
+diff --git a/test/cgi/test_cgi_header.rb b/test/cgi/test_cgi_header.rb
+index bab2d03..ec2f4de 100644
+--- a/test/cgi/test_cgi_header.rb
++++ b/test/cgi/test_cgi_header.rb
+@@ -176,6 +176,14 @@ class CGIHeaderTest < Test::Unit::TestCase
+   end
+ 
+ 
++  def test_cgi_http_header_crlf_injection
++    cgi = CGI.new
++    assert_raise(RuntimeError) { cgi.http_header("text/xhtml\r\nBOO") }
++    assert_raise(RuntimeError) { cgi.http_header("type" => "text/xhtml\r\nBOO") }
++    assert_raise(RuntimeError) { cgi.http_header("status" => "200 OK\r\nBOO") }
++    assert_raise(RuntimeError) { cgi.http_header("location" => "text/xhtml\r\nBOO") }
++  end
++
+ 
+   instance_methods.each do |method|
+     private method if method =~ /^test_(.*)/ && $1 != ENV['TEST']
+-- 
+2.25.1
+
diff --git a/meta/recipes-devtools/ruby/ruby_2.7.6.bb b/meta/recipes-devtools/ruby/ruby_2.7.6.bb
index 91ffde5fa3..7e6373bd24 100644
--- a/meta/recipes-devtools/ruby/ruby_2.7.6.bb
+++ b/meta/recipes-devtools/ruby/ruby_2.7.6.bb
@@ -8,6 +8,7 @@  SRC_URI += " \
            file://0001-Modify-shebang-of-libexec-y2racc-and-libexec-racc2y.patch \
            file://0001-template-Makefile.in-do-not-write-host-cross-cc-item.patch \
            file://CVE-2023-28756.patch \
+           file://CVE-2021-33621.patch \
            "
 
 SRC_URI[md5sum] = "f972fb0cce662966bec10d5c5f32d042"