r94860 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r94859‎ | r94860 | r94861 >
Date:03:31, 18 August 2011
Author:tstarling
Status:deferred
Tags:
Comment:
* Added LuaSandboxFunction::dump() and LuaSandbox::loadBinary() to allow precompiled chunks to be saved and loaded.
* Provided access to the relatively safe base library functions.
* Removed a couple of to-dos, as a scope reduction since there's still a lot of other parts to this project that need doing.
Modified paths:
  • /trunk/php/luasandbox/luasandbox.c (modified) (history)
  • /trunk/php/luasandbox/php_luasandbox.h (modified) (history)

Diff [purge]

Index: trunk/php/luasandbox/luasandbox.c
@@ -1,22 +1,3 @@
2 -
3 -/*
4 - * To do:
5 - * * Provide a LuaSandbox::doString() as a convenience wrapper for
6 - * LuaSandbox::loadString()->call(), analogous to luaL_dostring().
7 - *
8 - * * Add a PHP method for registering callback functions individually.
9 - *
10 - * * Provide a wrapper around the base library, which allows only a whitelist
11 - * of functions, not including e.g. loadfile(). This is slightly tricky
12 - * because the functions are not exported from the shared library. It's
13 - * necessary to call luaopen_base() on a separate lua_State, and then to
14 - * copy each function pointer to the destination lua_State using
15 - * lua_tocfunction().
16 - *
17 - * * Support lua_dump()
18 - * * Protect LuaSandbox->loadString() against malicious binaries
19 - */
20 -
212 #ifdef HAVE_CONFIG_H
223 #include "config.h"
234 #endif
@@ -36,6 +17,7 @@
3718 #include "zend_exceptions.h"
3819 #include "php_luasandbox.h"
3920 #include "luasandbox_timer.h"
 21+#include "ext/standard/php_smart_str.h"
4022
4123 static zend_object_value luasandbox_new(zend_class_entry *ce TSRMLS_DC);
4224 static lua_State * luasandbox_newstate(php_luasandbox_obj * intern);
@@ -47,9 +29,12 @@
4830 static void *luasandbox_alloc(void *ud, void *ptr, size_t osize, size_t nsize);
4931 static int luasandbox_panic(lua_State * L);
5032 static lua_State * luasandbox_state_from_zval(zval * this_ptr TSRMLS_DC);
 33+static void luasandbox_load_helper(int binary, INTERNAL_FUNCTION_PARAMETERS);
5134 static int luasandbox_find_field(lua_State * L, int index,
5235 char * spec, int specLength);
5336 static void luasandbox_set_timespec(struct timespec * dest, double source);
 37+static int luasandbox_function_init(zval * this_ptr, php_luasandboxfunction_obj ** pfunc,
 38+ lua_State ** pstate, php_luasandbox_obj ** psandbox TSRMLS_DC);
5439 static void luasandbox_call_helper(lua_State * L, php_luasandbox_obj * sandbox,
5540 zval *** args, zend_uint numArgs, zval * return_value TSRMLS_DC);
5641 static int luasandbox_push_zval(lua_State * L, zval * z);
@@ -61,9 +46,57 @@
6247 static void luasandbox_handle_error(lua_State * L, int status);
6348 static int luasandbox_push_hashtable(lua_State * L, HashTable * ht);
6449 static int luasandbox_call_php(lua_State * L);
 50+static int luasandbox_dump_writer(lua_State * L, const void * p, size_t sz, void * ud);
 51+static int luasandbox_base_tostring(lua_State * L);
6552
6653 char luasandbox_timeout_message[] = "The maximum execution time for this script was exceeded";
6754
 55+/**
 56+ * Allowed global variables. Omissions are:
 57+ * * pcall, xpcall: Changing the protected environment won't work with our
 58+ * current timeout method.
 59+ * * loadfile: insecure.
 60+ * * load, loadstring: Probably creates a protected environment so can't has
 61+ * the same problem as pcall. Also omitting these makes analysis of the
 62+ * code for runtime etc. feasible.
 63+ * * print: Not compatible with a sandbox environment
 64+ * * tostring: Provides addresses of tables and functions, which provides an
 65+ * easy ASLR workaround or heap address discovery mechanism for a memory
 66+ * corruption exploit.
 67+ * * Any new or undocumented functions like newproxy.
 68+ * * package: cpath, loadlib etc. are insecure.
 69+ * * coroutine: Not useful for our application so unreviewed at present.
 70+ * * io, file, os: insecure
 71+ * * debug: Provides various ways to break the sandbox, such as setupvalue()
 72+ * and getregistry().
 73+ */
 74+char * luasandbox_allowed_globals[] = {
 75+ // base
 76+ "assert",
 77+ "error",
 78+ "getmetatable",
 79+ "getfenv",
 80+ "getmetatable",
 81+ "ipairs",
 82+ "next",
 83+ "pairs",
 84+ "rawequal",
 85+ "rawget",
 86+ "rawset",
 87+ "select",
 88+ "setmetatable",
 89+ "tonumber",
 90+ "type",
 91+ "unpack",
 92+ "_G",
 93+ "_VERSION",
 94+ // libs
 95+ "string",
 96+ "table",
 97+ "math"
 98+};
 99+#define LUASANDBOX_NUM_ALLOWED_GLOBALS (sizeof(luasandbox_allowed_globals) / sizeof(char*))
 100+
68101 zend_class_entry *luasandbox_ce;
69102 zend_class_entry *luasandboxerror_ce;
70103 zend_class_entry *luasandboxplaceholder_ce;
@@ -77,6 +110,11 @@
78111 ZEND_ARG_INFO(0, chunkName)
79112 ZEND_END_ARG_INFO()
80113
 114+ZEND_BEGIN_ARG_INFO(arginfo_luasandbox_loadBinary, 0)
 115+ ZEND_ARG_INFO(0, code)
 116+ ZEND_ARG_INFO(0, chunkName)
 117+ZEND_END_ARG_INFO()
 118+
81119 ZEND_BEGIN_ARG_INFO(arginfo_luasandbox_setMemoryLimit, 0)
82120 ZEND_ARG_INFO(0, limit)
83121 ZEND_END_ARG_INFO()
@@ -99,6 +137,10 @@
100138 ZEND_BEGIN_ARG_INFO(arginfo_luasandboxfunction_call, 0)
101139 ZEND_ARG_INFO(0, ...)
102140 ZEND_END_ARG_INFO()
 141+
 142+ZEND_BEGIN_ARG_INFO(arginfo_luasandboxfunction_dump, 0)
 143+ZEND_END_ARG_INFO()
 144+
103145 /* }}} */
104146
105147 /** {{{ function entries */
@@ -108,6 +150,7 @@
109151
110152 const zend_function_entry luasandbox_methods[] = {
111153 PHP_ME(LuaSandbox, loadString, arginfo_luasandbox_loadString, 0)
 154+ PHP_ME(LuaSandbox, loadBinary, arginfo_luasandbox_loadBinary, 0)
112155 PHP_ME(LuaSandbox, setMemoryLimit, arginfo_luasandbox_setMemoryLimit, 0)
113156 PHP_ME(LuaSandbox, setCPULimit, arginfo_luasandbox_setCPULimit, 0)
114157 PHP_ME(LuaSandbox, callFunction, arginfo_luasandbox_callFunction, 0)
@@ -117,6 +160,7 @@
118161
119162 const zend_function_entry luasandboxfunction_methods[] = {
120163 PHP_ME(LuaSandboxFunction, call, arginfo_luasandboxfunction_call, 0)
 164+ PHP_ME(LuaSandboxFunction, dump, arginfo_luasandboxfunction_dump, 0)
121165 {NULL, NULL, NULL}
122166 };
123167
@@ -154,6 +198,7 @@
155199 */
156200 PHP_MINIT_FUNCTION(luasandbox)
157201 {
 202+ int i;
158203 /* If you have INI entries, uncomment these lines
159204 REGISTER_INI_ENTRIES();
160205 */
@@ -183,6 +228,14 @@
184229 luasandboxfunction_ce = zend_register_internal_class(&ce TSRMLS_CC);
185230 luasandboxfunction_ce->create_object = luasandboxfunction_new;
186231
 232+ // Initialise LUASANDBOX_G(allowed_globals)
 233+ LUASANDBOX_G(allowed_globals) = pemalloc(sizeof(HashTable), 1);
 234+ zend_hash_init(LUASANDBOX_G(allowed_globals), LUASANDBOX_NUM_ALLOWED_GLOBALS, NULL, NULL, 1);
 235+ for (i = 0; i < LUASANDBOX_NUM_ALLOWED_GLOBALS; i++) {
 236+ zend_hash_update(LUASANDBOX_G(allowed_globals),
 237+ luasandbox_allowed_globals[i], strlen(luasandbox_allowed_globals[i]) + 1,
 238+ "", 1, NULL);
 239+ }
187240 return SUCCESS;
188241 }
189242 /* }}} */
@@ -191,6 +244,8 @@
192245 */
193246 PHP_MSHUTDOWN_FUNCTION(luasandbox)
194247 {
 248+ zend_hash_destroy(LUASANDBOX_G(allowed_globals));
 249+ pefree(LUASANDBOX_G(allowed_globals), 1);
195250 return SUCCESS;
196251 }
197252 /* }}} */
@@ -276,10 +331,42 @@
277332 lua_atpanic(L, luasandbox_panic);
278333
279334 // Load some relatively safe standard libraries
 335+ luaopen_base(L);
280336 luaopen_string(L);
281337 luaopen_table(L);
282338 luaopen_math(L);
283339
 340+ // Remove any globals that aren't in a whitelist. This is mostly to remove
 341+ // unsafe functions from the base library.
 342+ lua_pushnil(L);
 343+ while (lua_next(L, LUA_GLOBALSINDEX) != 0) {
 344+ const char * key;
 345+ size_t key_len;
 346+ void * data;
 347+ lua_pop(L, 1);
 348+ if (lua_type(L, -1) != LUA_TSTRING) {
 349+ continue;
 350+ }
 351+ key = lua_tolstring(L, -1, &key_len);
 352+ if (zend_hash_find(LUASANDBOX_G(allowed_globals),
 353+ (char*)key, key_len + 1, &data) == FAILURE)
 354+ {
 355+ // Not allowed, delete it
 356+ lua_pushnil(L);
 357+ lua_setglobal(L, key);
 358+ }
 359+ }
 360+
 361+ // Install our own version of tostring
 362+ lua_pushcfunction(L, luasandbox_base_tostring);
 363+ lua_setglobal(L, "tostring");
 364+
 365+ // Remove string.dump: may expose private data
 366+ lua_getglobal(L, "string");
 367+ lua_pushnil(L);
 368+ lua_setfield(L, -2, "dump");
 369+ lua_pop(L, 1);
 370+
284371 // Create a table for storing chunks
285372 lua_newtable(L);
286373 lua_setfield(L, LUA_REGISTRYINDEX, "php_luasandbox_chunks");
@@ -431,8 +518,8 @@
432519 }
433520 /* }}} */
434521
435 -/** {{{ proto int LuaSandbox::loadString(string code, string chunkName) */
436 -PHP_METHOD(LuaSandbox, loadString)
 522+/** {{{ luasandbox_load_helper */
 523+static void luasandbox_load_helper(int binary, INTERNAL_FUNCTION_PARAMETERS)
437524 {
438525 char *code, *chunkName = NULL;
439526 int codeLength, chunkNameLength;
@@ -440,6 +527,7 @@
441528 lua_State * L = luasandbox_state_from_zval(getThis() TSRMLS_CC);
442529 size_t index;
443530 php_luasandboxfunction_obj * func_obj;
 531+ int have_mark;
444532
445533 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|s",
446534 &code, &codeLength, &chunkName, &chunkNameLength) == FAILURE) {
@@ -457,6 +545,21 @@
458546 }
459547 }
460548
 549+ // Check to see if the code is binary (with precompiled data mark) if this
 550+ // was called as loadBinary(), and plain code (without mark) if this was
 551+ // called as loadString()
 552+ have_mark = (php_memnstr(code, LUA_SIGNATURE,
 553+ sizeof(LUA_SIGNATURE) - 1, code + codeLength) != NULL);
 554+ if (binary && !have_mark) {
 555+ php_error_docref(NULL TSRMLS_CC, E_WARNING,
 556+ "the string does not appear to be a valid binary chunk");
 557+ RETURN_FALSE;
 558+ } else if (!binary && have_mark) {
 559+ php_error_docref(NULL TSRMLS_CC, E_WARNING,
 560+ "cannot load code with a Lua binary chunk marker escape sequence in it");
 561+ RETURN_FALSE;
 562+ }
 563+
461564 // Get the chunks table
462565 lua_getfield(L, LUA_REGISTRYINDEX, "php_luasandbox_chunks");
463566
@@ -489,9 +592,23 @@
490593 // Balance the stack
491594 lua_pop(L, 1);
492595 }
 596+/* }}} */
493597
 598+/** {{{ proto LuaSandboxFunction LuaSandbox::loadString(string code, string chunkName) */
 599+PHP_METHOD(LuaSandbox, loadString)
 600+{
 601+ luasandbox_load_helper(0, INTERNAL_FUNCTION_PARAM_PASSTHRU);
 602+}
 603+
494604 /* }}} */
495605
 606+/** {{{ proto LuaSandboxFunction LuaSandbox::loadBinary(string bin, string chunkName) */
 607+PHP_METHOD(LuaSandbox, loadBinary)
 608+{
 609+ luasandbox_load_helper(1, INTERNAL_FUNCTION_PARAM_PASSTHRU);
 610+}
 611+/* }}} */
 612+
496613 /** {{{ luasandbox_handle_error
497614 *
498615 * Handles the error return situation from lua_pcall() and lua_load(), where a
@@ -633,39 +750,58 @@
634751 }
635752 /* }}} */
636753
637 -/*** {{{ proto array LuaSandboxFunction::call(...)
 754+/** {{{ luasandbox_function_init
 755+ * Common initialisation for LuaSandboxFunction methods. Initialise the
 756+ * function, state and sandbox pointers, and push the function to the stack.
638757 */
 758+static int luasandbox_function_init(zval * this_ptr, php_luasandboxfunction_obj ** pfunc,
 759+ lua_State ** pstate, php_luasandbox_obj ** psandbox TSRMLS_DC)
 760+{
 761+ *pfunc = (php_luasandboxfunction_obj *)
 762+ zend_object_store_get_object(this_ptr TSRMLS_CC);
 763+ if (!*pfunc || !(*pfunc)->sandbox || !(*pfunc)->index) {
 764+ return 0;
 765+ }
 766+
 767+ *psandbox = (php_luasandbox_obj*)
 768+ zend_object_store_get_object((*pfunc)->sandbox TSRMLS_CC);
 769+ *pstate = (*psandbox)->state;
 770+
 771+ // Find the function
 772+ lua_getfield(*pstate, LUA_REGISTRYINDEX, "php_luasandbox_chunks");
 773+ lua_rawgeti(*pstate, -1, (*pfunc)->index);
 774+
 775+ // Remove the table from the stack
 776+ lua_remove(*pstate, -2);
 777+
 778+ return 1;
 779+}
 780+/* }}} */
 781+
 782+/** {{{ proto array LuaSandboxFunction::call(...)
 783+ */
639784 PHP_METHOD(LuaSandboxFunction, call)
640785 {
641786 zend_uint numArgs = 0;
642787 zval *** args = NULL;
643788
644 - php_luasandboxfunction_obj * func = (php_luasandboxfunction_obj *)
645 - zend_object_store_get_object(getThis() TSRMLS_CC);
 789+ php_luasandboxfunction_obj * func;
646790 lua_State * L;
647791 php_luasandbox_obj * sandbox;
648792
649 - if (!func || !func->sandbox || !func->index) {
 793+ if (!luasandbox_function_init(getThis(), &func, &L, &sandbox TSRMLS_CC)) {
650794 php_error_docref(NULL TSRMLS_CC, E_WARNING,
651795 "attempt to call uninitialized LuaSandboxFunction object" );
652796 RETURN_FALSE;
653797 }
654798
655 - sandbox = (php_luasandbox_obj*)
656 - zend_object_store_get_object(func->sandbox TSRMLS_CC);
657 - L = sandbox->state;
658 -
659799 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "*",
660800 &args, &numArgs) == FAILURE)
661801 {
662802 RETURN_FALSE;
663803 }
664804
665 - // Find the function
666 - lua_getfield(L, LUA_REGISTRYINDEX, "php_luasandbox_chunks");
667 - lua_rawgeti(L, -1, func->index);
668 -
669 - // Call it
 805+ // Call the function
670806 luasandbox_call_helper(L, sandbox, args, numArgs, return_value TSRMLS_CC);
671807
672808 // Delete varargs
@@ -983,7 +1119,7 @@
9841120
9851121 // Add the current table to the recursion guard hashtable
9861122 // Use the pointer as the key, zero-length data
987 - zend_hash_update(recursionGuard, (char*)&ptr, sizeof(void*), &data, 0, NULL);
 1123+ zend_hash_update(recursionGuard, (char*)&ptr, sizeof(void*), "", 1, NULL);
9881124
9891125 // Process the array
9901126 array_init(z);
@@ -1180,7 +1316,72 @@
11811317 }
11821318 /* }}} */
11831319
 1320+/** {{{ string LuaSandboxFunction::dump() */
 1321+PHP_METHOD(LuaSandboxFunction, dump)
 1322+{
 1323+ php_luasandboxfunction_obj * func;
 1324+ lua_State * L;
 1325+ php_luasandbox_obj * sandbox;
 1326+ smart_str buf = {0};
 1327+
 1328+ if (!luasandbox_function_init(getThis(), &func, &L, &sandbox TSRMLS_CC)) {
 1329+ php_error_docref(NULL TSRMLS_CC, E_WARNING,
 1330+ "attempt to call uninitialized LuaSandboxFunction object" );
 1331+ RETURN_FALSE;
 1332+ }
 1333+
 1334+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "") == FAILURE) {
 1335+ return;
 1336+ }
 1337+
 1338+ lua_dump(L, luasandbox_dump_writer, (void*)&buf);
 1339+ smart_str_0(&buf);
 1340+ if (buf.len) {
 1341+ RETURN_STRINGL(buf.c, buf.len, 0);
 1342+ } else {
 1343+ smart_str_free(&buf);
 1344+ RETURN_EMPTY_STRING();
 1345+ }
 1346+}
11841347 /* }}} */
 1348+
 1349+/** {{{ luasandbox_dump_writer */
 1350+static int luasandbox_dump_writer(lua_State * L, const void * p, size_t sz, void * ud)
 1351+{
 1352+ smart_str * buf = (smart_str *)ud;
 1353+ smart_str_appendl(buf, p, sz);
 1354+ return 0;
 1355+}
 1356+/* }}} */
 1357+
 1358+/** {{{ luasandbox_base_tostring
 1359+ * This is identical to luaB_tostring except that it does not call lua_topointer().
 1360+ */
 1361+static int luasandbox_base_tostring(lua_State * L)
 1362+{
 1363+ luaL_checkany(L, 1);
 1364+ if (luaL_callmeta(L, 1, "__tostring")) /* is there a metafield? */
 1365+ return 1; /* use its value */
 1366+ switch (lua_type(L, 1)) {
 1367+ case LUA_TNUMBER:
 1368+ lua_pushstring(L, lua_tostring(L, 1));
 1369+ break;
 1370+ case LUA_TSTRING:
 1371+ lua_pushvalue(L, 1);
 1372+ break;
 1373+ case LUA_TBOOLEAN:
 1374+ lua_pushstring(L, (lua_toboolean(L, 1) ? "true" : "false"));
 1375+ break;
 1376+ case LUA_TNIL:
 1377+ lua_pushliteral(L, "nil");
 1378+ break;
 1379+ default:
 1380+ lua_pushfstring(L, "%s", luaL_typename(L, 1));
 1381+ break;
 1382+ }
 1383+ return 1;
 1384+}
 1385+/* }}} */
11851386 /*
11861387 * Local variables:
11871388 * tab-width: 4
Index: trunk/php/luasandbox/php_luasandbox.h
@@ -27,17 +27,19 @@
2828 PHP_MINFO_FUNCTION(luasandbox);
2929
3030 PHP_METHOD(LuaSandbox, loadString);
31 -PHP_METHOD(LuaSandbox, doString);
 31+PHP_METHOD(LuaSandbox, loadBinary);
3232 PHP_METHOD(LuaSandbox, setMemoryLimit);
3333 PHP_METHOD(LuaSandbox, setCPULimit);
3434 PHP_METHOD(LuaSandbox, callFunction);
3535 PHP_METHOD(LuaSandbox, register);
3636
3737 PHP_METHOD(LuaSandboxFunction, call);
 38+PHP_METHOD(LuaSandboxFunction, dump);
3839
3940 ZEND_BEGIN_MODULE_GLOBALS(luasandbox)
4041 int signal_handler_installed;
4142 struct sigaction old_handler;
 43+ HashTable * allowed_globals;
4244 ZEND_END_MODULE_GLOBALS(luasandbox)
4345
4446 #ifdef ZTS

Status & tagging log