Version in base suite: 6.0.3.7+dfsg-2 Base version: rails_6.0.3.7+dfsg-2 Target version: rails_6.0.3.7+dfsg-2+deb11u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/r/rails/rails_6.0.3.7+dfsg-2.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/r/rails/rails_6.0.3.7+dfsg-2+deb11u1.dsc changelog | 27 + patches/CVE-2021-22942-1.patch | 54 +++ patches/CVE-2021-22942-2.patch | 147 ++++++++++ patches/CVE-2021-44528.patch | 167 +++++++++++ patches/CVE-2022-21831.patch | 579 +++++++++++++++++++++++++++++++++++++++++ patches/CVE-2022-22577.patch | 86 ++++++ patches/CVE-2022-23633-1.patch | 115 ++++++++ patches/CVE-2022-23633-2.patch | 26 + patches/CVE-2022-27777-1.patch | 326 +++++++++++++++++++++++ patches/CVE-2022-27777-2.patch | 41 ++ patches/CVE-2023-22792.patch | 136 +++++++++ patches/CVE-2023-22794-1.patch | 248 +++++++++++++++++ patches/CVE-2023-22794-2.patch | 122 ++++++++ patches/CVE-2023-22795.patch | 26 + patches/CVE-2023-22796.patch | 27 + patches/series | 14 16 files changed, 2141 insertions(+) diff -Nru rails-6.0.3.7+dfsg/debian/changelog rails-6.0.3.7+dfsg/debian/changelog --- rails-6.0.3.7+dfsg/debian/changelog 2021-07-08 19:03:18.000000000 +0000 +++ rails-6.0.3.7+dfsg/debian/changelog 2023-03-11 06:53:57.000000000 +0000 @@ -1,3 +1,30 @@ +rails (2:6.0.3.7+dfsg-2+deb11u1) bullseye-security; urgency=high + + * Non-maintainer upload by the Security Team. + * CVE-2021-22942: possible open redirect vulnerability in the Host + Authorization middleware. + * CVE-2021-44528: specially crafted "X-Forwarded-Host" headers in + combination with certain "allowed host" formats can lead to + redirection of users to a malicious website. + * CVE-2022-21831: code injection in Active Storage. + * CVE-2022-22577: XSS in Action Pack which can lead to bypass CSP + for non HTML like responses. + * CVE-2022-23633: thread local state for the next request may not be + reset when the response body has been fully closed. + * CVE-2022-27777: XSS in Action View which can lead to content + injection. + * CVE-2023-22792: regular expression based DoS with specially crafted + cookies and X_FORWARDED_HOST headers. + * CVE-2023-22794: malicious user input may be sent to the database + with insufficient sanitization and be able to inject SQL outside of + the comment. + * CVE-2023-22795: regular expression based DoS related to crafted + If-None-Match header. + * CVE-2023-22796: regular expression based DoS related to the + underscore method. + + -- Aron Xu Sat, 11 Mar 2023 14:53:57 +0800 + rails (2:6.0.3.7+dfsg-2) unstable; urgency=medium * Partially revert "Update minimum version of ruby-marcel to 1.0~". diff -Nru rails-6.0.3.7+dfsg/debian/patches/CVE-2021-22942-1.patch rails-6.0.3.7+dfsg/debian/patches/CVE-2021-22942-1.patch --- rails-6.0.3.7+dfsg/debian/patches/CVE-2021-22942-1.patch 1970-01-01 00:00:00.000000000 +0000 +++ rails-6.0.3.7+dfsg/debian/patches/CVE-2021-22942-1.patch 2023-02-26 06:53:40.000000000 +0000 @@ -0,0 +1,54 @@ +From a6a121163caf886796675bf9471cd25bfe49b978 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= + +Date: Wed, 10 Feb 2021 22:59:17 +0000 +Subject: [PATCH] Remove unnessary escape char in Regexp + +Fix the test by defining a valid host on the mocked requests. +--- + .../lib/action_dispatch/middleware/host_authorization.rb | 2 +- + railties/test/application/middleware/remote_ip_test.rb | 3 ++- + railties/test/isolation/abstract_unit.rb | 2 +- + 3 files changed, 4 insertions(+), 3 deletions(-) + +Index: rails-6.0.3.7+dfsg/actionpack/lib/action_dispatch/middleware/host_authorization.rb +=================================================================== +--- rails-6.0.3.7+dfsg.orig/actionpack/lib/action_dispatch/middleware/host_authorization.rb ++++ rails-6.0.3.7+dfsg/actionpack/lib/action_dispatch/middleware/host_authorization.rb +@@ -89,7 +89,7 @@ module ActionDispatch + def authorized?(request) + valid_host = / + \A +- (?[a-z0-9.-]+|\[[a-f0-9]*:[a-f0-9\.:]+\]) ++ (?[a-z0-9.-]+|\[[a-f0-9]*:[a-f0-9.:]+\]) + (:\d+)? + \z + /x +Index: rails-6.0.3.7+dfsg/railties/test/application/middleware/remote_ip_test.rb +=================================================================== +--- rails-6.0.3.7+dfsg.orig/railties/test/application/middleware/remote_ip_test.rb ++++ rails-6.0.3.7+dfsg/railties/test/application/middleware/remote_ip_test.rb +@@ -11,8 +11,9 @@ module ApplicationTests + def remote_ip(env = {}) + remote_ip = nil + env = Rack::MockRequest.env_for("/").merge(env).merge!( ++ "HTTP_HOST" => "example.com", + "action_dispatch.show_exceptions" => false, +- "action_dispatch.key_generator" => ActiveSupport::CachingKeyGenerator.new( ++ "action_dispatch.key_generator" => ActiveSupport::CachingKeyGenerator.new( + ActiveSupport::KeyGenerator.new("b3c631c314c0bbca50c1b2843150fe33", iterations: 1000) + ) + ) +Index: rails-6.0.3.7+dfsg/railties/test/isolation/abstract_unit.rb +=================================================================== +--- rails-6.0.3.7+dfsg.orig/railties/test/isolation/abstract_unit.rb ++++ rails-6.0.3.7+dfsg/railties/test/isolation/abstract_unit.rb +@@ -77,7 +77,7 @@ module TestHelpers + end + + def get(path) +- @app.call(::Rack::MockRequest.env_for(path)) ++ @app.call(::Rack::MockRequest.env_for(path, "HTTP_HOST" => "example.com")) + end + + def assert_welcome(resp) diff -Nru rails-6.0.3.7+dfsg/debian/patches/CVE-2021-22942-2.patch rails-6.0.3.7+dfsg/debian/patches/CVE-2021-22942-2.patch --- rails-6.0.3.7+dfsg/debian/patches/CVE-2021-22942-2.patch 1970-01-01 00:00:00.000000000 +0000 +++ rails-6.0.3.7+dfsg/debian/patches/CVE-2021-22942-2.patch 2023-02-26 06:53:40.000000000 +0000 @@ -0,0 +1,147 @@ +From 9fe57c0fc5561088a2df42e4438992591e9d917e Mon Sep 17 00:00:00 2001 +From: Jonathan Hefner +Date: Fri, 12 Feb 2021 12:59:54 -0600 +Subject: [PATCH] Refactor CVE-2021-22881 fix + +Follow-up to 83a6ac3fee8fd538ce7e0088913ff54f0f9bcb6f. + +This allows `HTTP_HOST` to be omitted as before, and reduces the number +of object allocations per request. + +Benchmark: + +```ruby + # frozen_string_literal: true +require "benchmark/memory" + +HOST = "example.com:80" +BEFORE_REGEXP = /\A(?[a-z0-9.-]+|\[[a-f0-9]*:[a-f0-9.:]+\])(:\d+)?\z/ +AFTER_REGEXP = /(?:\A|,[ ]?)([a-z0-9.-]+|\[[a-f0-9]*:[a-f0-9.:]+\])(?::\d+)?\z/i + +Benchmark.memory do |x| + x.report("BEFORE (non-nil X-Forwarded-Host)") do + origin_host = BEFORE_REGEXP.match(HOST.to_s.downcase)[:host] + forwarded_host = BEFORE_REGEXP.match(HOST.to_s.split(/,\s?/).last)[:host] + end + + x.report("BEFORE (nil X-Forwarded-Host)") do + origin_host = BEFORE_REGEXP.match(HOST.to_s.downcase)[:host] + forwarded_host = BEFORE_REGEXP.match(nil.to_s.split(/,\s?/).last) + end + + x.report("AFTER (non-nil X-Forwarded-Host)") do + origin_host = HOST&.slice(AFTER_REGEXP, 1) || "" + forwarded_host = HOST&.slice(AFTER_REGEXP, 1) || "" + end + + x.report("AFTER (nil X-Forwarded-Host)") do + origin_host = HOST&.slice(AFTER_REGEXP, 1) || "" + forwarded_host = nil&.slice(AFTER_REGEXP, 1) || "" + end +end +``` + +Results: + +``` +BEFORE (non-nil X-Forwarded-Host) + 616.000 memsize ( 208.000 retained) + 9.000 objects ( 2.000 retained) + 2.000 strings ( 1.000 retained) +BEFORE (nil X-Forwarded-Host) + 328.000 memsize ( 0.000 retained) + 5.000 objects ( 0.000 retained) + 2.000 strings ( 0.000 retained) +AFTER (non-nil X-Forwarded-Host) + 248.000 memsize ( 168.000 retained) + 3.000 objects ( 1.000 retained) + 1.000 strings ( 0.000 retained) +AFTER (nil X-Forwarded-Host) + 40.000 memsize ( 0.000 retained) + 1.000 objects ( 0.000 retained) + 1.000 strings ( 0.000 retained) +``` + +[CVE-2021-22942] +--- + .../middleware/host_authorization.rb | 22 +++++++------------ + .../test/dispatch/host_authorization_test.rb | 4 ++-- + .../application/middleware/remote_ip_test.rb | 1 - + railties/test/isolation/abstract_unit.rb | 2 +- + 4 files changed, 11 insertions(+), 18 deletions(-) + +Index: rails-6.0.3.7+dfsg/actionpack/lib/action_dispatch/middleware/host_authorization.rb +=================================================================== +--- rails-6.0.3.7+dfsg.orig/actionpack/lib/action_dispatch/middleware/host_authorization.rb ++++ rails-6.0.3.7+dfsg/actionpack/lib/action_dispatch/middleware/host_authorization.rb +@@ -86,21 +86,15 @@ module ActionDispatch + end + + private +- def authorized?(request) +- valid_host = / +- \A +- (?[a-z0-9.-]+|\[[a-f0-9]*:[a-f0-9.:]+\]) +- (:\d+)? +- \z +- /x ++ HOSTNAME = /[a-z0-9.-]+|\[[a-f0-9]*:[a-f0-9.:]+\]/i ++ VALID_ORIGIN_HOST = /\A(#{HOSTNAME})(?::\d+)?\z/ ++ VALID_FORWARDED_HOST = /(?:\A|,[ ]?)(#{HOSTNAME})(?::\d+)?\z/ + +- origin_host = valid_host.match( +- request.get_header("HTTP_HOST").to_s.downcase) +- forwarded_host = valid_host.match( +- request.x_forwarded_host.to_s.split(/,\s?/).last) ++ def authorized?(request) ++ origin_host = request.get_header("HTTP_HOST")&.slice(VALID_ORIGIN_HOST, 1) || "" ++ forwarded_host = request.x_forwarded_host&.slice(VALID_FORWARDED_HOST, 1) || "" + +- origin_host && @permissions.allows?(origin_host[:host]) && ( +- forwarded_host.nil? || @permissions.allows?(forwarded_host[:host])) ++ @permissions.allows?(origin_host) && (forwarded_host.blank? || @permissions.allows?(forwarded_host)) + end + + def mark_as_authorized(request) +Index: rails-6.0.3.7+dfsg/actionpack/test/dispatch/host_authorization_test.rb +=================================================================== +--- rails-6.0.3.7+dfsg.orig/actionpack/test/dispatch/host_authorization_test.rb ++++ rails-6.0.3.7+dfsg/actionpack/test/dispatch/host_authorization_test.rb +@@ -163,10 +163,10 @@ class HostAuthorizationTest < ActionDisp + @app = ActionDispatch::HostAuthorization.new(App, ".example.com") + + get "/", env: { +- "HOST" => "example.com#sub.example.com", ++ "HOST" => "attacker.com#x.example.com", + } + + assert_response :forbidden +- assert_match "Blocked host: example.com#sub.example.com", response.body ++ assert_match "Blocked host: attacker.com#x.example.com", response.body + end + end +Index: rails-6.0.3.7+dfsg/railties/test/application/middleware/remote_ip_test.rb +=================================================================== +--- rails-6.0.3.7+dfsg.orig/railties/test/application/middleware/remote_ip_test.rb ++++ rails-6.0.3.7+dfsg/railties/test/application/middleware/remote_ip_test.rb +@@ -11,7 +11,6 @@ module ApplicationTests + def remote_ip(env = {}) + remote_ip = nil + env = Rack::MockRequest.env_for("/").merge(env).merge!( +- "HTTP_HOST" => "example.com", + "action_dispatch.show_exceptions" => false, + "action_dispatch.key_generator" => ActiveSupport::CachingKeyGenerator.new( + ActiveSupport::KeyGenerator.new("b3c631c314c0bbca50c1b2843150fe33", iterations: 1000) +Index: rails-6.0.3.7+dfsg/railties/test/isolation/abstract_unit.rb +=================================================================== +--- rails-6.0.3.7+dfsg.orig/railties/test/isolation/abstract_unit.rb ++++ rails-6.0.3.7+dfsg/railties/test/isolation/abstract_unit.rb +@@ -77,7 +77,7 @@ module TestHelpers + end + + def get(path) +- @app.call(::Rack::MockRequest.env_for(path, "HTTP_HOST" => "example.com")) ++ @app.call(::Rack::MockRequest.env_for(path)) + end + + def assert_welcome(resp) diff -Nru rails-6.0.3.7+dfsg/debian/patches/CVE-2021-44528.patch rails-6.0.3.7+dfsg/debian/patches/CVE-2021-44528.patch --- rails-6.0.3.7+dfsg/debian/patches/CVE-2021-44528.patch 1970-01-01 00:00:00.000000000 +0000 +++ rails-6.0.3.7+dfsg/debian/patches/CVE-2021-44528.patch 2023-02-26 06:53:40.000000000 +0000 @@ -0,0 +1,167 @@ +From fd6a64fef1d0f7f40a8d4b046da882e83163299c Mon Sep 17 00:00:00 2001 +From: Stef Schenkelaars +Date: Wed, 7 Jul 2021 12:06:32 +0200 +Subject: [PATCH] Fix invalid forwarded host vulnerability + +Prior to this commit, it was possible to pass an unvalidated host +through the `X-Forwarded-Host` header. If the value of the header +was prefixed with a invalid domain character (for example a `/`), +it was always accepted as the actual host of that request. + +Since this host is used for all url helpers, an attacker could change +generated links and redirects. If the header is set to +`X-Forwarded-Host: //evil.hacker`, a redirect will be send to +`https:////evil.hacker/`. Browsers will ignore these four slashes +and redirect the user. + +[CVE-2021-44528] +--- + .../middleware/host_authorization.rb | 10 +-- + .../test/dispatch/host_authorization_test.rb | 89 ++++++++++++++++++- + 2 files changed, 91 insertions(+), 8 deletions(-) + +Index: rails-6.0.3.7+dfsg/actionpack/lib/action_dispatch/middleware/host_authorization.rb +=================================================================== +--- rails-6.0.3.7+dfsg.orig/actionpack/lib/action_dispatch/middleware/host_authorization.rb ++++ rails-6.0.3.7+dfsg/actionpack/lib/action_dispatch/middleware/host_authorization.rb +@@ -46,7 +46,7 @@ module ActionDispatch + + def sanitize_string(host) + if host.start_with?(".") +- /\A(.+\.)?#{Regexp.escape(host[1..-1])}\z/ ++ /\A([a-z0-9-]+\.)?#{Regexp.escape(host[1..-1])}\z/i + else + host + end +@@ -86,13 +86,9 @@ module ActionDispatch + end + + private +- HOSTNAME = /[a-z0-9.-]+|\[[a-f0-9]*:[a-f0-9.:]+\]/i +- VALID_ORIGIN_HOST = /\A(#{HOSTNAME})(?::\d+)?\z/ +- VALID_FORWARDED_HOST = /(?:\A|,[ ]?)(#{HOSTNAME})(?::\d+)?\z/ +- + def authorized?(request) +- origin_host = request.get_header("HTTP_HOST")&.slice(VALID_ORIGIN_HOST, 1) || "" +- forwarded_host = request.x_forwarded_host&.slice(VALID_FORWARDED_HOST, 1) || "" ++ origin_host = request.get_header("HTTP_HOST") ++ forwarded_host = request.x_forwarded_host&.split(/,\s?/)&.last + + @permissions.allows?(origin_host) && (forwarded_host.blank? || @permissions.allows?(forwarded_host)) + end +Index: rails-6.0.3.7+dfsg/actionpack/test/dispatch/host_authorization_test.rb +=================================================================== +--- rails-6.0.3.7+dfsg.orig/actionpack/test/dispatch/host_authorization_test.rb ++++ rails-6.0.3.7+dfsg/actionpack/test/dispatch/host_authorization_test.rb +@@ -111,6 +111,44 @@ class HostAuthorizationTest < ActionDisp + assert_match "Blocked host: 127.0.0.1", response.body + end + ++ test "blocks requests with spoofed relative X-FORWARDED-HOST" do ++ @app = ActionDispatch::HostAuthorization.new(App, ["www.example.com"]) ++ ++ get "/", env: { ++ "HTTP_X_FORWARDED_HOST" => "//randomhost.com", ++ "HOST" => "www.example.com", ++ "action_dispatch.show_detailed_exceptions" => true ++ } ++ ++ assert_response :forbidden ++ assert_match "Blocked host: //randomhost.com", response.body ++ end ++ ++ test "forwarded secondary hosts are allowed when permitted" do ++ @app = ActionDispatch::HostAuthorization.new(App, ".domain.com") ++ ++ get "/", env: { ++ "HTTP_X_FORWARDED_HOST" => "example.com, my-sub.domain.com", ++ "HOST" => "domain.com", ++ } ++ ++ assert_response :ok ++ assert_equal "Success", body ++ end ++ ++ test "forwarded secondary hosts are blocked when mismatch" do ++ @app = ActionDispatch::HostAuthorization.new(App, "domain.com") ++ ++ get "/", env: { ++ "HTTP_X_FORWARDED_HOST" => "domain.com, evil.com", ++ "HOST" => "domain.com", ++ "action_dispatch.show_detailed_exceptions" => true ++ } ++ ++ assert_response :forbidden ++ assert_match "Blocked host: evil.com", response.body ++ end ++ + test "does not consider IP addresses in X-FORWARDED-HOST spoofed when disabled" do + @app = ActionDispatch::HostAuthorization.new(App, nil) + +@@ -147,11 +185,23 @@ class HostAuthorizationTest < ActionDisp + assert_match "Blocked host: sub.domain.com", response.body + end + ++ test "sub-sub domains should not be permitted" do ++ @app = ActionDispatch::HostAuthorization.new(App, ".domain.com") ++ ++ get "/", env: { ++ "HOST" => "secondary.sub.domain.com", ++ "action_dispatch.show_detailed_exceptions" => true ++ } ++ ++ assert_response :forbidden ++ assert_match "Blocked host: secondary.sub.domain.com", response.body ++ end ++ + test "forwarded hosts are allowed when permitted" do + @app = ActionDispatch::HostAuthorization.new(App, ".domain.com") + + get "/", env: { +- "HTTP_X_FORWARDED_HOST" => "sub.domain.com", ++ "HTTP_X_FORWARDED_HOST" => "my-sub.domain.com", + "HOST" => "domain.com", + } + +@@ -169,4 +219,41 @@ class HostAuthorizationTest < ActionDisp + assert_response :forbidden + assert_match "Blocked host: attacker.com#x.example.com", response.body + end ++ ++ test "lots of NG hosts" do ++ ng_hosts = [ ++ "hacker%E3%80%82com", ++ "hacker%00.com", ++ "www.theirsite.com@yoursite.com", ++ "hacker.com/test/", ++ "hacker%252ecom", ++ ".hacker.com", ++ "/\/\/hacker.com/", ++ "/hacker.com", ++ "../hacker.com", ++ ".hacker.com", ++ "@hacker.com", ++ "hacker.com", ++ "hacker.com%23@example.com", ++ "hacker.com/.jpg", ++ "hacker.com\texample.com/", ++ "hacker.com/example.com", ++ "hacker.com\@example.com", ++ "hacker.com/example.com", ++ "hacker.com/" ++ ] ++ ++ @app = ActionDispatch::HostAuthorization.new(App, "example.com") ++ ++ ng_hosts.each do |host| ++ get "/", env: { ++ "HTTP_X_FORWARDED_HOST" => host, ++ "HOST" => "example.com", ++ "action_dispatch.show_detailed_exceptions" => true ++ } ++ ++ assert_response :forbidden ++ assert_match "Blocked host: #{host}", response.body ++ end ++ end + end diff -Nru rails-6.0.3.7+dfsg/debian/patches/CVE-2022-21831.patch rails-6.0.3.7+dfsg/debian/patches/CVE-2022-21831.patch --- rails-6.0.3.7+dfsg/debian/patches/CVE-2022-21831.patch 1970-01-01 00:00:00.000000000 +0000 +++ rails-6.0.3.7+dfsg/debian/patches/CVE-2022-21831.patch 2023-02-26 06:53:40.000000000 +0000 @@ -0,0 +1,579 @@ +From 92f64fec3136baabbebac97073c5213ea055dc53 Mon Sep 17 00:00:00 2001 +From: Zack +Date: Tue, 15 Feb 2022 10:00:57 -0500 +Subject: [PATCH] Added image transformation validation via configurable + allow-list. + +ImageProcessingTransformer now offers a configurable allow-list for +transformation methods in addition to a configurable deny-list for arguments. + +[CVE-2022-21831] +--- + activestorage/lib/active_storage.rb | 10 +- + activestorage/lib/active_storage/engine.rb | 16 + + .../image_processing_transformer.rb | 358 ++++++++++++++++++ + activestorage/test/models/variant_test.rb | 74 ++++ + .../test/application/configuration_test.rb | 29 ++ + 5 files changed, 483 insertions(+), 4 deletions(-) + +Index: rails-6.0.3.7+dfsg/activestorage/lib/active_storage.rb +=================================================================== +--- rails-6.0.3.7+dfsg.orig/activestorage/lib/active_storage.rb ++++ rails-6.0.3.7+dfsg/activestorage/lib/active_storage.rb +@@ -52,10 +52,12 @@ module ActiveStorage + + mattr_accessor :paths, default: {} + +- mattr_accessor :variable_content_types, default: [] +- mattr_accessor :binary_content_type, default: "application/octet-stream" +- mattr_accessor :content_types_to_serve_as_binary, default: [] +- mattr_accessor :content_types_allowed_inline, default: [] ++ mattr_accessor :variable_content_types, default: [] ++ mattr_accessor :binary_content_type, default: "application/octet-stream" ++ mattr_accessor :content_types_to_serve_as_binary, default: [] ++ mattr_accessor :content_types_allowed_inline, default: [] ++ mattr_accessor :supported_image_processing_methods, default: [] ++ mattr_accessor :unsupported_image_processing_arguments + + mattr_accessor :service_urls_expire_in, default: 5.minutes + +Index: rails-6.0.3.7+dfsg/activestorage/lib/active_storage/engine.rb +=================================================================== +--- rails-6.0.3.7+dfsg.orig/activestorage/lib/active_storage/engine.rb ++++ rails-6.0.3.7+dfsg/activestorage/lib/active_storage/engine.rb +@@ -63,6 +63,20 @@ module ActiveStorage + application/pdf + ) + ++ default_unsupported_image_processing_arguments = %w( ++ -debug ++ -display ++ -distribute-cache ++ -help ++ -path ++ -print ++ -set ++ -verbose ++ -version ++ -write ++ -write-mask ++ ) ++ + config.eager_load_namespaces << ActiveStorage + + initializer "active_storage.configs" do +@@ -74,6 +88,8 @@ module ActiveStorage + ActiveStorage.paths = app.config.active_storage.paths || {} + ActiveStorage.routes_prefix = app.config.active_storage.routes_prefix || "/rails/active_storage" + ++ ActiveStorage.supported_image_processing_methods = app.config.active_storage.supported_image_processing_methods || [] ++ ActiveStorage.unsupported_image_processing_arguments = app.config.active_storage.unsupported_image_processing_arguments || default_unsupported_image_processing_arguments + ActiveStorage.variable_content_types = app.config.active_storage.variable_content_types || [] + ActiveStorage.content_types_to_serve_as_binary = app.config.active_storage.content_types_to_serve_as_binary || [] + ActiveStorage.service_urls_expire_in = app.config.active_storage.service_urls_expire_in || 5.minutes +Index: rails-6.0.3.7+dfsg/activestorage/lib/active_storage/transformers/image_processing_transformer.rb +=================================================================== +--- rails-6.0.3.7+dfsg.orig/activestorage/lib/active_storage/transformers/image_processing_transformer.rb ++++ rails-6.0.3.7+dfsg/activestorage/lib/active_storage/transformers/image_processing_transformer.rb +@@ -6,6 +6,300 @@ module ActiveStorage + module Transformers + class ImageProcessingTransformer < Transformer + private ++ class UnsupportedImageProcessingMethod < StandardError; end ++ class UnsupportedImageProcessingArgument < StandardError; end ++ SUPPORTED_IMAGE_PROCESSING_METHODS = [ ++ "adaptive_blur", ++ "adaptive_resize", ++ "adaptive_sharpen", ++ "adjoin", ++ "affine", ++ "alpha", ++ "annotate", ++ "antialias", ++ "append", ++ "apply", ++ "attenuate", ++ "authenticate", ++ "auto_gamma", ++ "auto_level", ++ "auto_orient", ++ "auto_threshold", ++ "backdrop", ++ "background", ++ "bench", ++ "bias", ++ "bilateral_blur", ++ "black_point_compensation", ++ "black_threshold", ++ "blend", ++ "blue_primary", ++ "blue_shift", ++ "blur", ++ "border", ++ "bordercolor", ++ "borderwidth", ++ "brightness_contrast", ++ "cache", ++ "canny", ++ "caption", ++ "channel", ++ "channel_fx", ++ "charcoal", ++ "chop", ++ "clahe", ++ "clamp", ++ "clip", ++ "clip_path", ++ "clone", ++ "clut", ++ "coalesce", ++ "colorize", ++ "colormap", ++ "color_matrix", ++ "colors", ++ "colorspace", ++ "colourspace", ++ "color_threshold", ++ "combine", ++ "combine_options", ++ "comment", ++ "compare", ++ "complex", ++ "compose", ++ "composite", ++ "compress", ++ "connected_components", ++ "contrast", ++ "contrast_stretch", ++ "convert", ++ "convolve", ++ "copy", ++ "crop", ++ "cycle", ++ "deconstruct", ++ "define", ++ "delay", ++ "delete", ++ "density", ++ "depth", ++ "descend", ++ "deskew", ++ "despeckle", ++ "direction", ++ "displace", ++ "dispose", ++ "dissimilarity_threshold", ++ "dissolve", ++ "distort", ++ "dither", ++ "draw", ++ "duplicate", ++ "edge", ++ "emboss", ++ "encoding", ++ "endian", ++ "enhance", ++ "equalize", ++ "evaluate", ++ "evaluate_sequence", ++ "extent", ++ "extract", ++ "family", ++ "features", ++ "fft", ++ "fill", ++ "filter", ++ "flatten", ++ "flip", ++ "floodfill", ++ "flop", ++ "font", ++ "foreground", ++ "format", ++ "frame", ++ "function", ++ "fuzz", ++ "fx", ++ "gamma", ++ "gaussian_blur", ++ "geometry", ++ "gravity", ++ "grayscale", ++ "green_primary", ++ "hald_clut", ++ "highlight_color", ++ "hough_lines", ++ "iconGeometry", ++ "iconic", ++ "identify", ++ "ift", ++ "illuminant", ++ "immutable", ++ "implode", ++ "insert", ++ "intensity", ++ "intent", ++ "interlace", ++ "interline_spacing", ++ "interpolate", ++ "interpolative_resize", ++ "interword_spacing", ++ "kerning", ++ "kmeans", ++ "kuwahara", ++ "label", ++ "lat", ++ "layers", ++ "level", ++ "level_colors", ++ "limit", ++ "limits", ++ "linear_stretch", ++ "linewidth", ++ "liquid_rescale", ++ "list", ++ "loader", ++ "log", ++ "loop", ++ "lowlight_color", ++ "magnify", ++ "map", ++ "mattecolor", ++ "median", ++ "mean_shift", ++ "metric", ++ "mode", ++ "modulate", ++ "moments", ++ "monitor", ++ "monochrome", ++ "morph", ++ "morphology", ++ "mosaic", ++ "motion_blur", ++ "name", ++ "negate", ++ "noise", ++ "normalize", ++ "opaque", ++ "ordered_dither", ++ "orient", ++ "page", ++ "paint", ++ "pause", ++ "perceptible", ++ "ping", ++ "pointsize", ++ "polaroid", ++ "poly", ++ "posterize", ++ "precision", ++ "preview", ++ "process", ++ "quality", ++ "quantize", ++ "quiet", ++ "radial_blur", ++ "raise", ++ "random_threshold", ++ "range_threshold", ++ "red_primary", ++ "regard_warnings", ++ "region", ++ "remote", ++ "render", ++ "repage", ++ "resample", ++ "resize", ++ "resize_to_fill", ++ "resize_to_fit", ++ "resize_to_limit", ++ "resize_and_pad", ++ "respect_parentheses", ++ "reverse", ++ "roll", ++ "rotate", ++ "sample", ++ "sampling_factor", ++ "saver", ++ "scale", ++ "scene", ++ "screen", ++ "seed", ++ "segment", ++ "selective_blur", ++ "separate", ++ "sepia_tone", ++ "shade", ++ "shadow", ++ "shared_memory", ++ "sharpen", ++ "shave", ++ "shear", ++ "sigmoidal_contrast", ++ "silent", ++ "similarity_threshold", ++ "size", ++ "sketch", ++ "smush", ++ "snaps", ++ "solarize", ++ "sort_pixels", ++ "sparse_color", ++ "splice", ++ "spread", ++ "statistic", ++ "stegano", ++ "stereo", ++ "storage_type", ++ "stretch", ++ "strip", ++ "stroke", ++ "strokewidth", ++ "style", ++ "subimage_search", ++ "swap", ++ "swirl", ++ "synchronize", ++ "taint", ++ "text_font", ++ "threshold", ++ "thumbnail", ++ "tile_offset", ++ "tint", ++ "title", ++ "transform", ++ "transparent", ++ "transparent_color", ++ "transpose", ++ "transverse", ++ "treedepth", ++ "trim", ++ "type", ++ "undercolor", ++ "unique_colors", ++ "units", ++ "unsharp", ++ "update", ++ "valid_image", ++ "view", ++ "vignette", ++ "virtual_pixel", ++ "visual", ++ "watermark", ++ "wave", ++ "wavelet_denoise", ++ "weight", ++ "white_balance", ++ "white_point", ++ "white_threshold", ++ "window", ++ "window_group" ++ ].concat(ActiveStorage.supported_image_processing_methods) ++ ++ UNSUPPORTED_IMAGE_PROCESSING_ARGUMENTS = ActiveStorage.unsupported_image_processing_arguments ++ + def process(file, format:) + processor. + source(file). +@@ -21,6 +315,16 @@ module ActiveStorage + + def operations + transformations.each_with_object([]) do |(name, argument), list| ++ if ActiveStorage.variant_processor == :mini_magick ++ if name.to_s == "combine_options" ++ argument.each do |subtransformation_name, subtransformation_argument| ++ validate_transformation(subtransformation_name, subtransformation_argument) ++ end ++ else ++ validate_transformation(name, argument) ++ end ++ end ++ + if name.to_s == "combine_options" + ActiveSupport::Deprecation.warn <<~WARNING.squish + Active Storage's ImageProcessing transformer doesn't support :combine_options, +@@ -34,6 +338,60 @@ module ActiveStorage + end + end + end ++ ++ def validate_transformation(name, argument) ++ method_name = name.to_s.gsub("-","_") ++ ++ unless SUPPORTED_IMAGE_PROCESSING_METHODS.any? { |method| method_name == method } ++ raise UnsupportedImageProcessingMethod, <<~ERROR.squish ++ One or more of the provided transformation methods is not supported. ++ ERROR ++ end ++ ++ if argument.present? ++ if argument.is_a?(String) || argument.is_a?(Symbol) ++ validate_arg_string(argument) ++ elsif argument.is_a?(Array) ++ validate_arg_array(argument) ++ elsif argument.is_a?(Hash) ++ validate_arg_hash(argument) ++ end ++ end ++ end ++ ++ def validate_arg_string(argument) ++ if UNSUPPORTED_IMAGE_PROCESSING_ARGUMENTS.any? { |bad_arg| argument.to_s.downcase.include?(bad_arg) }; raise UnsupportedImageProcessingArgument end ++ end ++ ++ def validate_arg_array(argument) ++ argument.each do |arg| ++ if arg.is_a?(Integer) || arg.is_a?(Float) ++ next ++ elsif arg.is_a?(String) || arg.is_a?(Symbol) ++ validate_arg_string(arg) ++ elsif arg.is_a?(Array) ++ validate_arg_array(arg) ++ elsif arg.is_a?(Hash) ++ validate_arg_hash(arg) ++ end ++ end ++ end ++ ++ def validate_arg_hash(argument) ++ argument.each do |key, value| ++ validate_arg_string(key) ++ ++ if value.is_a?(Integer) || value.is_a?(Float) ++ next ++ elsif value.is_a?(String) || value.is_a?(Symbol) ++ validate_arg_string(value) ++ elsif value.is_a?(Array) ++ validate_arg_array(value) ++ elsif value.is_a?(Hash) ++ validate_arg_hash(value) ++ end ++ end ++ end + end + end + end +Index: rails-6.0.3.7+dfsg/activestorage/test/models/variant_test.rb +=================================================================== +--- rails-6.0.3.7+dfsg.orig/activestorage/test/models/variant_test.rb ++++ rails-6.0.3.7+dfsg/activestorage/test/models/variant_test.rb +@@ -196,4 +196,78 @@ class ActiveStorage::VariantTest < Activ + ensure + ActiveStorage.variant_processor = :mini_magick + end ++ ++ test "variations with unsupported methods in combine_options raise UnsupportedImageProcessingMethod" do ++ blob = create_file_blob(filename: "racecar.jpg") ++ ++ assert_raise(ActiveStorage::Transformers::ImageProcessingTransformer::UnsupportedImageProcessingMethod) do ++ blob.variant(combine_options: { ++ gravity: "center", ++ write: "/tmp/danger", ++ crop: "100x10000", ++ }).processed ++ end ++ end ++ ++ test "variations with dangerous argument in combine_options raise UnsupportedImageProcessingArgument" do ++ blob = create_file_blob(filename: "racecar.jpg") ++ ++ assert_raise(ActiveStorage::Transformers::ImageProcessingTransformer::UnsupportedImageProcessingArgument) do ++ blob.variant(combine_options: { ++ gravity: "center", ++ resize: "-write /tmp/danger", ++ crop: "100x10000", ++ }).processed ++ end ++ end ++ ++ test "variations with dangerous argument string raise UnsupportedImageProcessingArgument" do ++ blob = create_file_blob(filename: "racecar.jpg") ++ assert_raise(ActiveStorage::Transformers::ImageProcessingTransformer::UnsupportedImageProcessingArgument) do ++ blob.variant(resize: "-PaTh /tmp/file.erb").processed ++ end ++ end ++ ++ test "variations with dangerous argument array raise UnsupportedImageProcessingArgument" do ++ blob = create_file_blob(filename: "racecar.jpg") ++ assert_raise(ActiveStorage::Transformers::ImageProcessingTransformer::UnsupportedImageProcessingArgument) do ++ blob.variant(resize: [123, "-write", "/tmp/file.erb"]).processed ++ end ++ end ++ ++ test "variations with dangerous argument in a nested array raise UnsupportedImageProcessingArgument" do ++ blob = create_file_blob(filename: "racecar.jpg") ++ assert_raise(ActiveStorage::Transformers::ImageProcessingTransformer::UnsupportedImageProcessingArgument) do ++ blob.variant(resize: [123, ["-write", "/tmp/file.erb"], "/tmp/file.erb"]).processed ++ end ++ ++ assert_raise(ActiveStorage::Transformers::ImageProcessingTransformer::UnsupportedImageProcessingArgument) do ++ blob.variant(resize: [123, {"-write /tmp/file.erb": "something"}, "/tmp/file.erb"]).processed ++ end ++ end ++ ++ test "variations with dangerous argument hash raise UnsupportedImageProcessingArgument" do ++ blob = create_file_blob(filename: "racecar.jpg") ++ assert_raise(ActiveStorage::Transformers::ImageProcessingTransformer::UnsupportedImageProcessingArgument) do ++ blob.variant(saver: {"-write": "/tmp/file.erb"}).processed ++ end ++ end ++ ++ test "variations with dangerous argument in a nested hash raise UnsupportedImageProcessingArgument" do ++ blob = create_file_blob(filename: "racecar.jpg") ++ assert_raise(ActiveStorage::Transformers::ImageProcessingTransformer::UnsupportedImageProcessingArgument) do ++ blob.variant(saver: {"something": {"-write": "/tmp/file.erb"}}).processed ++ end ++ ++ assert_raise(ActiveStorage::Transformers::ImageProcessingTransformer::UnsupportedImageProcessingArgument) do ++ blob.variant(saver: {"something": ["-write", "/tmp/file.erb"]}).processed ++ end ++ end ++ ++ test "variations with unsupported methods raise UnsupportedImageProcessingMethod" do ++ blob = create_file_blob(filename: "racecar.jpg") ++ assert_raise(ActiveStorage::Transformers::ImageProcessingTransformer::UnsupportedImageProcessingMethod) do ++ blob.variant(system: "touch /tmp/dangerous").processed ++ end ++ end + end +Index: rails-6.0.3.7+dfsg/railties/test/application/configuration_test.rb +=================================================================== +--- rails-6.0.3.7+dfsg.orig/railties/test/application/configuration_test.rb ++++ rails-6.0.3.7+dfsg/railties/test/application/configuration_test.rb +@@ -2339,6 +2339,35 @@ module ApplicationTests + assert_equal Digest::SHA1, ActiveSupport::Digest.hash_digest_class + end + ++ test "ActiveStorage.supported_image_processing_methods can be configured via config.active_storage.supported_image_processing_methods" do ++ remove_from_config '.*config\.load_defaults.*\n' ++ ++ app_file "config/initializers/add_image_processing_methods.rb", <<-RUBY ++ Rails.application.config.active_storage.supported_image_processing_methods = ["write", "set"] ++ RUBY ++ ++ app "development" ++ ++ assert ActiveStorage.supported_image_processing_methods.include?("write") ++ assert ActiveStorage.supported_image_processing_methods.include?("set") ++ end ++ ++ test "ActiveStorage.unsupported_image_processing_arguments can be configured via config.active_storage.unsupported_image_processing_arguments" do ++ remove_from_config '.*config\.load_defaults.*\n' ++ ++ app_file "config/initializers/add_image_processing_arguments.rb", <<-RUBY ++ Rails.application.config.active_storage.unsupported_image_processing_arguments = %w( ++ -write ++ -danger ++ ) ++ RUBY ++ ++ app "development" ++ ++ assert ActiveStorage.unsupported_image_processing_arguments.include?("-danger") ++ refute ActiveStorage.unsupported_image_processing_arguments.include?("-set") ++ end ++ + test "custom serializers should be able to set via config.active_job.custom_serializers in an initializer" do + class ::DummySerializer < ActiveJob::Serializers::ObjectSerializer; end + diff -Nru rails-6.0.3.7+dfsg/debian/patches/CVE-2022-22577.patch rails-6.0.3.7+dfsg/debian/patches/CVE-2022-22577.patch --- rails-6.0.3.7+dfsg/debian/patches/CVE-2022-22577.patch 1970-01-01 00:00:00.000000000 +0000 +++ rails-6.0.3.7+dfsg/debian/patches/CVE-2022-22577.patch 2023-02-26 06:53:40.000000000 +0000 @@ -0,0 +1,86 @@ +From 5299b57d596ea274f77f5ffee2b79c6ee0255508 Mon Sep 17 00:00:00 2001 +From: Aaron Patterson +Date: Tue, 8 Mar 2022 13:23:15 -0800 +Subject: [PATCH] Merge pull request #44635 from imtayadeway/tjw/api-csp-i + +Generate content security policy for non-HTML responses +--- + .../http/content_security_policy.rb | 7 ------- + .../test/dispatch/content_security_policy_test.rb | 15 +++++++++++++++ + 2 files changed, 15 insertions(+), 7 deletions(-) + +diff --git a/actionpack/lib/action_dispatch/http/content_security_policy.rb b/actionpack/lib/action_dispatch/http/content_security_policy.rb +index 7dedecef34..50a3ec4bd1 100644 +--- a/actionpack/lib/action_dispatch/http/content_security_policy.rb ++++ b/actionpack/lib/action_dispatch/http/content_security_policy.rb +@@ -17,7 +17,6 @@ def call(env) + request = ActionDispatch::Request.new env + _, headers, _ = response = @app.call(env) + +- return response unless html_response?(headers) + return response if policy_present?(headers) + + if policy = request.content_security_policy +@@ -31,12 +30,6 @@ def call(env) + end + + private +- def html_response?(headers) +- if content_type = headers[CONTENT_TYPE] +- content_type =~ /html/ +- end +- end +- + def header_name(request) + if request.content_security_policy_report_only + POLICY_REPORT_ONLY +diff --git a/actionpack/test/dispatch/content_security_policy_test.rb b/actionpack/test/dispatch/content_security_policy_test.rb +index a4634626bb..9fd49ead24 100644 +--- a/actionpack/test/dispatch/content_security_policy_test.rb ++++ b/actionpack/test/dispatch/content_security_policy_test.rb +@@ -353,6 +353,11 @@ class PolicyController < ActionController::Base + + content_security_policy_report_only only: :report_only + ++ content_security_policy only: :api do |p| ++ p.default_src :none ++ p.frame_ancestors :none ++ end ++ + def index + head :ok + end +@@ -381,6 +386,10 @@ def no_policy + head :ok + end + ++ def api ++ render json: {} ++ end ++ + private + def condition? + params[:condition] == "true" +@@ -397,6 +406,7 @@ def condition? + get "/script-src", to: "policy#script_src" + get "/style-src", to: "policy#style_src" + get "/no-policy", to: "policy#no_policy" ++ get "/api", to: "policy#api" + end + end + +@@ -468,6 +478,11 @@ def test_generates_no_content_security_policy + assert_nil response.headers["Content-Security-Policy-Report-Only"] + end + ++ def test_generates_api_security_policy ++ get "/api" ++ assert_policy "default-src 'none'; frame-ancestors 'none'" ++ end ++ + private + def assert_policy(expected, report_only: false) + assert_response :success +-- +2.30.2 + diff -Nru rails-6.0.3.7+dfsg/debian/patches/CVE-2022-23633-1.patch rails-6.0.3.7+dfsg/debian/patches/CVE-2022-23633-1.patch --- rails-6.0.3.7+dfsg/debian/patches/CVE-2022-23633-1.patch 1970-01-01 00:00:00.000000000 +0000 +++ rails-6.0.3.7+dfsg/debian/patches/CVE-2022-23633-1.patch 2023-02-26 06:53:40.000000000 +0000 @@ -0,0 +1,115 @@ +From e9015f91dd685472f915f8aa1eb18b0e0763e013 Mon Sep 17 00:00:00 2001 +From: Jean Boussier +Date: Fri, 11 Feb 2022 13:09:30 +0100 +Subject: [PATCH] ActionDispatch::Executor don't fully trust `body#close` + +Under certain circumstances, the middleware isn't informed that the +response body has been fully closed which result in request state not +being fully reset before the next request. + +[CVE-2022-23633] +--- + .../action_dispatch/middleware/executor.rb | 2 +- + actionpack/test/dispatch/executor_test.rb | 21 ++++++++++++++ + .../lib/active_support/execution_wrapper.rb | 29 ++++++++++--------- + 3 files changed, 38 insertions(+), 14 deletions(-) + +diff --git a/actionpack/lib/action_dispatch/middleware/executor.rb b/actionpack/lib/action_dispatch/middleware/executor.rb +index 129b18d3d9..a32f916260 100644 +--- a/actionpack/lib/action_dispatch/middleware/executor.rb ++++ b/actionpack/lib/action_dispatch/middleware/executor.rb +@@ -9,7 +9,7 @@ def initialize(app, executor) + end + + def call(env) +- state = @executor.run! ++ state = @executor.run!(reset: true) + begin + response = @app.call(env) + returned = response << ::Rack::BodyProxy.new(response.pop) { state.complete! } +diff --git a/actionpack/test/dispatch/executor_test.rb b/actionpack/test/dispatch/executor_test.rb +index 5b8be39b6d..d0bf574009 100644 +--- a/actionpack/test/dispatch/executor_test.rb ++++ b/actionpack/test/dispatch/executor_test.rb +@@ -119,6 +119,27 @@ def test_callbacks_execute_in_shared_context + assert_not defined?(@in_shared_context) # it's not in the test itself + end + ++ def test_body_abandonned ++ total = 0 ++ ran = 0 ++ completed = 0 ++ ++ executor.to_run { total += 1; ran += 1 } ++ executor.to_complete { total += 1; completed += 1} ++ ++ stack = middleware(proc { [200, {}, "response"] }) ++ ++ requests_count = 5 ++ ++ requests_count.times do ++ stack.call({}) ++ end ++ ++ assert_equal (requests_count * 2) - 1, total ++ assert_equal requests_count, ran ++ assert_equal requests_count - 1, completed ++ end ++ + private + def call_and_return_body(&block) + app = middleware(block || proc { [200, {}, "response"] }) +diff --git a/activesupport/lib/active_support/execution_wrapper.rb b/activesupport/lib/active_support/execution_wrapper.rb +index ca810db584..07c4f435db 100644 +--- a/activesupport/lib/active_support/execution_wrapper.rb ++++ b/activesupport/lib/active_support/execution_wrapper.rb +@@ -63,18 +63,21 @@ def self.register_hook(hook, outer: false) + # after the work has been performed. + # + # Where possible, prefer +wrap+. +- def self.run! +- if active? +- Null ++ def self.run!(reset: false) ++ if reset ++ lost_instance = active.delete(Thread.current) ++ lost_instance&.complete! + else +- new.tap do |instance| +- success = nil +- begin +- instance.run! +- success = true +- ensure +- instance.complete! unless success +- end ++ return Null if active? ++ end ++ ++ new.tap do |instance| ++ success = nil ++ begin ++ instance.run! ++ success = true ++ ensure ++ instance.complete! unless success + end + end + end +@@ -103,11 +106,11 @@ def self.inherited(other) # :nodoc: + self.active = Concurrent::Hash.new + + def self.active? # :nodoc: +- @active[Thread.current] ++ @active.key?(Thread.current) + end + + def run! # :nodoc: +- self.class.active[Thread.current] = true ++ self.class.active[Thread.current] = self + run_callbacks(:run) + end + +-- +2.30.2 + diff -Nru rails-6.0.3.7+dfsg/debian/patches/CVE-2022-23633-2.patch rails-6.0.3.7+dfsg/debian/patches/CVE-2022-23633-2.patch --- rails-6.0.3.7+dfsg/debian/patches/CVE-2022-23633-2.patch 1970-01-01 00:00:00.000000000 +0000 +++ rails-6.0.3.7+dfsg/debian/patches/CVE-2022-23633-2.patch 2023-02-26 09:27:16.000000000 +0000 @@ -0,0 +1,26 @@ +From d1267768e9f57ebcf86ff7f011aca7fb08e733eb Mon Sep 17 00:00:00 2001 +From: Aaron Patterson +Date: Fri, 11 Feb 2022 11:23:01 -0800 +Subject: [PATCH] Fix reloader to work with new Executor signature + +This is a follow up to [CVE-2022-23633]. +--- + activesupport/lib/active_support/reloader.rb | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/activesupport/lib/active_support/reloader.rb b/activesupport/lib/active_support/reloader.rb +index 2f81cd4f80..e751866a27 100644 +--- a/activesupport/lib/active_support/reloader.rb ++++ b/activesupport/lib/active_support/reloader.rb +@@ -58,7 +58,7 @@ def self.reload! + prepare! + end + +- def self.run! # :nodoc: ++ def self.run!(reset: false) # :nodoc: + if check! + super + else +-- +2.30.2 + diff -Nru rails-6.0.3.7+dfsg/debian/patches/CVE-2022-27777-1.patch rails-6.0.3.7+dfsg/debian/patches/CVE-2022-27777-1.patch --- rails-6.0.3.7+dfsg/debian/patches/CVE-2022-27777-1.patch 1970-01-01 00:00:00.000000000 +0000 +++ rails-6.0.3.7+dfsg/debian/patches/CVE-2022-27777-1.patch 2023-02-26 06:53:40.000000000 +0000 @@ -0,0 +1,326 @@ +From 36a6dad07d572a0098c29d6d96a226638a7caa38 Mon Sep 17 00:00:00 2001 +From: Alvaro Martin Fraguas +Date: Tue, 12 Apr 2022 16:20:27 -0400 +Subject: [PATCH] Fix and add protections for XSS in names. + +Add the method ERB::Util.xml_name_escape to escape dangerous characters +in names of tags and names of attributes, following the specification of +XML. + +Use that method in the tag helpers of ActionView::Helpers. Add a deprecation +warning to the option :escape_attributes mentioning the new behavior and the +transition to :escape, to simplify by applying the option to the whole tag. + +[CVE-2022-27777] +--- + .../lib/action_view/helpers/tag_helper.rb | 39 +++++++- + actionview/test/template/tag_helper_test.rb | 91 +++++++++++++++++-- + .../core_ext/string/output_safety.rb | 28 ++++++ + .../test/core_ext/string_ext_test.rb | 26 ++++++ + 4 files changed, 171 insertions(+), 13 deletions(-) + +diff --git a/actionview/lib/action_view/helpers/tag_helper.rb b/actionview/lib/action_view/helpers/tag_helper.rb +index fc2654a684..7b51d18730 100644 +--- a/actionview/lib/action_view/helpers/tag_helper.rb ++++ b/actionview/lib/action_view/helpers/tag_helper.rb +@@ -41,18 +41,25 @@ def initialize(view_context) + @view_context = view_context + end + +- def tag_string(name, content = nil, escape_attributes: true, **options, &block) ++ def tag_string(name, content = nil, **options, &block) ++ escape = handle_deprecated_escape_options(options) + content = @view_context.capture(self, &block) if block_given? ++ + if VOID_ELEMENTS.include?(name) && content.nil? +- "<#{name.to_s.dasherize}#{tag_options(options, escape_attributes)}>".html_safe ++ "<#{name.to_s.dasherize}#{tag_options(options, escape)}>".html_safe + else +- content_tag_string(name.to_s.dasherize, content || "", options, escape_attributes) ++ content_tag_string(name.to_s.dasherize, content || "", options, escape) + end + end + + def content_tag_string(name, content, options, escape = true) + tag_options = tag_options(options, escape) if options +- content = ERB::Util.unwrapped_html_escape(content) if escape ++ ++ if escape ++ name = ERB::Util.xml_name_escape(name) ++ content = ERB::Util.unwrapped_html_escape(content) ++ end ++ + "<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name]}#{content}".html_safe + end + +@@ -85,6 +92,8 @@ def boolean_tag_option(key) + end + + def tag_option(key, value, escape) ++ key = ERB::Util.xml_name_escape(key) if escape ++ + if value.is_a?(Array) + value = escape ? safe_join(value, " ") : value.join(" ") + else +@@ -107,6 +116,27 @@ def respond_to_missing?(*args) + true + end + ++ def handle_deprecated_escape_options(options) ++ # The option :escape_attributes has been merged into the options hash to be ++ # able to warn when it is used, so we need to handle default values here. ++ escape_option_provided = options.has_key?(:escape) ++ escape_attributes_option_provided = options.has_key?(:escape_attributes) ++ ++ if escape_attributes_option_provided ++ ActiveSupport::Deprecation.warn(<<~MSG) ++ Use of the option :escape_attributes is deprecated. It currently \ ++ escapes both names and values of tags and attributes and it is \ ++ equivalent to :escape. If any of them are enabled, the escaping \ ++ is fully enabled. ++ MSG ++ end ++ ++ return true unless escape_option_provided || escape_attributes_option_provided ++ escape_option = options.delete(:escape) ++ escape_attributes_option = options.delete(:escape_attributes) ++ escape_option || escape_attributes_option ++ end ++ + def method_missing(called, *args, **options, &block) + tag_string(called, *args, **options, &block) + end +@@ -237,6 +267,7 @@ def tag(name = nil, options = nil, open = false, escape = true) + if name.nil? + tag_builder + else ++ name = ERB::Util.xml_name_escape(name) if escape + "<#{name}#{tag_builder.tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe + end + end +diff --git a/actionview/test/template/tag_helper_test.rb b/actionview/test/template/tag_helper_test.rb +index 6626f78678..2f53826c9c 100644 +--- a/actionview/test/template/tag_helper_test.rb ++++ b/actionview/test/template/tag_helper_test.rb +@@ -7,6 +7,8 @@ class TagHelperTest < ActionView::TestCase + + tests ActionView::Helpers::TagHelper + ++ COMMON_DANGEROUS_CHARS = "&<>\"' %*+,/;=^|" ++ + def test_tag + assert_equal "
", tag("br") + assert_equal "
", tag(:br, clear: "left") +@@ -86,6 +88,77 @@ def test_tag_builder_do_not_modify_html_safe_options + assert html_safe_str.html_safe? + end + ++ def test_tag_with_dangerous_name ++ assert_equal "<#{"_" * COMMON_DANGEROUS_CHARS.size} />", ++ tag(COMMON_DANGEROUS_CHARS) ++ ++ assert_equal "<#{COMMON_DANGEROUS_CHARS} />", ++ tag(COMMON_DANGEROUS_CHARS, nil, false, false) ++ end ++ ++ def test_tag_builder_with_dangerous_name ++ escaped_dangerous_chars = "_" * COMMON_DANGEROUS_CHARS.size ++ assert_equal "<#{escaped_dangerous_chars}>", ++ tag.public_send(COMMON_DANGEROUS_CHARS.to_sym) ++ ++ assert_equal "<#{COMMON_DANGEROUS_CHARS}>", ++ tag.public_send(COMMON_DANGEROUS_CHARS.to_sym, nil, escape: false) ++ end ++ ++ def test_tag_with_dangerous_aria_attribute_name ++ escaped_dangerous_chars = "_" * COMMON_DANGEROUS_CHARS.size ++ assert_equal "", ++ tag("the-name", aria: { COMMON_DANGEROUS_CHARS => "the value" }) ++ ++ assert_equal "", ++ tag("the-name", { aria: { COMMON_DANGEROUS_CHARS => "the value" } }, false, false) ++ end ++ ++ def test_tag_builder_with_dangerous_aria_attribute_name ++ escaped_dangerous_chars = "_" * COMMON_DANGEROUS_CHARS.size ++ assert_equal "", ++ tag.public_send(:"the-name", aria: { COMMON_DANGEROUS_CHARS => "the value" }) ++ ++ assert_equal "", ++ tag.public_send(:"the-name", aria: { COMMON_DANGEROUS_CHARS => "the value" }, escape: false) ++ end ++ ++ def test_tag_with_dangerous_data_attribute_name ++ escaped_dangerous_chars = "_" * COMMON_DANGEROUS_CHARS.size ++ assert_equal "", ++ tag("the-name", data: { COMMON_DANGEROUS_CHARS => "the value" }) ++ ++ assert_equal "", ++ tag("the-name", { data: { COMMON_DANGEROUS_CHARS => "the value" } }, false, false) ++ end ++ ++ def test_tag_builder_with_dangerous_data_attribute_name ++ escaped_dangerous_chars = "_" * COMMON_DANGEROUS_CHARS.size ++ assert_equal "", ++ tag.public_send(:"the-name", data: { COMMON_DANGEROUS_CHARS => "the value" }) ++ ++ assert_equal "", ++ tag.public_send(:"the-name", data: { COMMON_DANGEROUS_CHARS => "the value" }, escape: false) ++ end ++ ++ def test_tag_with_dangerous_unknown_attribute_name ++ escaped_dangerous_chars = "_" * COMMON_DANGEROUS_CHARS.size ++ assert_equal "", ++ tag("the-name", COMMON_DANGEROUS_CHARS => "the value") ++ ++ assert_equal "", ++ tag("the-name", { COMMON_DANGEROUS_CHARS => "the value" }, false, false) ++ end ++ ++ def test_tag_builder_with_dangerous_unknown_attribute_name ++ escaped_dangerous_chars = "_" * COMMON_DANGEROUS_CHARS.size ++ assert_equal "", ++ tag.public_send(:"the-name", COMMON_DANGEROUS_CHARS => "the value") ++ ++ assert_equal "", ++ tag.public_send(:"the-name", COMMON_DANGEROUS_CHARS => "the value", escape: false) ++ end ++ + def test_content_tag + assert_equal "Create", content_tag("a", "Create", "href" => "create") + assert_predicate content_tag("a", "Create", "href" => "create"), :html_safe? +@@ -105,7 +178,7 @@ def test_tag_builder_with_content + assert_equal "

<script>evil_js</script>

", + tag.p("") + assert_equal "

", +- tag.p("", escape_attributes: false) ++ tag.p("", escape: false) + end + + def test_tag_builder_nested +@@ -220,10 +293,10 @@ def test_content_tag_with_unescaped_array_class + end + + def test_tag_builder_with_unescaped_array_class +- str = tag.p "limelight", class: ["song", "play>"], escape_attributes: false ++ str = tag.p "limelight", class: ["song", "play>"], escape: false + assert_equal "

\">limelight

", str + +- str = tag.p "limelight", class: ["song", ["play>"]], escape_attributes: false ++ str = tag.p "limelight", class: ["song", ["play>"]], escape: false + assert_equal "

\">limelight

", str + end + +@@ -242,7 +315,7 @@ def test_content_tag_with_unescaped_empty_array_class + end + + def test_tag_builder_with_unescaped_empty_array_class +- str = tag.p "limelight", class: [], escape_attributes: false ++ str = tag.p "limelight", class: [], escape: false + assert_equal '

limelight

', str + end + +@@ -313,11 +386,11 @@ def test_disable_escaping + end + + def test_tag_builder_disable_escaping +- assert_equal '', tag.a(href: "&", escape_attributes: false) +- assert_equal 'cnt', tag.a(href: "&", escape_attributes: false) { "cnt" } +- assert_equal '
', tag.br("data-hidden": "&", escape_attributes: false) +- assert_equal 'content', tag.a("content", href: "&", escape_attributes: false) +- assert_equal 'content', tag.a(href: "&", escape_attributes: false) { "content" } ++ assert_equal '', tag.a(href: "&", escape: false) ++ assert_equal 'cnt', tag.a(href: "&", escape: false) { "cnt" } ++ assert_equal '
', tag.br("data-hidden": "&", escape: false) ++ assert_equal 'content', tag.a("content", href: "&", escape: false) ++ assert_equal 'content', tag.a(href: "&", escape: false) { "content" } + end + + def test_data_attributes + +diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb +index 5b2b3162f2..a7ae901585 100644 +--- a/activesupport/lib/active_support/core_ext/string/output_safety.rb ++++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb +@@ -12,6 +12,14 @@ module Util + HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+)|(#[xX][\dA-Fa-f]+));)/ + JSON_ESCAPE_REGEXP = /[\u2028\u2029&><]/u + ++ # Following XML requirements: https://www.w3.org/TR/REC-xml/#NT-Name ++ TAG_NAME_START_REGEXP_SET = ":A-Z_a-z\u{C0}-\u{D6}\u{D8}-\u{F6}\u{F8}-\u{2FF}\u{370}-\u{37D}\u{37F}-\u{1FFF}" \ ++ "\u{200C}-\u{200D}\u{2070}-\u{218F}\u{2C00}-\u{2FEF}\u{3001}-\u{D7FF}\u{F900}-\u{FDCF}" \ ++ "\u{FDF0}-\u{FFFD}\u{10000}-\u{EFFFF}" ++ TAG_NAME_START_REGEXP = /[^#{TAG_NAME_START_REGEXP_SET}]/ ++ TAG_NAME_FOLLOWING_REGEXP = /[^#{TAG_NAME_START_REGEXP_SET}\-.0-9\u{B7}\u{0300}-\u{036F}\u{203F}-\u{2040}]/ ++ TAG_NAME_REPLACEMENT_CHAR = "_" ++ + # A utility method for escaping HTML tag characters. + # This method is also aliased as h. + # +@@ -116,6 +124,26 @@ def json_escape(s) + end + + module_function :json_escape ++ ++ # A utility method for escaping XML names of tags and names of attributes. ++ # ++ # xml_name_escape('1 < 2 & 3') ++ # # => "1___2___3" ++ # ++ # It follows the requirements of the specification: https://www.w3.org/TR/REC-xml/#NT-Name ++ def xml_name_escape(name) ++ name = name.to_s ++ return "" if name.blank? ++ ++ starting_char = name[0].gsub(TAG_NAME_START_REGEXP, TAG_NAME_REPLACEMENT_CHAR) ++ ++ return starting_char if name.size == 1 ++ ++ following_chars = name[1..-1].gsub(TAG_NAME_FOLLOWING_REGEXP, TAG_NAME_REPLACEMENT_CHAR) ++ ++ starting_char + following_chars ++ end ++ module_function :xml_name_escape + end + end + +diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb +index c5a000b67a..90e0aaaf0e 100644 +--- a/activesupport/test/core_ext/string_ext_test.rb ++++ b/activesupport/test/core_ext/string_ext_test.rb +@@ -1009,6 +1009,32 @@ def to_s + expected = "© <" + assert_equal expected, ERB::Util.html_escape_once(string) + end ++ ++ test "ERB::Util.xml_name_escape should escape unsafe characters for XML names" do ++ unsafe_char = ">" ++ safe_char = "Á" ++ safe_char_after_start = "3" ++ ++ assert_equal "_", ERB::Util.xml_name_escape(unsafe_char) ++ assert_equal "_#{safe_char}", ERB::Util.xml_name_escape(unsafe_char + safe_char) ++ assert_equal "__", ERB::Util.xml_name_escape(unsafe_char * 2) ++ ++ assert_equal "__#{safe_char}_", ++ ERB::Util.xml_name_escape("#{unsafe_char * 2}#{safe_char}#{unsafe_char}") ++ ++ assert_equal safe_char + safe_char_after_start, ++ ERB::Util.xml_name_escape(safe_char + safe_char_after_start) ++ ++ assert_equal "_#{safe_char}", ++ ERB::Util.xml_name_escape(safe_char_after_start + safe_char) ++ ++ assert_equal "img_src_nonexistent_onerror_alert_1_", ++ ERB::Util.xml_name_escape("img src=nonexistent onerror=alert(1)") ++ ++ common_dangerous_chars = "&<>\"' %*+,/;=^|" ++ assert_equal "_" * common_dangerous_chars.size, ++ ERB::Util.xml_name_escape(common_dangerous_chars) ++ end + end + + class StringExcludeTest < ActiveSupport::TestCase +-- +2.30.2 + diff -Nru rails-6.0.3.7+dfsg/debian/patches/CVE-2022-27777-2.patch rails-6.0.3.7+dfsg/debian/patches/CVE-2022-27777-2.patch --- rails-6.0.3.7+dfsg/debian/patches/CVE-2022-27777-2.patch 1970-01-01 00:00:00.000000000 +0000 +++ rails-6.0.3.7+dfsg/debian/patches/CVE-2022-27777-2.patch 2023-02-26 06:53:40.000000000 +0000 @@ -0,0 +1,41 @@ +From 1b5df893d82a27da907e9b8b75deff13179d1df3 Mon Sep 17 00:00:00 2001 +From: "Eileen M. Uchitelle" +Date: Thu, 5 May 2022 14:46:19 -0400 +Subject: [PATCH] Merge pull request #45027 from + rails/fix-tag-helper-regression + +Fix tag helper regression +--- + actionview/test/template/tag_helper_test.rb | 2 ++ + .../lib/active_support/core_ext/string/output_safety.rb | 2 +- + 2 files changed, 3 insertions(+), 1 deletion(-) + +diff --git a/actionview/test/template/tag_helper_test.rb b/actionview/test/template/tag_helper_test.rb +index 2f53826c9c..21061181b8 100644 +--- a/actionview/test/template/tag_helper_test.rb ++++ b/actionview/test/template/tag_helper_test.rb +@@ -168,6 +168,8 @@ def test_content_tag + content_tag(:p, "") + assert_equal "

", + content_tag(:p, "", nil, false) ++ assert_equal "
test
", ++ content_tag(:div, "test", "@click": "triggerNav()") + end + + def test_tag_builder_with_content +diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb +index a7ae901585..0426d05f4d 100644 +--- a/activesupport/lib/active_support/core_ext/string/output_safety.rb ++++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb +@@ -13,7 +13,7 @@ module Util + JSON_ESCAPE_REGEXP = /[\u2028\u2029&><]/u + + # Following XML requirements: https://www.w3.org/TR/REC-xml/#NT-Name +- TAG_NAME_START_REGEXP_SET = ":A-Z_a-z\u{C0}-\u{D6}\u{D8}-\u{F6}\u{F8}-\u{2FF}\u{370}-\u{37D}\u{37F}-\u{1FFF}" \ ++ TAG_NAME_START_REGEXP_SET = "@:A-Z_a-z\u{C0}-\u{D6}\u{D8}-\u{F6}\u{F8}-\u{2FF}\u{370}-\u{37D}\u{37F}-\u{1FFF}" \ + "\u{200C}-\u{200D}\u{2070}-\u{218F}\u{2C00}-\u{2FEF}\u{3001}-\u{D7FF}\u{F900}-\u{FDCF}" \ + "\u{FDF0}-\u{FFFD}\u{10000}-\u{EFFFF}" + TAG_NAME_START_REGEXP = /[^#{TAG_NAME_START_REGEXP_SET}]/ +-- +2.30.2 + diff -Nru rails-6.0.3.7+dfsg/debian/patches/CVE-2023-22792.patch rails-6.0.3.7+dfsg/debian/patches/CVE-2023-22792.patch --- rails-6.0.3.7+dfsg/debian/patches/CVE-2023-22792.patch 1970-01-01 00:00:00.000000000 +0000 +++ rails-6.0.3.7+dfsg/debian/patches/CVE-2023-22792.patch 2023-02-26 06:53:40.000000000 +0000 @@ -0,0 +1,136 @@ +From 7a7f37f146aa977350cf914eba20a95ce371485f Mon Sep 17 00:00:00 2001 +From: sabulikia +Date: Thu, 7 Jul 2022 16:10:20 -0400 +Subject: [PATCH] Use string#split instead of regex for domain parts + +[CVE-2023-22792] +--- + .../lib/action_dispatch/middleware/cookies.rb | 48 +++++++++++-------- + actionpack/test/dispatch/cookies_test.rb | 26 ++++++++++ + 2 files changed, 54 insertions(+), 20 deletions(-) + +Index: rails-6.0.3.7+dfsg/actionpack/lib/action_dispatch/middleware/cookies.rb +=================================================================== +--- rails-6.0.3.7+dfsg.orig/actionpack/lib/action_dispatch/middleware/cookies.rb ++++ rails-6.0.3.7+dfsg/actionpack/lib/action_dispatch/middleware/cookies.rb +@@ -271,20 +271,6 @@ module ActionDispatch + class CookieJar #:nodoc: + include Enumerable, ChainedCookieJars + +- # This regular expression is used to split the levels of a domain. +- # The top level domain can be any string without a period or +- # **.**, ***.** style TLDs like co.uk or com.au +- # +- # www.example.co.uk gives: +- # $& => example.co.uk +- # +- # example.com gives: +- # $& => example.com +- # +- # lots.of.subdomains.example.local gives: +- # $& => example.local +- DOMAIN_REGEXP = /[^.]*\.([^.]*|..\...|...\...)$/ +- + def self.build(req, cookies) + new(req).tap do |hash| + hash.update(cookies) +@@ -354,13 +340,35 @@ module ActionDispatch + options[:path] ||= "/" + + if options[:domain] == :all || options[:domain] == "all" +- # If there is a provided tld length then we use it otherwise default domain regexp. +- domain_regexp = options[:tld_length] ? /([^.]+\.?){#{options[:tld_length]}}$/ : DOMAIN_REGEXP ++ cookie_domain = "" ++ dot_splitted_host = request.host.split('.', -1) ++ ++ # Case where request.host is not an IP address or it's an invalid domain ++ # (ip confirms to the domain structure we expect so we explicitly check for ip) ++ if request.host.match?(/^[\d.]+$/) || dot_splitted_host.include?("") || dot_splitted_host.length == 1 ++ options[:domain] = nil ++ return ++ end ++ ++ # If there is a provided tld length then we use it otherwise default domain. ++ if options[:tld_length].present? ++ # Case where the tld_length provided is valid ++ if dot_splitted_host.length >= options[:tld_length] ++ cookie_domain = dot_splitted_host.last(options[:tld_length]).join('.') ++ end ++ # Case where tld_length is not provided ++ else ++ # Regular TLDs ++ if !(/([^.]{2,3}\.[^.]{2})$/.match?(request.host)) ++ cookie_domain = dot_splitted_host.last(2).join('.') ++ # **.**, ***.** style TLDs like co.uk and com.au ++ else ++ cookie_domain = dot_splitted_host.last(3).join('.') ++ end ++ end + +- # If host is not ip and matches domain regexp. +- # (ip confirms to domain regexp so we explicitly check for ip) +- options[:domain] = if (request.host !~ /^[\d.]+$/) && (request.host =~ domain_regexp) +- ".#{$&}" ++ options[:domain] = if cookie_domain.present? ++ ".#{cookie_domain}" + end + elsif options[:domain].is_a? Array + # If host matches one of the supplied domains without a dot in front of it. +Index: rails-6.0.3.7+dfsg/actionpack/test/dispatch/cookies_test.rb +=================================================================== +--- rails-6.0.3.7+dfsg.orig/actionpack/test/dispatch/cookies_test.rb ++++ rails-6.0.3.7+dfsg/actionpack/test/dispatch/cookies_test.rb +@@ -247,6 +247,11 @@ class CookiesTest < ActionController::Te + head :ok + end + ++ def set_cookie_with_domain_and_longer_tld ++ cookies[:user_name] = { value: "rizwanreza", domain: :all, tld_length: 4 } ++ head :ok ++ end ++ + def delete_cookie_with_domain_and_tld + cookies.delete(:user_name, domain: :all, tld_length: 2) + head :ok +@@ -926,6 +931,13 @@ class CookiesTest < ActionController::Te + assert_cookie_header "user_name=rizwanreza; domain=.nextangle.com.au; path=/" + end + ++ def test_cookie_with_all_domain_option_using_australian_style_tld_and_two_subdomains ++ @request.host = "x.nextangle.com.au" ++ get :set_cookie_with_domain ++ assert_response :success ++ assert_cookie_header "user_name=rizwanreza; domain=.nextangle.com.au; path=/; SameSite=Lax" ++ end ++ + def test_cookie_with_all_domain_option_using_uk_style_tld + @request.host = "nextangle.co.uk" + get :set_cookie_with_domain +@@ -933,6 +945,13 @@ class CookiesTest < ActionController::Te + assert_cookie_header "user_name=rizwanreza; domain=.nextangle.co.uk; path=/" + end + ++ def test_cookie_with_all_domain_option_using_uk_style_tld_and_two_subdomains ++ @request.host = "x.nextangle.co.uk" ++ get :set_cookie_with_domain ++ assert_response :success ++ assert_cookie_header "user_name=rizwanreza; domain=.nextangle.co.uk; path=/; SameSite=Lax" ++ end ++ + def test_cookie_with_all_domain_option_using_host_with_port + @request.host = "nextangle.local:3000" + get :set_cookie_with_domain +@@ -995,6 +1014,13 @@ class CookiesTest < ActionController::Te + assert_cookie_header "user_name=rizwanreza; domain=.nextangle.local; path=/" + end + ++ def test_cookie_with_all_domain_option_using_longer_tld_length ++ @request.host = "x.y.z.t.com" ++ get :set_cookie_with_domain_and_longer_tld ++ assert_response :success ++ assert_cookie_header "user_name=rizwanreza; domain=.y.z.t.com; path=/; SameSite=Lax" ++ end ++ + def test_deleting_cookie_with_all_domain_option_and_tld_length + request.cookies[:user_name] = "Joe" + get :delete_cookie_with_domain_and_tld diff -Nru rails-6.0.3.7+dfsg/debian/patches/CVE-2023-22794-1.patch rails-6.0.3.7+dfsg/debian/patches/CVE-2023-22794-1.patch --- rails-6.0.3.7+dfsg/debian/patches/CVE-2023-22794-1.patch 1970-01-01 00:00:00.000000000 +0000 +++ rails-6.0.3.7+dfsg/debian/patches/CVE-2023-22794-1.patch 2023-02-26 06:53:40.000000000 +0000 @@ -0,0 +1,248 @@ +From 7d699dad334996838dd529e4b75e1648692d56f8 Mon Sep 17 00:00:00 2001 +From: Ryuta Kamizono +Date: Mon, 8 Jul 2019 09:26:03 +0900 +Subject: [PATCH] Should `Regexp.escape` quoted table name in regex + +It is for agnostic test case, since quoted table name may include `.` +for all adapters, and `[` / `]` for sqlserver adapter. +--- + .../cases/adapters/mysql2/annotate_test.rb | 37 --------------- + .../adapters/postgresql/annotate_test.rb | 37 --------------- + .../cases/adapters/sqlite3/annotate_test.rb | 37 --------------- + activerecord/test/cases/annotate_test.rb | 46 +++++++++++++++++++ + activerecord/test/cases/batches_test.rb | 2 +- + activerecord/test/cases/finder_test.rb | 3 +- + activerecord/test/cases/inheritance_test.rb | 4 +- + 7 files changed, 51 insertions(+), 115 deletions(-) + delete mode 100644 activerecord/test/cases/adapters/mysql2/annotate_test.rb + delete mode 100644 activerecord/test/cases/adapters/postgresql/annotate_test.rb + delete mode 100644 activerecord/test/cases/adapters/sqlite3/annotate_test.rb + create mode 100644 activerecord/test/cases/annotate_test.rb + +diff --git a/activerecord/test/cases/adapters/mysql2/annotate_test.rb b/activerecord/test/cases/adapters/mysql2/annotate_test.rb +deleted file mode 100644 +index b512540073..0000000000 +--- a/activerecord/test/cases/adapters/mysql2/annotate_test.rb ++++ /dev/null +@@ -1,37 +0,0 @@ +-# frozen_string_literal: true +- +-require "cases/helper" +-require "models/post" +- +-class Mysql2AnnotateTest < ActiveRecord::Mysql2TestCase +- fixtures :posts +- +- def test_annotate_wraps_content_in_an_inline_comment +- assert_sql(%r{\ASELECT `posts`\.`id` FROM `posts` /\* foo \*/}) do +- posts = Post.select(:id).annotate("foo") +- assert posts.first +- end +- end +- +- def test_annotate_is_sanitized +- assert_sql(%r{\ASELECT `posts`\.`id` FROM `posts` /\* foo \*/}) do +- posts = Post.select(:id).annotate("*/foo/*") +- assert posts.first +- end +- +- assert_sql(%r{\ASELECT `posts`\.`id` FROM `posts` /\* foo \*/}) do +- posts = Post.select(:id).annotate("**//foo//**") +- assert posts.first +- end +- +- assert_sql(%r{\ASELECT `posts`\.`id` FROM `posts` /\* foo \*/ /\* bar \*/}) do +- posts = Post.select(:id).annotate("*/foo/*").annotate("*/bar") +- assert posts.first +- end +- +- assert_sql(%r{\ASELECT `posts`\.`id` FROM `posts` /\* \+ MAX_EXECUTION_TIME\(1\) \*/}) do +- posts = Post.select(:id).annotate("+ MAX_EXECUTION_TIME(1)") +- assert posts.first +- end +- end +-end +diff --git a/activerecord/test/cases/adapters/postgresql/annotate_test.rb b/activerecord/test/cases/adapters/postgresql/annotate_test.rb +deleted file mode 100644 +index 42a2861511..0000000000 +--- a/activerecord/test/cases/adapters/postgresql/annotate_test.rb ++++ /dev/null +@@ -1,37 +0,0 @@ +-# frozen_string_literal: true +- +-require "cases/helper" +-require "models/post" +- +-class PostgresqlAnnotateTest < ActiveRecord::PostgreSQLTestCase +- fixtures :posts +- +- def test_annotate_wraps_content_in_an_inline_comment +- assert_sql(%r{\ASELECT "posts"\."id" FROM "posts" /\* foo \*/}) do +- posts = Post.select(:id).annotate("foo") +- assert posts.first +- end +- end +- +- def test_annotate_is_sanitized +- assert_sql(%r{\ASELECT "posts"\."id" FROM "posts" /\* foo \*/}) do +- posts = Post.select(:id).annotate("*/foo/*") +- assert posts.first +- end +- +- assert_sql(%r{\ASELECT "posts"\."id" FROM "posts" /\* foo \*/}) do +- posts = Post.select(:id).annotate("**//foo//**") +- assert posts.first +- end +- +- assert_sql(%r{\ASELECT "posts"\."id" FROM "posts" /\* foo \*/ /\* bar \*/}) do +- posts = Post.select(:id).annotate("*/foo/*").annotate("*/bar") +- assert posts.first +- end +- +- assert_sql(%r{\ASELECT "posts"\."id" FROM "posts" /\* \+ MAX_EXECUTION_TIME\(1\) \*/}) do +- posts = Post.select(:id).annotate("+ MAX_EXECUTION_TIME(1)") +- assert posts.first +- end +- end +-end +diff --git a/activerecord/test/cases/adapters/sqlite3/annotate_test.rb b/activerecord/test/cases/adapters/sqlite3/annotate_test.rb +deleted file mode 100644 +index 6567a5eca3..0000000000 +--- a/activerecord/test/cases/adapters/sqlite3/annotate_test.rb ++++ /dev/null +@@ -1,37 +0,0 @@ +-# frozen_string_literal: true +- +-require "cases/helper" +-require "models/post" +- +-class SQLite3AnnotateTest < ActiveRecord::SQLite3TestCase +- fixtures :posts +- +- def test_annotate_wraps_content_in_an_inline_comment +- assert_sql(%r{\ASELECT "posts"\."id" FROM "posts" /\* foo \*/}) do +- posts = Post.select(:id).annotate("foo") +- assert posts.first +- end +- end +- +- def test_annotate_is_sanitized +- assert_sql(%r{\ASELECT "posts"\."id" FROM "posts" /\* foo \*/}) do +- posts = Post.select(:id).annotate("*/foo/*") +- assert posts.first +- end +- +- assert_sql(%r{\ASELECT "posts"\."id" FROM "posts" /\* foo \*/}) do +- posts = Post.select(:id).annotate("**//foo//**") +- assert posts.first +- end +- +- assert_sql(%r{\ASELECT "posts"\."id" FROM "posts" /\* foo \*/ /\* bar \*/}) do +- posts = Post.select(:id).annotate("*/foo/*").annotate("*/bar") +- assert posts.first +- end +- +- assert_sql(%r{\ASELECT "posts"\."id" FROM "posts" /\* \+ MAX_EXECUTION_TIME\(1\) \*/}) do +- posts = Post.select(:id).annotate("+ MAX_EXECUTION_TIME(1)") +- assert posts.first +- end +- end +-end +diff --git a/activerecord/test/cases/annotate_test.rb b/activerecord/test/cases/annotate_test.rb +new file mode 100644 +index 0000000000..4d71d28f83 +--- /dev/null ++++ b/activerecord/test/cases/annotate_test.rb +@@ -0,0 +1,46 @@ ++# frozen_string_literal: true ++ ++require "cases/helper" ++require "models/post" ++ ++class AnnotateTest < ActiveRecord::TestCase ++ fixtures :posts ++ ++ def test_annotate_wraps_content_in_an_inline_comment ++ quoted_posts_id, quoted_posts = regexp_escape_table_name("posts.id"), regexp_escape_table_name("posts") ++ ++ assert_sql(%r{\ASELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/}i) do ++ posts = Post.select(:id).annotate("foo") ++ assert posts.first ++ end ++ end ++ ++ def test_annotate_is_sanitized ++ quoted_posts_id, quoted_posts = regexp_escape_table_name("posts.id"), regexp_escape_table_name("posts") ++ ++ assert_sql(%r{\ASELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/}i) do ++ posts = Post.select(:id).annotate("*/foo/*") ++ assert posts.first ++ end ++ ++ assert_sql(%r{\ASELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/}i) do ++ posts = Post.select(:id).annotate("**//foo//**") ++ assert posts.first ++ end ++ ++ assert_sql(%r{\ASELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/ /\* bar \*/}i) do ++ posts = Post.select(:id).annotate("*/foo/*").annotate("*/bar") ++ assert posts.first ++ end ++ ++ assert_sql(%r{\ASELECT #{quoted_posts_id} FROM #{quoted_posts} /\* \+ MAX_EXECUTION_TIME\(1\) \*/}i) do ++ posts = Post.select(:id).annotate("+ MAX_EXECUTION_TIME(1)") ++ assert posts.first ++ end ++ end ++ ++ private ++ def regexp_escape_table_name(name) ++ Regexp.escape(Post.connection.quote_table_name(name)) ++ end ++end +diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb +index cf6e280898..0d0bf39f79 100644 +--- a/activerecord/test/cases/batches_test.rb ++++ b/activerecord/test/cases/batches_test.rb +@@ -146,7 +146,7 @@ def test_find_in_batches_shouldnt_execute_query_unless_needed + + def test_find_in_batches_should_quote_batch_order + c = Post.connection +- assert_sql(/ORDER BY #{c.quote_table_name('posts')}\.#{c.quote_column_name('id')}/) do ++ assert_sql(/ORDER BY #{Regexp.escape(c.quote_table_name("posts.id"))}/i) do + Post.find_in_batches(batch_size: 1) do |batch| + assert_kind_of Array, batch + assert_kind_of Post, batch.first +diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb +index 3aa610f86b..3752fd42e3 100644 +--- a/activerecord/test/cases/finder_test.rb ++++ b/activerecord/test/cases/finder_test.rb +@@ -245,7 +245,8 @@ def test_exists_passing_active_record_object_is_not_permitted + end + + def test_exists_does_not_select_columns_without_alias +- assert_sql(/SELECT\W+1 AS one FROM ["`]topics["`]/i) do ++ c = Topic.connection ++ assert_sql(/SELECT 1 AS one FROM #{Regexp.escape(c.quote_table_name("topics"))}/i) do + Topic.exists? + end + end +diff --git a/activerecord/test/cases/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb +index 629167e9ed..01e4878c3f 100644 +--- a/activerecord/test/cases/inheritance_test.rb ++++ b/activerecord/test/cases/inheritance_test.rb +@@ -471,9 +471,9 @@ def test_alt_eager_loading + end + + def test_eager_load_belongs_to_primary_key_quoting +- con = Account.connection ++ c = Account.connection + bind_param = Arel::Nodes::BindParam.new(nil) +- assert_sql(/#{con.quote_table_name('companies')}\.#{con.quote_column_name('id')} = (?:#{Regexp.quote(bind_param.to_sql)}|1)/) do ++ assert_sql(/#{Regexp.escape(c.quote_table_name("companies.id"))} = (?:#{Regexp.escape(bind_param.to_sql)}|1)/i) do + Account.all.merge!(includes: :firm).find(1) + end + end +-- +2.30.2 + diff -Nru rails-6.0.3.7+dfsg/debian/patches/CVE-2023-22794-2.patch rails-6.0.3.7+dfsg/debian/patches/CVE-2023-22794-2.patch --- rails-6.0.3.7+dfsg/debian/patches/CVE-2023-22794-2.patch 1970-01-01 00:00:00.000000000 +0000 +++ rails-6.0.3.7+dfsg/debian/patches/CVE-2023-22794-2.patch 2023-02-26 06:53:40.000000000 +0000 @@ -0,0 +1,122 @@ +From 048e9fc05e18c91838a44e60175e475de8b2aad5 Mon Sep 17 00:00:00 2001 +From: John Hawthorn +Date: Tue, 6 Sep 2022 15:49:26 -0700 +Subject: [PATCH] Make sanitize_as_sql_comment more strict + +Though this method was likely never meant to take user input, it was +attempting sanitization. That sanitization could be bypassed with +carefully crafted input. + +This commit makes the sanitization more robust by replacing any +occurrances of "/*" or "*/" with "/ *" or "* /". It also performs a +first pass to remove one surrounding comment to avoid compatibility +issues for users relying on the existing removal. + +This also clarifies in the documentation of annotate that it should not +be provided user input. + +[CVE-2023-22794] +--- + .../connection_adapters/abstract/quoting.rb | 11 ++++++++++- + .../lib/active_record/relation/query_methods.rb | 2 ++ + activerecord/test/cases/annotate_test.rb | 11 ++++++++--- + activerecord/test/cases/relation_test.rb | 10 +++------- + 4 files changed, 23 insertions(+), 11 deletions(-) + +diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +index 143cd32606..aac5bfe0a0 100644 +--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb ++++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +@@ -138,7 +138,16 @@ def quoted_binary(value) # :nodoc: + end + + def sanitize_as_sql_comment(value) # :nodoc: +- value.to_s.gsub(%r{ (/ (?: | \g<1>) \*) \+? \s* | \s* (\* (?: | \g<2>) /) }x, "") ++ # Sanitize a string to appear within a SQL comment ++ # For compatibility, this also surrounding "/*+", "/*", and "*/" ++ # charcacters, possibly with single surrounding space. ++ # Then follows that by replacing any internal "*/" or "/ *" with ++ # "* /" or "/ *" ++ comment = value.to_s.dup ++ comment.gsub!(%r{\A\s*/\*\+?\s?|\s?\*/\s*\Z}, "") ++ comment.gsub!("*/", "* /") ++ comment.gsub!("/*", "/ *") ++ comment + end + + def column_name_matcher # :nodoc: +diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb +index 5451ebef46..8be5d5b0a1 100644 +--- a/activerecord/lib/active_record/relation/query_methods.rb ++++ b/activerecord/lib/active_record/relation/query_methods.rb +@@ -1035,6 +1035,8 @@ def skip_preloading! # :nodoc: + # # SELECT "users"."name" FROM "users" /* selecting */ /* user */ /* names */ + # + # The SQL block comment delimiters, "/*" and "*/", will be added automatically. ++ # ++ # Some escaping is performed, however untrusted user input should not be used. + def annotate(*args) + check_if_method_has_arguments!(:annotate, args) + spawn.annotate!(*args) +diff --git a/activerecord/test/cases/annotate_test.rb b/activerecord/test/cases/annotate_test.rb +index 4d71d28f83..21b95a97a1 100644 +--- a/activerecord/test/cases/annotate_test.rb ++++ b/activerecord/test/cases/annotate_test.rb +@@ -18,17 +18,22 @@ def test_annotate_wraps_content_in_an_inline_comment + def test_annotate_is_sanitized + quoted_posts_id, quoted_posts = regexp_escape_table_name("posts.id"), regexp_escape_table_name("posts") + +- assert_sql(%r{\ASELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/}i) do ++ assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* \* /foo/ \* \*/}i) do + posts = Post.select(:id).annotate("*/foo/*") + assert posts.first + end + +- assert_sql(%r{\ASELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/}i) do ++ assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* \*\* //foo// \*\* \*/}i) do + posts = Post.select(:id).annotate("**//foo//**") + assert posts.first + end + +- assert_sql(%r{\ASELECT #{quoted_posts_id} FROM #{quoted_posts} /\* foo \*/ /\* bar \*/}i) do ++ assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* \* \* //foo// \* \* \*/}i) do ++ posts = Post.select(:id).annotate("* *//foo//* *") ++ assert posts.first ++ end ++ ++ assert_sql(%r{SELECT #{quoted_posts_id} FROM #{quoted_posts} /\* \* /foo/ \* \*/ /\* \* /bar \*/}i) do + posts = Post.select(:id).annotate("*/foo/*").annotate("*/bar") + assert posts.first + end +diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb +index ba0d96b68c..48b752a127 100644 +--- a/activerecord/test/cases/relation_test.rb ++++ b/activerecord/test/cases/relation_test.rb +@@ -345,7 +345,7 @@ def test_relation_with_annotation_chains_sql_comments + + def test_relation_with_annotation_filters_sql_comment_delimiters + post_with_annotation = Post.where(id: 1).annotate("**//foo//**") +- assert_match %r{= 1 /\* foo \*/}, post_with_annotation.to_sql ++ assert_includes post_with_annotation.to_sql, "= 1 /* ** //foo// ** */" + end + + def test_relation_with_annotation_includes_comment_in_count_query +@@ -367,13 +367,9 @@ def test_relation_without_annotation_does_not_include_an_empty_comment + + def test_relation_with_optimizer_hints_filters_sql_comment_delimiters + post_with_hint = Post.where(id: 1).optimizer_hints("**//BADHINT//**") +- assert_match %r{BADHINT}, post_with_hint.to_sql +- assert_no_match %r{\*/BADHINT}, post_with_hint.to_sql +- assert_no_match %r{\*//BADHINT}, post_with_hint.to_sql +- assert_no_match %r{BADHINT/\*}, post_with_hint.to_sql +- assert_no_match %r{BADHINT//\*}, post_with_hint.to_sql ++ assert_includes post_with_hint.to_sql, "/*+ ** //BADHINT// ** */" + post_with_hint = Post.where(id: 1).optimizer_hints("/*+ BADHINT */") +- assert_match %r{/\*\+ BADHINT \*/}, post_with_hint.to_sql ++ assert_includes post_with_hint.to_sql, "/*+ BADHINT */" + end + + def test_does_not_duplicate_optimizer_hints_on_merge +-- +2.30.2 + diff -Nru rails-6.0.3.7+dfsg/debian/patches/CVE-2023-22795.patch rails-6.0.3.7+dfsg/debian/patches/CVE-2023-22795.patch --- rails-6.0.3.7+dfsg/debian/patches/CVE-2023-22795.patch 1970-01-01 00:00:00.000000000 +0000 +++ rails-6.0.3.7+dfsg/debian/patches/CVE-2023-22795.patch 2023-02-26 06:53:40.000000000 +0000 @@ -0,0 +1,26 @@ +From 484fc9185db6c6a6a49ab458b11f9366da02bab2 Mon Sep 17 00:00:00 2001 +From: John Hawthorn +Date: Fri, 13 Jan 2023 15:54:40 -0800 +Subject: [PATCH] Avoid regex backtracking on If-None-Match header + +[CVE-2023-22795] +--- + actionpack/lib/action_dispatch/http/cache.rb | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb +index 9c46c5c8a4..d9d6f32534 100644 +--- a/actionpack/lib/action_dispatch/http/cache.rb ++++ b/actionpack/lib/action_dispatch/http/cache.rb +@@ -18,7 +18,7 @@ def if_none_match + end + + def if_none_match_etags +- if_none_match ? if_none_match.split(/\s*,\s*/) : [] ++ if_none_match ? if_none_match.split(",").each(&:strip!) : [] + end + + def not_modified?(modified_at) +-- +2.30.2 + diff -Nru rails-6.0.3.7+dfsg/debian/patches/CVE-2023-22796.patch rails-6.0.3.7+dfsg/debian/patches/CVE-2023-22796.patch --- rails-6.0.3.7+dfsg/debian/patches/CVE-2023-22796.patch 1970-01-01 00:00:00.000000000 +0000 +++ rails-6.0.3.7+dfsg/debian/patches/CVE-2023-22796.patch 2023-02-26 06:53:40.000000000 +0000 @@ -0,0 +1,27 @@ +From 4b383e6936d7a72b5dc839f526c9a9aeb280acae Mon Sep 17 00:00:00 2001 +From: John Hawthorn +Date: Wed, 11 Jan 2023 10:14:55 -0800 +Subject: [PATCH] Avoid regex backtracking in Inflector.underscore + +[CVE-2023-22796] +--- + activesupport/lib/active_support/inflector/methods.rb | 3 +-- + 1 file changed, 1 insertion(+), 2 deletions(-) + +diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb +index ad136532bf..acb86fe1a4 100644 +--- a/activesupport/lib/active_support/inflector/methods.rb ++++ b/activesupport/lib/active_support/inflector/methods.rb +@@ -93,8 +93,7 @@ def underscore(camel_cased_word) + return camel_cased_word unless /[A-Z-]|::/.match?(camel_cased_word) + word = camel_cased_word.to_s.gsub("::", "/") + word.gsub!(inflections.acronyms_underscore_regex) { "#{$1 && '_' }#{$2.downcase}" } +- word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2') +- word.gsub!(/([a-z\d])([A-Z])/, '\1_\2') ++ word.gsub!(/([A-Z])(?=[A-Z][a-z])|([a-z\d])(?=[A-Z])/) { ($1 || $2) << "_" } + word.tr!("-", "_") + word.downcase! + word +-- +2.30.2 + diff -Nru rails-6.0.3.7+dfsg/debian/patches/series rails-6.0.3.7+dfsg/debian/patches/series --- rails-6.0.3.7+dfsg/debian/patches/series 2021-07-08 19:00:16.000000000 +0000 +++ rails-6.0.3.7+dfsg/debian/patches/series 2023-03-11 06:53:57.000000000 +0000 @@ -12,3 +12,17 @@ adapt-to-babel7.patch replace-webdrivers.patch relax-marcel.patch +CVE-2021-22942-1.patch +CVE-2021-22942-2.patch +CVE-2021-44528.patch +CVE-2022-21831.patch +CVE-2022-22577.patch +CVE-2022-23633-1.patch +CVE-2022-23633-2.patch +CVE-2022-27777-1.patch +CVE-2022-27777-2.patch +CVE-2023-22792.patch +CVE-2023-22794-1.patch +CVE-2023-22794-2.patch +CVE-2023-22795.patch +CVE-2023-22796.patch