Interruption to the database can cache a redirect to the installer page for all users

Created on 1 March 2022, over 2 years ago
Updated 22 August 2024, about 2 months ago

Problem/Motivation

If the database connection is interrupted momentarily, and a database exception is thrown, there is a set of circumstances where a redirect to the installer page could be stored into the page cache if it uses a non-database storage. This will persist and redirect all users for a specific page to the installer page until the cache is cleared.

Steps to reproduce

Difficult to reproduce but with a specific forced failure point the problematic code paths can be demonstrated. Albeit, this forced failure is a specific exception that is unlikely to occur and the real cause is slightly more involved and explained after the steps to reproduce quickly.

1. Install page_cache and configure it to cache for 24 hours
2. Edit user module src/Form/UserLoginForm.php and throw an instance of \Drupal\Core\Database\DatabaseNotFoundException
3. Access /user/login using a CLI tool that shows headers
4. Note that X-Drupal-Cache: MISS and you receive a redirect
5. Access /user/login again
6. Note that X-Drupal-Cache: HIT and you receive the same redirect

Main contributor to the issue

The subscriber at core/lib/Drupal/Core/EventSubscriber/RedirectResponseSubscriber.php will see a redirect to the installer and see that it is not a SecuredRedirectResponse and will subsequently convert it to a LocalRedirectResponse. Unfortunately, this is cacheable, whereas the original redirect was not.

The redirect to the installer is generated by the subscriber in core/lib/Drupal/Core/EventSubscriber/ExceptionDetectNeedsInstallSubscriber.php. This generates a regular RedirectResponse. The fix may be for it to return a LocalRedirectResponse with max-age set to 0, or a TrustedRedirectResponse with cacheability also disabled.

Specific on the database failure, and a minor contributor

Looking at code it is attempting to avoid a redirect to the installer, as you can see in core/lib/Drupal/Core/Installer/InstallerRedirectTrait.php in the method shouldRedirectToInstaller. This works in general except for the final check for tableExists on the sessions table, which will occur in the case of the database connection being lost and a PDOException being raised (MySQL in my case). However, this tableExists does not work.

The MySQL schema driver at core/lib/Drupal/Core/Database/Driver/mysql/Schema.php implements tableExists in a way that will never throw an exception. It will always assume any failure to be the table not existing. This causes the lost connection to generate the original installer redirect.

This issue won't occur for anyone running PgSQL or SQLite as those don't capture exceptions from the tableExists check.

Proposed resolution

1. Return a LocalRedirectResponse with no cacheability from the exception listener so that the page_cache module on the stack can be told not to cache it. *OR* Implement a new response that does not implement cacheability but does implement SecuredRedirectResponse - currently there are no implementations that do not implement CacheableResponseInterface.
(TBF I have realised the max-age 0 is ignored by page_cache currently still... so the latter may be better option, or a new response policy to deny this specific redirect.)

2. (Will raise in a follow up) Improve exception handling in MySQL schema so that it can correctly throw if there are connectivity issues, allowing the shouldRedirectToInstaller to behave in a better way and avoid showing installer pages to visitors when a MySQL database connection is interrupted

Remaining tasks

1. Fix caching issue of the installer redirect
2. https://www.drupal.org/project/drupal/issues/3267207 β†’ (Related and alternate fix that may be blocked on MySQL issues: https://www.drupal.org/project/drupal/issues/2797141 β†’ )
3. Write test coverage
4. update for trigger_error

User interface changes

None

API changes

None, unless a new redirect response is added that implements SecuredRedirectResponse but not CacheableResponseInterface

Data model changes

None

Release notes snippet

N/A

πŸ› Bug report
Status

Needs work

Version

11.0 πŸ”₯

Component
InstallΒ  β†’

Last updated 4 days ago

No maintainer
Created by

πŸ‡¬πŸ‡§United Kingdom Driskell

Live updates comments and jobs are added and updated live.
  • Needs tests

    The change is currently missing an automated test that fails when run with the original code, and succeeds when the bug has been fixed.

  • Needs change record

    A change record needs to be drafted before an issue is committed. Note: Change records used to be called change notifications.

Sign in to follow issues

Merge Requests

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

Production build 0.71.5 2024