Page MenuHomePhabricator

CVE-2026-34089: Memory leak in Scribunto causes runJobs.php to run out of memory
Closed, ResolvedPublicSecurity

Description

Filing as a security task since the reproduction steps below can be used to crash the jobrunner on any third party wiki using runJobs.php.

Reproduction steps

  1. Create Module:MemLeakTest with
return {
    main = function ( f )
        local a = 'Paaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
        a = a..a..a..a..a
        a = a..a..a..a
        a = a..a..a..a
        a = a..a..a..a
        a = a..a..a..a
        a = a..a..a..a
        a = a..a..a..a
        a = a..a..a..a
        a = a..a..a..a
        a = a..a
        return a
    end,
    -- edit this line to trigger jobs
}
  1. Create MemLeakTest/1 through 9 with {{#invoke:MemLeakTest|main}}
  2. Run runJobs.php --wait --type=refreshLinks
  3. Edit Module:MemLeakTest to trigger the jobs
➜  MainContrib git:(main) mwutil run runJobs.php -- --wait --type=refreshLinks
2026-03-05 21:12:29 refreshLinks Module:MemLeakTest table=templatelinks recursive=1 rootJobIsSelf=1 rootJobSignature=394e16ee44bc345d000ce77814c050eb9e356d73 rootJobTimestamp=20260305211229 causeAction=edit-page causeAgent=CentralAdmin namespace=828 title=MemLeakTest requestId=bbb9f46d54ed3cca29430242 (id=1450,timestamp=20260305211229) STARTING
2026-03-05 21:12:29 refreshLinks Module:MemLeakTest table=templatelinks recursive=1 rootJobIsSelf=1 rootJobSignature=394e16ee44bc345d000ce77814c050eb9e356d73 rootJobTimestamp=20260305211229 causeAction=edit-page causeAgent=CentralAdmin namespace=828 title=MemLeakTest requestId=bbb9f46d54ed3cca29430242 (id=1450,timestamp=20260305211229) t=12 good
2026-03-05 21:12:29 refreshLinks Module:MemLeakTest pages={"184":[0,"MemLeakTest/9"]} rootJobSignature=394e16ee44bc345d000ce77814c050eb9e356d73 rootJobTimestamp=20260305211229 triggeredRecursive=1 causeAction=edit-page causeAgent=CentralAdmin namespace=828 title=MemLeakTest requestId=bbb9f46d54ed3cca29430242 (id=1463,timestamp=20260305211229) STARTING
2026-03-05 21:12:29 refreshLinks Module:MemLeakTest pages={"184":[0,"MemLeakTest/9"]} rootJobSignature=394e16ee44bc345d000ce77814c050eb9e356d73 rootJobTimestamp=20260305211229 triggeredRecursive=1 causeAction=edit-page causeAgent=CentralAdmin namespace=828 title=MemLeakTest requestId=bbb9f46d54ed3cca29430242 (id=1463,timestamp=20260305211229) t=268 good
2026-03-05 21:12:29 refreshLinks Module:MemLeakTest pages={"180":[0,"MemLeakTest/5"]} rootJobSignature=394e16ee44bc345d000ce77814c050eb9e356d73 rootJobTimestamp=20260305211229 triggeredRecursive=1 causeAction=edit-page causeAgent=CentralAdmin namespace=828 title=MemLeakTest requestId=bbb9f46d54ed3cca29430242 (id=1459,timestamp=20260305211229) STARTING
2026-03-05 21:12:29 refreshLinks Module:MemLeakTest pages={"180":[0,"MemLeakTest/5"]} rootJobSignature=394e16ee44bc345d000ce77814c050eb9e356d73 rootJobTimestamp=20260305211229 triggeredRecursive=1 causeAction=edit-page causeAgent=CentralAdmin namespace=828 title=MemLeakTest requestId=bbb9f46d54ed3cca29430242 (id=1459,timestamp=20260305211229) t=38 good
2026-03-05 21:12:29 refreshLinks Module:MemLeakTest pages={"182":[0,"MemLeakTest/7"]} rootJobSignature=394e16ee44bc345d000ce77814c050eb9e356d73 rootJobTimestamp=20260305211229 triggeredRecursive=1 causeAction=edit-page causeAgent=CentralAdmin namespace=828 title=MemLeakTest requestId=bbb9f46d54ed3cca29430242 (id=1461,timestamp=20260305211229) STARTING
2026-03-05 21:12:29 refreshLinks Module:MemLeakTest pages={"182":[0,"MemLeakTest/7"]} rootJobSignature=394e16ee44bc345d000ce77814c050eb9e356d73 rootJobTimestamp=20260305211229 triggeredRecursive=1 causeAction=edit-page causeAgent=CentralAdmin namespace=828 title=MemLeakTest requestId=bbb9f46d54ed3cca29430242 (id=1461,timestamp=20260305211229) t=26 good
2026-03-05 21:12:29 refreshLinks Module:MemLeakTest pages={"178":[0,"MemLeakTest/3"]} rootJobSignature=394e16ee44bc345d000ce77814c050eb9e356d73 rootJobTimestamp=20260305211229 triggeredRecursive=1 causeAction=edit-page causeAgent=CentralAdmin namespace=828 title=MemLeakTest requestId=bbb9f46d54ed3cca29430242 (id=1457,timestamp=20260305211229) STARTING
2026-03-05 21:12:29 refreshLinks Module:MemLeakTest pages={"178":[0,"MemLeakTest/3"]} rootJobSignature=394e16ee44bc345d000ce77814c050eb9e356d73 rootJobTimestamp=20260305211229 triggeredRecursive=1 causeAction=edit-page causeAgent=CentralAdmin namespace=828 title=MemLeakTest requestId=bbb9f46d54ed3cca29430242 (id=1457,timestamp=20260305211229) t=32 good
2026-03-05 21:12:29 refreshLinks Module:MemLeakTest pages={"181":[0,"MemLeakTest/6"]} rootJobSignature=394e16ee44bc345d000ce77814c050eb9e356d73 rootJobTimestamp=20260305211229 triggeredRecursive=1 causeAction=edit-page causeAgent=CentralAdmin namespace=828 title=MemLeakTest requestId=bbb9f46d54ed3cca29430242 (id=1460,timestamp=20260305211229) STARTING
PHP Fatal error:  Allowed memory size of 157286400 bytes exhausted (tried to allocate 8847385 bytes) in /var/www/html/w/extensions/Scribunto/includes/Engines/LuaSandbox/LuaSandboxInterpreter.php on line 137

Cause

There is a memory leak in Scribunto since 47e6d1175363a29ccc3909a3d25b31751699c19a, which changed the logic to use a WeakMap to store the engine objects. The entries are not garbage collected because there is likely a reference cycle somewhere.

Calling ->destroy() manually seems to fix the issue:

public function destroyEngineForParser( Parser $parser ): void {
+		if ( isset( $this->engineForParser[$parser] ) ) {
+			$this->engineForParser[$parser]->destroy();
+		}
+
 		unset( $this->engineForParser[$parser] );
 	}

Additional information

MW 1.46.0-alpha (d03de87)
PHP 8.3.30 (fpm-fcgi)
LuaSandbox 4.1.3
Lua 5.1.5
Scribunto 723f367

Event Timeline

SomeRandomDeveloper added a subscriber: Umherirrender.

After seeing T419161: PHP Fatal Error from line 97 of /srv/mediawiki/php-1.46.0-wmf.18/extensions/Scribunto/includes/Engines/LuaSandbox/LuaSandboxInterpreter.php: Allowed memory size of 1468006400 bytes exhausted (tried to allocate 20480 bytes), I remembered I wanted to report this, because even though AFAIK the WMF doesn't run more than one job in one request, it's still possible that a job caused multiple parses and it ran out of memory because of that.

sbassett added a project: Vuln-DoS.
sbassett added a project: SecTeam-Processed.
sbassett added a subscriber: cscott.

Since nobody has come up with a better fix yet, and the next security release will likely happen soon, I think we should just fix this for now by calling destroy() on the engine object in destroyEngineForParser():

sbassett changed the task status from Open to In Progress.Mon, Mar 23, 6:58 PM
sbassett triaged this task as Medium priority.
sbassett moved this task from Watching to Security Patch To Deploy on the Security-Team board.
sbassett subscribed.

Planning to deploy this during today's (2026-03-23) security deployment window.

Mstyles removed a project: Patch-For-Review.
Mstyles subscribed.

Since nobody has come up with a better fix yet, and the next security release will likely happen soon, I think we should just fix this for now by calling destroy() on the engine object in destroyEngineForParser():

Deployed

Mstyles added a parent task: Restricted Task.Mon, Mar 23, 9:52 PM
Reedy renamed this task from Memory leak in Scribunto causes runJobs.php to run out of memory to CVE-2026-34098: Memory leak in Scribunto causes runJobs.php to run out of memory.Wed, Mar 25, 7:24 PM
Reedy renamed this task from CVE-2026-34098: Memory leak in Scribunto causes runJobs.php to run out of memory to CVE-2026-34089: Memory leak in Scribunto causes runJobs.php to run out of memory.Wed, Mar 25, 7:53 PM

Change #1265609 had a related patch set uploaded (by Reedy; author: SomeRandomDeveloper):

[mediawiki/extensions/Scribunto@master] SECURITY: Always call engine destructor to fix memory leak

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

Change #1265609 merged by jenkins-bot:

[mediawiki/extensions/Scribunto@master] SECURITY: Always call engine destructor to fix memory leak

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

Change #1265627 had a related patch set uploaded (by Reedy; author: SomeRandomDeveloper):

[mediawiki/extensions/Scribunto@REL1_45] SECURITY: Always call engine destructor to fix memory leak

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

Change #1265627 merged by jenkins-bot:

[mediawiki/extensions/Scribunto@REL1_45] SECURITY: Always call engine destructor to fix memory leak

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

sbassett changed the visibility from "Custom Policy" to "Public (No Login Required)".
sbassett changed the edit policy from "Custom Policy" to "All Users".
sbassett changed Risk Rating from N/A to Medium.
sbassett moved this task from Watching to Our Part Is Done on the Security-Team board.