Version in base suite: 4.3.8-1 Base version: puma_4.3.8-1 Target version: puma_4.3.8-1+deb11u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/p/puma/puma_4.3.8-1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/p/puma/puma_4.3.8-1+deb11u1.dsc changelog | 8 gbp.conf | 3 patches/security-fix-cve-2022-23634.patch | 27 + patches/security-fix-cve-2022-24790.patch | 421 ++++++++++++++++++++++++++++++ patches/series | 2 salsa-ci.yml | 4 6 files changed, 465 insertions(+) diff -Nru puma-4.3.8/debian/changelog puma-4.3.8/debian/changelog --- puma-4.3.8/debian/changelog 2021-05-26 04:54:19.000000000 +0000 +++ puma-4.3.8/debian/changelog 2022-04-08 02:28:08.000000000 +0000 @@ -1,3 +1,11 @@ +puma (4.3.8-1+deb11u1) bullseye-security; urgency=medium + + * Team upload. + * d/p: Add security patch for CVE-2022-23634 (closes: #1005391) + * d/p: Add security patch for CVE-2022-24790 (closes: #1008723) + + -- Mohammed Bilal Fri, 08 Apr 2022 02:28:08 +0000 + puma (4.3.8-1) unstable; urgency=medium * New upstream version 4.3.8 (Closes: #989054) (Fixes: CVE-2021-29509) diff -Nru puma-4.3.8/debian/gbp.conf puma-4.3.8/debian/gbp.conf --- puma-4.3.8/debian/gbp.conf 1970-01-01 00:00:00.000000000 +0000 +++ puma-4.3.8/debian/gbp.conf 2021-06-20 20:26:46.000000000 +0000 @@ -0,0 +1,3 @@ +[DEFAULT] +pristine-tar = true +verbose = true diff -Nru puma-4.3.8/debian/patches/security-fix-cve-2022-23634.patch puma-4.3.8/debian/patches/security-fix-cve-2022-23634.patch --- puma-4.3.8/debian/patches/security-fix-cve-2022-23634.patch 1970-01-01 00:00:00.000000000 +0000 +++ puma-4.3.8/debian/patches/security-fix-cve-2022-23634.patch 2022-04-08 02:28:08.000000000 +0000 @@ -0,0 +1,27 @@ +Description: Applies security patch for CVE-2022-23634 +Author: Mohammed Bilal = 1 ++ raise HttpParserError , "#{TE_ERR_MSG}, multiple chunked: '#{te}'" ++ elsif !te_valid ++ raise HttpParserError501, "#{TE_ERR_MSG}, unknown value: '#{te}'" + end +- elsif CHUNKED.casecmp(te) == 0 +- return setup_chunked_body(body) ++ elsif te_lwr == CHUNKED ++ @env.delete TRANSFER_ENCODING2 ++ return setup_chunked_body body ++ elsif ALLOWED_TRANSFER_ENCODING.include? te_lwr ++ raise HttpParserError , "#{TE_ERR_MSG}, single value must be chunked: '#{te}'" ++ else ++ raise HttpParserError501 , "#{TE_ERR_MSG}, unknown value: '#{te}'" + end + end + +@@ -301,7 +328,12 @@ + + cl = @env[CONTENT_LENGTH] + +- unless cl ++ if cl ++ # cannot contain characters that are not \d ++ if cl =~ CONTENT_LENGTH_VALUE_INVALID ++ raise HttpParserError, "Invalid Content-Length: #{cl.inspect}" ++ end ++ else + @buffer = body.empty? ? nil : body + @body = EmptyBody + set_ready +@@ -450,7 +482,13 @@ + while !io.eof? + line = io.gets + if line.end_with?("\r\n") +- len = line.strip.to_i(16) ++ # Puma doesn't process chunk extensions, but should parse if they're ++ # present, which is the reason for the semicolon regex ++ chunk_hex = line.strip[/\A[^;]+/] ++ if chunk_hex =~ CHUNK_SIZE_INVALID ++ raise HttpParserError, "Invalid chunk size: '#{chunk_hex}'" ++ end ++ len = chunk_hex.to_i(16) + if len == 0 + @in_last_chunk = true + @body.rewind +@@ -481,7 +519,12 @@ + + case + when got == len +- write_chunk(part[0..-3]) # to skip the ending \r\n ++ # proper chunked segment must end with "\r\n" ++ if part.end_with? CHUNK_VALID_ENDING ++ write_chunk(part[0..-3]) # to skip the ending \r\n ++ else ++ raise HttpParserError, "Chunk size mismatch" ++ end + when got <= len - 2 + write_chunk(part) + @partial_part_left = len - part.size +--- a/lib/puma/const.rb ++++ b/lib/puma/const.rb +@@ -76,7 +76,7 @@ + 508 => 'Loop Detected', + 510 => 'Not Extended', + 511 => 'Network Authentication Required' +- } ++ }.freeze + + # For some HTTP status codes the client only expects headers. + # +@@ -85,7 +85,7 @@ + 204 => true, + 205 => true, + 304 => true +- } ++ }.freeze + + # Frequently used constants when constructing requests or responses. Many times + # the constant just refers to a string with the same contents. Using these constants +@@ -144,9 +144,11 @@ + 408 => "HTTP/1.1 408 Request Timeout\r\nConnection: close\r\nServer: Puma #{PUMA_VERSION}\r\n\r\n".freeze, + # Indicate that there was an internal error, obviously. + 500 => "HTTP/1.1 500 Internal Server Error\r\n\r\n".freeze, ++ # Incorrect or invalid header value ++ 501 => "HTTP/1.1 501 Not Implemented\r\n\r\n".freeze, + # A common header for indicating the server is too busy. Not used yet. + 503 => "HTTP/1.1 503 Service Unavailable\r\n\r\nBUSY".freeze +- } ++ }.freeze + + # The basic max request size we'll try to read. + CHUNK_SIZE = 16 * 1024 +--- a/lib/puma/server.rb ++++ b/lib/puma/server.rb +@@ -321,6 +321,10 @@ + client.close + + @events.parse_error self, client.env, e ++ rescue HttpParserError501 => e ++ client.write_error(501) ++ client.close ++ @events.parse_error self, client.env, e + rescue ConnectionError, EOFError + client.close + else +@@ -530,7 +534,12 @@ + client.write_error(400) + + @events.parse_error self, client.env, e ++ rescue HttpParserError501 => e ++ lowlevel_error(e, client.env) ++ ++ client.write_error(501) + ++ @events.parse_error self, client.env, e + # Server error + rescue StandardError => e + lowlevel_error(e, client.env) +--- a/test/test_puma_server.rb ++++ b/test/test_puma_server.rb +@@ -446,17 +446,20 @@ + def test_chunked_request + body = nil + content_length = nil ++ transfer_encoding = nil + server_run app: ->(env) { + body = env['rack.input'].read + content_length = env['CONTENT_LENGTH'] ++ transfer_encoding = env['HTTP_TRANSFER_ENCODING'] + [200, {}, [""]] + } + +- data = send_http_and_read "GET / HTTP/1.1\r\nConnection: close\r\nTransfer-Encoding: chunked\r\n\r\n1\r\nh\r\n4\r\nello\r\n0\r\n\r\n" ++ data = send_http_and_read "GET / HTTP/1.1\r\nConnection: close\r\nTransfer-Encoding: gzip,chunked\r\n\r\n1\r\nh\r\n4\r\nello\r\n0\r\n\r\n" + + assert_equal "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", data + assert_equal "hello", body + assert_equal 5, content_length ++ assert_nil transfer_encoding + end + + def test_chunked_request_pause_before_value +--- /dev/null ++++ b/test/test_request_invalid.rb +@@ -0,0 +1,218 @@ ++require_relative "helper" ++require "puma/events" ++ ++# These tests check for invalid request headers and metadata. ++# Content-Length, Transfer-Encoding, and chunked body size ++# values are checked for validity ++# ++# See https://datatracker.ietf.org/doc/html/rfc7230 ++# ++# https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.2 Content-Length ++# https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.1 Transfer-Encoding ++# https://datatracker.ietf.org/doc/html/rfc7230#section-4.1 chunked body size ++# ++class TestRequestInvalid < Minitest::Test ++ # running parallel seems to take longer... ++ # parallelize_me! unless JRUBY_HEAD ++ ++ GET_PREFIX = "GET / HTTP/1.1\r\nConnection: close\r\n" ++ CHUNKED = "1\r\nH\r\n4\r\nello\r\n5\r\nWorld\r\n0\r\n\r\n" ++ ++ def setup ++ @host = '127.0.0.1' ++ ++ @ios = [] ++ ++ # this app should never be called, used for debugging ++ app = ->(env) { ++ body = ''.dup ++ env.each do |k,v| ++ body << "#{k} = #{v}\n" ++ if k == 'rack.input' ++ body << "#{v.read}\n" ++ end ++ end ++ [200, {}, [body]] ++ } ++ ++ events = Puma::Events.strings ++ @server = Puma::Server.new app, events ++ @port = (@server.add_tcp_listener @host, 0).addr[1] ++ @server.run ++ sleep 0.15 if Puma.jruby? ++ end ++ ++ def teardown ++ @server.stop(true) ++ @ios.each { |io| io.close if io && !io.closed? } ++ end ++ ++ def send_http_and_read(req) ++ send_http(req).read ++ end ++ ++ def send_http(req) ++ new_connection << req ++ end ++ ++ def new_connection ++ TCPSocket.new(@host, @port).tap {|sock| @ios << sock} ++ end ++ ++ def assert_status(str, status = 400) ++ assert str.start_with?("HTTP/1.1 #{status}"), "'#{str[/[^\r]+/]}' should be #{status}" ++ end ++ ++ # ──────────────────────────────────── below are invalid Content-Length ++ ++ def test_content_length_multiple ++ te = [ ++ 'Content-Length: 5', ++ 'Content-Length: 5' ++ ].join "\r\n" ++ ++ data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\nHello\r\n\r\n" ++ ++ assert_status data ++ end ++ ++ def test_content_length_bad_characters_1 ++ te = 'Content-Length: 5.01' ++ ++ data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\nHello\r\n\r\n" ++ ++ assert_status data ++ end ++ ++ def test_content_length_bad_characters_2 ++ te = 'Content-Length: +5' ++ ++ data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\nHello\r\n\r\n" ++ ++ assert_status data ++ end ++ ++ def test_content_length_bad_characters_3 ++ te = 'Content-Length: 5 test' ++ ++ data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\nHello\r\n\r\n" ++ ++ assert_status data ++ end ++ ++ # ──────────────────────────────────── below are invalid Transfer-Encoding ++ ++ def test_transfer_encoding_chunked_not_last ++ te = [ ++ 'Transfer-Encoding: chunked', ++ 'Transfer-Encoding: gzip' ++ ].join "\r\n" ++ ++ data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\n#{CHUNKED}" ++ ++ assert_status data ++ end ++ ++ def test_transfer_encoding_chunked_multiple ++ te = [ ++ 'Transfer-Encoding: chunked', ++ 'Transfer-Encoding: gzip', ++ 'Transfer-Encoding: chunked' ++ ].join "\r\n" ++ ++ data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\n#{CHUNKED}" ++ ++ assert_status data ++ end ++ ++ def test_transfer_encoding_invalid_single ++ te = 'Transfer-Encoding: xchunked' ++ ++ data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\n#{CHUNKED}" ++ ++ assert_status data, 501 ++ end ++ ++ def test_transfer_encoding_invalid_multiple ++ te = [ ++ 'Transfer-Encoding: x_gzip', ++ 'Transfer-Encoding: gzip', ++ 'Transfer-Encoding: chunked' ++ ].join "\r\n" ++ ++ data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\n#{CHUNKED}" ++ assert_status data, 501 ++ end ++ ++ def test_transfer_encoding_single_not_chunked ++ te = 'Transfer-Encoding: gzip' ++ ++ data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\n#{CHUNKED}" ++ ++ assert_status data ++ end ++ ++ # ──────────────────────────────────── below are invalid chunked size ++ ++ def test_chunked_size_bad_characters_1 ++ te = 'Transfer-Encoding: chunked' ++ chunked ='5.01' ++ ++ data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\n1\r\nh\r\n#{chunked}\r\nHello\r\n0\r\n\r\n" ++ ++ assert_status data ++ end ++ ++ def test_chunked_size_bad_characters_2 ++ te = 'Transfer-Encoding: chunked' ++ chunked ='+5' ++ ++ data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\n1\r\nh\r\n#{chunked}\r\nHello\r\n0\r\n\r\n" ++ ++ assert_status data ++ end ++ ++ def test_chunked_size_bad_characters_3 ++ te = 'Transfer-Encoding: chunked' ++ chunked ='5 bad' ++ ++ data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\n1\r\nh\r\n#{chunked}\r\nHello\r\n0\r\n\r\n" ++ ++ assert_status data ++ end ++ ++ def test_chunked_size_bad_characters_4 ++ te = 'Transfer-Encoding: chunked' ++ chunked ='0xA' ++ ++ data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\n1\r\nh\r\n#{chunked}\r\nHelloHello\r\n0\r\n\r\n" ++ ++ assert_status data ++ end ++ ++ # size is less than bytesize ++ def test_chunked_size_mismatch_1 ++ te = 'Transfer-Encoding: chunked' ++ chunked = ++ "5\r\nHello\r\n" \ ++ "4\r\nWorld\r\n" \ ++ "0" ++ ++ data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\n#{chunked}\r\n\r\n" ++ ++ assert_status data ++ end ++ ++ # size is greater than bytesize ++ def test_chunked_size_mismatch_2 ++ te = 'Transfer-Encoding: chunked' ++ chunked = ++ "5\r\nHello\r\n" \ ++ "6\r\nWorld\r\n" \ ++ "0" ++ ++ data = send_http_and_read "#{GET_PREFIX}#{te}\r\n\r\n#{chunked}\r\n\r\n" ++ ++ assert_status data ++ end ++end diff -Nru puma-4.3.8/debian/patches/series puma-4.3.8/debian/patches/series --- puma-4.3.8/debian/patches/series 2021-05-26 04:54:19.000000000 +0000 +++ puma-4.3.8/debian/patches/series 2022-04-08 02:28:08.000000000 +0000 @@ -1,3 +1,5 @@ +security-fix-cve-2022-24790.patch +security-fix-cve-2022-23634.patch 0004-puma.gemspec-drop-git-usage.patch 0011-disable-minitest-extensions.patch 0012-disable-cli-ssl-tests.patch diff -Nru puma-4.3.8/debian/salsa-ci.yml puma-4.3.8/debian/salsa-ci.yml --- puma-4.3.8/debian/salsa-ci.yml 1970-01-01 00:00:00.000000000 +0000 +++ puma-4.3.8/debian/salsa-ci.yml 2021-05-26 04:53:04.000000000 +0000 @@ -0,0 +1,4 @@ +--- +include: + - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/salsa-ci.yml + - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/pipeline-jobs.yml