Skip to content

Commit

Permalink
Ensure YAML safe loading in Rails 6.1
Browse files Browse the repository at this point in the history
As part of the fix for CVE-2022-32224 Rails intruduced safe YAML loading
and the `ActiveRecord.yaml_column_permitted_classes` config.

PaperTrail added support for respecting the new configuration here
#1397

The CVE-2022-32224 fix was also backported to Rails versions 5.2.8.1,
6.0.5.1, and, 6.1.6.1, however the name of the confiuration is slightly
different from that in Rails 7.x.

    7.0.3.1 ActiveRecord.yaml_column_permitted_classes
    6.1.6.1 ActiveRecord::Base.yaml_column_permitted_classes
    6.0.5.1 ActiveRecord::Base.yaml_column_permitted_classes
    5.2.8.1 ActiveRecord::Base.yaml_column_permitted_classes

PaperTrail currently doesn't support this alternative configuration
naming, which means it will silent fall back to unsafe YAML loading.

This commit updates `PaperTrail::Serializers::YAML` to be compatible
with safe YAML loading for the Rails 5.2 / 6.0 / 6.1 branches.
  • Loading branch information
Tim Connor committed Aug 26, 2022
1 parent 6eb5c39 commit 51b17d3
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 18 deletions.
26 changes: 22 additions & 4 deletions lib/paper_trail/serializers/yaml.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def load(string)
if use_safe_load?
::YAML.safe_load(
string,
permitted_classes: ::ActiveRecord.yaml_column_permitted_classes,
permitted_classes: yaml_column_permitted_classes,
aliases: true
)
elsif ::YAML.respond_to?(:unsafe_load)
Expand All @@ -39,10 +39,28 @@ def where_object_condition(arel_field, field, value)

private

# `use_yaml_unsafe_load` was added in 7.0.3.1, will be removed in 7.1.0?
def use_safe_load?
defined?(ActiveRecord.use_yaml_unsafe_load) &&
!ActiveRecord.use_yaml_unsafe_load
if Rails.gem_version >= Gem::Version.new('7.0.3.1')
# `use_yaml_unsafe_load` may be removed in the future, at which point safe loading will be
# the default.
!defined?(ActiveRecord.use_yaml_unsafe_load) || !ActiveRecord.use_yaml_unsafe_load
elsif defined?(ActiveRecord::Base.use_yaml_unsafe_load)
# Rails 5.2.8.1, 6.0.5.1, 6.1.6.1
!ActiveRecord::Base.use_yaml_unsafe_load
else
false
end
end

def yaml_column_permitted_classes
if Rails.gem_version >= Gem::Version.new('7.0.3.1')
ActiveRecord.yaml_column_permitted_classes
elsif defined?(ActiveRecord::Base.yaml_column_permitted_classes)
# Rails 5.2.8.1, 6.0.5.1, 6.1.6.1
ActiveRecord::Base.yaml_column_permitted_classes
else
[]
end
end
end
end
Expand Down
27 changes: 16 additions & 11 deletions spec/dummy_app/config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,23 @@ class Application < Rails::Application
config.active_record.sqlite3.represent_boolean_as_integer = true
end

# `use_yaml_unsafe_load` was added in 7.0.3.1, will be removed in 7.1.0?
if ::ActiveRecord.respond_to?(:use_yaml_unsafe_load)
YAML_COLUMN_PERMITTED_CLASSES = [
::ActiveRecord::Type::Time::Value,
::ActiveSupport::TimeWithZone,
::ActiveSupport::TimeZone,
::BigDecimal,
::Date,
::Symbol,
::Time
]

# `use_yaml_unsafe_load` was added in 5.2.8.1, 6.0.5.1, 6.1.6.1, and 7.0.3.1
if Rails.gem_version >= Gem::Version.new('7.0.3.1')
::ActiveRecord.use_yaml_unsafe_load = false
::ActiveRecord.yaml_column_permitted_classes = [
::ActiveRecord::Type::Time::Value,
::ActiveSupport::TimeWithZone,
::ActiveSupport::TimeZone,
::BigDecimal,
::Date,
::Symbol,
::Time
]
::ActiveRecord.yaml_column_permitted_classes = YAML_COLUMN_PERMITTED_CLASSES
elsif ::ActiveRecord::Base.respond_to?(:use_yaml_unsafe_load)
::ActiveRecord::Base.use_yaml_unsafe_load = false
::ActiveRecord::Base.yaml_column_permitted_classes = YAML_COLUMN_PERMITTED_CLASSES
end
end
end
16 changes: 13 additions & 3 deletions spec/paper_trail/serializers/yaml_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ module Serializers
end

it "calls the expected load method based on Psych version" do
# `use_yaml_unsafe_load` was added in 7.0.3.1, will be removed in 7.1.0?
if defined?(ActiveRecord.use_yaml_unsafe_load) && !ActiveRecord.use_yaml_unsafe_load
# `use_yaml_unsafe_load` was added in 5.2.8.1, 6.0.5.1, 6.1.6.1, and 7.0.3.1
if rails_supports_safe_load?
allow(::YAML).to receive(:safe_load)
described_class.load("string")
expect(::YAML).to have_received(:safe_load)
# Psych 4+ implements .unsafe_load
# Psych 4+ implements .unsafe_load
elsif ::YAML.respond_to?(:unsafe_load)
allow(::YAML).to receive(:unsafe_load)
described_class.load("string")
Expand Down Expand Up @@ -60,6 +60,16 @@ module Serializers
expect(arel_value(matches.right)).to eq("%\narg1: Val 1\n%")
end
end

private

def rails_supports_safe_load?
# Rails 7.0.3.1 onwards will always support YAML safe loading
return true if Rails.gem_version >= Gem::Version.new('7.0.3.1')

# Older Rails versions may or may not, depending on whether they have been patched.
defined?(ActiveRecord::Base.use_yaml_unsafe_load)
end
end
end
end

0 comments on commit 51b17d3

Please sign in to comment.