Jump to content

LuaSandbox: Difference between revisions

From mediawiki.org
Content deleted Content added
update for 4.0.0
m restore translation units
 
(46 intermediate revisions by 26 users not shown)
Line 1: Line 1:
<languages />
{{for|the MediaWiki extension that allows use of sandboxed Lua code|Extension:Scribunto}}
''<translate><!--T:1--> For the MediaWiki extension that allows use of sandboxed Lua code, see <tvar name=1>{{ll|Extension:Scribunto}}</tvar></translate>''


<translate>
<!--T:2-->
'''LuaSandbox''' is an extension for PHP 7 and PHP 8 to allow safely running untrusted Lua 5.1 code from within PHP, which will generally be faster than shelling out to a Lua binary and using inter-process communication.
'''LuaSandbox''' is an extension for PHP 7 and PHP 8 to allow safely running untrusted Lua 5.1 code from within PHP, which will generally be faster than shelling out to a Lua binary and using inter-process communication.


== Installation ==
== Installation == <!--T:3-->


=== Linux package manager === <!--T:4-->
=== Prepackaged ===
</translate>
<translate><!--T:5--> LuaSandbox is available on Linux in the [<tvar name=debian>https://packages.debian.org/stable/php-luasandbox</tvar> Debian] and [<tvar name=ubuntu>https://packages.ubuntu.com/search?keywords=php-luasandbox&searchon=names</tvar> Ubuntu] repositories.</translate>
<translate><!--T:6--> Install with the following command:</translate>


<syntaxhighlight lang="bash">
LuaSandbox is available in Debian 10 and Ubuntu 18.04 and later. Install with the following command:
<syntaxhighlight lang="bash">sudo apt-get install php-luasandbox</syntaxhighlight>
apt install php-luasandbox -y
</syntaxhighlight>

<translate>
<!--T:87-->
LuaSandbox can be installed on RPM-based distributions (Fedora, RedHat, SUSE, etc.) using <code>yum</code> from [<tvar name=1>https://rpms.remirepo.net/</tvar> Remi's repository] ([<tvar name=url>https://rpmfind.net/linux/rpm2html/search.php?query=php-pecl-luasandbox</tvar> package list]).</translate>

=== PIE ===

<translate><!--T:10--> To install LuaSandbox with <tvar name=2>[https://php.github.io/pie/ PIE]</tvar>, first get the correct Lua 5.1 library as described below under "[[<tvar name=1>#Build from source</tvar>|build from source]]".</translate>
<translate><!--T:11--> Then run:</translate>

<code>pie install wikimedia/luasandbox</code>

See [https://packagist.org/packages/wikimedia/luasandbox LuaSandbox in Packagist].


<translate>
=== PECL ===
=== PECL === <!--T:7-->
</translate>
PECL is deprecated as of 2026.
New releases will not be published to PECL.


LuaSandbox is now available in PECL. See [https://pecl.php.net/package/LuaSandbox our package page]. First get the correct Lua 5.1 library as described below under "manual installation". Then run:
To install LuaSandbox with PECL, first get the correct Lua 5.1 library, then run:


<pre>
sudo pecl install luasandbox
pecl install luasandbox
</pre>


<translate>
=== Manual installation ===
=== Build from source === <!--T:12-->


<!--T:13-->
==== Requirements ====
'''Requirements''':
Install the headers and library files for PHP and [https://www.lua.org/ Lua] 5.1.
* For Debian-derived Linux distributions, such as Ubuntu: <syntaxhighlight lang="bash">sudo apt-get install php-dev liblua5.1-0-dev</syntaxhighlight>
* For Centos/Redhat-derived Linux distributions: <syntaxhighlight lang="bash">sudo yum install php-devel lua lua-devel</syntaxhighlight>
* For Mac OS X: <syntaxhighlight lang="bash">brew install lua</syntaxhighlight>


<!--T:14-->
==== Download ====
Install the headers and library files for PHP and [<tvar name=url>https://www.lua.org/</tvar> Lua] 5.1.
</translate>
* <translate><!--T:15--> For Debian-derived Linux distributions, such as Ubuntu:</translate> <syntaxhighlight lang="bash">apt install php-dev liblua5.1-0-dev -y</syntaxhighlight>
* <translate><!--T:16--> For CentOS/Redhat-derived Linux distributions:</translate> <syntaxhighlight lang="bash">yum install php-devel lua5.1 lua5.1-devel</syntaxhighlight>


<translate>
<!--T:19-->
Download the source code into an appropriate directory from git:
Download the source code into an appropriate directory from git:
</translate>
<syntaxhighlight lang="bash">git clone https://gerrit.wikimedia.org/r/mediawiki/php/luasandbox.git</syntaxhighlight>


<syntaxhighlight lang="bash">
Or {{DownloadGitMaster|base=mediawiki-php-|luasandbox|download a snapshot}} and unpack.
git clone https://gerrit.wikimedia.org/r/mediawiki/php/luasandbox.git
</syntaxhighlight>


<translate>
==== Build ====
<!--T:20-->
Or {{<tvar name=1>DownloadGitMaster|base=mediawiki-php-|luasandbox</tvar>|download a snapshot}} and unpack.


<!--T:22-->
<code>luasandbox</code> here is the directory that luasandbox git repository was cloned to.
<tvar name=1><code>luasandbox/</code></tvar> is the directory with the LuaSandbox source code.
</translate>


<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
cd luasandbox
cd luasandbox/
phpize && ./configure && make && sudo make install
phpize && ./configure && make && sudo make install
</syntaxhighlight>
</syntaxhighlight>


Then add <code>extension=luasandbox.so</code> to the PHP configuration in an appropriate place. For example, in modern Debian-derived distributions you'd add a file to <code>/etc/php/$version/mods-available</code> (where <code>$version</code> is the version of PHP for which you complied LuaSandbox) and use the <code>phpenmod</code> command to enable it.
<translate><!--T:23--> Then add <tvar name=1><code>extension = luasandbox</code></tvar> to the PHP configuration in an appropriate place.</translate>
<translate><!--T:24--> For example, in modern Debian-derived distributions you would add a file to <tvar name=1><code>/etc/php/$version/mods-available</code></tvar> (where <tvar name=2><code>$version</code></tvar> is the version of PHP for which you complied LuaSandbox) and use the <tvar name=3><code>phpenmod</code></tvar> command to enable it.</translate>


If you're using LuaSandbox with a web application such as MediaWiki, you'll need to restart your webserver or php-fpm for PHP to load the extension. After such reload, you should see LuaSandbox in the output of <syntaxhighlight lang="php" inline>phpinfo()</syntaxhighlight> and <syntaxhighlight lang="php" inline>get_loaded_extensions()</syntaxhighlight> (and, for MediaWiki with Scribunto installed, [[Special:Version]]).
<translate><!--T:25--> If you are using LuaSandbox with a web application such as MediaWiki, you will need to restart your web server or <tvar name=1><code>php-fpm</code></tvar> for PHP to load the extension.</translate>
<translate><!--T:26--> After such reload, you should see LuaSandbox in the output of <tvar name=1>{{phpi|phpinfo()}}</tvar> and <tvar name=2>{{phpi|get_loaded_extensions()}}</tvar> (and, for MediaWiki with Scribunto installed, <tvar name=3>[[Special:Version]]</tvar>).</translate>

== Examples ==


<translate>
== Examples == <!--T:27-->
</translate>
<syntaxhighlight lang="php">
<syntaxhighlight lang="php">
$sandbox = new LuaSandbox;
$sandbox = new LuaSandbox;
Line 51: Line 90:
$sandbox->setCPULimit( 10 );
$sandbox->setCPULimit( 10 );


// Register some functions in the Lua environment
// <translate nowrap><!--T:28--> Register some functions in the Lua environment</translate>


function frobnosticate( $v ) {
function frobnosticate( $v ) {
Line 63: Line 102:
},
},
'error' => function () {
'error' => function () {
throw new LuaSandboxRuntimeError( "Something is wrong" );
throw new LuaSandboxRuntimeError( "<translate nowrap><!--T:29--> Something is wrong</translate>" );
}
}
] );
] );


// Execute some Lua code, including callbacks into PHP and into Lua
// <translate nowrap><!--T:30--> Execute some Lua code, including callbacks into PHP and into Lua</translate>


$luaCode = <<<EOF
$luaCode = <<<EOF
Line 80: Line 119:
assert( $frob->call( 4000 ) === [ 4242 ] );
assert( $frob->call( 4000 ) === [ 4242 ] );


// PHP-thrown LuaSandboxRuntimeError exceptions can be caught inside Lua
// <translate nowrap><!--T:31--> PHP-thrown LuaSandboxRuntimeError exceptions can be caught inside Lua</translate>


list( $ok, $message ) = $sandbox->loadString( 'return pcall( php.error )' )->call();
list( $ok, $message ) = $sandbox->loadString( 'return pcall( php.error )' )->call();
Line 87: Line 126:
</syntaxhighlight>
</syntaxhighlight>


<translate>
== Documentation ==
== Documentation == <!--T:32-->


<!--T:33-->
This extension is now documented in [https://www.php.net/manual/en/book.luasandbox.php the PHP manual].
Our documentation now lives within the upstream PHP manual at <tvar name=url>https://www.php.net/book.luasandbox</tvar>.


<!--T:34-->
If you want to change the manual, you can either submit a pull request against the [https://github.com/php/doc-en/tree/master/reference/luasandbox PHP manual repository in GitHub], or you can change our mirror of the LuaSandbox chapter in the extension's Gerrit project.
If you want to change the manual, you can either submit a pull request against the [<tvar name=url>https://github.com/php/doc-en/tree/master/reference/luasandbox</tvar> PHP manual repository in GitHub], or change our mirror of the LuaSandbox chapter in the extension's Gerrit project.


== Differences from standard Lua ==
== Differences from standard Lua == <!--T:35-->


<!--T:36-->
LuaSandbox provides a sandboxed environment which differs in some ways from standard Lua 5.1.
LuaSandbox provides a sandboxed environment which differs in some ways from standard Lua 5.1.


<!--T:37-->
The following functions and packages are not available:
The following functions and packages are not available:
</translate>
* [https://www.lua.org/manual/5.1/manual.html#pdf-dofile <code>dofile()</code>], [https://www.lua.org/manual/5.1/manual.html#pdf-loadfile <code>loadfile()</code>], and the [https://www.lua.org/manual/5.1/manual.html#5.7 <code>io</code> package], as they allow direct filesystem access. If needed, filesystem access should be done via PHP callbacks.
* The [https://www.lua.org/manual/5.1/manual.html#5.3 <code>package</code> package], including <code>require()</code> and <code>module()</code>, as it depends heavily on direct filesystem access. A pure-Lua rewrite such as [[phab:diffusion/ELUA/browse/master/includes/engines/LuaCommon/lualib/package.lua|that used in Scribunto]] may be used instead.
* [https://www.lua.org/manual/5.1/manual.html#pdf-load <code>load()</code>] and [https://www.lua.org/manual/5.1/manual.html#pdf-loadstring <code>loadstring()</code>], to allow for static analysis of Lua code.
* [https://www.lua.org/manual/5.1/manual.html#pdf-print <code>print()</code>], since it outputs to standard output. If needed, output should be done via PHP callbacks.
* Most of the [https://www.lua.org/manual/5.1/manual.html#5.8 <code>os</code> package], as it allows manipulation of the process and executing of other processes.
** <code>os.clock()</code>, <code>os.date()</code>, <code>os.difftime()</code>, and <code>os.time()</code> remain available.
* Most of the [https://www.lua.org/manual/5.1/manual.html#5.9 <code>debug</code> package], as it allows manipulation of Lua state and metadata in ways that can break sandboxing.
** <code>debug.traceback()</code> remains available.
* [https://www.lua.org/manual/5.1/manual.html#pdf-string.dump <code>string.dump()</code>], as it may expose internal data.
* [https://www.lua.org/manual/5.1/manual.html#pdf-collectgarbage <code>collectgarbage()</code>], [https://www.lua.org/manual/5.1/manual.html#pdf-gcinfo <code>gcinfo()</code>], and the [https://www.lua.org/manual/5.1/manual.html#2.11 <code>coroutine</code> package] have not been reviewed for security.


* <translate><!--T:38--> <tvar name=1>[https://www.lua.org/manual/5.1/manual.html#pdf-dofile <code>dofile()</code>]</tvar>, <tvar name=2>[https://www.lua.org/manual/5.1/manual.html#pdf-loadfile <code>loadfile()</code>]</tvar>, and the <tvar name=4>[https://www.lua.org/manual/5.1/manual.html#5.7 <code>io</code>]</tvar> package, as they allow direct filesystem access.</translate> <translate><!--T:39--> If needed, filesystem access should be done via PHP callbacks.</translate>
* <translate><!--T:40--> The <tvar name=1>[https://www.lua.org/manual/5.1/manual.html#5.3 <code>package</code>]</tvar> package, including <tvar name=2><code>require()</code></tvar> and <tvar name=3><code>module()</code></tvar>, as it depends heavily on direct filesystem access.</translate> <translate><!--T:41--> A pure-Lua rewrite such as [[<tvar name=1>phab:diffusion/ELUA/browse/master/includes/Engines/LuaCommon/lualib/package.lua</tvar>|that used in Scribunto]] may be used instead.</translate>
* <translate><!--T:42--> <tvar name=1>[https://www.lua.org/manual/5.1/manual.html#pdf-load <code>load()</code>]</tvar> and <tvar name=2>[https://www.lua.org/manual/5.1/manual.html#pdf-loadstring <code>loadstring()</code>]</tvar>, to allow for static analysis of Lua code.</translate>
* <translate><!--T:43--> <tvar name=1>[https://www.lua.org/manual/5.1/manual.html#pdf-print <code>print()</code>]</tvar>, since it outputs to standard output.</translate> <translate><!--T:44--> If needed, output should be done via PHP callbacks.</translate>
* <translate><!--T:45--> Most of the <tvar name=1>[https://www.lua.org/manual/5.1/manual.html#5.8 <code>os</code>]</tvar> package, as it allows manipulation of the process and executing of other processes.</translate>
** <translate><!--T:46--> <tvar name=1><code>os.clock()</code></tvar>, <tvar name=2><code>os.date()</code></tvar>, <tvar name=3><code>os.difftime()</code></tvar>, and <tvar name=4><code>os.time()</code></tvar> remain available.</translate>
* <translate><!--T:47--> Most of the <tvar name=1>[https://www.lua.org/manual/5.1/manual.html#5.9 <code>debug</code>]</tvar> package, as it allows manipulation of Lua state and metadata in ways that can break sandboxing.</translate>
** <translate><!--T:48--> <tvar name=1><code>debug.traceback()</code></tvar> remains available.</translate>
* <translate><!--T:49--> <tvar name=1>[https://www.lua.org/manual/5.1/manual.html#pdf-string.dump <code>string.dump()</code>]</tvar>, as it may expose internal data.</translate>
* <translate><!--T:50--> <tvar name=1>[https://www.lua.org/manual/5.1/manual.html#pdf-collectgarbage <code>collectgarbage()</code>]</tvar>, <tvar name=2>[https://www.lua.org/manual/5.1/manual.html#pdf-gcinfo <code>gcinfo()</code>]</tvar>, and the <tvar name=3>[https://www.lua.org/manual/5.1/manual.html#2.11 <code>coroutine</code>]</tvar> package have not been reviewed for security.</translate>

<translate>
<!--T:51-->
The following features have been modified:
The following features have been modified:
</translate>
* [https://www.lua.org/manual/5.1/manual.html#pdf-pcall <code>pcall()</code>] and [https://www.lua.org/manual/5.1/manual.html#pdf-xpcall <code>xpcall()</code>] cannot catch certain errors, particularly timeout errors.

* [https://www.lua.org/manual/5.1/manual.html#pdf-tostring <code>tostring()</code>] does not include pointer addresses.
* [https://www.lua.org/manual/5.1/manual.html#pdf-string.match <code>string.match()</code>] has been patched to limit the recursion depth and to periodically check for a timeout.
* <translate><!--T:52--> <tvar name=1>[https://www.lua.org/manual/5.1/manual.html#pdf-pcall <code>pcall()</code>]</tvar> and <tvar name=2>[https://www.lua.org/manual/5.1/manual.html#pdf-xpcall <code>xpcall()</code>]</tvar> cannot catch certain errors, particularly timeout errors.</translate>
* [https://www.lua.org/manual/5.1/manual.html#pdf-math.random <code>math.random()</code>] and [https://www.lua.org/manual/5.1/manual.html#pdf-math.random <code>math.randomseed()</code>] are replaced with versions that don't share state with PHP's <code>rand()</code>.
* <translate><!--T:53--> <tvar name=1>[https://www.lua.org/manual/5.1/manual.html#pdf-tostring <code>tostring()</code>]</tvar> does not include pointer addresses.</translate>
* The Lua 5.2 [https://www.lua.org/manual/5.2/manual.html#pdf-pairs <code>__pairs</code>] and [https://www.lua.org/manual/5.2/manual.html#pdf-ipairs <code>__ipairs</code>] metamethods are supported by <code>pairs()</code> and <code>ipairs()</code>.
* <translate><!--T:54--> <tvar name=1>[https://www.lua.org/manual/5.1/manual.html#pdf-string.match <code>string.match()</code>]</tvar> has been patched to limit the recursion depth and to periodically check for a timeout.</translate>
* <translate><!--T:55--> <tvar name=1>[https://www.lua.org/manual/5.1/manual.html#pdf-math.random <code>math.random()</code>]</tvar> and <tvar name=2>[https://www.lua.org/manual/5.1/manual.html#pdf-math.random <code>math.randomseed()</code>]</tvar> are replaced with versions that don't share state with PHP's <tvar name=3><code>rand()</code></tvar>.</translate>
* <translate><!--T:56--> The Lua 5.2 <tvar name=1>[https://www.lua.org/manual/5.2/manual.html#pdf-pairs <code>__pairs</code>]</tvar> and <tvar name=2>[https://www.lua.org/manual/5.2/manual.html#pdf-ipairs <code>__ipairs</code>]</tvar> metamethods are supported by <tvar name=3><code>pairs()</code></tvar> and <tvar name=4><code>ipairs()</code></tvar>.</translate>

<translate>
== History == <!--T:57-->
</translate>
<translate><!--T:58--> Over the years, MediaWiki's wikitext template language gained more features and grew more complicated.</translate>
<translate><!--T:59--> As early as 2009, MediaWiki developers began discussing the idea of embedding a real scripting language instead of continuing to make wikitext more complex.</translate>


<translate><!--T:60--> Requirements for such a project included a strong sandbox and strict limitations on memory and CPU time usage, since it would be executing untrusted user code on production servers.</translate>
== History ==
<translate><!--T:61--> It would need to be usable by shelling out to a standalone binary, with the ability to be run in-process via a PHP extension for better performance being a major benefit.</translate>


<translate><!--T:62--> When development started in earnest circa 2011, four candidate languages were identified: Lua, JavaScript, PHP, or a hypothetical "WikiScript" language to be developed.</translate>
Over the years, MediaWiki's wikitext template "language" gained more features and grew more complicated. As early as 2009, MediaWiki developers began discussing the idea of embedding a real scripting language instead of continuing to make wikitext more complex.
<translate><!--T:63--> Lua had several advantages:</translate>


* <translate><!--T:64--> Small (170K standalone) and fast.</translate> <translate><!--T:65--> The existence of LuaJIT was also considered a benefit.</translate>
Requirements for such a project included a strong sandbox and strict limitations on memory and CPU time usage, since it would be executing untrusted user code on production servers. It would need to be usable by shelling out to a standalone binary, with the ability to be run in-process via a PHP extension for better performance being a major benefit.
* <translate><!--T:66--> Designed for embedding, including easy hooks for CPU and memory limiting.</translate>
* <translate><!--T:67--> Easy sandboxing, no internal globals.</translate>
* <translate><!--T:68--> Detailed reference manual, including instructions on embedding.</translate>


<translate>
When development started in earnest circa 2011, four candidate languages were identified: Lua, JavaScript, PHP, or a hypothetical "WikiScript" language to be developed. Lua had several advantages:
<!--T:69-->
* Small (170K standalone) and fast. The existence of LuaJIT was also considered a benefit.
* Designed for embedding, including easy hooks for CPU and memory limiting.
* Easy sandboxing, no internal globals.
* Detailed reference manual, including instructions on embedding.
The main disadvantage was that it wasn't known as widely as JavaScript.
The main disadvantage was that it wasn't known as widely as JavaScript.


<!--T:70-->
JavaScript, in the form of the V8 engine at the time, had several disadvantages:
JavaScript, in the form of the V8 engine at the time, had several disadvantages:
</translate>
* Minimal documentation on embedding.
* Continued support for embedding unclear.
* No allocation hook.
* Huge standalone binary.


* <translate><!--T:71--> Minimal documentation on embedding.</translate>
The Rhino engine was worse, as being written in Java it couldn't sanely be embedded in PHP at all. PHP itself was rejected since proper embedding and sandboxing would have been extremely difficult and pre-parsing would have been slow, and "WikiScript" would have been a much larger project in that it would have required developing an interpreter (or two) from scratch.
* <translate><!--T:72--> Continued support for embedding unclear.</translate>
* <translate><!--T:73--> No allocation hook.</translate>
* <translate><!--T:74--> Huge standalone binary.</translate>


<translate><!--T:75--> The Rhino engine was worse, as being written in Java it couldn't sanely be embedded in PHP at all.</translate>
Thus, Lua was chosen, specifically version 5.1 that was available at the time, and this PHP extension was developed. The changes made to function environment handling in 5.2 have prevented a simple upgrade since, see [[phab:T178146]] for details.
<translate><!--T:76--> PHP itself was rejected since proper embedding and sandboxing would have been extremely difficult and pre-parsing would have been slow, and "WikiScript" would have been a much larger project in that it would have required developing an interpreter (or two) from scratch.</translate>


<translate><!--T:77--> Thus, Lua was chosen, specifically version 5.1 that was available at the time, and this PHP extension was developed.</translate>
== External links ==
<translate><!--T:78--> The changes made to function environment handling in 5.2 have prevented a simple upgrade since, see <tvar name=1>[[phab:T178146]]</tvar> for details.</translate>


<translate>
* [[git:mediawiki/php/luasandbox|Source code]]
== External links == <!--T:79-->
* [https://packages.debian.org/stable/php-luasandbox Debian package]
</translate>
* [https://doc.wikimedia.org/mediawiki-php-luasandbox/master/ Code documentation]
* [[File:Gerrit icon.svg|28px|link=Special:MyLanguage/Gerrit]] [[git:mediawiki/php/luasandbox|Gerrit <translate><!--T:80--> source code</translate>]]
** [[File:Wikimedia Phabricator logo.svg|28px|link=Special:MyLanguage/Phabricator]] [[phab:diffusion/MLUS/repository/master/|Phabricator <translate><!--T:83--> mirror</translate>]]
** [[File:Octicons-mark-github.svg|28px|link=Special:MyLanguage/Gerrit/GitHub|class=skin-invert-image]] [https://github.com/wikimedia/mediawiki-php-luasandbox GitHub <translate><!--T:84--> mirror</translate>]
* [https://packages.debian.org/stable/php-luasandbox <translate><!--T:81--> Debian package</translate>]
* [https://doc.wikimedia.org/mediawiki-php-luasandbox/master/annotated.html <translate><!--T:82--> Code documentation</translate>]


[[Category:PHP extensions]]
[[Category:PHP extensions{{#translation:}}]]

Latest revision as of 11:19, 30 July 2026

For the MediaWiki extension that allows use of sandboxed Lua code, see Extension:Scribunto

LuaSandbox is an extension for PHP 7 and PHP 8 to allow safely running untrusted Lua 5.1 code from within PHP, which will generally be faster than shelling out to a Lua binary and using inter-process communication.

Installation

[edit | edit source]

Linux package manager

[edit | edit source]

LuaSandbox is available on Linux in the Debian and Ubuntu repositories. Install with the following command:

apt install php-luasandbox -y

LuaSandbox can be installed on RPM-based distributions (Fedora, RedHat, SUSE, etc.) using yum from Remi's repository (package list).

To install LuaSandbox with PIE, first get the correct Lua 5.1 library as described below under "build from source". Then run:

pie install wikimedia/luasandbox

See LuaSandbox in Packagist.

PECL is deprecated as of 2026. New releases will not be published to PECL.

To install LuaSandbox with PECL, first get the correct Lua 5.1 library, then run:

pecl install luasandbox

Build from source

[edit | edit source]

Requirements:

Install the headers and library files for PHP and Lua 5.1.

  • For Debian-derived Linux distributions, such as Ubuntu:
    apt install php-dev liblua5.1-0-dev -y
    
  • For CentOS/Redhat-derived Linux distributions:
    yum install php-devel lua5.1 lua5.1-devel
    

Download the source code into an appropriate directory from git:

git clone https://gerrit.wikimedia.org/r/mediawiki/php/luasandbox.git

Or download a snapshot and unpack.

luasandbox/ is the directory with the LuaSandbox source code.

cd luasandbox/
phpize && ./configure && make && sudo make install

Then add extension = luasandbox to the PHP configuration in an appropriate place. For example, in modern Debian-derived distributions you would add a file to /etc/php/$version/mods-available (where $version is the version of PHP for which you complied LuaSandbox) and use the phpenmod command to enable it.

If you are using LuaSandbox with a web application such as MediaWiki, you will need to restart your web server or php-fpm for PHP to load the extension. After such reload, you should see LuaSandbox in the output of phpinfo() and get_loaded_extensions() (and, for MediaWiki with Scribunto installed, Special:Version).

Examples

[edit | edit source]
$sandbox = new LuaSandbox;
$sandbox->setMemoryLimit( 50 * 1024 * 1024 );
$sandbox->setCPULimit( 10 );

// Register some functions in the Lua environment

function frobnosticate( $v ) {
    return [ $v + 42 ];
}

$sandbox->registerLibrary( 'php', [
    'frobnosticate' => 'frobnosticate',
    'output' => function ( $string ) {
        echo "$string\n";
    },
    'error' => function () {
        throw new LuaSandboxRuntimeError( "Something is wrong" );
    }
] );

// Execute some Lua code, including callbacks into PHP and into Lua

$luaCode = <<<EOF
php.output( "Hello, world" );

return "Hi", function ( v )
    return php.frobnosticate( v + 200 )
end
EOF;

list( $hi, $frob ) = $sandbox->loadString( $luaCode )->call();
assert( $frob->call( 4000 ) === [ 4242 ] );

// PHP-thrown LuaSandboxRuntimeError exceptions can be caught inside Lua

list( $ok, $message ) = $sandbox->loadString( 'return pcall( php.error )' )->call();
assert( !$ok );
assert( $message === 'Something is wrong' );

Documentation

[edit | edit source]

Our documentation now lives within the upstream PHP manual at https://www.php.net/book.luasandbox.

If you want to change the manual, you can either submit a pull request against the PHP manual repository in GitHub, or change our mirror of the LuaSandbox chapter in the extension's Gerrit project.

Differences from standard Lua

[edit | edit source]

LuaSandbox provides a sandboxed environment which differs in some ways from standard Lua 5.1.

The following functions and packages are not available:

  • dofile(), loadfile(), and the io package, as they allow direct filesystem access. If needed, filesystem access should be done via PHP callbacks.
  • The package package, including require() and module(), as it depends heavily on direct filesystem access. A pure-Lua rewrite such as that used in Scribunto may be used instead.
  • load() and loadstring(), to allow for static analysis of Lua code.
  • print(), since it outputs to standard output. If needed, output should be done via PHP callbacks.
  • Most of the os package, as it allows manipulation of the process and executing of other processes.
    • os.clock(), os.date(), os.difftime(), and os.time() remain available.
  • Most of the debug package, as it allows manipulation of Lua state and metadata in ways that can break sandboxing.
    • debug.traceback() remains available.
  • string.dump(), as it may expose internal data.
  • collectgarbage(), gcinfo(), and the coroutine package have not been reviewed for security.

The following features have been modified:

History

[edit | edit source]

Over the years, MediaWiki's wikitext template language gained more features and grew more complicated. As early as 2009, MediaWiki developers began discussing the idea of embedding a real scripting language instead of continuing to make wikitext more complex.

Requirements for such a project included a strong sandbox and strict limitations on memory and CPU time usage, since it would be executing untrusted user code on production servers. It would need to be usable by shelling out to a standalone binary, with the ability to be run in-process via a PHP extension for better performance being a major benefit.

When development started in earnest circa 2011, four candidate languages were identified: Lua, JavaScript, PHP, or a hypothetical "WikiScript" language to be developed. Lua had several advantages:

  • Small (170K standalone) and fast. The existence of LuaJIT was also considered a benefit.
  • Designed for embedding, including easy hooks for CPU and memory limiting.
  • Easy sandboxing, no internal globals.
  • Detailed reference manual, including instructions on embedding.

The main disadvantage was that it wasn't known as widely as JavaScript.

JavaScript, in the form of the V8 engine at the time, had several disadvantages:

  • Minimal documentation on embedding.
  • Continued support for embedding unclear.
  • No allocation hook.
  • Huge standalone binary.

The Rhino engine was worse, as being written in Java it couldn't sanely be embedded in PHP at all. PHP itself was rejected since proper embedding and sandboxing would have been extremely difficult and pre-parsing would have been slow, and "WikiScript" would have been a much larger project in that it would have required developing an interpreter (or two) from scratch.

Thus, Lua was chosen, specifically version 5.1 that was available at the time, and this PHP extension was developed. The changes made to function environment handling in 5.2 have prevented a simple upgrade since, see phab:T178146 for details.

[edit | edit source]