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

Reply via email to