Skip to content

Commit 467e339

Browse files
dylanahsmithtenderlove
authored andcommitted
activesupport: Deprecate Marshal.load on raw cache read in RedisCacheStore
The same value for the `raw` option should be provided for both reading and writing to avoid Marshal.load being called on untrusted data. [CVE-2020-8165]
1 parent f7e077f commit 467e339

File tree

6 files changed

+36
-30
lines changed

6 files changed

+36
-30
lines changed

activesupport/lib/active_support/cache/redis_cache_store.rb

+16-11
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,6 @@ class RedisCacheStore < Store
7070
# Support raw values in the local cache strategy.
7171
module LocalCacheWithRaw # :nodoc:
7272
private
73-
def read_entry(key, options)
74-
entry = super
75-
if options[:raw] && local_cache && entry
76-
entry = deserialize_entry(entry.value)
77-
end
78-
entry
79-
end
80-
8173
def write_entry(key, entry, options)
8274
if options[:raw] && local_cache
8375
raw_entry = Entry.new(serialize_entry(entry, raw: true))
@@ -328,7 +320,8 @@ def set_redis_capabilities
328320
# Read an entry from the cache.
329321
def read_entry(key, options = nil)
330322
failsafe :read_entry do
331-
deserialize_entry redis.with { |c| c.get(key) }
323+
raw = options&.fetch(:raw, false)
324+
deserialize_entry(redis.with { |c| c.get(key) }, raw: raw)
332325
end
333326
end
334327

@@ -343,6 +336,7 @@ def read_multi_entries(names, _options)
343336
def read_multi_mget(*names)
344337
options = names.extract_options!
345338
options = merged_options(options)
339+
raw = options&.fetch(:raw, false)
346340

347341
keys = names.map { |name| normalize_key(name, options) }
348342

@@ -352,7 +346,7 @@ def read_multi_mget(*names)
352346

353347
names.zip(values).each_with_object({}) do |(name, value), results|
354348
if value
355-
entry = deserialize_entry(value)
349+
entry = deserialize_entry(value, raw: raw)
356350
unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(name, options))
357351
results[name] = entry.value
358352
end
@@ -421,9 +415,20 @@ def truncate_key(key)
421415
end
422416
end
423417

424-
def deserialize_entry(serialized_entry)
418+
def deserialize_entry(serialized_entry, raw:)
425419
if serialized_entry
426420
entry = Marshal.load(serialized_entry) rescue serialized_entry
421+
422+
written_raw = serialized_entry.equal?(entry)
423+
if raw != written_raw
424+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
425+
Using a different value for the raw option when reading and writing
426+
to a cache key is deprecated for :redis_cache_store and Rails 6.0
427+
will stop automatically detecting the format when reading to avoid
428+
marshal loading untrusted raw strings.
429+
MSG
430+
end
431+
427432
entry.is_a?(Entry) ? entry : Entry.new(entry)
428433
end
429434
end

activesupport/test/cache/behaviors/cache_increment_decrement_behavior.rb

+6-6
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,23 @@
33
module CacheIncrementDecrementBehavior
44
def test_increment
55
@cache.write("foo", 1, raw: true)
6-
assert_equal 1, @cache.read("foo").to_i
6+
assert_equal 1, @cache.read("foo", raw: true).to_i
77
assert_equal 2, @cache.increment("foo")
8-
assert_equal 2, @cache.read("foo").to_i
8+
assert_equal 2, @cache.read("foo", raw: true).to_i
99
assert_equal 3, @cache.increment("foo")
10-
assert_equal 3, @cache.read("foo").to_i
10+
assert_equal 3, @cache.read("foo", raw: true).to_i
1111

1212
missing = @cache.increment("bar")
1313
assert(missing.nil? || missing == 1)
1414
end
1515

1616
def test_decrement
1717
@cache.write("foo", 3, raw: true)
18-
assert_equal 3, @cache.read("foo").to_i
18+
assert_equal 3, @cache.read("foo", raw: true).to_i
1919
assert_equal 2, @cache.decrement("foo")
20-
assert_equal 2, @cache.read("foo").to_i
20+
assert_equal 2, @cache.read("foo", raw: true).to_i
2121
assert_equal 1, @cache.decrement("foo")
22-
assert_equal 1, @cache.read("foo").to_i
22+
assert_equal 1, @cache.read("foo", raw: true).to_i
2323

2424
missing = @cache.decrement("bar")
2525
assert(missing.nil? || missing == -1)

activesupport/test/cache/behaviors/cache_store_behavior.rb

+3-3
Original file line numberDiff line numberDiff line change
@@ -392,8 +392,8 @@ def test_race_condition_protection
392392
def test_crazy_key_characters
393393
crazy_key = "#/:*(<+=> )&$%@?;'\"\'`~-"
394394
assert @cache.write(crazy_key, "1", raw: true)
395-
assert_equal "1", @cache.read(crazy_key)
396-
assert_equal "1", @cache.fetch(crazy_key)
395+
assert_equal "1", @cache.read(crazy_key, raw: true)
396+
assert_equal "1", @cache.fetch(crazy_key, raw: true)
397397
assert @cache.delete(crazy_key)
398398
assert_equal "2", @cache.fetch(crazy_key, raw: true) { "2" }
399399
assert_equal 3, @cache.increment(crazy_key)
@@ -417,7 +417,7 @@ def test_cache_hit_instrumentation
417417
@events << ActiveSupport::Notifications::Event.new(*args)
418418
end
419419
assert @cache.write(key, "1", raw: true)
420-
assert @cache.fetch(key) {}
420+
assert @cache.fetch(key, raw: true) {}
421421
assert_equal 1, @events.length
422422
assert_equal "cache_read.active_support", @events[0].name
423423
assert_equal :fetch, @events[0].payload[:super_operation]

activesupport/test/cache/behaviors/encoded_key_cache_behavior.rb

+4-4
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ module EncodedKeyCacheBehavior
88
define_method "test_#{encoding.name.underscore}_encoded_values" do
99
key = "foo".dup.force_encoding(encoding)
1010
assert @cache.write(key, "1", raw: true)
11-
assert_equal "1", @cache.read(key)
12-
assert_equal "1", @cache.fetch(key)
11+
assert_equal "1", @cache.read(key, raw: true)
12+
assert_equal "1", @cache.fetch(key, raw: true)
1313
assert @cache.delete(key)
1414
assert_equal "2", @cache.fetch(key, raw: true) { "2" }
1515
assert_equal 3, @cache.increment(key)
@@ -20,8 +20,8 @@ module EncodedKeyCacheBehavior
2020
def test_common_utf8_values
2121
key = "\xC3\xBCmlaut".dup.force_encoding(Encoding::UTF_8)
2222
assert @cache.write(key, "1", raw: true)
23-
assert_equal "1", @cache.read(key)
24-
assert_equal "1", @cache.fetch(key)
23+
assert_equal "1", @cache.read(key, raw: true)
24+
assert_equal "1", @cache.fetch(key, raw: true)
2525
assert @cache.delete(key)
2626
assert_equal "2", @cache.fetch(key, raw: true) { "2" }
2727
assert_equal 3, @cache.increment(key)

activesupport/test/cache/behaviors/local_cache_behavior.rb

+5-5
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def test_local_cache_of_increment
106106
@cache.write("foo", 1, raw: true)
107107
@peek.write("foo", 2, raw: true)
108108
@cache.increment("foo")
109-
assert_equal 3, @cache.read("foo")
109+
assert_equal 3, @cache.read("foo", raw: true)
110110
end
111111
end
112112

@@ -115,7 +115,7 @@ def test_local_cache_of_decrement
115115
@cache.write("foo", 1, raw: true)
116116
@peek.write("foo", 3, raw: true)
117117
@cache.decrement("foo")
118-
assert_equal 2, @cache.read("foo")
118+
assert_equal 2, @cache.read("foo", raw: true)
119119
end
120120
end
121121

@@ -133,9 +133,9 @@ def test_local_cache_of_read_multi
133133
@cache.with_local_cache do
134134
@cache.write("foo", "foo", raw: true)
135135
@cache.write("bar", "bar", raw: true)
136-
values = @cache.read_multi("foo", "bar")
137-
assert_equal "foo", @cache.read("foo")
138-
assert_equal "bar", @cache.read("bar")
136+
values = @cache.read_multi("foo", "bar", raw: true)
137+
assert_equal "foo", @cache.read("foo", raw: true)
138+
assert_equal "bar", @cache.read("bar", raw: true)
139139
assert_equal "foo", values["foo"]
140140
assert_equal "bar", values["bar"]
141141
end

activesupport/test/cache/stores/redis_cache_store_test.rb

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@
1414
# Emulates a latency on Redis's back-end for the key latency to facilitate
1515
# connection pool testing.
1616
class SlowRedis < Redis
17-
def get(key, options = {})
17+
def get(key)
1818
if key =~ /latency/
1919
sleep 3
20+
super
2021
else
2122
super
2223
end

0 commit comments

Comments
 (0)