Page MenuHomePhabricator

Memory exhausted when $wgBlockDisablesLogin=true
Closed, ResolvedPublicBUG REPORT

Description

What is the problem?

If I have GlobalBlocking installed, $wgBlockDisablesLogin = true and I am logged in as a locally unblocked user, if I load any page on my wiki it will time out with an exception of the form (the PHP file isn't always the same):

PHP Fatal Error from line 190 of /var/www/html/w/includes/Permissions/GroupPermissionsLookup.php: Allowed memory size of 134217728 bytes exhausted (tried to allocate 12288 bytes)

In the web logs I see thousands of the same line repeated:

...
[BlockManager] Block cache miss with key BlockCacheKey{request=none,user=#1051,replica}
[rdbms] Wikimedia\Rdbms\LoadBalancer::reuseOrOpenConnectionForNewRef: reusing connection for 0/enwiki
[rdbms] MediaWiki\Block\DatabaseBlockStore::newLoad [0.315ms] mariadb-main: SELECT  bl_id,bt_address,bt_user,bt_user_text,bl_timestamp,bt_auto,bl_anon_only,bl_create_account,bl_enable_autoblock,bl_expiry,bl_deleted,bl_block_email,bl_allow_usertalk,bl_parent_block_id,bl_sitewide,bl_by_actor,block_by_actor.actor_user AS `bl_by`,block_by_actor.actor_name AS `bl_by_text`,comment_bl_reason.comment_text AS `bl_reason_text`,comment_bl_reason.comment_data AS `bl_reason_data`,comment_bl_reason.comment_id AS `bl_reason_cid`  FROM `block` JOIN `block_target` ON ((bt_id=bl_target)) JOIN `actor` `block_by_actor` ON ((actor_id=bl_by_actor)) JOIN `comment` `comment_bl_reason` ON ((comment_bl_reason.comment_id = bl_reason_id))   WHERE ((bt_user = 15233))  
[BlockManager] Block cache miss with key BlockCacheKey{request=none,user=#1051,replica}
[rdbms] Wikimedia\Rdbms\LoadBalancer::reuseOrOpenConnectionForNewRef: reusing connection for 0/enwiki
[rdbms] MediaWiki\Block\DatabaseBlockStore::newLoad [0.289ms] mariadb-main: SELECT  bl_id,bt_address,bt_user,bt_user_text,bl_timestamp,bt_auto,bl_anon_only,bl_create_account,bl_enable_autoblock,bl_expiry,bl_deleted,bl_block_email,bl_allow_usertalk,bl_parent_block_id,bl_sitewide,bl_by_actor,block_by_actor.actor_user AS `bl_by`,block_by_actor.actor_name AS `bl_by_text`,comment_bl_reason.comment_text AS `bl_reason_text`,comment_bl_reason.comment_data AS `bl_reason_data`,comment_bl_reason.comment_id AS `bl_reason_cid`  FROM `block` JOIN `block_target` ON ((bt_id=bl_target)) JOIN `actor` `block_by_actor` ON ((actor_id=bl_by_actor)) JOIN `comment` `comment_bl_reason` ON ((comment_bl_reason.comment_id = bl_reason_id))   WHERE ((bt_user = 15233))  
...

It seemed to start happening after https://gerrit.wikimedia.org/r/c/mediawiki/extensions/GlobalBlocking/+/1005621 change for T358155.

I have reproduced it on:

  • a CentralAuth connected local docker environment with MariaDB
  • a completely new local docker development environment with SQLite3, only GlobalBlocking installed and no blocks
Steps to reproduce problem
  1. On a wiki with GlobalBlocking installed and $wgBlockDisablesLogin = true;
  2. Login as a user who is not locally blocked (whether they are globally blocked does not seem to matter)

Expected behaviour: You login successfully and the page loads
Observed behaviour: It times out

Environment

Wiki(s): local docker MediaWiki 1.44.0-alpha (891bce1) 08:15, 20 January 2025. GlobalBlocking – (f06f561) 07:23, 20 January 2025.

Event Timeline

Found the cause:

  1. A call to BlockManager::getBlock is made
  2. This runs the GetUserBlock hook
  3. This calls the GlobalBlocking implementation of the hook
  4. This leads to a call to GlobalBlockLookup::getUserBlock
  5. Then, to a private helper method , which calls Authority::isAllowedAny
  6. When this is a UserAuthority instance, this calls PermissionManager::userHasAnyRight
  7. Which then calls PermissionManager::getUserPermissions
  8. When $wgBlockDisablesLogin = true;, this causes a check to be made if the user is currently blocked
  9. Which then calls BlockManager::getBlock

We should be able to break the loop somewhere in GlobalBlockLookup, in a similar way to how it is done in core.

To support this we need to:

  1. Update GetUserBlock to pass the $request passed to BlockManager::getBlock to handlers of the hook
  2. Update GlobalBlockLookup::getUserBlock to accept the $request and pass it through
  3. Update the private helper method ::getUserBlockDetails to accept the $request and if it is null then do not call Authority::isAllowed

Per the docs for BlockManager::getBlock, the $request would be null if the user has IP block exempt or IP blocks are not being checked.

Change #1124753 had a related patch set uploaded (by Máté Szabó; author: Máté Szabó):

[mediawiki/core@master] permissions: Avoid potential infinite loop if BlockDisablesLogin = true

https://gerrit.wikimedia.org/r/1124753

Change #1124753 merged by jenkins-bot:

[mediawiki/core@master] permissions: Avoid potential infinite loop if BlockDisablesLogin = true

https://gerrit.wikimedia.org/r/1124753

Change #1127179 had a related patch set uploaded (by Reedy; author: Máté Szabó):

[mediawiki/core@REL1_43] permissions: Avoid potential infinite loop if BlockDisablesLogin = true

https://gerrit.wikimedia.org/r/1127179

Change #1127180 had a related patch set uploaded (by Reedy; author: Máté Szabó):

[mediawiki/core@REL1_42] permissions: Avoid potential infinite loop if BlockDisablesLogin = true

https://gerrit.wikimedia.org/r/1127180

Change #1127180 merged by jenkins-bot:

[mediawiki/core@REL1_42] permissions: Avoid potential infinite loop if BlockDisablesLogin = true

https://gerrit.wikimedia.org/r/1127180

Change #1127179 merged by jenkins-bot:

[mediawiki/core@REL1_43] permissions: Avoid potential infinite loop if BlockDisablesLogin = true

https://gerrit.wikimedia.org/r/1127179

Djackson-ctr subscribed.

QA is completed, I have verified the new code has been implemented and is functioning as expected (With GlobalBlocking extension installed, and using the LocalSettings.php configuration setting $wgBlockDisablesLogin = true, all pages that are accessed by the blocked-but-not-locally-blocked user will now load successfully without timing-out).