r113977 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r113976‎ | r113977 | r113978 >
Date:23:34, 15 March 2012
Author:tstarling
Status:deferred
Tags:
Comment:
Split off some more modules from luasandbox.c, it was getting a bit too long.
Modified paths:
  • /trunk/php/luasandbox/alloc.c (added) (history)
  • /trunk/php/luasandbox/config.m4 (modified) (history)
  • /trunk/php/luasandbox/data_conversion.c (added) (history)
  • /trunk/php/luasandbox/library.c (added) (history)
  • /trunk/php/luasandbox/luasandbox.c (modified) (history)
  • /trunk/php/luasandbox/php_luasandbox.h (modified) (history)

Diff [purge]

Index: trunk/php/luasandbox/data_conversion.c
@@ -0,0 +1,341 @@
 2+
 3+#ifdef HAVE_CONFIG_H
 4+#include "config.h"
 5+#endif
 6+
 7+#include <lua.h>
 8+#include <lauxlib.h>
 9+#include <limits.h>
 10+#include <float.h>
 11+
 12+#include "php.h"
 13+#include "php_luasandbox.h"
 14+
 15+static void luasandbox_lua_to_array(HashTable *ht, lua_State *L, int index,
 16+ zval * sandbox_zval, HashTable * recursionGuard TSRMLS_DC);
 17+static int luasandbox_free_zval_userdata(lua_State * L);
 18+static int luasandbox_push_hashtable(lua_State * L, HashTable * ht);
 19+
 20+extern zend_class_entry *luasandboxfunction_ce;
 21+extern zend_class_entry *luasandboxplaceholder_ce;
 22+
 23+/** {{{ luasandbox_data_conversion_init
 24+ *
 25+ * Set up a lua_State so that this module can work with it.
 26+ */
 27+void luasandbox_data_conversion_init(lua_State * L)
 28+{
 29+ // Create the metatable for zval destruction
 30+ lua_createtable(L, 0, 1);
 31+ lua_pushcfunction(L, luasandbox_free_zval_userdata);
 32+ lua_setfield(L, -2, "__gc");
 33+ lua_setfield(L, LUA_REGISTRYINDEX, "php_luasandbox_zval_metatable");
 34+}
 35+/* }}} */
 36+
 37+/** {{{ luasandbox_push_zval
 38+ *
 39+ * Convert a zval to an appropriate Lua type and push the resulting value on to
 40+ * the stack.
 41+ */
 42+int luasandbox_push_zval(lua_State * L, zval * z)
 43+{
 44+ switch (Z_TYPE_P(z)) {
 45+ case IS_NULL:
 46+ lua_pushnil(L);
 47+ break;
 48+ case IS_LONG:
 49+ lua_pushinteger(L, Z_LVAL_P(z));
 50+ break;
 51+ case IS_DOUBLE:
 52+ lua_pushnumber(L, Z_DVAL_P(z));
 53+ break;
 54+ case IS_BOOL:
 55+ lua_pushboolean(L, Z_BVAL_P(z));
 56+ break;
 57+ case IS_ARRAY:
 58+ if (!luasandbox_push_hashtable(L, Z_ARRVAL_P(z))) {
 59+ return 0;
 60+ }
 61+ break;
 62+ case IS_OBJECT: {
 63+ zend_class_entry * objce;
 64+
 65+ objce = Z_OBJCE_P(z);
 66+ if (instanceof_function(objce, luasandboxfunction_ce TSRMLS_CC)) {
 67+ php_luasandboxfunction_obj * func_obj;
 68+
 69+ func_obj = (php_luasandboxfunction_obj *)zend_object_store_get_object(z TSRMLS_CC);
 70+
 71+ lua_getfield(L, LUA_REGISTRYINDEX, "php_luasandbox_chunks");
 72+ lua_rawgeti(L, -1, func_obj->index);
 73+ lua_remove(L, -2);
 74+ break;
 75+ }
 76+
 77+ if (!luasandbox_push_hashtable(L, Z_OBJPROP_P(z))) {
 78+ return 0;
 79+ }
 80+ break;
 81+ }
 82+ case IS_STRING:
 83+ lua_pushlstring(L, Z_STRVAL_P(z), Z_STRLEN_P(z));
 84+ break;
 85+ case IS_RESOURCE:
 86+ case IS_CONSTANT:
 87+ case IS_CONSTANT_ARRAY:
 88+ default:
 89+ return 0;
 90+ }
 91+ return 1;
 92+}
 93+/* }}} */
 94+
 95+/** {{{ luasandbox_free_zval_userdata
 96+ *
 97+ * Free a zval given to Lua by luasandbox_push_zval_userdata.
 98+ */
 99+static int luasandbox_free_zval_userdata(lua_State * L)
 100+{
 101+ zval ** zpp = (zval**)lua_touserdata(L, 1);
 102+ php_luasandbox_obj * intern = luasandbox_get_php_obj(L);
 103+ luasandbox_enter_php(L, intern);
 104+ if (zpp && *zpp) {
 105+ zval_ptr_dtor(zpp);
 106+ }
 107+ *zpp = NULL;
 108+ luasandbox_leave_php(L, intern);
 109+ return 0;
 110+}
 111+/* }}} */
 112+
 113+/** {{{ luasandbox_push_zval_userdata
 114+ *
 115+ * Push a full userdata on to the stack, which stores a zval* in its block.
 116+ * Increment its reference count and set its metatable so that it will be freed
 117+ * at the appropriate time.
 118+ */
 119+void luasandbox_push_zval_userdata(lua_State * L, zval * z)
 120+{
 121+ zval ** ud;
 122+ ud = (zval**)lua_newuserdata(L, sizeof(zval*));
 123+ *ud = z;
 124+ Z_ADDREF_P(z);
 125+
 126+ lua_getfield(L, LUA_REGISTRYINDEX, "php_luasandbox_zval_metatable");
 127+ lua_setmetatable(L, -2);
 128+}
 129+/* }}} */
 130+
 131+/** {{{ luasandbox_push_hashtable
 132+ *
 133+ * Helper function for luasandbox_push_zval. Create a new table on the top of
 134+ * the stack and add the zvals in the HashTable to it.
 135+ */
 136+static int luasandbox_push_hashtable(lua_State * L, HashTable * ht)
 137+{
 138+ Bucket * p;
 139+
 140+ // Recursion requires an arbitrary amount of stack space so we have to
 141+ // check the stack.
 142+ luaL_checkstack(L, 10, "converting PHP array to Lua");
 143+
 144+ lua_newtable(L);
 145+ if (!ht || !ht->nNumOfElements) {
 146+ return 1;
 147+ }
 148+ if (ht->nApplyCount) {
 149+ php_error_docref(NULL TSRMLS_CC, E_WARNING, "recursion detected");
 150+ return 0;
 151+ }
 152+ ht->nApplyCount++;
 153+ for (p = ht->pListHead; p; p = p->pListNext) {
 154+ if (p->nKeyLength) {
 155+ lua_pushlstring(L, p->arKey, p->nKeyLength - 1);
 156+ } else {
 157+ lua_pushinteger(L, p->h);
 158+ }
 159+
 160+ if (!luasandbox_push_zval(L, *(zval**)p->pData)) {
 161+ // Failed to process that data value
 162+ // Pop the key and the half-constructed table
 163+ lua_pop(L, 2);
 164+ ht->nApplyCount--;
 165+ return 0;
 166+ }
 167+
 168+ lua_settable(L, -3);
 169+ }
 170+ ht->nApplyCount--;
 171+ return 1;
 172+}
 173+/* }}} */
 174+
 175+/** {{{ luasandbox_lua_to_zval
 176+ *
 177+ * Convert a lua value to a zval.
 178+ *
 179+ * If a value is encountered that can't be converted to a zval, a LuaPlaceholder
 180+ * object is returned instead.
 181+ *
 182+ * @param z A pointer to the destination zval
 183+ * @param L The lua state
 184+ * @param index The stack index to the input value
 185+ * @param sandbox_zval A zval poiting to a valid LuaSandbox object which will be
 186+ * used for the parent object of any LuaSandboxFunction objects created.
 187+ * @param recursionGuard A hashtable for keeping track of tables that have been
 188+ * processed, to allow infinite recursion to be avoided. External callers
 189+ * should set this to NULL.
 190+ */
 191+void luasandbox_lua_to_zval(zval * z, lua_State * L, int index,
 192+ zval * sandbox_zval, HashTable * recursionGuard TSRMLS_DC)
 193+{
 194+ switch (lua_type(L, index)) {
 195+ case LUA_TNIL:
 196+ ZVAL_NULL(z);
 197+ break;
 198+ case LUA_TNUMBER: {
 199+ long i;
 200+ double d, integerPart, fractionalPart;
 201+ // Lua only provides a single number type
 202+ // Convert it to a PHP integer if that can be done without loss
 203+ // of precision
 204+ d = lua_tonumber(L, index);
 205+ fractionalPart = modf(d, &integerPart);
 206+ if (fractionalPart == 0.0 && integerPart >= LONG_MIN && integerPart <= LONG_MAX) {
 207+ // The number is small enough to fit inside an int. But has it already
 208+ // been truncated by squeezing it into a double? This is only relevant
 209+ // where the integer size is greater than the mantissa size.
 210+ i = (long)integerPart;
 211+ if (LONG_MAX < (1LL << DBL_MANT_DIG)
 212+ || labs(i) < (1L << DBL_MANT_DIG))
 213+ {
 214+ ZVAL_LONG(z, i);
 215+ } else {
 216+ ZVAL_DOUBLE(z, d);
 217+ }
 218+ } else {
 219+ ZVAL_DOUBLE(z, d);
 220+ }
 221+ break;
 222+ }
 223+ case LUA_TBOOLEAN:
 224+ ZVAL_BOOL(z, lua_toboolean(L, index));
 225+ break;
 226+ case LUA_TSTRING: {
 227+ const char * str;
 228+ size_t length;
 229+ str = lua_tolstring(L, index, &length);
 230+ ZVAL_STRINGL(z, str, length, 1);
 231+ break;
 232+ }
 233+ case LUA_TTABLE: {
 234+ const void * ptr = lua_topointer(L, index);
 235+ void * data = NULL;
 236+ int allocated = 0;
 237+ if (recursionGuard) {
 238+ // Check for circular reference (infinite recursion)
 239+ if (zend_hash_find(recursionGuard, (char*)&ptr, sizeof(void*), &data) == SUCCESS) {
 240+ // Found circular reference!
 241+ object_init_ex(z, luasandboxplaceholder_ce);
 242+ break;
 243+ }
 244+ } else {
 245+ ALLOC_HASHTABLE(recursionGuard);
 246+ zend_hash_init(recursionGuard, 1, NULL, NULL, 0);
 247+ allocated = 1;
 248+ }
 249+
 250+ // Add the current table to the recursion guard hashtable
 251+ // Use the pointer as the key, zero-length data
 252+ zend_hash_update(recursionGuard, (char*)&ptr, sizeof(void*), "", 1, NULL);
 253+
 254+ // Process the array
 255+ array_init(z);
 256+ luasandbox_lua_to_array(Z_ARRVAL_P(z), L, index, sandbox_zval, recursionGuard TSRMLS_CC);
 257+
 258+ if (allocated) {
 259+ zend_hash_destroy(recursionGuard);
 260+ FREE_HASHTABLE(recursionGuard);
 261+ }
 262+ break;
 263+ }
 264+ case LUA_TFUNCTION: {
 265+ int func_index;
 266+ php_luasandboxfunction_obj * func_obj;
 267+ php_luasandbox_obj * sandbox = (php_luasandbox_obj*)
 268+ zend_object_store_get_object(sandbox_zval);
 269+
 270+ // Normalise the input index so that we can push without invalidating it.
 271+ if (index < 0) {
 272+ index += lua_gettop(L) + 1;
 273+ }
 274+
 275+ // Get the chunks table
 276+ lua_getfield(L, LUA_REGISTRYINDEX, "php_luasandbox_chunks");
 277+
 278+ // Get the next free index
 279+ if (sandbox->function_index >= INT_MAX) {
 280+ ZVAL_NULL(z);
 281+ lua_pop(L, 1);
 282+ break;
 283+ }
 284+ func_index = ++(sandbox->function_index);
 285+
 286+ // Store it in the chunks table
 287+ lua_pushvalue(L, index);
 288+ lua_rawseti(L, -2, func_index);
 289+
 290+ // Create a LuaSandboxFunction object to hold a reference to the function
 291+ object_init_ex(z, luasandboxfunction_ce);
 292+ func_obj = (php_luasandboxfunction_obj*)zend_object_store_get_object(z);
 293+ func_obj->index = func_index;
 294+ func_obj->sandbox = sandbox_zval;
 295+ Z_ADDREF_P(sandbox_zval);
 296+
 297+ // Balance the stack
 298+ lua_pop(L, 1);
 299+ break;
 300+ }
 301+ case LUA_TUSERDATA:
 302+ case LUA_TTHREAD:
 303+ case LUA_TLIGHTUSERDATA:
 304+ default:
 305+ // TODO: provide derived classes for each type
 306+ object_init_ex(z, luasandboxplaceholder_ce);
 307+ }
 308+}
 309+/* }}} */
 310+
 311+/** {{{ luasandbox_lua_to_array
 312+ *
 313+ * Append the elements of the table in the specified index to the given HashTable.
 314+ */
 315+static void luasandbox_lua_to_array(HashTable *ht, lua_State *L, int index,
 316+ zval * sandbox_zval, HashTable * recursionGuard TSRMLS_DC)
 317+{
 318+ const char * str;
 319+ size_t length;
 320+ zval *value;
 321+
 322+ // Normalise the input index so that we can push without invalidating it.
 323+ if (index < 0) {
 324+ index += lua_gettop(L) + 1;
 325+ }
 326+
 327+ lua_pushnil(L);
 328+ while (lua_next(L, index) != 0) {
 329+ MAKE_STD_ZVAL(value);
 330+ luasandbox_lua_to_zval(value, L, -1, sandbox_zval, recursionGuard TSRMLS_CC);
 331+
 332+ // Make a copy of the key so that we can call lua_tolstring() which is destructive
 333+ lua_pushvalue(L, -2);
 334+ str = lua_tolstring(L, -1, &length);
 335+ zend_hash_update(ht, str, length + 1, (void*)&value, sizeof(zval*), NULL);
 336+
 337+ // Delete the copy and the value
 338+ lua_pop(L, 2);
 339+ }
 340+}
 341+/* }}} */
 342+
Property changes on: trunk/php/luasandbox/data_conversion.c
___________________________________________________________________
Added: svn:eol-style
1343 + native
Index: trunk/php/luasandbox/luasandbox.c
@@ -4,11 +4,7 @@
55
66 #include <lua.h>
77 #include <lauxlib.h>
8 -#include <lualib.h>
98 #include <math.h>
10 -#include <limits.h>
11 -#include <float.h>
12 -#include <signal.h>
139 #include <time.h>
1410 #include <stdlib.h>
1511
@@ -20,10 +16,6 @@
2117 #include "luasandbox_timer.h"
2218 #include "ext/standard/php_smart_str.h"
2319
24 -#if defined(LUA_JITLIBNAME) && (SSIZE_MAX >> 32) > 0
25 -#define LUASANDBOX_LJ_64
26 -#endif
27 -
2820 #define CHECK_VALID_STATE(state) \
2921 if (!state) { \
3022 php_error_docref(NULL TSRMLS_CC, E_WARNING, "invalid LuaSandbox state"); \
@@ -36,11 +28,6 @@
3729 static zend_object_value luasandboxfunction_new(zend_class_entry *ce TSRMLS_CC);
3830 static void luasandboxfunction_free_storage(void *object TSRMLS_DC);
3931 static void luasandboxfunction_destroy(void *object, zend_object_handle handle TSRMLS_DC);
40 -static int luasandbox_free_zval_userdata(lua_State * L);
41 -static inline int luasandbox_update_memory_accounting(php_luasandbox_obj* obj,
42 - size_t osize, size_t nsize);
43 -static void *luasandbox_php_alloc(void *ud, void *ptr, size_t osize, size_t nsize);
44 -static void *luasandbox_passthru_alloc(void *ud, void *ptr, size_t osize, size_t nsize);
4532 static int luasandbox_panic(lua_State * L);
4633 static lua_State * luasandbox_state_from_zval(zval * this_ptr TSRMLS_DC);
4734 static void luasandbox_load_helper(int binary, INTERNAL_FUNCTION_PARAMETERS);
@@ -52,70 +39,13 @@
5340 static void luasandbox_call_helper(lua_State * L, zval * sandbox_zval,
5441 php_luasandbox_obj * sandbox,
5542 zval *** args, zend_uint numArgs, zval * return_value TSRMLS_DC);
56 -static int luasandbox_push_zval(lua_State * L, zval * z);
57 -static void luasandbox_push_zval_userdata(lua_State * L, zval * z);
58 -static void luasandbox_lua_to_zval(zval * z, lua_State * L, int index,
59 - zval * sandbox_zval, HashTable * recursionGuard TSRMLS_DC);
60 -static void luasandbox_lua_to_array(HashTable *ht, lua_State *L, int index,
61 - zval * sandbox_zval, HashTable * recursionGuard TSRMLS_DC);
62 -static php_luasandbox_obj * luasandbox_get_php_obj(lua_State * L);
6343 static void luasandbox_handle_error(lua_State * L, int status);
6444 static void luasandbox_handle_emergency_timeout(php_luasandbox_obj * sandbox);
65 -static int luasandbox_push_hashtable(lua_State * L, HashTable * ht);
6645 static int luasandbox_call_php(lua_State * L);
6746 static int luasandbox_dump_writer(lua_State * L, const void * p, size_t sz, void * ud);
68 -static int luasandbox_base_tostring(lua_State * L);
69 -static int luasandbox_math_random(lua_State * L);
70 -static int luasandbox_math_randomseed(lua_State * L);
7147
7248 char luasandbox_timeout_message[] = "The maximum execution time for this script was exceeded";
7349
74 -/**
75 - * Allowed global variables. Omissions are:
76 - * * pcall, xpcall: Changing the protected environment won't work with our
77 - * current timeout method.
78 - * * loadfile: insecure.
79 - * * load, loadstring: Probably creates a protected environment so has
80 - * the same problem as pcall. Also omitting these makes analysis of the
81 - * code for runtime etc. feasible.
82 - * * print: Not compatible with a sandbox environment
83 - * * tostring: Provides addresses of tables and functions, which provides an
84 - * easy ASLR workaround or heap address discovery mechanism for a memory
85 - * corruption exploit.
86 - * * Any new or undocumented functions like newproxy.
87 - * * package: cpath, loadlib etc. are insecure.
88 - * * coroutine: Not useful for our application so unreviewed at present.
89 - * * io, file, os: insecure
90 - * * debug: Provides various ways to break the sandbox, such as setupvalue()
91 - * and getregistry().
92 - */
93 -char * luasandbox_allowed_globals[] = {
94 - // base
95 - "assert",
96 - "error",
97 - "getmetatable",
98 - "getfenv",
99 - "getmetatable",
100 - "ipairs",
101 - "next",
102 - "pairs",
103 - "rawequal",
104 - "rawget",
105 - "rawset",
106 - "select",
107 - "setmetatable",
108 - "tonumber",
109 - "type",
110 - "unpack",
111 - "_G",
112 - "_VERSION",
113 - // libs
114 - "string",
115 - "table",
116 - "math"
117 -};
118 -#define LUASANDBOX_NUM_ALLOWED_GLOBALS (sizeof(luasandbox_allowed_globals) / sizeof(char*))
119 -
12050 zend_class_entry *luasandbox_ce;
12151 zend_class_entry *luasandboxerror_ce;
12252 zend_class_entry *luasandboxemergencytimeout_ce;
@@ -227,7 +157,6 @@
228158 */
229159 PHP_MINIT_FUNCTION(luasandbox)
230160 {
231 - int i;
232161 /* If you have INI entries, uncomment these lines
233162 REGISTER_INI_ENTRIES();
234163 */
@@ -261,14 +190,7 @@
262191 luasandboxfunction_ce = zend_register_internal_class(&ce TSRMLS_CC);
263192 luasandboxfunction_ce->create_object = luasandboxfunction_new;
264193
265 - // Initialise LUASANDBOX_G(allowed_globals)
266 - LUASANDBOX_G(allowed_globals) = pemalloc(sizeof(HashTable), 1);
267 - zend_hash_init(LUASANDBOX_G(allowed_globals), LUASANDBOX_NUM_ALLOWED_GLOBALS, NULL, NULL, 1);
268 - for (i = 0; i < LUASANDBOX_NUM_ALLOWED_GLOBALS; i++) {
269 - zend_hash_update(LUASANDBOX_G(allowed_globals),
270 - luasandbox_allowed_globals[i], strlen(luasandbox_allowed_globals[i]) + 1,
271 - "", 1, NULL);
272 - }
 194+ LUASANDBOX_G(allowed_globals) = NULL;
273195 return SUCCESS;
274196 }
275197 /* }}} */
@@ -277,8 +199,7 @@
278200 */
279201 PHP_MSHUTDOWN_FUNCTION(luasandbox)
280202 {
281 - zend_hash_destroy(LUASANDBOX_G(allowed_globals));
282 - pefree(LUASANDBOX_G(allowed_globals), 1);
 203+ luasandbox_lib_shutdown(TSRMLS_C);
283204 return SUCCESS;
284205 }
285206 /* }}} */
@@ -303,37 +224,6 @@
304225 }
305226 /* }}} */
306227
307 -/** {{{ luasandbox_enter_php
308 - *
309 - * This function must be called each time a C function is entered from Lua
310 - * and the PHP state needs to be accessed in any way. Before exiting the
311 - * function, luasandbox_leave_php() must be called.
312 - *
313 - * This sets a flag which indicates to the timeout signal handler that it is
314 - * unsafe to call longjmp() to return control to PHP. If the flag is not
315 - * correctly set, memory may be corrupted and security compromised.
316 - */
317 -static inline void luasandbox_enter_php(lua_State * L, php_luasandbox_obj * intern)
318 -{
319 - intern->in_php ++;
320 - if (intern->timed_out) {
321 - intern->in_php --;
322 - luaL_error(L, luasandbox_timeout_message);
323 - }
324 -}
325 -/* }}} */
326 -
327 -/** {{{ luasandbox_leave_php
328 - *
329 - * This function must be called after luasandbox_enter_php, before the callback
330 - * from Lua returns.
331 - */
332 -static inline void luasandbox_leave_php(lua_State * L, php_luasandbox_obj * intern)
333 -{
334 - intern->in_php --;
335 -}
336 -/* }}} */
337 -
338228 /** {{{ luasandbox_new
339229 *
340230 * "new" handler for the LuaSandbox class
@@ -347,7 +237,7 @@
348238 intern = emalloc(sizeof(php_luasandbox_obj));
349239 memset(intern, 0, sizeof(php_luasandbox_obj));
350240 zend_object_std_init(&intern->std, ce TSRMLS_CC);
351 - intern->memory_limit = (size_t)-1;
 241+ intern->alloc.memory_limit = (size_t)-1;
352242
353243 // Initialise the Lua state
354244 intern->state = luasandbox_newstate(intern);
@@ -370,73 +260,21 @@
371261 */
372262 static lua_State * luasandbox_newstate(php_luasandbox_obj * intern)
373263 {
374 - lua_State * L;
 264+ lua_State * L = luasandbox_alloc_new_state(&intern->alloc, intern);
375265
376 -#ifdef LUASANDBOX_LJ_64
377 - // The 64-bit version of LuaJIT needs to use its own allocator
378 - L = luaL_newstate();
379266 if (L == NULL) {
380267 php_error_docref(NULL TSRMLS_CC, E_ERROR,
381268 "Attempt to allocate a new Lua state failed");
382269 }
383 - intern->old_alloc = lua_getallocf(L, &intern->old_alloc_ud);
384 - lua_setallocf(L, luasandbox_passthru_alloc, intern);
385 -#else
386 - L = lua_newstate(luasandbox_php_alloc, intern);
387 -#endif
388270
389271 lua_atpanic(L, luasandbox_panic);
390 -
391 - // Load some relatively safe standard libraries
392 - lua_pushcfunction(L, luaopen_base);
393 - lua_call(L, 0, 0);
394 - lua_pushcfunction(L, luaopen_string);
395 - lua_call(L, 0, 0);
396 - lua_pushcfunction(L, luaopen_table);
397 - lua_call(L, 0, 0);
398 - lua_pushcfunction(L, luaopen_math);
399 - lua_call(L, 0, 0);
400 -
401 - // Remove any globals that aren't in a whitelist. This is mostly to remove
402 - // unsafe functions from the base library.
403 - lua_pushnil(L);
404 - while (lua_next(L, LUA_GLOBALSINDEX) != 0) {
405 - const char * key;
406 - size_t key_len;
407 - void * data;
408 - lua_pop(L, 1);
409 - if (lua_type(L, -1) != LUA_TSTRING) {
410 - continue;
411 - }
412 - key = lua_tolstring(L, -1, &key_len);
413 - if (zend_hash_find(LUASANDBOX_G(allowed_globals),
414 - (char*)key, key_len + 1, &data) == FAILURE)
415 - {
416 - // Not allowed, delete it
417 - lua_pushnil(L);
418 - lua_setglobal(L, key);
419 - }
420 - }
421 -
422 - // Install our own version of tostring
423 - lua_pushcfunction(L, luasandbox_base_tostring);
424 - lua_setglobal(L, "tostring");
425 -
426 - // Remove string.dump: may expose private data
427 - lua_getglobal(L, "string");
428 - lua_pushnil(L);
429 - lua_setfield(L, -2, "dump");
430 - lua_pop(L, 1);
431 -
432 - // Install our own versions of math.random and math.randomseed
433 - lua_getglobal(L, "math");
434 - lua_pushcfunction(L, luasandbox_math_random);
435 - lua_setfield(L, -2, "random");
436 - lua_pushcfunction(L, luasandbox_math_randomseed);
437 - lua_setfield(L, -2, "randomseed");
438 - lua_pop(L, 1);
439272
 273+ // Register the standard library
 274+ luasandbox_lib_register(L TSRMLS_CC);
440275
 276+ // Set up the data conversion module
 277+ luasandbox_data_conversion_init(L);
 278+
441279 // Create a table for storing chunks
442280 lua_newtable(L);
443281 lua_setfield(L, LUA_REGISTRYINDEX, "php_luasandbox_chunks");
@@ -445,12 +283,6 @@
446284 lua_pushlightuserdata(L, (void*)intern);
447285 lua_setfield(L, LUA_REGISTRYINDEX, "php_luasandbox_obj");
448286
449 - // Create the metatable for zval destruction
450 - lua_createtable(L, 0, 1);
451 - lua_pushcfunction(L, luasandbox_free_zval_userdata);
452 - lua_setfield(L, -2, "__gc");
453 - lua_setfield(L, LUA_REGISTRYINDEX, "php_luasandbox_zval_metatable");
454 -
455287 return L;
456288 }
457289 /* }}} */
@@ -464,16 +296,10 @@
465297 php_luasandbox_obj * intern = (php_luasandbox_obj*)object;
466298
467299 if (intern->state) {
468 - // In 64-bit LuaJIT mode, restore the old allocator before calling
469 - // lua_close() because lua_close() actually checks that the value of the
470 - // function pointer is unchanged before destroying the underlying
471 - // allocator. If the allocator has been changed, the mmap is not freed.
472 -#ifdef LUASANDBOX_LJ_64
473 - lua_setallocf(intern->state, intern->old_alloc, intern->old_alloc_ud);
474 -#endif
475 -
476 - lua_close(intern->state);
 300+ luasandbox_alloc_delete_state(&intern->alloc, intern->state);
477301 intern->state = NULL;
 302+
 303+ intern->state = NULL;
478304 }
479305 zend_object_std_dtor(&intern->std);
480306 efree(object);
@@ -547,97 +373,6 @@
548374 }
549375 /* }}} */
550376
551 -/** {{{ luasandbox_free_zval_userdata
552 - *
553 - * Free a zval given to Lua by luasandbox_push_zval_userdata.
554 - */
555 -static int luasandbox_free_zval_userdata(lua_State * L)
556 -{
557 - zval ** zpp = (zval**)lua_touserdata(L, 1);
558 - php_luasandbox_obj * intern = luasandbox_get_php_obj(L);
559 - luasandbox_enter_php(L, intern);
560 - if (zpp && *zpp) {
561 - zval_ptr_dtor(zpp);
562 - }
563 - *zpp = NULL;
564 - luasandbox_leave_php(L, intern);
565 - return 0;
566 -}
567 -/* }}} */
568 -
569 -/** {{{ luasandbox_php_alloc
570 - *
571 - * The Lua allocator function. Use PHP's request-local allocator as a backend.
572 - * Account for memory usage and deny the allocation request if the amount
573 - * allocated is above the user-specified limit.
574 - */
575 -static void *luasandbox_php_alloc(void *ud, void *ptr, size_t osize, size_t nsize)
576 -{
577 - php_luasandbox_obj * obj = (php_luasandbox_obj*)ud;
578 - void * nptr;
579 - obj->in_php ++;
580 - if (!luasandbox_update_memory_accounting(obj, osize, nsize)) {
581 - obj->in_php --;
582 - return NULL;
583 - }
584 -
585 - if (nsize == 0) {
586 - if (ptr) {
587 - efree(ptr);
588 - }
589 - nptr = NULL;
590 - } else if (osize == 0) {
591 - nptr = emalloc(nsize);
592 - } else {
593 - nptr = erealloc(ptr, nsize);
594 - }
595 - obj->in_php --;
596 - return nptr;
597 -}
598 -/* }}} */
599 -
600 -/** {{{ luasandbox_passthru_alloc
601 - *
602 - * A Lua allocator function for use with LuaJIT on a 64-bit platform. Pass
603 - * allocation requests through to the standard allocator, which is customised
604 - * on this platform to always return memory from the lower 2GB of address
605 - * space.
606 - */
607 -static void *luasandbox_passthru_alloc(void *ud, void *ptr, size_t osize, size_t nsize)
608 -{
609 - php_luasandbox_obj * obj = (php_luasandbox_obj*)ud;
610 - if (!luasandbox_update_memory_accounting(obj, osize, nsize)) {
611 - return NULL;
612 - }
613 - return obj->old_alloc(obj->old_alloc_ud, ptr, osize, nsize);
614 -}
615 -/* }}} */
616 -
617 -/** {{{ luasandbox_update_memory_accounting
618 - *
619 - * Update memory usage statistics for the given memory allocation request.
620 - * Returns 1 if the allocation should be allowed, 0 if it should fail.
621 - */
622 -static inline int luasandbox_update_memory_accounting(php_luasandbox_obj* obj,
623 - size_t osize, size_t nsize)
624 -{
625 - if (nsize > obj->memory_limit
626 - || obj->memory_usage > obj->memory_limit - nsize)
627 - {
628 - // Memory limit exceeded
629 - return 0;
630 - }
631 -
632 - if (osize > nsize && obj->memory_usage + nsize < osize) {
633 - // Negative memory usage -- do not update
634 - return 1;
635 - }
636 -
637 - obj->memory_usage += nsize - osize;
638 - return 1;
639 -}
640 -/* }}} */
641 -
642377 /** {{{ luasandbox_panic
643378 *
644379 * The Lua panic function. It is necessary to raise an E_ERROR, and thus do a
@@ -828,7 +563,7 @@
829564 RETURN_FALSE;
830565 }
831566
832 - intern->memory_limit = limit;
 567+ intern->alloc.memory_limit = limit;
833568 }
834569 /* }}} */
835570
@@ -935,7 +670,7 @@
936671 RETURN_FALSE;
937672 }
938673
939 - RETURN_LONG(sandbox->memory_usage);
 674+ RETURN_LONG(sandbox->alloc.memory_usage);
940675 }
941676 /* }}} */
942677
@@ -1034,6 +769,7 @@
1035770 {
1036771 php_error_docref(NULL TSRMLS_CC, E_ERROR, "LuaSandboxFunction cannot be constructed directly");
1037772 }
 773+/* }}} */
1038774
1039775 /** {{{ proto array LuaSandboxFunction::call(...)
1040776 *
@@ -1216,299 +952,11 @@
1217953 }
1218954 /* }}} */
1219955
1220 -/** {{{ luasandbox_push_zval
1221 - *
1222 - * Convert a zval to an appropriate Lua type and push the resulting value on to
1223 - * the stack.
1224 - */
1225 -static int luasandbox_push_zval(lua_State * L, zval * z)
1226 -{
1227 - switch (Z_TYPE_P(z)) {
1228 - case IS_NULL:
1229 - lua_pushnil(L);
1230 - break;
1231 - case IS_LONG:
1232 - lua_pushinteger(L, Z_LVAL_P(z));
1233 - break;
1234 - case IS_DOUBLE:
1235 - lua_pushnumber(L, Z_DVAL_P(z));
1236 - break;
1237 - case IS_BOOL:
1238 - lua_pushboolean(L, Z_BVAL_P(z));
1239 - break;
1240 - case IS_ARRAY:
1241 - if (!luasandbox_push_hashtable(L, Z_ARRVAL_P(z))) {
1242 - return 0;
1243 - }
1244 - break;
1245 - case IS_OBJECT: {
1246 - zend_class_entry * objce;
1247 -
1248 - objce = Z_OBJCE_P(z);
1249 - if (instanceof_function(objce, luasandboxfunction_ce TSRMLS_CC)) {
1250 - php_luasandboxfunction_obj * func_obj;
1251 -
1252 - func_obj = (php_luasandboxfunction_obj *)zend_object_store_get_object(z TSRMLS_CC);
1253 -
1254 - lua_getfield(L, LUA_REGISTRYINDEX, "php_luasandbox_chunks");
1255 - lua_rawgeti(L, -1, func_obj->index);
1256 - lua_remove(L, -2);
1257 - break;
1258 - }
1259 -
1260 - if (!luasandbox_push_hashtable(L, Z_OBJPROP_P(z))) {
1261 - return 0;
1262 - }
1263 - break;
1264 - }
1265 - case IS_STRING:
1266 - lua_pushlstring(L, Z_STRVAL_P(z), Z_STRLEN_P(z));
1267 - break;
1268 - case IS_RESOURCE:
1269 - case IS_CONSTANT:
1270 - case IS_CONSTANT_ARRAY:
1271 - default:
1272 - return 0;
1273 - }
1274 - return 1;
1275 -}
1276 -/* }}} */
1277 -
1278 -/** {{{ luasandbox_push_zval_userdata
1279 - *
1280 - * Push a full userdata on to the stack, which stores a zval* in its block.
1281 - * Increment its reference count and set its metatable so that it will be freed
1282 - * at the appropriate time.
1283 - */
1284 -static void luasandbox_push_zval_userdata(lua_State * L, zval * z)
1285 -{
1286 - zval ** ud;
1287 - ud = (zval**)lua_newuserdata(L, sizeof(zval*));
1288 - *ud = z;
1289 - Z_ADDREF_P(z);
1290 -
1291 - lua_getfield(L, LUA_REGISTRYINDEX, "php_luasandbox_zval_metatable");
1292 - lua_setmetatable(L, -2);
1293 -}
1294 -/* }}} */
1295 -
1296 -/** {{{ luasandbox_push_hashtable
1297 - *
1298 - * Helper function for luasandbox_push_zval. Create a new table on the top of
1299 - * the stack and add the zvals in the HashTable to it.
1300 - */
1301 -static int luasandbox_push_hashtable(lua_State * L, HashTable * ht)
1302 -{
1303 - Bucket * p;
1304 -
1305 - // Recursion requires an arbitrary amount of stack space so we have to
1306 - // check the stack.
1307 - luaL_checkstack(L, 10, "converting PHP array to Lua");
1308 -
1309 - lua_newtable(L);
1310 - if (!ht || !ht->nNumOfElements) {
1311 - return 1;
1312 - }
1313 - if (ht->nApplyCount) {
1314 - php_error_docref(NULL TSRMLS_CC, E_WARNING, "recursion detected");
1315 - return 0;
1316 - }
1317 - ht->nApplyCount++;
1318 - for (p = ht->pListHead; p; p = p->pListNext) {
1319 - if (p->nKeyLength) {
1320 - lua_pushlstring(L, p->arKey, p->nKeyLength - 1);
1321 - } else {
1322 - lua_pushinteger(L, p->h);
1323 - }
1324 -
1325 - if (!luasandbox_push_zval(L, *(zval**)p->pData)) {
1326 - // Failed to process that data value
1327 - // Pop the key and the half-constructed table
1328 - lua_pop(L, 2);
1329 - ht->nApplyCount--;
1330 - return 0;
1331 - }
1332 -
1333 - lua_settable(L, -3);
1334 - }
1335 - ht->nApplyCount--;
1336 - return 1;
1337 -}
1338 -/* }}} */
1339 -
1340 -/** {{{ luasandbox_lua_to_zval
1341 - *
1342 - * Convert a lua value to a zval.
1343 - *
1344 - * If a value is encountered that can't be converted to a zval, a LuaPlaceholder
1345 - * object is returned instead.
1346 - *
1347 - * @param z A pointer to the destination zval
1348 - * @param L The lua state
1349 - * @param index The stack index to the input value
1350 - * @param sandbox_zval A zval poiting to a valid LuaSandbox object which will be
1351 - * used for the parent object of any LuaSandboxFunction objects created.
1352 - * @param recursionGuard A hashtable for keeping track of tables that have been
1353 - * processed, to allow infinite recursion to be avoided. External callers
1354 - * should set this to NULL.
1355 - */
1356 -static void luasandbox_lua_to_zval(zval * z, lua_State * L, int index,
1357 - zval * sandbox_zval, HashTable * recursionGuard TSRMLS_DC)
1358 -{
1359 - switch (lua_type(L, index)) {
1360 - case LUA_TNIL:
1361 - ZVAL_NULL(z);
1362 - break;
1363 - case LUA_TNUMBER: {
1364 - long i;
1365 - double d, integerPart, fractionalPart;
1366 - // Lua only provides a single number type
1367 - // Convert it to a PHP integer if that can be done without loss
1368 - // of precision
1369 - d = lua_tonumber(L, index);
1370 - fractionalPart = modf(d, &integerPart);
1371 - if (fractionalPart == 0.0 && integerPart >= LONG_MIN && integerPart <= LONG_MAX) {
1372 - // The number is small enough to fit inside an int. But has it already
1373 - // been truncated by squeezing it into a double? This is only relevant
1374 - // where the integer size is greater than the mantissa size.
1375 - i = (long)integerPart;
1376 - if (LONG_MAX < (1LL << DBL_MANT_DIG)
1377 - || labs(i) < (1L << DBL_MANT_DIG))
1378 - {
1379 - ZVAL_LONG(z, i);
1380 - } else {
1381 - ZVAL_DOUBLE(z, d);
1382 - }
1383 - } else {
1384 - ZVAL_DOUBLE(z, d);
1385 - }
1386 - break;
1387 - }
1388 - case LUA_TBOOLEAN:
1389 - ZVAL_BOOL(z, lua_toboolean(L, index));
1390 - break;
1391 - case LUA_TSTRING: {
1392 - const char * str;
1393 - size_t length;
1394 - str = lua_tolstring(L, index, &length);
1395 - ZVAL_STRINGL(z, str, length, 1);
1396 - break;
1397 - }
1398 - case LUA_TTABLE: {
1399 - const void * ptr = lua_topointer(L, index);
1400 - void * data = NULL;
1401 - int allocated = 0;
1402 - if (recursionGuard) {
1403 - // Check for circular reference (infinite recursion)
1404 - if (zend_hash_find(recursionGuard, (char*)&ptr, sizeof(void*), &data) == SUCCESS) {
1405 - // Found circular reference!
1406 - object_init_ex(z, luasandboxplaceholder_ce);
1407 - break;
1408 - }
1409 - } else {
1410 - ALLOC_HASHTABLE(recursionGuard);
1411 - zend_hash_init(recursionGuard, 1, NULL, NULL, 0);
1412 - allocated = 1;
1413 - }
1414 -
1415 - // Add the current table to the recursion guard hashtable
1416 - // Use the pointer as the key, zero-length data
1417 - zend_hash_update(recursionGuard, (char*)&ptr, sizeof(void*), "", 1, NULL);
1418 -
1419 - // Process the array
1420 - array_init(z);
1421 - luasandbox_lua_to_array(Z_ARRVAL_P(z), L, index, sandbox_zval, recursionGuard TSRMLS_CC);
1422 -
1423 - if (allocated) {
1424 - zend_hash_destroy(recursionGuard);
1425 - FREE_HASHTABLE(recursionGuard);
1426 - }
1427 - break;
1428 - }
1429 - case LUA_TFUNCTION: {
1430 - int func_index;
1431 - php_luasandboxfunction_obj * func_obj;
1432 - php_luasandbox_obj * sandbox = (php_luasandbox_obj*)
1433 - zend_object_store_get_object(sandbox_zval);
1434 -
1435 - // Normalise the input index so that we can push without invalidating it.
1436 - if (index < 0) {
1437 - index += lua_gettop(L) + 1;
1438 - }
1439 -
1440 - // Get the chunks table
1441 - lua_getfield(L, LUA_REGISTRYINDEX, "php_luasandbox_chunks");
1442 -
1443 - // Get the next free index
1444 - if (sandbox->function_index >= INT_MAX) {
1445 - ZVAL_NULL(z);
1446 - lua_pop(L, 1);
1447 - break;
1448 - }
1449 - func_index = ++(sandbox->function_index);
1450 -
1451 - // Store it in the chunks table
1452 - lua_pushvalue(L, index);
1453 - lua_rawseti(L, -2, func_index);
1454 -
1455 - // Create a LuaSandboxFunction object to hold a reference to the function
1456 - object_init_ex(z, luasandboxfunction_ce);
1457 - func_obj = (php_luasandboxfunction_obj*)zend_object_store_get_object(z);
1458 - func_obj->index = func_index;
1459 - func_obj->sandbox = sandbox_zval;
1460 - Z_ADDREF_P(sandbox_zval);
1461 -
1462 - // Balance the stack
1463 - lua_pop(L, 1);
1464 - break;
1465 - }
1466 - case LUA_TUSERDATA:
1467 - case LUA_TTHREAD:
1468 - case LUA_TLIGHTUSERDATA:
1469 - default:
1470 - // TODO: provide derived classes for each type
1471 - object_init_ex(z, luasandboxplaceholder_ce);
1472 - }
1473 -}
1474 -/* }}} */
1475 -
1476 -/** {{{ luasandbox_lua_to_array
1477 - *
1478 - * Append the elements of the table in the specified index to the given HashTable.
1479 - */
1480 -static void luasandbox_lua_to_array(HashTable *ht, lua_State *L, int index,
1481 - zval * sandbox_zval, HashTable * recursionGuard TSRMLS_DC)
1482 -{
1483 - const char * str;
1484 - size_t length;
1485 - zval *value;
1486 -
1487 - // Normalise the input index so that we can push without invalidating it.
1488 - if (index < 0) {
1489 - index += lua_gettop(L) + 1;
1490 - }
1491 -
1492 - lua_pushnil(L);
1493 - while (lua_next(L, index) != 0) {
1494 - MAKE_STD_ZVAL(value);
1495 - luasandbox_lua_to_zval(value, L, -1, sandbox_zval, recursionGuard TSRMLS_CC);
1496 -
1497 - // Make a copy of the key so that we can call lua_tolstring() which is destructive
1498 - lua_pushvalue(L, -2);
1499 - str = lua_tolstring(L, -1, &length);
1500 - zend_hash_update(ht, str, length + 1, (void*)&value, sizeof(zval*), NULL);
1501 -
1502 - // Delete the copy and the value
1503 - lua_pop(L, 2);
1504 - }
1505 -}
1506 -/* }}} */
1507 -
1508956 /** {{{ luasandbox_get_php_obj
1509957 *
1510958 * Get the object data for a lua state.
1511959 */
1512 -static php_luasandbox_obj * luasandbox_get_php_obj(lua_State * L)
 960+php_luasandbox_obj * luasandbox_get_php_obj(lua_State * L)
1513961 {
1514962 php_luasandbox_obj * obj;
1515963 lua_getfield(L, LUA_REGISTRYINDEX, "php_luasandbox_obj");
@@ -1718,84 +1166,6 @@
17191167 return 0;
17201168 }
17211169 /* }}} */
1722 -
1723 -/** {{{ luasandbox_base_tostring
1724 - *
1725 - * This is identical to luaB_tostring except that it does not call lua_topointer().
1726 - */
1727 -static int luasandbox_base_tostring(lua_State * L)
1728 -{
1729 - luaL_checkany(L, 1);
1730 - if (luaL_callmeta(L, 1, "__tostring")) /* is there a metafield? */
1731 - return 1; /* use its value */
1732 - switch (lua_type(L, 1)) {
1733 - case LUA_TNUMBER:
1734 - lua_pushstring(L, lua_tostring(L, 1));
1735 - break;
1736 - case LUA_TSTRING:
1737 - lua_pushvalue(L, 1);
1738 - break;
1739 - case LUA_TBOOLEAN:
1740 - lua_pushstring(L, (lua_toboolean(L, 1) ? "true" : "false"));
1741 - break;
1742 - case LUA_TNIL:
1743 - lua_pushliteral(L, "nil");
1744 - break;
1745 - default:
1746 - lua_pushfstring(L, "%s", luaL_typename(L, 1));
1747 - break;
1748 - }
1749 - return 1;
1750 -}
1751 -/* }}} */
1752 -
1753 -/** {{{ luasandbox_math_random
1754 - *
1755 - * A math.random implementation that doesn't share state with PHP's rand()
1756 - */
1757 -static int luasandbox_math_random(lua_State * L)
1758 -{
1759 - php_luasandbox_obj * sandbox = luasandbox_get_php_obj(L);
1760 -
1761 - int i = rand_r(&sandbox->random_seed);
1762 - if (i >= RAND_MAX) {
1763 - i -= RAND_MAX;
1764 - }
1765 - lua_Number r = (lua_Number)i / (lua_Number)RAND_MAX;
1766 - switch (lua_gettop(L)) { /* check number of arguments */
1767 - case 0: { /* no arguments */
1768 - lua_pushnumber(L, r); /* Number between 0 and 1 */
1769 - break;
1770 - }
1771 - case 1: { /* only upper limit */
1772 - int u = luaL_checkint(L, 1);
1773 - luaL_argcheck(L, 1<=u, 1, "interval is empty");
1774 - lua_pushnumber(L, floor(r*u)+1); /* int between 1 and `u' */
1775 - break;
1776 - }
1777 - case 2: { /* lower and upper limits */
1778 - int l = luaL_checkint(L, 1);
1779 - int u = luaL_checkint(L, 2);
1780 - luaL_argcheck(L, l<=u, 2, "interval is empty");
1781 - lua_pushnumber(L, floor(r*(u-l+1))+l); /* int between `l' and `u' */
1782 - break;
1783 - }
1784 - default: return luaL_error(L, "wrong number of arguments");
1785 - }
1786 - return 1;
1787 -}
1788 -/* }}} */
1789 -
1790 -/** {{{ luasandbox_math_randomseed
1791 - *
1792 - * Set the seed for the custom math.random.
1793 - */
1794 -static int luasandbox_math_randomseed(lua_State * L)
1795 -{
1796 - php_luasandbox_obj * sandbox = luasandbox_get_php_obj(L);
1797 - sandbox->random_seed = (unsigned int)luaL_checkint(L, 1);
1798 -}
1799 -/* }}} */
18001170 /*
18011171 * Local variables:
18021172 * tab-width: 4
Index: trunk/php/luasandbox/config.m4
@@ -26,5 +26,5 @@
2727 PHP_EVAL_LIBLINE("-lrt", LUASANDBOX_SHARED_LIBADD)
2828
2929 PHP_SUBST(LUASANDBOX_SHARED_LIBADD)
30 - PHP_NEW_EXTENSION(luasandbox, luasandbox.c timer.c, $ext_shared)
 30+ PHP_NEW_EXTENSION(luasandbox, alloc.c data_conversion.c library.c luasandbox.c timer.c, $ext_shared)
3131 fi
Index: trunk/php/luasandbox/library.c
@@ -0,0 +1,237 @@
 2+/**
 3+ * This file holds the library of functions which are written in C and exposed
 4+ * to Lua code, and the code which manages registration of both the custom
 5+ * library and the parts of the standard Lua library which we allow.
 6+ */
 7+
 8+#ifdef HAVE_CONFIG_H
 9+#include "config.h"
 10+#endif
 11+
 12+#include <lua.h>
 13+#include <lauxlib.h>
 14+#include <lualib.h>
 15+
 16+#include "php.h"
 17+#include "php_luasandbox.h"
 18+
 19+static HashTable * luasandbox_lib_get_allowed_globals(TSRMLS_D);
 20+
 21+static int luasandbox_base_tostring(lua_State * L);
 22+static int luasandbox_math_random(lua_State * L);
 23+static int luasandbox_math_randomseed(lua_State * L);
 24+
 25+/**
 26+ * Allowed global variables. Omissions are:
 27+ * * pcall, xpcall: Changing the protected environment won't work with our
 28+ * current timeout method.
 29+ * * loadfile: insecure.
 30+ * * load, loadstring: Probably creates a protected environment so has
 31+ * the same problem as pcall. Also omitting these makes analysis of the
 32+ * code for runtime etc. feasible.
 33+ * * print: Not compatible with a sandbox environment
 34+ * * tostring: Provides addresses of tables and functions, which provides an
 35+ * easy ASLR workaround or heap address discovery mechanism for a memory
 36+ * corruption exploit.
 37+ * * Any new or undocumented functions like newproxy.
 38+ * * package: cpath, loadlib etc. are insecure.
 39+ * * coroutine: Not useful for our application so unreviewed at present.
 40+ * * io, file, os: insecure
 41+ * * debug: Provides various ways to break the sandbox, such as setupvalue()
 42+ * and getregistry().
 43+ */
 44+char * luasandbox_allowed_globals[] = {
 45+ // base
 46+ "assert",
 47+ "error",
 48+ "getmetatable",
 49+ "getfenv",
 50+ "getmetatable",
 51+ "ipairs",
 52+ "next",
 53+ "pairs",
 54+ "rawequal",
 55+ "rawget",
 56+ "rawset",
 57+ "select",
 58+ "setmetatable",
 59+ "tonumber",
 60+ "type",
 61+ "unpack",
 62+ "_G",
 63+ "_VERSION",
 64+ // libs
 65+ "string",
 66+ "table",
 67+ "math"
 68+};
 69+#define LUASANDBOX_NUM_ALLOWED_GLOBALS (sizeof(luasandbox_allowed_globals) / sizeof(char*))
 70+
 71+ZEND_EXTERN_MODULE_GLOBALS(luasandbox);
 72+
 73+/** {{{ luasandbox_lib_register
 74+ */
 75+void luasandbox_lib_register(lua_State * L TSRMLS_DC)
 76+{
 77+ // Load some relatively safe standard libraries
 78+ lua_pushcfunction(L, luaopen_base);
 79+ lua_call(L, 0, 0);
 80+ lua_pushcfunction(L, luaopen_string);
 81+ lua_call(L, 0, 0);
 82+ lua_pushcfunction(L, luaopen_table);
 83+ lua_call(L, 0, 0);
 84+ lua_pushcfunction(L, luaopen_math);
 85+ lua_call(L, 0, 0);
 86+
 87+ // Remove any globals that aren't in a whitelist. This is mostly to remove
 88+ // unsafe functions from the base library.
 89+ lua_pushnil(L);
 90+ while (lua_next(L, LUA_GLOBALSINDEX) != 0) {
 91+ const char * key;
 92+ size_t key_len;
 93+ void * data;
 94+ lua_pop(L, 1);
 95+ if (lua_type(L, -1) != LUA_TSTRING) {
 96+ continue;
 97+ }
 98+ key = lua_tolstring(L, -1, &key_len);
 99+ if (zend_hash_find(luasandbox_lib_get_allowed_globals(),
 100+ (char*)key, key_len + 1, &data) == FAILURE)
 101+ {
 102+ // Not allowed, delete it
 103+ lua_pushnil(L);
 104+ lua_setglobal(L, key);
 105+ }
 106+ }
 107+
 108+ // Install our own version of tostring
 109+ lua_pushcfunction(L, luasandbox_base_tostring);
 110+ lua_setglobal(L, "tostring");
 111+
 112+ // Remove string.dump: may expose private data
 113+ lua_getglobal(L, "string");
 114+ lua_pushnil(L);
 115+ lua_setfield(L, -2, "dump");
 116+ lua_pop(L, 1);
 117+
 118+ // Install our own versions of math.random and math.randomseed
 119+ lua_getglobal(L, "math");
 120+ lua_pushcfunction(L, luasandbox_math_random);
 121+ lua_setfield(L, -2, "random");
 122+ lua_pushcfunction(L, luasandbox_math_randomseed);
 123+ lua_setfield(L, -2, "randomseed");
 124+ lua_pop(L, 1);
 125+}
 126+/* }}} */
 127+
 128+/** {{{ luasandbox_lib_shutdown */
 129+void luasandbox_lib_shutdown(TSRMLS_D)
 130+{
 131+ if (LUASANDBOX_G(allowed_globals)) {
 132+ zend_hash_destroy(LUASANDBOX_G(allowed_globals));
 133+ pefree(LUASANDBOX_G(allowed_globals), 1);
 134+ }
 135+}
 136+/* }}} */
 137+
 138+/** {{{ luasandbox_lib_get_allowed_globals
 139+ *
 140+ * Get a HashTable of allowed global variables
 141+ */
 142+static HashTable * luasandbox_lib_get_allowed_globals(TSRMLS_D)
 143+{
 144+ int i;
 145+ if (LUASANDBOX_G(allowed_globals)) {
 146+ return LUASANDBOX_G(allowed_globals);
 147+ }
 148+
 149+ LUASANDBOX_G(allowed_globals) = pemalloc(sizeof(HashTable), 1);
 150+ zend_hash_init(LUASANDBOX_G(allowed_globals), LUASANDBOX_NUM_ALLOWED_GLOBALS, NULL, NULL, 1);
 151+ for (i = 0; i < LUASANDBOX_NUM_ALLOWED_GLOBALS; i++) {
 152+ zend_hash_update(LUASANDBOX_G(allowed_globals),
 153+ luasandbox_allowed_globals[i], strlen(luasandbox_allowed_globals[i]) + 1,
 154+ "", 1, NULL);
 155+ }
 156+
 157+ return LUASANDBOX_G(allowed_globals);
 158+}
 159+/* }}} */
 160+
 161+/** {{{ luasandbox_base_tostring
 162+ *
 163+ * This is identical to luaB_tostring except that it does not call lua_topointer().
 164+ */
 165+static int luasandbox_base_tostring(lua_State * L)
 166+{
 167+ luaL_checkany(L, 1);
 168+ if (luaL_callmeta(L, 1, "__tostring")) /* is there a metafield? */
 169+ return 1; /* use its value */
 170+ switch (lua_type(L, 1)) {
 171+ case LUA_TNUMBER:
 172+ lua_pushstring(L, lua_tostring(L, 1));
 173+ break;
 174+ case LUA_TSTRING:
 175+ lua_pushvalue(L, 1);
 176+ break;
 177+ case LUA_TBOOLEAN:
 178+ lua_pushstring(L, (lua_toboolean(L, 1) ? "true" : "false"));
 179+ break;
 180+ case LUA_TNIL:
 181+ lua_pushliteral(L, "nil");
 182+ break;
 183+ default:
 184+ lua_pushfstring(L, "%s", luaL_typename(L, 1));
 185+ break;
 186+ }
 187+ return 1;
 188+}
 189+/* }}} */
 190+
 191+/** {{{ luasandbox_math_random
 192+ *
 193+ * A math.random implementation that doesn't share state with PHP's rand()
 194+ */
 195+static int luasandbox_math_random(lua_State * L)
 196+{
 197+ php_luasandbox_obj * sandbox = luasandbox_get_php_obj(L);
 198+
 199+ int i = rand_r(&sandbox->random_seed);
 200+ if (i >= RAND_MAX) {
 201+ i -= RAND_MAX;
 202+ }
 203+ lua_Number r = (lua_Number)i / (lua_Number)RAND_MAX;
 204+ switch (lua_gettop(L)) { /* check number of arguments */
 205+ case 0: { /* no arguments */
 206+ lua_pushnumber(L, r); /* Number between 0 and 1 */
 207+ break;
 208+ }
 209+ case 1: { /* only upper limit */
 210+ int u = luaL_checkint(L, 1);
 211+ luaL_argcheck(L, 1<=u, 1, "interval is empty");
 212+ lua_pushnumber(L, floor(r*u)+1); /* int between 1 and `u' */
 213+ break;
 214+ }
 215+ case 2: { /* lower and upper limits */
 216+ int l = luaL_checkint(L, 1);
 217+ int u = luaL_checkint(L, 2);
 218+ luaL_argcheck(L, l<=u, 2, "interval is empty");
 219+ lua_pushnumber(L, floor(r*(u-l+1))+l); /* int between `l' and `u' */
 220+ break;
 221+ }
 222+ default: return luaL_error(L, "wrong number of arguments");
 223+ }
 224+ return 1;
 225+}
 226+/* }}} */
 227+
 228+/** {{{ luasandbox_math_randomseed
 229+ *
 230+ * Set the seed for the custom math.random.
 231+ */
 232+static int luasandbox_math_randomseed(lua_State * L)
 233+{
 234+ php_luasandbox_obj * sandbox = luasandbox_get_php_obj(L);
 235+ sandbox->random_seed = (unsigned int)luaL_checkint(L, 1);
 236+}
 237+/* }}} */
 238+
Property changes on: trunk/php/luasandbox/library.c
___________________________________________________________________
Added: svn:eol-style
1239 + native
Index: trunk/php/luasandbox/alloc.c
@@ -0,0 +1,126 @@
 2+/**
 3+ * The Lua allocator hook
 4+ */
 5+
 6+#ifdef HAVE_CONFIG_H
 7+#include "config.h"
 8+#endif
 9+
 10+#include <lua.h>
 11+#include <lauxlib.h>
 12+
 13+#include "php.h"
 14+#include "php_luasandbox.h"
 15+
 16+#if defined(LUA_JITLIBNAME) && (SSIZE_MAX >> 32) > 0
 17+#define LUASANDBOX_LJ_64
 18+#endif
 19+
 20+static inline int luasandbox_update_memory_accounting(php_luasandbox_alloc * obj,
 21+ size_t osize, size_t nsize);
 22+static void *luasandbox_php_alloc(void *ud, void *ptr, size_t osize, size_t nsize);
 23+static void *luasandbox_passthru_alloc(void *ud, void *ptr, size_t osize, size_t nsize);
 24+
 25+lua_State * luasandbox_alloc_new_state(php_luasandbox_alloc * alloc, php_luasandbox_obj * sandbox)
 26+{
 27+ lua_State * L;
 28+#ifdef LUASANDBOX_LJ_64
 29+ // The 64-bit version of LuaJIT needs to use its own allocator
 30+ L = luaL_newstate();
 31+ if (L) {
 32+ alloc->old_alloc = lua_getallocf(L, &alloc->old_alloc_ud);
 33+ lua_setallocf(L, luasandbox_passthru_alloc, sandbox);
 34+ }
 35+#else
 36+ L = lua_newstate(luasandbox_php_alloc, sandbox);
 37+#endif
 38+ return L;
 39+}
 40+
 41+void luasandbox_alloc_delete_state(php_luasandbox_alloc * alloc, lua_State * L)
 42+{
 43+ // In 64-bit LuaJIT mode, restore the old allocator before calling
 44+ // lua_close() because lua_close() actually checks that the value of the
 45+ // function pointer is unchanged before destroying the underlying
 46+ // allocator. If the allocator has been changed, the mmap is not freed.
 47+#ifdef LUASANDBOX_LJ_64
 48+ lua_setallocf(alloc->state, alloc->old_alloc, alloc->old_alloc_ud);
 49+#endif
 50+
 51+ lua_close(L);
 52+}
 53+
 54+
 55+/** {{{ luasandbox_update_memory_accounting
 56+ *
 57+ * Update memory usage statistics for the given memory allocation request.
 58+ * Returns 1 if the allocation should be allowed, 0 if it should fail.
 59+ */
 60+static inline int luasandbox_update_memory_accounting(php_luasandbox_alloc * alloc,
 61+ size_t osize, size_t nsize)
 62+{
 63+ if (nsize > alloc->memory_limit
 64+ || alloc->memory_usage > alloc->memory_limit - nsize)
 65+ {
 66+ // Memory limit exceeded
 67+ return 0;
 68+ }
 69+
 70+ if (osize > nsize && alloc->memory_usage + nsize < osize) {
 71+ // Negative memory usage -- do not update
 72+ return 1;
 73+ }
 74+
 75+ alloc->memory_usage += nsize - osize;
 76+ return 1;
 77+}
 78+/* }}} */
 79+
 80+/** {{{ luasandbox_php_alloc
 81+ *
 82+ * The Lua allocator function. Use PHP's request-local allocator as a backend.
 83+ * Account for memory usage and deny the allocation request if the amount
 84+ * allocated is above the user-specified limit.
 85+ */
 86+static void *luasandbox_php_alloc(void *ud, void *ptr, size_t osize, size_t nsize)
 87+{
 88+ php_luasandbox_obj * obj = (php_luasandbox_obj*)ud;
 89+ void * nptr;
 90+ obj->in_php ++;
 91+ if (!luasandbox_update_memory_accounting(&obj->alloc, osize, nsize)) {
 92+ obj->in_php --;
 93+ return NULL;
 94+ }
 95+
 96+ if (nsize == 0) {
 97+ if (ptr) {
 98+ efree(ptr);
 99+ }
 100+ nptr = NULL;
 101+ } else if (osize == 0) {
 102+ nptr = emalloc(nsize);
 103+ } else {
 104+ nptr = erealloc(ptr, nsize);
 105+ }
 106+ obj->in_php --;
 107+ return nptr;
 108+}
 109+/* }}} */
 110+
 111+/** {{{ luasandbox_passthru_alloc
 112+ *
 113+ * A Lua allocator function for use with LuaJIT on a 64-bit platform. Pass
 114+ * allocation requests through to the standard allocator, which is customised
 115+ * on this platform to always return memory from the lower 2GB of address
 116+ * space.
 117+ */
 118+static void *luasandbox_passthru_alloc(void *ud, void *ptr, size_t osize, size_t nsize)
 119+{
 120+ php_luasandbox_obj * obj = (php_luasandbox_obj*)ud;
 121+ if (!luasandbox_update_memory_accounting(&obj->alloc, osize, nsize)) {
 122+ return NULL;
 123+ }
 124+ return obj->alloc.old_alloc(obj->alloc.old_alloc_ud, ptr, osize, nsize);
 125+}
 126+/* }}} */
 127+
Property changes on: trunk/php/luasandbox/alloc.c
___________________________________________________________________
Added: svn:eol-style
1128 + native
Index: trunk/php/luasandbox/php_luasandbox.h
@@ -3,7 +3,24 @@
44 #define PHP_LUASANDBOX_H
55
66 #include <lua.h>
 7+#include <signal.h>
78
 9+/* alloc.c */
 10+
 11+typedef struct {
 12+ lua_Alloc old_alloc;
 13+ void * old_alloc_ud;
 14+ size_t memory_limit;
 15+ size_t memory_usage;
 16+} php_luasandbox_alloc;
 17+
 18+struct _php_luasandbox_obj;
 19+
 20+lua_State * luasandbox_alloc_new_state(php_luasandbox_alloc * alloc, struct _php_luasandbox_obj * sandbox);
 21+void luasandbox_alloc_delete_state(php_luasandbox_alloc * alloc, lua_State * L);
 22+
 23+/* luasandbox.c */
 24+
825 extern zend_module_entry luasandbox_module_entry;
926 extern char luasandbox_timeout_message[];
1027
@@ -53,10 +70,7 @@
5471 struct _php_luasandbox_obj {
5572 zend_object std;
5673 lua_State * state;
57 - size_t memory_limit;
58 - size_t memory_usage;
59 - lua_Alloc old_alloc;
60 - void * old_alloc_ud;
 74+ php_luasandbox_alloc alloc;
6175 int in_php;
6276 int in_lua;
6377 zval * current_zval; /* The zval for the LuaSandbox which is currently executing Lua code */
@@ -77,5 +91,53 @@
7892 };
7993 typedef struct _php_luasandboxfunction_obj php_luasandboxfunction_obj;
8094
 95+
 96+php_luasandbox_obj * luasandbox_get_php_obj(lua_State * L);
 97+
 98+/** {{{ luasandbox_enter_php
 99+ *
 100+ * This function must be called each time a C function is entered from Lua
 101+ * and the PHP state needs to be accessed in any way. Before exiting the
 102+ * function, luasandbox_leave_php() must be called.
 103+ *
 104+ * This sets a flag which indicates to the timeout signal handler that it is
 105+ * unsafe to call longjmp() to return control to PHP. If the flag is not
 106+ * correctly set, memory may be corrupted and security compromised.
 107+ */
 108+inline void luasandbox_enter_php(lua_State * L, php_luasandbox_obj * intern)
 109+{
 110+ intern->in_php ++;
 111+ if (intern->timed_out) {
 112+ intern->in_php --;
 113+ luaL_error(L, luasandbox_timeout_message);
 114+ }
 115+}
 116+/* }}} */
 117+
 118+/** {{{ luasandbox_leave_php
 119+ *
 120+ * This function must be called after luasandbox_enter_php, before the callback
 121+ * from Lua returns.
 122+ */
 123+inline void luasandbox_leave_php(lua_State * L, php_luasandbox_obj * intern)
 124+{
 125+ intern->in_php --;
 126+}
 127+/* }}} */
 128+
 129+/* library.c */
 130+
 131+void luasandbox_lib_register(lua_State * L TSRMLS_DC);
 132+void luasandbox_lib_shutdown(TSRMLS_D);
 133+
 134+/* data_conversion.c */
 135+
 136+void luasandbox_data_conversion_init(lua_State * L);
 137+
 138+int luasandbox_push_zval(lua_State * L, zval * z);
 139+void luasandbox_push_zval_userdata(lua_State * L, zval * z);
 140+void luasandbox_lua_to_zval(zval * z, lua_State * L, int index,
 141+ zval * sandbox_zval, HashTable * recursionGuard TSRMLS_DC);
 142+
81143 #endif /* PHP_LUASANDBOX_H */
82144

Status & tagging log