?
avatar eorisis
eorisis
17 Feb 2017

Steps to reproduce the issue

This is on a Joomla 3.7.0-beta2.
First go to the Joomla Global Configuration and set Error Reporting to "None". Save.
Then, stop mysql server. In my case I use Debian, so all I have to do is:

  • Make sure it is running (just to know)
    systemctl status mysql.service

  • Then stop it
    systemctl stop mysql.service

Before any server script automatically starts it again, all we need to do is refresh the Joomla site.

Expected result

Blank page, nothing to show, similar to what would happen if there was a php error and Error Reporting option in Global Configuration was set to "None".

Actual result

  • Using the PDO driver we get the error:
    Error displaying the error page: Application Instantiation Error: Could not connect to PDO: SQLSTATE[HY000] [2002] Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)

  • Using the MySQL or MySQLi drivers, we get the error:
    Error displaying the error page: Application Instantiation Error: Could not connect to MySQL.

System information (as much as possible)

PHP Built On: Linux development-server 3.16.0-4-amd64 #1 SMP Debian 3.16.39-1 (2016-12-30) x86_64
Database Version: 5.5.54-0+deb8u1
Database Collation: utf8mb4_general_ci
Database Connection Collation utf8mb4_general_ci
PHP Version: 5.6.30-0+deb8u1
Web Server: Apache
WebServer to PHP Interface: cgi-fcgi
Joomla! Version: Joomla! 3.7.0-beta2 Beta [ Amani ] 8-February-2017 14:11 GMT
Joomla! Platform Version: Joomla Platform 13.1.0 Stable [ Curiosity ] 24-Apr-2013 00:00 GMT
User Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:51.0) Gecko/20100101 Firefox/51.0

Additional comments

The reason I consider this issue a serious problem is because it exposes data to the public on a production server environment, which is something that should not happen. When rebooting a server machine or even restarting mysql service, or even if mysql goes down at some point, we get an error while mysql is down originating from the Joomla framework which should not be there on a production server setup, while the Error Reporting option in Global Configuration is set to "None". While not everyone has access to a server setup in order to set the web server process to wait before the mysql server starts, as in most server environments the web server will indeed start before mysql, I do not think even changing this order would be THE fix. In this particular case, PHP is responsible for handling such errors.

How the error originates:

  • In case of PDO driver:
    JDatabaseDriverPdo class executing connect() in file /libraries/joomla/database/driver/pdo.php
    Line 309 will throw an error like this:
    Could not connect to PDO: SQLSTATE[HY000] [2002] Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)

  • In case of MySQL / MySQLi drivers, it takes place in files mysql.php and mysqli.php respectively in a similar way (same path /libraries/joomla/database/driver/).
    MySQL / MySQLi Error Example:
    Could not connect to MySQL.

  • Then later on, JErrorPage will render() that error. in this process it will first execute line 61:
    $app = JFactory::getApplication();

  • Which will add:
    Application Instantiation Error:

  • And on line 148 it will be prefixed with:
    Error displaying the error page:
    ..because of:
    $message = 'Error displaying the error page';

..resulting in the mentioned errors above.

The fix:

Once we do want the error logged, but we only want it to disappear from the browser, all we have to do is not echo() it. So in the file:
/libraries/cms/error/page.php

Lines 148 to 162 are responsible for this:

$message = 'Error displaying the error page';

if ($isException)
{
	$message .= ': ';

	if (isset($e))
	{
		$message .= $e->getMessage() . ': ';
	}

	$message .= $error->getMessage();
}

echo $message;

What I recommend doing is enclosing these lines in a condition which will respect the Error Reporting setting. I originally checked the global config setting, but now this is the most safe and correct way to do it. In this case I only have to check if the integer of ini_get('display_errors') is not equal to 0. I do this because one may have Error Reporting set to System Default in the Joomla Global Configuration and configure the vhost or php.ini on the server to have display_errors= either 0 or off.

// Make sure we do not display sensitive data in production environments
if ((int)ini_get('display_errors') != 0)
{
	$message = 'Error displaying the error page';

	if ($isException)
	{
		$message .= ': ';

		if (isset($e))
		{
			$message .= $e->getMessage() . ': ';
		}

		$message .= $error->getMessage();
	}

	echo $message;
}

A simplified version of the IF statement could also work:
if (ini_get('display_errors')) {...}

I have attached the modified version of the file for one to check (updated version of my original suggestion -- See comments below).
page.tar.gz

Of course one can be more specific in what to display in case of a different Error Reporting setting, but that only if one wants to do so.

Thanks for your attention,
George

avatar eorisis eorisis - open - 17 Feb 2017
avatar joomla-cms-bot joomla-cms-bot - change - 17 Feb 2017
Labels Added: ?
avatar joomla-cms-bot joomla-cms-bot - labeled - 17 Feb 2017
avatar eorisis eorisis - edited - 17 Feb 2017
avatar mbabker
mbabker - comment - 17 Feb 2017

The intent of that last part of the check is if there is a fatal error rendering the error.php template file to still display at least the error message that would be used by default in the template. Granted, many templates these days are not directly displaying the Exception message but use a more generic message or layout, but considering this is a message of last resort I don't know if I would make this part of the error handler aware of the error reporting configuration.

avatar eorisis
eorisis - comment - 17 Feb 2017

Hi mbabker,

I do think that a production setup in regards to security is far more important than the reason for allowing such errors to go public. What is the point of setting Error Reporting to none if we are to still allow errors to be reported ? And of course, if one wants to see the errors they would never set Error Reporting to None would they ?

avatar mbabker
mbabker - comment - 17 Feb 2017

The error reporting setting is actually for managing PHP's internal error reporting configuration, not related to whether Joomla will display errors built by its own code such as in the global exception handler. Otherwise you could argue that setting should disable the error page in full if you've turned off error reporting and the user should just see a blank screen on any error condition instead of a rendered template file.

To reach that specific output, you have to have a scenario where Joomla critically fails during its startup process (to the point an application isn't yet set in the factory, as you've pointed out) or the error page in the template tries to do something that results in an error. So it is legitimately a last resort effort to tell the user something went wrong. I don't think it should result completely in a blank screen as you propose here. If anything is going to be conditional, it should be whether the Exception message is displayed, but nothing more.

With 3.7 a database connection failure shouldn't result in triggering that code path thanks to #13074 (the reason it will do that now is because sessions are eagerly started in the application constructor which includes a SELECT query and potential INSERT query; this is now deferred until the application's execution begins which is after a point the application is set to JFactory).

avatar eorisis
eorisis - comment - 17 Feb 2017

Before I go and check the #13074 issue you gave me, a quick answer of mine is that, right after the message gets echoed, the code runs jexit(1); which will end the page anyway ? Correct me if I am wrong, and let me check the issue you mention.

avatar mbabker
mbabker - comment - 17 Feb 2017

It does run jexit() because that method is the global error handler so it is purposing shutting down the application. In truth I don't think that call is actually needed though (we shouldn't need to immediately call exit; but rather just let it return and let PHP finish the shutdown process).

avatar eorisis
eorisis - comment - 17 Feb 2017

I am not really sure in which cases JErrorPage will reach to execute echo $message; but to all the tests I have done so far, it does not take place on an html page (say on a 404), rather, it will always echo a message only, and just that (white browser background). Is that the case ?

avatar mbabker
mbabker - comment - 17 Feb 2017

Ways to reach that path:

  • JErrorPage::render() receives a non-Exception/Throwable object as its parameter (it's not typehinted to allow PHP 7 Throwables to be handled and this can't be polyfilled in PHP 5)
  • The logic to render a template's error.php file ends up throwing an Exception (the simplest way to mock this is to just add a throw new Exception('Testing error handling'); in that file). Other ways that might do it before trying to render error.php would be if JFactory::getApplication() or JFactory::getLanguage() result in an Exception being thrown.
avatar eorisis
eorisis - comment - 17 Feb 2017

Indeed. Then the DB errors should be handled differently.

avatar eorisis
eorisis - comment - 17 Feb 2017

One kinda dirty way is to also ask something like this:
(get_class($error) == 'JDatabaseExceptionConnecting')

But that would require that in the future all db errors call the same class.

avatar mbabker
mbabker - comment - 17 Feb 2017

Honestly, I don't think the handling when it reaches this point should differentiate based on the Exception subclass. If there is going to be any change (and personally I don't think it needs to be because this is a desperate attempt to tell the user something's broken instead of having a useless blank screen which wouldn't even get logged into PHP since it's either going to be a non-Exception object that would never trigger PHP's error logging or the result of an error thrown by Joomla rendering the page which won't log because the handler is catching the error), at most it should just be a conditional around displaying the Exception message (as in "Error displaying the error page" should always show up if you get to that point in the code).

avatar eorisis
eorisis - comment - 17 Feb 2017

Well we are not only talking about the admin or webmaster but also the visitor, which should not see db error messages that reveals operating system paths -- it is just very lame and proves no security measures are taken. Now once this condition is only at the very last part of this file, which only is about to echo() a message and then exit(), any logging has been done already anyway. So yes it is conditional around displayng the Exception message as you say, but only caring in this case of serious errors such as db ones.

In any case, If I was to do this to my own cms I would not just ask for the name of the class, I would rewrite the whole thing. This is why I consider this "if" statement there more of a dirty hack than of a serious approach.

If I knew that the file is not only responsible for serious errors but also renders less important ones I would maybe post a different approach all together.

avatar mbabker
mbabker - comment - 17 Feb 2017

Any error reaching this point in the code is serious and could result in a security leak. Filesystem errors as an example. Hence the reason I wouldn't do conditions based on Exception subclasses. We also have no idea what information is displayed in the Exception message, so saying any Exception type is blindly safe to render the message for isn't very accurate.

If there's an error rendering the error page (the entire try/catch block in the $isException condition at the start of that method), nothing gets logged to PHP for that error case because Joomla's catching that error and going to its fallback mechanism without either trying to log using our own API or an error_log(); call. So while the primary error triggering the page may have been logged to PHP and/or Joomla's logging mechanisms, the secondary error resulting in you hitting that section of code isn't.

So the only thing that should be conditional, if any conditional behavior based on error reporting is going to be introduced, should be the display of the Exception message. If it's off don't show it, but if error_reporting() > 0 then it should show the message regardless of type.

avatar eorisis
eorisis - comment - 17 Feb 2017

If I understand you well, in /includes/framework.php we get the error_reporting(0); by checking if $config->error_reporting is either none or 0. Which is my original approach but you were against it, no ?

avatar mbabker
mbabker - comment - 17 Feb 2017

The approach you suggested wrapped the entire echo statement in that type of check. I would only wrap https://github.com/joomla/joomla-cms/blob/staging/libraries/cms/error/page.php#L150-L160 so that "Error displaying the error page" is always echoed regardless of the error reporting configuration. That to me seems like an acceptable compromise, showing SOMETHING would be better than a blank screen.

avatar eorisis
eorisis - comment - 17 Feb 2017

Oh, one would have it set to System Default default and have it set to off or 0 in the vhost. So yeah the question should not be the global config setting, rather:

if ((int)ini_get('display_errors') != 0)

Agreed.

avatar eorisis
eorisis - comment - 17 Feb 2017

Ok that is a good compromise, I just tried it and I got:
Error displaying the error page

avatar eorisis
eorisis - comment - 17 Feb 2017

It now looks like this:

$message = 'Error displaying the error page';

if ((int)ini_get('display_errors') != 0)
{
	if ($isException)
	{
		$message .= ': ';

		if (isset($e))
		{
			$message .= $e->getMessage() . ': ';
		}

		$message .= $error->getMessage();
	}
}

echo $message;

page.tar.gz

avatar eorisis
eorisis - comment - 17 Feb 2017

I switched it around, this way it is faster (possibly by some nanoseconds):

$message = 'Error displaying the error page';

if ($isException)
{
	// Make sure we do not display sensitive data in production environments
	if ((int)ini_get('display_errors') != 0)
	{
		$message .= ': ';

		if (isset($e))
		{
			$message .= $e->getMessage() . ': ';
		}

		$message .= $error->getMessage();
	}
}

echo $message;

page.tar.gz

avatar eorisis eorisis - change - 17 Feb 2017
The description was changed
avatar eorisis eorisis - edited - 17 Feb 2017
avatar eorisis eorisis - change - 17 Feb 2017
The description was changed
avatar eorisis eorisis - edited - 17 Feb 2017
avatar eorisis eorisis - change - 17 Feb 2017
The description was changed
avatar eorisis eorisis - edited - 17 Feb 2017
avatar eorisis eorisis - change - 17 Feb 2017
The description was changed
avatar eorisis eorisis - edited - 17 Feb 2017
avatar eorisis eorisis - change - 17 Feb 2017
The description was changed
avatar eorisis eorisis - edited - 17 Feb 2017
avatar eorisis eorisis - change - 17 Feb 2017
The description was changed
avatar eorisis eorisis - edited - 17 Feb 2017
avatar eorisis eorisis - change - 17 Feb 2017
The description was changed
avatar eorisis eorisis - edited - 17 Feb 2017
avatar eorisis eorisis - change - 17 Feb 2017
The description was changed
avatar eorisis eorisis - edited - 17 Feb 2017
avatar eorisis eorisis - change - 17 Feb 2017
The description was changed
avatar eorisis eorisis - edited - 17 Feb 2017
avatar eorisis eorisis - change - 17 Feb 2017
The description was changed
avatar eorisis eorisis - edited - 17 Feb 2017
avatar eorisis eorisis - change - 17 Feb 2017
The description was changed
avatar eorisis eorisis - edited - 17 Feb 2017
avatar rdeutz
rdeutz - comment - 24 Feb 2017

Haven't read the messages here but I have the PR so this can be closed if I am not mistaken.

avatar rdeutz rdeutz - change - 24 Feb 2017
Status New Closed
Closed_Date 0000-00-00 00:00:00 2017-02-24 21:05:54
Closed_By rdeutz
avatar rdeutz rdeutz - close - 24 Feb 2017

Add a Comment

Login with GitHub to post a comment