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