Skip to content

Commit f11c6ac

Browse files
authored
Merge pull request rails#49716 from Shopify/invalid-compressed-cache-entries
Handle outdated Marshal payloads in Cache::Entry with 6.1 cache_format
2 parents 10d880d + a6be798 commit f11c6ac

File tree

5 files changed

+58
-11
lines changed

5 files changed

+58
-11
lines changed

activesupport/lib/active_support/cache.rb

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,17 @@ def fetch(name, options = nil, &block)
459459
instrument(:read, name, options) do |payload|
460460
cached_entry = read_entry(key, **options, event: payload)
461461
entry = handle_expired_entry(cached_entry, key, options)
462-
entry = nil if entry && entry.mismatched?(normalize_version(name, options))
462+
if entry
463+
if entry.mismatched?(normalize_version(name, options))
464+
entry = nil
465+
else
466+
begin
467+
entry.value
468+
rescue DeserializationError
469+
entry = nil
470+
end
471+
end
472+
end
463473
payload[:super_operation] = :fetch if payload
464474
payload[:hit] = !!entry if payload
465475
end
@@ -511,7 +521,12 @@ def read(name, options = nil)
511521
nil
512522
else
513523
payload[:hit] = true if payload
514-
entry.value
524+
begin
525+
entry.value
526+
rescue DeserializationError
527+
payload[:hit] = false
528+
nil
529+
end
515530
end
516531
else
517532
payload[:hit] = false if payload

activesupport/lib/active_support/cache/entry.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,13 @@ def pack
121121

122122
private
123123
def uncompress(value)
124-
Marshal.load(Zlib::Inflate.inflate(value))
124+
marshal_load(Zlib::Inflate.inflate(value))
125+
end
126+
127+
def marshal_load(payload)
128+
Marshal.load(payload)
129+
rescue ArgumentError => error
130+
raise Cache::DeserializationError, error.message
125131
end
126132
end
127133
end

activesupport/lib/active_support/cache/mem_cache_store.rb

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,14 +270,22 @@ def write_serialized_entry(key, payload, **options)
270270
def read_multi_entries(names, **options)
271271
keys_to_names = names.index_by { |name| normalize_key(name, options) }
272272

273-
raw_values = @data.with { |c| c.get_multi(keys_to_names.keys) }
273+
raw_values = begin
274+
@data.with { |c| c.get_multi(keys_to_names.keys) }
275+
rescue Dalli::UnmarshalError
276+
{}
277+
end
278+
274279
values = {}
275280

276281
raw_values.each do |key, value|
277282
entry = deserialize_entry(value, raw: options[:raw])
278283

279284
unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(keys_to_names[key], options))
280-
values[keys_to_names[key]] = entry.value
285+
begin
286+
values[keys_to_names[key]] = entry.value
287+
rescue DeserializationError
288+
end
281289
end
282290
end
283291

activesupport/lib/active_support/cache/redis_cache_store.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,10 @@ def read_multi_entries(names, **options)
332332
if value
333333
entry = deserialize_entry(value, raw: raw)
334334
unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(name, options))
335-
results[name] = entry.value
335+
begin
336+
results[name] = entry.value
337+
rescue DeserializationError
338+
end
336339
end
337340
end
338341
end

activesupport/test/cache/behaviors/cache_store_format_version_behavior.rb

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,16 +64,31 @@ module CacheStoreFormatVersionBehavior
6464

6565
test "Marshal undefined class/module deserialization error with #{format_version} format" do
6666
key = "marshal-#{rand}"
67-
self.class.const_set(:Foo, Class.new)
67+
self.class.const_set(:RemovedConstant, Class.new)
6868
@store = with_format(format_version) { lookup_store }
69-
@store.write(key, self.class::Foo.new)
70-
assert_instance_of self.class::Foo, @store.read(key)
69+
@store.write(key, self.class::RemovedConstant.new)
70+
assert_instance_of self.class::RemovedConstant, @store.read(key)
7171

72-
self.class.send(:remove_const, :Foo)
72+
self.class.send(:remove_const, :RemovedConstant)
7373
assert_nil @store.read(key)
7474
assert_equal false, @store.exist?(key)
7575
ensure
76-
self.class.send(:remove_const, :Foo) rescue nil
76+
self.class.send(:remove_const, :RemovedConstant) rescue nil
77+
end
78+
79+
test "Compressed Marshal undefined class/module deserialization error with #{format_version} format" do
80+
key = "marshal-#{rand}"
81+
self.class.const_set(:RemovedConstant, Class.new)
82+
@store = with_format(format_version) { lookup_store }
83+
@store.write(key, self.class::RemovedConstant.new, compress: true, compress_threshold: 1)
84+
assert_instance_of self.class::RemovedConstant, @store.read(key)
85+
86+
self.class.send(:remove_const, :RemovedConstant)
87+
assert_nil @store.read(key)
88+
assert_equal({}, @store.read_multi(key))
89+
assert_equal("new-value", @store.fetch(key) { "new-value" })
90+
ensure
91+
self.class.send(:remove_const, :RemovedConstant) rescue nil
7792
end
7893
end
7994

0 commit comments

Comments
 (0)