Hi, Sorry forgot to attach the debdiff in initial mail. Attaching in this one.
--abhijith
diff -Nru puma-6.6.0/debian/changelog puma-6.6.0/debian/changelog --- puma-6.6.0/debian/changelog 2025-07-17 15:50:19.000000000 +0530 +++ puma-6.6.0/debian/changelog 2026-09-10 10:35:33.000000000 +0530 @@ -1,3 +1,13 @@ +puma (6.6.0-4+deb13u1) trixie; urgency=medium + + * Team upload + * Fix CVE-2026-47736: Puma PROXY protocol v1 parser allows remote memory + exhaustion + * Fix CVE-2026-47737: Puma PROXY protocol v1 accepts repeated protocol + headers on persistent connections + + -- Abhijith PA <[email protected]> Thu, 10 Sep 2026 10:35:33 +0530 + puma (6.6.0-4) unstable; urgency=medium * Team upload. diff -Nru puma-6.6.0/debian/patches/CVE-2026-47736_47737.patch puma-6.6.0/debian/patches/CVE-2026-47736_47737.patch --- puma-6.6.0/debian/patches/CVE-2026-47736_47737.patch 1970-01-01 05:30:00.000000000 +0530 +++ puma-6.6.0/debian/patches/CVE-2026-47736_47737.patch 2026-09-10 10:30:37.000000000 +0530 @@ -0,0 +1,168 @@ +From ebe9db3929ab8299d19c8f5b41e8ef4f4b22fa58 Mon Sep 17 00:00:00 2001 +From: Nate Berkopec <[email protected]> +Date: Wed, 27 May 2026 08:33:21 +0900 +Subject: [PATCH] 7.2.1 backport (#3947) + +* Limit and anchor PROXY protocol parsing + +(cherry picked from commit f1f37d5822aa8ffe23e7d0d16e9a1f3b0eaf9bfd) + +* Parse PROXY protocol only once per connection + +(cherry picked from commit 8310c17cd43784723ed41a94d2a747da256306de) +--- + lib/puma/client.rb | 38 ++++++++++++++------- + lib/puma/const.rb | 3 +- + test/test_puma_server.rb | 71 ++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 100 insertions(+), 12 deletions(-) + +--- a/lib/puma/client.rb ++++ b/lib/puma/client.rb +@@ -158,7 +158,7 @@ module Puma + @parser.reset + @io_buffer.reset + @read_header = true +- @read_proxy = !!@expect_proxy_proto ++ @read_proxy = !!@expect_proxy_proto && @requests_served.zero? + @env = @proto_env.dup + @parsed_bytes = 0 + @ready = false +@@ -214,20 +214,36 @@ module Puma + def try_to_parse_proxy_protocol + if @read_proxy + if @expect_proxy_proto == :v1 +- if @buffer.include? "\r\n" +- if md = PROXY_PROTOCOL_V1_REGEX.match(@buffer) +- if md[1] +- @peerip = md[1].split(" ")[0] ++ crlf_index = @buffer.index "\r\n" ++ ++ unless crlf_index ++ if "PROXY ".start_with? @buffer ++ return false ++ elsif @buffer.start_with? "PROXY " ++ if @buffer.bytesize >= PROXY_PROTOCOL_V1_MAX_LENGTH ++ raise ConnectionError, "PROXY protocol v1 line is too long" + end +- @buffer = md.post_match ++ return false + end +- # if the buffer has a \r\n but doesn't have a PROXY protocol +- # request, this is just HTTP from a non-PROXY client; move on ++ + @read_proxy = false +- return @buffer.size > 0 +- else +- return false ++ return true ++ end ++ ++ if @buffer.start_with?("PROXY ") && crlf_index + 2 > PROXY_PROTOCOL_V1_MAX_LENGTH ++ raise ConnectionError, "PROXY protocol v1 line is too long" ++ end ++ ++ if md = PROXY_PROTOCOL_V1_REGEX.match(@buffer) ++ if md[1] ++ @peerip = md[1].split(" ")[0] ++ end ++ @buffer = md.post_match + end ++ # if the buffer has a \r\n but doesn't have a PROXY protocol ++ # request, this is just HTTP from a non-PROXY client; move on ++ @read_proxy = false ++ return @buffer.size > 0 + end + end + true +--- a/lib/puma/const.rb ++++ b/lib/puma/const.rb +@@ -292,7 +292,8 @@ module Puma + # Banned keys of response header + BANNED_HEADER_KEY = /\A(rack\.|status\z)/.freeze + +- PROXY_PROTOCOL_V1_REGEX = /^PROXY (?:TCP4|TCP6|UNKNOWN) ([^\r]+)\r\n/.freeze ++ PROXY_PROTOCOL_V1_REGEX = /\APROXY (?:TCP4|TCP6|UNKNOWN) ([^\r]+)\r\n/.freeze ++ PROXY_PROTOCOL_V1_MAX_LENGTH = 107 + + # All constants are prefixed with `PIPE_` to avoid name collisions. + module PipeRequest +--- a/test/test_puma_server.rb ++++ b/test/test_puma_server.rb +@@ -1478,6 +1478,77 @@ class TestPumaServer < TimeoutTestCase + assert_equal 'fd00::1', remote_addr + end + ++ def test_proxy_protocol_rejects_line_without_crlf_at_max_length ++ server_run(remote_address: :proxy_protocol, remote_address_proxy_protocol: :v1) do ++ [200, {}, ["Hello"]] ++ end ++ ++ socket = new_socket ++ socket << "PROXY #{'A' * (Puma::Const::PROXY_PROTOCOL_V1_MAX_LENGTH - "PROXY ".bytesize)}" ++ ++ assert_raises EOFError, Errno::ECONNABORTED, Errno::ECONNRESET do ++ socket.read_response(timeout: 1) ++ end ++ end ++ ++ def test_proxy_protocol_allows_non_proxy_requests_over_max_length ++ server_run(remote_address: :proxy_protocol, remote_address_proxy_protocol: :v1) do ++ [200, {}, ["Hello"]] ++ end ++ ++ response = send_http_read_response "GET /#{'a' * Puma::Const::PROXY_PROTOCOL_V1_MAX_LENGTH} HTTP/1.0\r\n\r\n" ++ ++ assert_equal "HTTP/1.0 200 OK", response.status ++ assert_equal "Hello", response.body ++ end ++ ++ def test_proxy_protocol_ignores_embedded_proxy_line_in_http_body ++ server_run(remote_address: :proxy_protocol, remote_address_proxy_protocol: :v1) do |env| ++ body = env['rack.input'].read ++ [200, {'Content-Length' => body.bytesize.to_s}, [body]] ++ end ++ ++ body = "prefix\nPROXY TCP4 1.1.1.1 2.2.2.2 3 4\r\nsuffix" ++ response = send_http_read_response "POST / HTTP/1.1\r\nHost: test.com\r\nContent-Length: #{body.bytesize}\r\n\r\n#{body}" ++ ++ assert_equal "HTTP/1.1 200 OK", response.status ++ assert_equal body, response.body ++ end ++ ++ def test_proxy_protocol_only_parses_first_request_on_connection ++ server_run(remote_address: :proxy_protocol, remote_address_proxy_protocol: :v1) do |env| ++ [200, {}, [env["REMOTE_ADDR"]]] ++ end ++ ++ socket = send_proxy_v1_http("GET /one HTTP/1.1\r\nHost: test.com\r\n\r\n", "1.2.3.4") ++ ++ response = socket.read_response ++ assert_equal "HTTP/1.1 200 OK", response.status ++ assert_equal "1.2.3.4", response.body ++ ++ socket << "PROXY TCP4 127.0.0.1 127.0.0.1 10000 80\r\nGET /two HTTP/1.1\r\nHost: test.com\r\nConnection: close\r\n\r\n" ++ ++ assert_match %r{\AHTTP/1\.[01] 400 Bad Request\z}, socket.read_response.status ++ end ++ ++ def test_proxy_protocol_allows_proxy_http_method_on_keep_alive ++ server_run(remote_address: :proxy_protocol, remote_address_proxy_protocol: :v1, supported_http_methods: :any) do |env| ++ [200, {}, [env["REMOTE_ADDR"]]] ++ end ++ ++ socket = send_proxy_v1_http("GET /one HTTP/1.1\r\nHost: test.com\r\n\r\n", "1.2.3.4") ++ ++ response = socket.read_response ++ assert_equal "HTTP/1.1 200 OK", response.status ++ assert_equal "1.2.3.4", response.body ++ ++ socket << "PROXY /two HTTP/1.1\r\nHost: test.com\r\nConnection: close\r\n\r\n" ++ ++ response = socket.read_response ++ assert_equal "HTTP/1.1 200 OK", response.status ++ assert_equal "1.2.3.4", response.body ++ end ++ + # To comply with the Rack spec, we have to split header field values + # containing newlines into multiple headers. + def assert_does_not_allow_http_injection(app, opts = {}) diff -Nru puma-6.6.0/debian/patches/series puma-6.6.0/debian/patches/series --- puma-6.6.0/debian/patches/series 2025-07-17 15:50:19.000000000 +0530 +++ puma-6.6.0/debian/patches/series 2026-09-10 10:35:33.000000000 +0530 @@ -10,3 +10,4 @@ 0010-Disable-test-concurrency-without-concurrent_ruby.patch 0019-adapt-test-certs-generation.patch 0020-update-ex-certs.patch +CVE-2026-47736_47737.patch

