Problem/Motivation
Cron.php does not check for maintenance mode correctly and causes drupal_deliver_html_page() to call drupal_render_page() with an integer argument under some circumstances.
The problem is when a site has a custom 403 page set, maintenance mode is active and cron.php is accessed.
1. cron.php will call drupal_access_denied().
2. drupal_access_denied() will call drupal_deliver_html_page().
3. And drupal_deliver_html_page() will fail to recognize the maintenance mode, as the $return variable will have a value of MENU_SITE_OFFLINE:
case MENU_ACCESS_DENIED:
// Print a 403 page.
drupal_add_http_header('Status', '403 Forbidden');
watchdog('access denied', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
// Keep old path for reference, and to allow forms to redirect to it.
if (!isset($_GET['destination'])) {
// Make sure that the current path is not interpreted as external URL.
if (!url_is_external($_GET['q'])) {
$_GET['destination'] = $_GET['q'];
}
}
$path = drupal_get_normal_path(variable_get('site_403', ''));
if ($path && $path != $_GET['q']) {
// Custom 403 handler. Set the active item in case there are tabs to
// display or other dependencies on the path.
menu_set_active_item($path);
$return = menu_execute_active_handler($path, FALSE);
}
if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) {
// Standard 403 handler.
drupal_set_title(t('Access denied'));
$return = t('You are not authorized to access this page.');
}
print drupal_render_page($return);
break;
This will cause errors:
Cannot use a scalar value as an array block.module:271
Invalid argument supplied for foreach() common.inc:6594
Cannot use a scalar value as an array common.inc:6051
Cannot use a scalar value as an array common.inc:6106
Steps to reproduce
Start with a clean site (ie. minimal profile), then:
- Switch on maintenance_mode
drush vset maintenance_mode 1 - Set a custom access denied page
drush vset site_403 'non-empty' - Access the cron.php of your site
curl https://example.com/cron.php
The result is this set of errors:
Cannot use a scalar value as an array block.module:271
Invalid argument supplied for foreach() common.inc:6594
Cannot use a scalar value as an array common.inc:6051
Cannot use a scalar value as an array common.inc:6106
Depending on what othen modules you have activated there might be even more errors.
Proposed resolution
Fix the conditions in cron.php to return drupal_site_offline() instead of drupal_access_denied() when the maintenance mode is active.
==== Original report ====
Summary
In drupal_deliver_html_page() in some circumstances menu_execute_active_handler() is called and its return value is directly passed to drupal_render_page() even if it is an integer
Steps to reproduce
Start with a clean site (ie. minimal profile), then:
- Switch on maintenance_mode
drush vset maintenance_mode 1 - Set a custom access denied page
drush vset site_403 'non-empty' - Access the cron.php of your site
curl https://example.com/cron.php
The result is this set of errors:
Cannot use a scalar value as an array block.module:271
Invalid argument supplied for foreach() common.inc:6594
Cannot use a scalar value as an array common.inc:6051
Cannot use a scalar value as an array common.inc:6106
Depending on what othen modules you have activated there might be even more errors.
Analysis
drupal_deliver_html_page() checks only for MENU_NOT_FOUND and MENU_ACCESS_DENIED before passing the value to drupal_render_page(). This is not sufficent.
Proposed solution
Check for is_int($return) instead of just specific integers.
| Comment | File | Size | Author |
|---|---|---|---|
| #10 | 3007538-10.patch | 2.53 KB | poker10 |
| #10 | 3007538-10_test-only.patch | 1.61 KB | poker10 |
| #2 | 3007538-dont-render-integers-2.patch | 1.07 KB | torotil |
Comments
Comment #2
torotil commentedHere is an attempt to fix this.
Comment #3
torotil commentedComment #4
torotil commentedComment #5
torotil commentedComment #6
damienmckennaWith this step:
What is something? Is it a node system path, e.g. "node/123"? Is it a node alias? Is it the system path for a custom menu item? This might be the cause of the problem.
Comment #7
torotil commentedHi Damien,
the steps to reproduce are complete if you take the commands literally. It doesn’t matter what’s the actual value of
site_403as long as it’s not empty (or more preciselydrupal_get_normal_path(variable_get('site_403'), '')must be non-empty). It’s simply about this block of code:If the first if is executed, but the site is in maintenance mode then
menu_execute_active_handler($path, FALSE);returnsMENU_SITE_OFFLINE(=4). The second if is not executed. Thendrupal_render_page(4)is called.Comment #8
torotil commentedComment #9
Jorrit commentedTo clarify the problem:
menu_execute_active_handler()may returnMENU_SITE_OFFLINEand this is passed directly todrupal_render_page()indrupal_deliver_html_page().The patch works for any case where the above situation happens.
A different solution for the specific case that torotil and I encounter would be to replace
drupal_access_denied()withdrupal_site_offline()on line 22 of cron.php.Comment #10
poker10 commentedThanks for the patch and report. I think that the
menu_execute_active_handler()can hypothetically return other integers via this part of the code:So changing that condition to
is_int($return)could potentially cause problems in other edge cases. I have tested this and it seems reproducible only with cron.php - if you visit any other URL (existing, non-existing, or without access) then the correct maintenance page is returned in each case. So I think it will be better to fix this directly in cron.php.Yes, but we cannot change both
drupal_access_denied()in cron.php to thedrupal_site_offline(), because in the regular scenario (without maintenance mode) the user should get an access denied when thecron_keyis empty / invalid. But what if we switch the conditions in cron.php to check the maintenance mode first and then only thecron_key? Then we can change the firstdrupal_access_denied()to thedrupal_site_offline()and also keep the seconddrupal_access_denied(). See my proposed patch (not attaching an interdiff as my changes are completely different). Let's check if this passes the tests.Comment #12
poker10 commentedAdding a tag for the other maintainer review.
Comment #13
poker10 commentedComment #14
mcdruid commentedThis LGTM (but I am not the Framework Manager!)
Comment #16
poker10 commentedDiscussed this on Slack with other D7 maintainers and @Fabianx reviewed this as well:
We will fix the issue in the
cron.php, as this was the only place where the problem was reproduced and it should be a safer approach. If another usecase would appear in the future, we will take a look at that separately.Updated the IS and title, over to @mcdruid for commit. Thanks!
Comment #18
mcdruid commentedThank you everybody!