From 6383bb2aa64aa1b682d8dadcc23a3f5d4871a385 Mon Sep 17 00:00:00 2001 From: Nathaniel Catchpole <catch@35733.no-reply.drupal.org> Date: Tue, 1 Jul 2014 10:56:37 +0100 Subject: [PATCH] Issue #2293163 by Wim Leers, pwolanin: Fixed MemoryBackend's cache entries may be modified unintentionally thanks to object references. --- core/lib/Drupal/Core/Cache/MemoryBackend.php | 18 ++++++++++++++---- .../Cache/GenericCacheBackendUnitTestBase.php | 19 +++++++++++++++++++ 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/core/lib/Drupal/Core/Cache/MemoryBackend.php b/core/lib/Drupal/Core/Cache/MemoryBackend.php index 8cd759cebf2a..ce8e199b9671 100644 --- a/core/lib/Drupal/Core/Cache/MemoryBackend.php +++ b/core/lib/Drupal/Core/Cache/MemoryBackend.php @@ -73,6 +73,9 @@ public function getMultiple(&$cids, $allow_invalid = FALSE) { * * @param object $cache * An item loaded from cache_get() or cache_get_multiple(). + * @param bool $allow_invalid + * (optional) If TRUE, cache items may be returned even if they have expired + * or been invalidated. * * @return mixed * The item with data as appropriate or FALSE if there is no @@ -82,15 +85,22 @@ protected function prepareItem($cache, $allow_invalid) { if (!isset($cache->data)) { return FALSE; } + // The object passed into this function is the one stored in $this->cache. + // We must clone it as part of the preparation step so that the actual + // cache object is not affected by the unserialize() call or other + // manipulations of the returned object. + + $prepared = clone $cache; + $prepared->data = unserialize($prepared->data); // Check expire time. - $cache->valid = $cache->expire == Cache::PERMANENT || $cache->expire >= REQUEST_TIME; + $prepared->valid = $prepared->expire == Cache::PERMANENT || $prepared->expire >= REQUEST_TIME; - if (!$allow_invalid && !$cache->valid) { + if (!$allow_invalid && !$prepared->valid) { return FALSE; } - return $cache; + return $prepared; } /** @@ -99,7 +109,7 @@ protected function prepareItem($cache, $allow_invalid) { public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array()) { $this->cache[$cid] = (object) array( 'cid' => $cid, - 'data' => $data, + 'data' => serialize($data), 'created' => REQUEST_TIME, 'expire' => $expire, 'tags' => $this->flattenTags($tags), diff --git a/core/modules/system/src/Tests/Cache/GenericCacheBackendUnitTestBase.php b/core/modules/system/src/Tests/Cache/GenericCacheBackendUnitTestBase.php index f6ae8f8c262a..cf02d13bcfeb 100644 --- a/core/modules/system/src/Tests/Cache/GenericCacheBackendUnitTestBase.php +++ b/core/modules/system/src/Tests/Cache/GenericCacheBackendUnitTestBase.php @@ -188,6 +188,25 @@ public function testSetGet() { $cached = $backend->get('test6'); $this->assert(is_object($cached), "Backend returned an object for cache id test6."); $this->assertIdentical($with_variable, $cached->data); + + // Make sure that a cached object is not affected by changing the original. + $data = new \stdClass(); + $data->value = 1; + $data->obj = new \stdClass(); + $data->obj->value = 2; + $backend->set('test7', $data); + $expected_data = clone $data; + // Add a property to the original. It should not appear in the cached data. + $data->this_should_not_be_in_the_cache = TRUE; + $cached = $backend->get('test7'); + $this->assert(is_object($cached), "Backend returned an object for cache id test7."); + $this->assertEqual($expected_data, $cached->data); + $this->assertFalse(isset($cached->data->this_should_not_be_in_the_cache)); + // Add a property to the cache data. It should not appear when we fetch + // the data from cache again. + $cached->data->this_should_not_be_in_the_cache = TRUE; + $fresh_cached = $backend->get('test7'); + $this->assertFalse(isset($fresh_cached->data->this_should_not_be_in_the_cache)); } /** -- GitLab