Enterprise_PageCache is Borked In 1.14.3.X

Published: January 20, 2017

Tags:

If you are thinking about installing or upgrading to Magento Enterprise 1.14.3.X read this post right now. Enterprise_PageCache is completely borked in both 1.14.3.0 and 1.14.3.1. The repro steps for the bug are not only dead simple, but also extremely common user behavior…


Steps To Reproduce

  1. Ensure that full page cache is turned on
  2. Navigate to a category page
  3. Apply any layered navigation filter
  4. Remove the filter you just applied

Expected result

Unfiltered category page is displayed

Actual result

Filtered category page continues to display


What’s worse, the cache for the unfiltered category page is now poisoned for any user who visits that page!

The good news it that there’s a patch available, SUPEE-9465. If that’s all you’re looking for I’ve posted it here.

However, because it’s interesting, let’s take at what went wrong starting in 1.14.3.0.

Cache Key Generation

I’m not going to go super deep on this topic (check out my Nomad Mage talk if you’re interested), but when visiting the category page Enterprise_PageCache_Model_Processor_Category is responsible for generating the cache key for the request. When the page is not coming from cache, getPageIdInApp is called to provide the cache key for saving the request. On the flip side, when Enterprise_PageCache attempts to load the response from cache, it calls getPageIdWithoutApp to get the key.

The Issue

The problem lies in the _getQueryParams method in Enterprise_PageCache_Model_Processor_Category. Specifically you’ll see this line in a vanilla 1.14.3.X install.

$queryParams = $this->_filterInputParameters(array_merge($this->_getSessionParams(), $_GET));

Prior to 1.14.3.X that line looked like this…

$queryParams = array_merge($this->_getSessionParams(), $_GET);

_filterInputParameters is a new method introduced in 1.14.3.0. If you look at it you’ll see it winds up stripping off anything not in the protected $_paramsMap property.

protected function _filterInputParameters($inputParameters)
{
    return array_intersect_key(
        !$inputParameters
            ? array()
            : $inputParameters,
        array_flip($this->_paramsMap)
    );
}

Here’s what’s in $_paramsMap

protected $_paramsMap = array(
    'display_mode'  => 'mode',
    'limit_page'    => 'limit',
    'sort_order'    => 'order',
    'sort_direction'=> 'dir',
);

The problem is that _getQueryParams is called by getPageIdInApp. What this means is that when saving an uncached category page, the cache key will not account for anything other than the parameters in $_paramsMap. In other words, viewing a filtered page will always poison the cache for unfiltered pages because the response will be saved with the same key that is generated when filters are not applied.

The Solution

To remediate the issue, SUPEE-9465 reverts that change.

@@ -180,7 +182,7 @@ class Enterprise_PageCache_Model_Processor_Category extends Enterprise_PageCache
     protected function _getQueryParams()
     {
         if (is_null($this->_queryParams)) {
-            $queryParams = $this->_filterInputParameters(array_merge($this->_getSessionParams(), $_GET));
+            $queryParams = array_merge($this->_getSessionParams(), $_GET);

It also marks _filterInputParameters as deprecated and removes all usage.

--- app/code/core/Enterprise/PageCache/Model/Processor/Category.php
+++ app/code/core/Enterprise/PageCache/Model/Processor/Category.php
@@ -49,6 +49,8 @@ class Enterprise_PageCache_Model_Processor_Category extends Enterprise_PageCache
      * Filter input parameters using parameters map
      * @param array $inputParameters
      *
+     * @deprecated
+     *
      * @return array
      */
     protected function _filterInputParameters($inputParameters)

My Thoughts

Personally, I’m shocked by the fact that Magento introduced this bug in v1.14.3.0 and released v1.14.3.1 without including the fix. From my perspective the severity of this issue is extreme…

In my opinion, releasing v1.14.3.2 with SUPEE-9465 included should be a top priority for Magento. Put plainly the latest version of Magento Enterprise contains a very high severity defect.

Conclusion

Hopefully this post help save a few people from time and money debugging this issue. If you have any questions or comments, feel free to drop a note below, or, as always, you can reach me on Twitter as well.

Hi, I'm Max!

I'm a software developer who mainly works in PHP, but also dabbles in Ruby and Go. Technical topics that interest me are monitoring, security and performance.

During the day I solve challenging technical problems at Something Digital where I mainly work with the Magento platform. I also blog about tech, work on open source and hunt for bugs.

If you'd like to get in touch with me the best way is on Twitter.