719-286-0751 [email protected]

Magento Homepage Redirect / Install Redirect Bug

Magento Homepage Redirect / Install Redirect Bug

I wanted to get a brief article out on this nasty bug, which has affected a few of our clients. The bug manifests in the following way:

  1. Requests will “randomly” redirect the user to the homepage
  2. Upon further inspection, you will see headers like:
    302 – Moved Temporarily
    Location: http://domain.com/install
    302 – Moved Temporarily
    Location: http://domain.com
  3. Sometimes your browser will crash, saying “Too many redirects” or “Redirect limit exceeded”

What the heck is going on?? As several of our clients have noted, the bug seems entirely random – some pages will work fine, others won’t.

The Problem

To understand what’s happening, we have to backup to a Magento security release meant to address an XML Bomb attack (for a description of this attack, read here: https://www.soapui.org/security-testing/security-scans/xml-bomb.html). Magento added some security features that involve the use of


libxml_disable_entity_loader(true);

Now, this function is meant to prevent users from exploiting the aforementioned vulnerability. However, there are 2 php bugs that come into play on a server using php-fpm.

The two bugs are:

The first bug means that if we run libxml_disable_entity_loader(true) our process can no longer load any files using simplexml_load_file. The second bug means that if we call libxml_disable_entity_loader(true) on a thread – it will persist through all requests serviced by that thread, and any other threads spawned while it’s disabled.

So, this means the bug is much nastier on a php-fpm setup than on a prefork setup like apache (although the bug can still be an issue). Magento uses libxml_disable_entity_loader(true) when servicing certain API requests. You will notice in app/code/core/Zend/Xml/Security.php that the Magento core team has made some accommodations (in newer versions) for php fpm setups:


if (!self::isPhpFpm()) {
            $loadEntities = libxml_disable_entity_loader(true);
            $useInternalXmlErrors = libxml_use_internal_errors(true);
        }

However, this sort of fpm check is NOT used in the following locations:

  • lib/Zend/Soap/Wsdl.php
  • lib/Zend/XmlRpc/Request.php
  • lib/Zend/XmlRpc/Response.php
  • lib/Zend/Feed/Reader.php
  • app/code/core/Mage/Api2/Model/Request/Interpreter/Xml.php

OK – so, the question is: why is not being able to use simplexml_load_file a big deal?

Take a look at this code from Mage.php


public static function isInstalled($options = array())
    {
        if (self::$_isInstalled === null) {
            self::setRoot();

            if (is_string($options)) {
                $options = array('etc_dir' => $options);
            }
            $etcDir = self::getRoot() . DS . 'etc';
            if (!empty($options['etc_dir'])) {
                $etcDir = $options['etc_dir'];
            }
            $localConfigFile = $etcDir . DS . 'local.xml';

            self::$_isInstalled = false;

            if (is_readable($localConfigFile)) {
                $localConfig = simplexml_load_file($localConfigFile);
                date_default_timezone_set('UTC');
                if (($date = $localConfig->global->install->date) && strtotime($date)) {
                    self::$_isInstalled = true;
                }
            }
        }
        return self::$_isInstalled;
    }

Notice the simplexml_load_file call right there. Basically, if Magento can’t load app/etc/local.xml it convinces itself there is no application installed! Let’s break down what happens:

  1. A WSDL call or XML RPC call forces libxml_disable_entity_loader(true)
  2. This setting is persisted through multiple requests on the given thread.
  3. If a thread where libxml_disable_entity_loader(true) services another (non api) request, Magento is unable to load its configuration file.
  4. Magento redirects to /install
  5. If a different thread that is not corrupted picks up the request, it notes Magento *is* installed and redirects back to the homepage.
  6. If a different thread that is corrupted (or the same thread) picks up the request, you begin an infinite redirect cycle.

The Fix

The fix is to add the following to your index.php file, above Mage::run


if (function_exists('libxml_disable_entity_loader')) {
    libxml_disable_entity_loader(false);
}
Mage::run($mageRunCode, $mageRunType);

That’s it! Amazing…only a couple lines of code! The fix works by forcing each new request to “reset” the libxml_disable_entity_loader status, allowing Magento to read its configuration file.

Alan Barber is the Lead Web Developer at Cadence Labs and a Magento Certified developer.

3 Comments

  1. Anil Taheam

    Thanks ! it worked

    Reply
  2. g

    Thank you!! 2 days of troubleshooting & we were still having this error. 2 minutes of editing the code & brilliant- It Worked!!

    Reply
  3. Ricardo

    Thank you!! Worked like a charm!

    Reply

Submit a Comment

Your email address will not be published. Required fields are marked *

Install our webapp on your iPhone! Tap and then Add to homescreen.
Share This