From ea1b0b7ba76ccc941758c085cded2c08d481a785 Mon Sep 17 00:00:00 2001 From: shixuantong Date: Thu, 24 Jul 2025 10:28:18 +0800 Subject: [PATCH] fix CVE-2025-6442 --- backport-CVE-2025-6442.patch | 425 ++++++++++++++++++++++++++ backport-fix-ReDoS-parse_header.patch | 34 +++ ruby.spec | 7 +- 3 files changed, 465 insertions(+), 1 deletion(-) create mode 100644 backport-CVE-2025-6442.patch create mode 100644 backport-fix-ReDoS-parse_header.patch diff --git a/backport-CVE-2025-6442.patch b/backport-CVE-2025-6442.patch new file mode 100644 index 0000000..e884971 --- /dev/null +++ b/backport-CVE-2025-6442.patch @@ -0,0 +1,425 @@ +From ee60354bcb84ec33b9245e1d1aa6e1f7e8132101 Mon Sep 17 00:00:00 2001 +From: Jeremy Evans +Date: Tue, 25 Jun 2024 14:39:04 -0700 +Subject: [PATCH] Require CRLF line endings in request line and headers + +Disallow bare CR, LF, NUL in header and request lines. Tighten +parsing of request lines to only allow single spaces, as specified +in the RFCs. + +Forcing this RFC-compliant behavior breaks a lot of tests, so +fix the tests to correctly use CRLF instead of LF for requests +(other than the specific checks for handling of bad requests). + +Fixes #137 +--- + lib/webrick/httprequest.rb | 4 +- + lib/webrick/httputils.rb | 10 ++- + test/webrick/test_filehandler.rb | 2 +- + test/webrick/test_httprequest.rb | 142 ++++++++++++++++++++++++++----- + 4 files changed, 128 insertions(+), 30 deletions(-) + +diff --git a/lib/webrick/httprequest.rb b/lib/webrick/httprequest.rb +index 820acb2..d96f522 100644 +--- a/lib/webrick/httprequest.rb ++++ b/lib/webrick/httprequest.rb +@@ -425,7 +425,7 @@ module WEBrick + end + @request_time = Time.now + raise HTTPStatus::EOFError unless @request_line +- if /^(\S+)\s+(\S++)(?:\s+HTTP\/(\d+\.\d+))?\r?\n/mo =~ @request_line ++ if /^(\S+) (\S++)(?: HTTP\/(\d+\.\d+))?\r\n/mo =~ @request_line + @request_method = $1 + @unparsed_uri = $2 + @http_version = HTTPVersion.new($3 ? $3 : "0.9") +@@ -438,7 +438,7 @@ module WEBrick + def read_header(socket) + if socket + while line = read_line(socket) +- break if /\A(#{CRLF}|#{LF})\z/om =~ line ++ break if /\A#{CRLF}\z/om =~ line + if (@request_bytes += line.bytesize) > MAX_HEADER_LENGTH + raise HTTPStatus::RequestEntityTooLarge, 'headers too large' + end +diff --git a/lib/webrick/httputils.rb b/lib/webrick/httputils.rb +index cd1bf32..99b33d5 100644 +--- a/lib/webrick/httputils.rb ++++ b/lib/webrick/httputils.rb +@@ -145,16 +145,18 @@ module WEBrick + field = nil + raw.each_line{|line| + case line +- when /^([A-Za-z0-9!\#$%&'*+\-.^_`|~]+):(.*?)\z/om +- field, value = $1, $2.strip ++ when /^([A-Za-z0-9!\#$%&'*+\-.^_`|~]+):([^\r\n\0]*?)\r\n\z/om ++ field, value = $1, $2 + field.downcase! + header[field] = [] unless header.has_key?(field) + header[field] << value +- when /^\s+(.*?)/om +- value = line.strip ++ when /^\s+([^\r\n\0]*?)\r\n/om + unless field + raise HTTPStatus::BadRequest, "bad header '#{line}'." + end ++ value = line ++ value.lstrip! ++ value.slice!(-2..-1) + header[field][-1] << " " << value + else + raise HTTPStatus::BadRequest, "bad header '#{line}'." +diff --git a/test/webrick/test_filehandler.rb b/test/webrick/test_filehandler.rb +index 99bc142..8a91cf2 100644 +--- a/test/webrick/test_filehandler.rb ++++ b/test/webrick/test_filehandler.rb +@@ -32,7 +32,7 @@ class WEBrick::TestFileHandler < Test::Unit::TestCase + Range: #{range_spec} + + END_OF_REQUEST +- return StringIO.new(msg.gsub(/^ {6}/, "")) ++ return StringIO.new(msg.gsub(/^ {6}/, "").gsub("\n", "\r\n")) + end + + def make_range_response(file, range_spec) +diff --git a/test/webrick/test_httprequest.rb b/test/webrick/test_httprequest.rb +index cce9b91..7ef1bab 100644 +--- a/test/webrick/test_httprequest.rb ++++ b/test/webrick/test_httprequest.rb +@@ -11,7 +11,7 @@ class TestWEBrickHTTPRequest < Test::Unit::TestCase + + def test_simple_request + msg = <<-_end_of_message_ +-GET / ++GET /\r + _end_of_message_ + req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) + req.parse(StringIO.new(msg)) +@@ -24,7 +24,7 @@ GET / + foobar # HTTP/0.9 request don't have header nor entity body. + _end_of_message_ + req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) +- req.parse(StringIO.new(msg.gsub(/^ {6}/, ""))) ++ req.parse(StringIO.new(msg.gsub(/^ {6}/, "").gsub("\n", "\r\n"))) + assert_equal("GET", req.request_method) + assert_equal("/", req.unparsed_uri) + assert_equal(WEBrick::HTTPVersion.new("0.9"), req.http_version) +@@ -41,7 +41,7 @@ GET / + + _end_of_message_ + req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) +- req.parse(StringIO.new(msg.gsub(/^ {6}/, ""))) ++ req.parse(StringIO.new(msg.gsub(/^ {6}/, "").gsub("\n", "\r\n"))) + assert_equal("GET", req.request_method) + assert_equal("/", req.unparsed_uri) + assert_equal(WEBrick::HTTPVersion.new("1.0"), req.http_version) +@@ -58,7 +58,7 @@ GET / + + _end_of_message_ + req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) +- req.parse(StringIO.new(msg.gsub(/^ {6}/, ""))) ++ req.parse(StringIO.new(msg.gsub(/^ {6}/, "").gsub("\n", "\r\n"))) + assert_equal("GET", req.request_method) + assert_equal("/path", req.unparsed_uri) + assert_equal("", req.script_name) +@@ -77,6 +77,96 @@ GET / + _end_of_message_ + req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) + assert_raise(WEBrick::HTTPStatus::RequestURITooLarge){ ++ req.parse(StringIO.new(msg.gsub(/^ {6}/, "").gsub("\n", "\r\n"))) ++ } ++ end ++ ++ def test_bare_lf_request_line ++ msg = <<-_end_of_message_ ++ GET / HTTP/1.1 ++ Content-Length: 0\r ++ \r ++ _end_of_message_ ++ req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) ++ assert_raise(WEBrick::HTTPStatus::BadRequest){ ++ req.parse(StringIO.new(msg.gsub(/^ {6}/, ""))) ++ } ++ end ++ ++ def test_bare_lf_header ++ msg = <<-_end_of_message_ ++ GET / HTTP/1.1\r ++ Content-Length: 0 ++ \r ++ _end_of_message_ ++ req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) ++ assert_raise(WEBrick::HTTPStatus::BadRequest){ ++ req.parse(StringIO.new(msg.gsub(/^ {6}/, ""))) ++ } ++ end ++ ++ def test_bare_cr_request_line ++ msg = <<-_end_of_message_ ++ GET / HTTP/1.1\r\r ++ Content-Length: 0\r ++ \r ++ _end_of_message_ ++ req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) ++ assert_raise(WEBrick::HTTPStatus::BadRequest){ ++ req.parse(StringIO.new(msg.gsub(/^ {6}/, ""))) ++ } ++ end ++ ++ def test_bare_cr_header ++ msg = <<-_end_of_message_ ++ GET / HTTP/1.1\r ++ Content-Type: foo\rbar\r ++ \r ++ _end_of_message_ ++ req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) ++ assert_raise(WEBrick::HTTPStatus::BadRequest){ ++ req.parse(StringIO.new(msg.gsub(/^ {6}/, ""))) ++ } ++ end ++ ++ def test_invalid_request_lines ++ msg = <<-_end_of_message_ ++ GET / HTTP/1.1\r ++ Content-Length: 0\r ++ \r ++ _end_of_message_ ++ req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) ++ assert_raise(WEBrick::HTTPStatus::BadRequest){ ++ req.parse(StringIO.new(msg.gsub(/^ {6}/, ""))) ++ } ++ ++ msg = <<-_end_of_message_ ++ GET / HTTP/1.1\r ++ Content-Length: 0\r ++ \r ++ _end_of_message_ ++ req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) ++ assert_raise(WEBrick::HTTPStatus::BadRequest){ ++ req.parse(StringIO.new(msg.gsub(/^ {6}/, ""))) ++ } ++ ++ msg = <<-_end_of_message_ ++ GET /\r HTTP/1.1\r ++ Content-Length: 0\r ++ \r ++ _end_of_message_ ++ req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) ++ assert_raise(WEBrick::HTTPStatus::BadRequest){ ++ req.parse(StringIO.new(msg.gsub(/^ {6}/, ""))) ++ } ++ ++ msg = <<-_end_of_message_ ++ GET / HTTP/1.1 \r ++ Content-Length: 0\r ++ \r ++ _end_of_message_ ++ req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) ++ assert_raise(WEBrick::HTTPStatus::BadRequest){ + req.parse(StringIO.new(msg.gsub(/^ {6}/, ""))) + } + end +@@ -111,13 +201,13 @@ GET / + Accept-Language: en;q=0.5, *; q=0 + Accept-Language: ja + Content-Type: text/plain +- Content-Length: 7 ++ Content-Length: 8 + X-Empty-Header: + + foobar + _end_of_message_ + req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) +- req.parse(StringIO.new(msg.gsub(/^ {6}/, ""))) ++ req.parse(StringIO.new(msg.gsub(/^ {6}/, "").gsub("\n", "\r\n"))) + assert_equal( + URI.parse("http://test.ruby-lang.org:8080/path"), req.request_uri) + assert_equal("test.ruby-lang.org", req.host) +@@ -128,9 +218,9 @@ GET / + req.accept) + assert_equal(%w(gzip compress identity *), req.accept_encoding) + assert_equal(%w(ja en *), req.accept_language) +- assert_equal(7, req.content_length) ++ assert_equal(8, req.content_length) + assert_equal("text/plain", req.content_type) +- assert_equal("foobar\n", req.body) ++ assert_equal("foobar\r\n", req.body) + assert_equal("", req["x-empty-header"]) + assert_equal(nil, req["x-no-header"]) + assert(req.query.empty?) +@@ -139,7 +229,7 @@ GET / + def test_parse_header2() + msg = <<-_end_of_message_ + POST /foo/bar/../baz?q=a HTTP/1.0 +- Content-Length: 9 ++ Content-Length: 10 + User-Agent: + FOO BAR + BAZ +@@ -147,14 +237,14 @@ GET / + hogehoge + _end_of_message_ + req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) +- req.parse(StringIO.new(msg.gsub(/^ {6}/, ""))) ++ req.parse(StringIO.new(msg.gsub(/^ {6}/, "").gsub("\n", "\r\n"))) + assert_equal("POST", req.request_method) + assert_equal("/foo/baz", req.path) + assert_equal("", req.script_name) + assert_equal("/foo/baz", req.path_info) +- assert_equal("9", req['content-length']) ++ assert_equal("10", req['content-length']) + assert_equal("FOO BAR BAZ", req['user-agent']) +- assert_equal("hogehoge\n", req.body) ++ assert_equal("hogehoge\r\n", req.body) + end + + def test_parse_headers3 +@@ -164,7 +254,7 @@ GET / + + _end_of_message_ + req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) +- req.parse(StringIO.new(msg.gsub(/^ {6}/, ""))) ++ req.parse(StringIO.new(msg.gsub(/^ {6}/, "").gsub("\n", "\r\n"))) + assert_equal(URI.parse("http://test.ruby-lang.org/path"), req.request_uri) + assert_equal("test.ruby-lang.org", req.host) + assert_equal(80, req.port) +@@ -175,7 +265,7 @@ GET / + + _end_of_message_ + req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) +- req.parse(StringIO.new(msg.gsub(/^ {6}/, ""))) ++ req.parse(StringIO.new(msg.gsub(/^ {6}/, "").gsub("\n", "\r\n"))) + assert_equal(URI.parse("http://192.168.1.1/path"), req.request_uri) + assert_equal("192.168.1.1", req.host) + assert_equal(80, req.port) +@@ -186,7 +276,7 @@ GET / + + _end_of_message_ + req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) +- req.parse(StringIO.new(msg.gsub(/^ {6}/, ""))) ++ req.parse(StringIO.new(msg.gsub(/^ {6}/, "").gsub("\n", "\r\n"))) + assert_equal(URI.parse("http://[fe80::208:dff:feef:98c7]/path"), + req.request_uri) + assert_equal("[fe80::208:dff:feef:98c7]", req.host) +@@ -198,7 +288,7 @@ GET / + + _end_of_message_ + req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) +- req.parse(StringIO.new(msg.gsub(/^ {6}/, ""))) ++ req.parse(StringIO.new(msg.gsub(/^ {6}/, "").gsub("\n", "\r\n"))) + assert_equal(URI.parse("http://192.168.1.1:8080/path"), req.request_uri) + assert_equal("192.168.1.1", req.host) + assert_equal(8080, req.port) +@@ -209,7 +299,7 @@ GET / + + _end_of_message_ + req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) +- req.parse(StringIO.new(msg.gsub(/^ {6}/, ""))) ++ req.parse(StringIO.new(msg.gsub(/^ {6}/, "").gsub("\n", "\r\n"))) + assert_equal(URI.parse("http://[fe80::208:dff:feef:98c7]:8080/path"), + req.request_uri) + assert_equal("[fe80::208:dff:feef:98c7]", req.host) +@@ -224,7 +314,7 @@ GET / + + _end_of_message_ + req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) +- req.parse(StringIO.new(msg.gsub(/^ {6}/, ""))) ++ req.parse(StringIO.new(msg.gsub(/^ {6}/, "").gsub("\n", "\r\n"))) + query = req.query + assert_equal("1", query["foo"]) + assert_equal(["1", "2", "3"], query["foo"].to_ary) +@@ -244,7 +334,7 @@ GET / + #{param} + _end_of_message_ + req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) +- req.parse(StringIO.new(msg.gsub(/^ {6}/, ""))) ++ req.parse(StringIO.new(msg.gsub(/^ {6}/, "").gsub("\n", "\r\n"))) + query = req.query + assert_equal("1", query["foo"]) + assert_equal(["1", "2", "3"], query["foo"].to_ary) +@@ -261,7 +351,7 @@ GET / + Transfer-Encoding: chunked + + _end_of_message_ +- msg.gsub!(/^ {6}/, "") ++ msg.gsub!("\n", "\r\n") + open(__FILE__){|io| + while chunk = io.read(100) + msg << chunk.size.to_s(16) << crlf +@@ -286,6 +376,7 @@ GET / + + _end_of_message_ + msg.gsub!(/^ {6}/, "") ++ msg.gsub!("\n", "\r\n") + req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) + req.parse(StringIO.new(msg)) + assert_equal("server.example.com", req.server_name) +@@ -306,6 +397,7 @@ GET / + + _end_of_message_ + msg.gsub!(/^ {6}/, "") ++ msg.gsub!("\n", "\r\n") + req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) + req.parse(StringIO.new(msg)) + assert_equal("server.example.com", req.server_name) +@@ -328,6 +420,7 @@ GET / + + _end_of_message_ + msg.gsub!(/^ {6}/, "") ++ msg.gsub!("\n", "\r\n") + req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) + req.parse(StringIO.new(msg)) + assert_equal("server.example.com", req.server_name) +@@ -350,6 +443,7 @@ GET / + + _end_of_message_ + msg.gsub!(/^ {6}/, "") ++ msg.gsub!("\n", "\r\n") + req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) + req.parse(StringIO.new(msg)) + assert_equal("server1.example.com", req.server_name) +@@ -367,6 +461,7 @@ GET / + + _end_of_message_ + msg.gsub!(/^ {6}/, "") ++ msg.gsub!("\n", "\r\n") + req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) + req.parse(StringIO.new(msg)) + assert req['expect'] +@@ -383,6 +478,7 @@ GET / + + _end_of_message_ + msg.gsub!(/^ {6}/, "") ++ msg.gsub!("\n", "\r\n") + req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) + req.parse(StringIO.new(msg)) + assert !req['expect'] +@@ -402,7 +498,7 @@ GET / + _end_of_message_ + assert_raise(WEBrick::HTTPStatus::LengthRequired){ + req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) +- req.parse(StringIO.new(msg.gsub(/^ {6}/, ""))) ++ req.parse(StringIO.new(msg.gsub(/^ {6}/, "").gsub("\n", "\r\n"))) + req.body + } + +@@ -415,7 +511,7 @@ GET / + _end_of_message_ + assert_raise(WEBrick::HTTPStatus::BadRequest){ + req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) +- req.parse(StringIO.new(msg.gsub(/^ {6}/, ""))) ++ req.parse(StringIO.new(msg.gsub(/^ {6}/, "").gsub("\n", "\r\n"))) + req.body + } + +@@ -428,7 +524,7 @@ GET / + _end_of_message_ + assert_raise(WEBrick::HTTPStatus::NotImplemented){ + req = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) +- req.parse(StringIO.new(msg.gsub(/^ {6}/, ""))) ++ req.parse(StringIO.new(msg.gsub(/^ {6}/, "").gsub("\n", "\r\n"))) + req.body + } + end +-- +2.27.0 + diff --git a/backport-fix-ReDoS-parse_header.patch b/backport-fix-ReDoS-parse_header.patch new file mode 100644 index 0000000..484a25f --- /dev/null +++ b/backport-fix-ReDoS-parse_header.patch @@ -0,0 +1,34 @@ +From c5b7622af70ae6ca26a4067840c52128206c2b3f Mon Sep 17 00:00:00 2001 +From: ooooooo_q +Date: Sun, 16 Apr 2023 21:17:37 +0900 +Subject: [PATCH] fix ReDoS parse_header + +--- + lib/webrick/httputils.rb | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/lib/webrick/httputils.rb b/lib/webrick/httputils.rb +index d95147c..d82f95d 100644 +--- a/lib/webrick/httputils.rb ++++ b/lib/webrick/httputils.rb +@@ -157,13 +157,13 @@ module WEBrick + field = nil + raw.each_line{|line| + case line +- when /^([A-Za-z0-9!\#$%&'*+\-.^_`|~]+):\s*(.*?)\s*\z/om +- field, value = $1, $2 ++ when /^([A-Za-z0-9!\#$%&'*+\-.^_`|~]+):(.*?)\z/om ++ field, value = $1, $2.strip + field.downcase! + header[field] = [] unless header.has_key?(field) + header[field] << value +- when /^\s+(.*?)\s*\z/om +- value = $1 ++ when /^\s+(.*?)/om ++ value = line.strip + unless field + raise HTTPStatus::BadRequest, "bad header '#{line}'." + end +-- +2.27.0 + diff --git a/ruby.spec b/ruby.spec index 5ecdaa1..9c4e032 100644 --- a/ruby.spec +++ b/ruby.spec @@ -1,6 +1,6 @@ Name: ruby Version: 2.5.8 -Release: 134 +Release: 135 Summary: Object-oriented scripting language interpreter License: (Ruby or BSD) and Public Domain and MIT and CC0 and zlib and UCD URL: https://www.ruby-lang.org/ @@ -100,6 +100,8 @@ Patch6056: backport-0001-CVE-2025-43857.patch Patch6057: backport-0002-CVE-2025-43857.patch Patch6058: backport-0003-CVE-2025-43857.patch Patch6059: backport-0004-CVE-2025-43857.patch +Patch6060: backport-fix-ReDoS-parse_header.patch +Patch6061: backport-CVE-2025-6442.patch Patch9000: add-require_relative-helper-to-uninitialized-constan.patch @@ -639,6 +641,9 @@ make runruby TESTRUN_SCRIPT=%{SOURCE13} %exclude %{gem_dir}/gems/xmlrpc-0.3.0/.* %changelog +* Thu Jul 24 2025 shixuantong - 2.5.8-135 +- fix CVE-2025-6442 + * Thu Jun 05 2025 shixuantong - 2.5.8-134 - fix CVE-2025-43857 -- Gitee