Index: trunk/php/luasandbox/luasandbox.c |
— | — | @@ -1,22 +1,10 @@ |
2 | 2 | |
3 | 3 | /* |
4 | 4 | * To do: |
5 | | - * * Move sandbox.scripts to the registry, and don't provide access to it to |
6 | | - * user scripts, to avoid the potential for a panic in |
7 | | - * LuaSandbox::loadString() if sandbox.scripts is overwritten. |
8 | | - * |
9 | | - * * Remove LuaSandbox::callScript(). Instead, have LuaSandbox::loadString() |
10 | | - * return a LuaSandboxFunction object with a call() method. Store the |
11 | | - * script functions in a table indexed by integer, instead of using the |
12 | | - * chunk name, so that nothing strange will happen if there are duplicate |
13 | | - * chunk names. Delete the function from the Lua state when the object is |
14 | | - * destroyed. |
15 | | - * |
16 | 5 | * * Provide a LuaSandbox::doString() as a convenience wrapper for |
17 | 6 | * LuaSandbox::loadString()->call(), analogous to luaL_dostring(). |
18 | 7 | * |
19 | | - * * Add PHP methods for registering callback functions, both individually |
20 | | - * and in bulk (a library creation function). |
| 8 | + * * Add a PHP method for registering callback functions individually. |
21 | 9 | * |
22 | 10 | * * Provide a wrapper around the base library, which allows only a whitelist |
23 | 11 | * of functions, not including e.g. loadfile(). This is slightly tricky |
— | — | @@ -25,22 +13,8 @@ |
26 | 14 | * copy each function pointer to the destination lua_State using |
27 | 15 | * lua_tocfunction(). |
28 | 16 | * |
29 | | - * * Add CPU time limits. |
30 | | - * |
31 | | - * Doing a longjmp() from a signal handler destroys anything that the |
32 | | - * call stack may have been modifying at the time. Allowing continued |
33 | | - * access to such state will allow security vulnerabilities (SIG32-C). |
34 | | - * |
35 | | - * So I propose having two timeouts. When the first expires, a debug hook |
36 | | - * is set which calls lua_error(), and a flag is set prohibiting dispatch |
37 | | - * of any PHP callback. When the second expires, emergency action is |
38 | | - * taken. If a PHP callback has been dispatched and we are waiting for it |
39 | | - * to return, zend_error() will need to be called with E_ERROR, to safely |
40 | | - * destroy the PHP state. This mirrors the behaviour of normal Zend timeouts. |
41 | | - * Otherwise, lua_error() should be called, to return control to the |
42 | | - * lua_pcall() caller, which should then destroy the lua state. |
43 | | - * |
44 | | - * * Add LuaSandbox::getMemoryUsage(). |
| 17 | + * * Support lua_dump() |
| 18 | + * * Protect LuaSandbox->loadString() against malicious binaries |
45 | 19 | */ |
46 | 20 | |
47 | 21 | #ifdef HAVE_CONFIG_H |
— | — | @@ -49,38 +23,54 @@ |
50 | 24 | |
51 | 25 | #include <lua.h> |
52 | 26 | #include <lauxlib.h> |
| 27 | +#include <lualib.h> |
53 | 28 | #include <math.h> |
54 | 29 | #include <limits.h> |
55 | 30 | #include <float.h> |
| 31 | +#include <signal.h> |
| 32 | +#include <time.h> |
56 | 33 | |
57 | 34 | #include "php.h" |
58 | 35 | #include "php_ini.h" |
59 | 36 | #include "ext/standard/info.h" |
| 37 | +#include "zend_exceptions.h" |
60 | 38 | #include "php_luasandbox.h" |
61 | | -#include "zend_exceptions.h" |
| 39 | +#include "luasandbox_timer.h" |
62 | 40 | |
63 | 41 | static zend_object_value luasandbox_new(zend_class_entry *ce TSRMLS_DC); |
| 42 | +static lua_State * luasandbox_newstate(php_luasandbox_obj * intern); |
64 | 43 | static void luasandbox_free_storage(void *object TSRMLS_DC); |
| 44 | +static zend_object_value luasandboxfunction_new(zend_class_entry *ce TSRMLS_CC); |
| 45 | +static void luasandboxfunction_free_storage(void *object TSRMLS_DC); |
| 46 | +static void luasandboxfunction_destroy(void *object, zend_object_handle handle TSRMLS_DC); |
| 47 | +static int luasandbox_free_zval_userdata(lua_State * L); |
65 | 48 | static void *luasandbox_alloc(void *ud, void *ptr, size_t osize, size_t nsize); |
66 | 49 | static int luasandbox_panic(lua_State * L); |
67 | | -static lua_State * luasandbox_newstate(); |
68 | | -static lua_State * getLuaState(zval * this_ptr); |
| 50 | +static lua_State * luasandbox_state_from_zval(zval * this_ptr TSRMLS_DC); |
69 | 51 | static int luasandbox_find_field(lua_State * L, int index, |
70 | 52 | char * spec, int specLength); |
71 | | -static void luasandbox_call_helper(INTERNAL_FUNCTION_PARAMETERS, int index); |
| 53 | +static void luasandbox_set_timespec(struct timespec * dest, double source); |
| 54 | +static void luasandbox_call_helper(lua_State * L, php_luasandbox_obj * sandbox, |
| 55 | + zval *** args, zend_uint numArgs, zval * return_value TSRMLS_DC); |
72 | 56 | static int luasandbox_push_zval(lua_State * L, zval * z); |
| 57 | +static void luasandbox_push_zval_userdata(lua_State * L, zval * z); |
73 | 58 | static zval * luasandbox_lua_to_zval(lua_State * L, int index, HashTable * recursionGuard); |
74 | 59 | static void luasandbox_lua_to_array(HashTable *ht, lua_State *L, int index, |
75 | 60 | HashTable * recursionGuard); |
76 | 61 | static php_luasandbox_obj * luasandbox_get_php_obj(lua_State * L); |
77 | | -static int luasandbox_method_getMemoryUsage(lua_State * L); |
78 | 62 | static void luasandbox_handle_error(lua_State * L, int status); |
79 | 63 | static int luasandbox_push_hashtable(lua_State * L, HashTable * ht); |
| 64 | +static int luasandbox_call_php(lua_State * L); |
80 | 65 | |
| 66 | +char luasandbox_timeout_message[] = "The maximum execution time for this script was exceeded"; |
| 67 | + |
81 | 68 | zend_class_entry *luasandbox_ce; |
82 | 69 | zend_class_entry *luasandboxerror_ce; |
83 | 70 | zend_class_entry *luasandboxplaceholder_ce; |
| 71 | +zend_class_entry *luasandboxfunction_ce; |
84 | 72 | |
| 73 | +ZEND_DECLARE_MODULE_GLOBALS(luasandbox); |
| 74 | + |
85 | 75 | /** {{{ arginfo */ |
86 | 76 | ZEND_BEGIN_ARG_INFO(arginfo_luasandbox_loadString, 0) |
87 | 77 | ZEND_ARG_INFO(0, code) |
— | — | @@ -91,13 +81,22 @@ |
92 | 82 | ZEND_ARG_INFO(0, limit) |
93 | 83 | ZEND_END_ARG_INFO() |
94 | 84 | |
| 85 | +ZEND_BEGIN_ARG_INFO(arginfo_luasandbox_setCPULimit, 0) |
| 86 | + ZEND_ARG_INFO(0, normal_limit) |
| 87 | + ZEND_ARG_INFO(0, emergency_limit) |
| 88 | +ZEND_END_ARG_INFO() |
| 89 | + |
95 | 90 | ZEND_BEGIN_ARG_INFO(arginfo_luasandbox_callFunction, 0) |
96 | 91 | ZEND_ARG_INFO(0, name) |
97 | 92 | ZEND_ARG_INFO(0, ...) |
98 | 93 | ZEND_END_ARG_INFO() |
99 | 94 | |
100 | | -ZEND_BEGIN_ARG_INFO(arginfo_luasandbox_callScript, 0) |
101 | | - ZEND_ARG_INFO(0, name) |
| 95 | +ZEND_BEGIN_ARG_INFO(arginfo_luasandbox_register, 0) |
| 96 | + ZEND_ARG_INFO(0, libname) |
| 97 | + ZEND_ARG_INFO(0, functions) |
| 98 | +ZEND_END_ARG_INFO() |
| 99 | + |
| 100 | +ZEND_BEGIN_ARG_INFO(arginfo_luasandboxfunction_call, 0) |
102 | 101 | ZEND_ARG_INFO(0, ...) |
103 | 102 | ZEND_END_ARG_INFO() |
104 | 103 | /* }}} */ |
— | — | @@ -110,19 +109,19 @@ |
111 | 110 | const zend_function_entry luasandbox_methods[] = { |
112 | 111 | PHP_ME(LuaSandbox, loadString, arginfo_luasandbox_loadString, 0) |
113 | 112 | PHP_ME(LuaSandbox, setMemoryLimit, arginfo_luasandbox_setMemoryLimit, 0) |
| 113 | + PHP_ME(LuaSandbox, setCPULimit, arginfo_luasandbox_setCPULimit, 0) |
114 | 114 | PHP_ME(LuaSandbox, callFunction, arginfo_luasandbox_callFunction, 0) |
115 | | - PHP_ME(LuaSandbox, callScript, arginfo_luasandbox_callScript, 0) |
| 115 | + PHP_ME(LuaSandbox, register, arginfo_luasandbox_register, 0) |
116 | 116 | {NULL, NULL, NULL} |
117 | 117 | }; |
118 | 118 | |
119 | | -const zend_function_entry luasandbox_empty_methods[] = { |
| 119 | +const zend_function_entry luasandboxfunction_methods[] = { |
| 120 | + PHP_ME(LuaSandboxFunction, call, arginfo_luasandboxfunction_call, 0) |
120 | 121 | {NULL, NULL, NULL} |
121 | 122 | }; |
122 | 123 | |
123 | | -/** sandbox method names and their C functions */ |
124 | | -static const luaL_Reg luasandbox_lua_methods[] = { |
125 | | - {"getMemoryUsage", luasandbox_method_getMemoryUsage}, |
126 | | - {NULL, NULL} |
| 124 | +const zend_function_entry luasandbox_empty_methods[] = { |
| 125 | + {NULL, NULL, NULL} |
127 | 126 | }; |
128 | 127 | |
129 | 128 | /* }}} */ |
— | — | @@ -138,7 +137,7 @@ |
139 | 138 | PHP_MINIT(luasandbox), |
140 | 139 | PHP_MSHUTDOWN(luasandbox), |
141 | 140 | NULL, /* RINIT */ |
142 | | - NULL, /* RSHUTDOWN */ |
| 141 | + PHP_RSHUTDOWN(luasandbox), /* RSHUTDOWN */ |
143 | 142 | PHP_MINFO(luasandbox), |
144 | 143 | #if ZEND_MODULE_API_NO >= 20010901 |
145 | 144 | "0.1", |
— | — | @@ -151,6 +150,98 @@ |
152 | 151 | ZEND_GET_MODULE(luasandbox) |
153 | 152 | #endif |
154 | 153 | |
| 154 | +/* {{{ PHP_MINIT_FUNCTION |
| 155 | + */ |
| 156 | +PHP_MINIT_FUNCTION(luasandbox) |
| 157 | +{ |
| 158 | + /* If you have INI entries, uncomment these lines |
| 159 | + REGISTER_INI_ENTRIES(); |
| 160 | + */ |
| 161 | + |
| 162 | + zend_class_entry ce; |
| 163 | + INIT_CLASS_ENTRY(ce, "LuaSandbox", luasandbox_methods); |
| 164 | + luasandbox_ce = zend_register_internal_class(&ce TSRMLS_CC); |
| 165 | + luasandbox_ce->create_object = luasandbox_new; |
| 166 | + |
| 167 | + INIT_CLASS_ENTRY(ce, "LuaSandboxError", luasandbox_empty_methods); |
| 168 | + luasandboxerror_ce = zend_register_internal_class_ex( |
| 169 | + &ce, zend_exception_get_default(TSRMLS_C), NULL TSRMLS_CC); |
| 170 | + |
| 171 | + zend_declare_class_constant_long(luasandboxerror_ce, |
| 172 | + "RUN", sizeof("RUN"), LUA_ERRRUN); |
| 173 | + zend_declare_class_constant_long(luasandboxerror_ce, |
| 174 | + "SYNTAX", sizeof("SYNTAX"), LUA_ERRSYNTAX); |
| 175 | + zend_declare_class_constant_long(luasandboxerror_ce, |
| 176 | + "MEM", sizeof("MEM"), LUA_ERRMEM); |
| 177 | + zend_declare_class_constant_long(luasandboxerror_ce, |
| 178 | + "ERR", sizeof("ERR"), LUA_ERRERR); |
| 179 | + |
| 180 | + INIT_CLASS_ENTRY(ce, "LuaSandboxPlaceholder", luasandbox_empty_methods); |
| 181 | + luasandboxplaceholder_ce = zend_register_internal_class(&ce TSRMLS_CC); |
| 182 | + |
| 183 | + INIT_CLASS_ENTRY(ce, "LuaSandboxFunction", luasandboxfunction_methods); |
| 184 | + luasandboxfunction_ce = zend_register_internal_class(&ce TSRMLS_CC); |
| 185 | + luasandboxfunction_ce->create_object = luasandboxfunction_new; |
| 186 | + |
| 187 | + return SUCCESS; |
| 188 | +} |
| 189 | +/* }}} */ |
| 190 | + |
| 191 | +/* {{{ PHP_MSHUTDOWN_FUNCTION |
| 192 | + */ |
| 193 | +PHP_MSHUTDOWN_FUNCTION(luasandbox) |
| 194 | +{ |
| 195 | + return SUCCESS; |
| 196 | +} |
| 197 | +/* }}} */ |
| 198 | + |
| 199 | +/* {{{ PHP_RSHUTDOWN_FUNCTION */ |
| 200 | +PHP_RSHUTDOWN_FUNCTION(luasandbox) |
| 201 | +{ |
| 202 | + if (LUASANDBOX_G(signal_handler_installed)) { |
| 203 | + luasandbox_timer_remove_handler(&LUASANDBOX_G(old_handler)); |
| 204 | + } |
| 205 | + return SUCCESS; |
| 206 | +} |
| 207 | +/* }}} */ |
| 208 | + |
| 209 | +/* {{{ PHP_MINFO_FUNCTION |
| 210 | + */ |
| 211 | +PHP_MINFO_FUNCTION(luasandbox) |
| 212 | +{ |
| 213 | + php_info_print_table_start(); |
| 214 | + php_info_print_table_header(2, "luasandbox support", "enabled"); |
| 215 | + php_info_print_table_end(); |
| 216 | +} |
| 217 | +/* }}} */ |
| 218 | + |
| 219 | +/** {{{ luasandbox_enter_php |
| 220 | + * This function must be called each time a C function is entered from Lua |
| 221 | + * and the PHP state needs to be accessed in any way. Before exiting the |
| 222 | + * function, luasandbox_leave_php() must be called. |
| 223 | + * |
| 224 | + * This sets a flag which indicates to the timeout signal handler that it is |
| 225 | + * unsafe to call longjmp() to return control to PHP. If the flag is not |
| 226 | + * correctly set, memory may be corrupted and security compromised. |
| 227 | + */ |
| 228 | +static inline void luasandbox_enter_php(lua_State * L, php_luasandbox_obj * intern) |
| 229 | +{ |
| 230 | + intern->in_php = 1; |
| 231 | + if (intern->timed_out) { |
| 232 | + intern->in_php = 0; |
| 233 | + luaL_error(L, luasandbox_timeout_message); |
| 234 | + } |
| 235 | +} |
| 236 | +/* }}} */ |
| 237 | + |
| 238 | +/** {{{ luasandbox_leave_php |
| 239 | + */ |
| 240 | +static inline void luasandbox_leave_php(lua_State * L, php_luasandbox_obj * intern) |
| 241 | +{ |
| 242 | + intern->in_php = 0; |
| 243 | +} |
| 244 | +/* }}} */ |
| 245 | + |
155 | 246 | /** {{{ luasandbox_new */ |
156 | 247 | static zend_object_value luasandbox_new(zend_class_entry *ce TSRMLS_DC) |
157 | 248 | { |
— | — | @@ -167,10 +258,10 @@ |
168 | 259 | |
169 | 260 | // Put the object into the store |
170 | 261 | retval.handle = zend_objects_store_put( |
171 | | - intern, |
172 | | - (zend_objects_store_dtor_t)zend_objects_destroy_object, |
173 | | - (zend_objects_free_object_storage_t)luasandbox_free_storage, |
174 | | - NULL TSRMLS_CC); |
| 262 | + intern, |
| 263 | + (zend_objects_store_dtor_t)zend_objects_destroy_object, |
| 264 | + (zend_objects_free_object_storage_t)luasandbox_free_storage, |
| 265 | + NULL TSRMLS_CC); |
175 | 266 | retval.handlers = zend_get_std_object_handlers(); |
176 | 267 | return retval; |
177 | 268 | } |
— | — | @@ -189,22 +280,20 @@ |
190 | 281 | luaopen_table(L); |
191 | 282 | luaopen_math(L); |
192 | 283 | |
193 | | - // Create the "sandbox" global |
| 284 | + // Create a table for storing chunks |
194 | 285 | lua_newtable(L); |
195 | | - // Add its methods |
196 | | - luaL_register(L, NULL, luasandbox_lua_methods); |
197 | | - |
198 | | - // Add its properties |
199 | | - lua_newtable(L); |
200 | | - lua_setfield(L, -2, "scripts"); |
201 | | - |
202 | | - // Move the new table from the stack to the sandbox global |
203 | | - lua_setglobal(L, "sandbox"); |
| 286 | + lua_setfield(L, LUA_REGISTRYINDEX, "php_luasandbox_chunks"); |
204 | 287 | |
205 | 288 | // Register a pointer to the PHP object so that C functions can find it |
206 | 289 | lua_pushlightuserdata(L, (void*)intern); |
207 | 290 | lua_setfield(L, LUA_REGISTRYINDEX, "php_luasandbox_obj"); |
208 | 291 | |
| 292 | + // Create the metatable for zval destruction |
| 293 | + lua_createtable(L, 0, 1); |
| 294 | + lua_pushcfunction(L, luasandbox_free_zval_userdata); |
| 295 | + lua_setfield(L, -2, "__gc"); |
| 296 | + lua_setfield(L, LUA_REGISTRYINDEX, "php_luasandbox_zval_metatable"); |
| 297 | + |
209 | 298 | return L; |
210 | 299 | } |
211 | 300 | /* }}} */ |
— | — | @@ -220,64 +309,89 @@ |
221 | 310 | } |
222 | 311 | /* }}} */ |
223 | 312 | |
224 | | -/* {{{ PHP_MINIT_FUNCTION |
225 | | - */ |
226 | | -PHP_MINIT_FUNCTION(luasandbox) |
| 313 | +/** {{{ luasandboxfunction_new */ |
| 314 | +static zend_object_value luasandboxfunction_new(zend_class_entry *ce TSRMLS_CC) |
227 | 315 | { |
228 | | - /* If you have INI entries, uncomment these lines |
229 | | - REGISTER_INI_ENTRIES(); |
230 | | - */ |
| 316 | + php_luasandboxfunction_obj * intern; |
| 317 | + zend_object_value retval; |
| 318 | + |
| 319 | + // Create the internal object |
| 320 | + intern = emalloc(sizeof(php_luasandboxfunction_obj)); |
| 321 | + memset(intern, 0, sizeof(php_luasandboxfunction_obj)); |
| 322 | + zend_object_std_init(&intern->std, ce TSRMLS_CC); |
231 | 323 | |
232 | | - zend_class_entry ce; |
233 | | - INIT_CLASS_ENTRY(ce, "LuaSandbox", luasandbox_methods); |
234 | | - luasandbox_ce = zend_register_internal_class(&ce TSRMLS_CC); |
235 | | - luasandbox_ce->create_object = luasandbox_new; |
| 324 | + // Put the object into the store |
| 325 | + retval.handle = zend_objects_store_put( |
| 326 | + intern, |
| 327 | + (zend_objects_store_dtor_t)luasandboxfunction_destroy, |
| 328 | + (zend_objects_free_object_storage_t)luasandboxfunction_free_storage, |
| 329 | + NULL TSRMLS_CC); |
| 330 | + retval.handlers = zend_get_std_object_handlers(); |
| 331 | + return retval; |
| 332 | +} |
| 333 | +/* }}} */ |
236 | 334 | |
237 | | - INIT_CLASS_ENTRY(ce, "LuaSandboxError", luasandbox_empty_methods); |
238 | | - luasandboxerror_ce = zend_register_internal_class_ex( |
239 | | - &ce, zend_exception_get_default(TSRMLS_C), NULL TSRMLS_CC); |
| 335 | +/** {{{ luasandboxfunction_destroy */ |
| 336 | +static void luasandboxfunction_destroy(void *object, zend_object_handle handle TSRMLS_DC) |
| 337 | +{ |
| 338 | + php_luasandboxfunction_obj * func = (php_luasandboxfunction_obj*)object; |
| 339 | + if (func->sandbox) { |
| 340 | + php_luasandbox_obj * sandbox = (php_luasandbox_obj*) |
| 341 | + zend_object_store_get_object(func->sandbox TSRMLS_CC); |
| 342 | + lua_State * L = sandbox->state; |
240 | 343 | |
241 | | - zend_declare_class_constant_long(luasandboxerror_ce, |
242 | | - "RUN", sizeof("RUN"), LUA_ERRRUN); |
243 | | - zend_declare_class_constant_long(luasandboxerror_ce, |
244 | | - "SYNTAX", sizeof("SYNTAX"), LUA_ERRSYNTAX); |
245 | | - zend_declare_class_constant_long(luasandboxerror_ce, |
246 | | - "MEM", sizeof("MEM"), LUA_ERRMEM); |
247 | | - zend_declare_class_constant_long(luasandboxerror_ce, |
248 | | - "ERR", sizeof("ERR"), LUA_ERRERR); |
| 344 | + // Delete the chunk |
| 345 | + if (func->index) { |
| 346 | + lua_getfield(L, LUA_REGISTRYINDEX, "php_luasandbox_chunks"); |
| 347 | + lua_pushnil(L); |
| 348 | + lua_rawseti(L, -2, func->index); |
| 349 | + lua_pop(L, 1); |
| 350 | + } |
249 | 351 | |
250 | | - INIT_CLASS_ENTRY(ce, "LuaSandboxPlaceholder", luasandbox_empty_methods); |
251 | | - luasandboxplaceholder_ce = zend_register_internal_class(&ce TSRMLS_CC); |
252 | | - return SUCCESS; |
| 352 | + // Delete the parent reference |
| 353 | + zval_ptr_dtor(&func->sandbox); |
| 354 | + } |
253 | 355 | } |
254 | 356 | /* }}} */ |
255 | 357 | |
256 | | -/* {{{ PHP_MSHUTDOWN_FUNCTION |
257 | | - */ |
258 | | -PHP_MSHUTDOWN_FUNCTION(luasandbox) |
| 358 | +/** {{{ luasandboxfunction_free_storage */ |
| 359 | +static void luasandboxfunction_free_storage(void *object TSRMLS_DC) |
259 | 360 | { |
260 | | - return SUCCESS; |
| 361 | + php_luasandboxfunction_obj * func = (php_luasandboxfunction_obj*)object; |
| 362 | + zend_object_std_dtor(&func->std); |
| 363 | + efree(object); |
261 | 364 | } |
262 | 365 | /* }}} */ |
263 | 366 | |
264 | | -/* {{{ PHP_MINFO_FUNCTION |
| 367 | +/** {{{ luasandbox_free_zval_userdata |
| 368 | + * Free a zval given to Lua by luasandbox_push_zval_userdata. |
265 | 369 | */ |
266 | | -PHP_MINFO_FUNCTION(luasandbox) |
| 370 | +static int luasandbox_free_zval_userdata(lua_State * L) |
267 | 371 | { |
268 | | - php_info_print_table_start(); |
269 | | - php_info_print_table_header(2, "luasandbox support", "enabled"); |
270 | | - php_info_print_table_end(); |
| 372 | + zval ** zpp = (zval**)lua_touserdata(L, 1); |
| 373 | + php_luasandbox_obj * intern = luasandbox_get_php_obj(L); |
| 374 | + luasandbox_enter_php(L, intern); |
| 375 | + if (zpp && *zpp) { |
| 376 | + zval_ptr_dtor(zpp); |
| 377 | + } |
| 378 | + *zpp = NULL; |
| 379 | + luasandbox_leave_php(L, intern); |
| 380 | + return 0; |
271 | 381 | } |
272 | 382 | /* }}} */ |
273 | 383 | |
274 | 384 | /** {{{ luasandbox_alloc */ |
275 | 385 | static void *luasandbox_alloc(void *ud, void *ptr, size_t osize, size_t nsize) |
276 | 386 | { |
| 387 | + php_luasandbox_obj * obj = (php_luasandbox_obj*)ud; |
| 388 | + void * nptr; |
| 389 | + obj->in_php = 1; |
| 390 | + |
277 | 391 | // Update memory usage accounting |
278 | | - php_luasandbox_obj * obj = (php_luasandbox_obj*)ud; |
279 | 392 | if (obj->memory_usage + nsize < obj->memory_usage) { |
280 | 393 | // Overflow |
281 | 394 | // No PHP error because it's plausible that untrusted scripts could do this. |
| 395 | + obj->in_php = 0; |
282 | 396 | return NULL; |
283 | 397 | } |
284 | 398 | |
— | — | @@ -287,12 +401,14 @@ |
288 | 402 | if (ptr) { |
289 | 403 | efree(ptr); |
290 | 404 | } |
291 | | - return NULL; |
| 405 | + nptr = NULL; |
292 | 406 | } else if (osize == 0) { |
293 | | - return emalloc(nsize); |
| 407 | + nptr = emalloc(nsize); |
294 | 408 | } else { |
295 | | - return erealloc(ptr, nsize); |
| 409 | + nptr = erealloc(ptr, nsize); |
296 | 410 | } |
| 411 | + obj->in_php = 0; |
| 412 | + return nptr; |
297 | 413 | } |
298 | 414 | /* }}} */ |
299 | 415 | |
— | — | @@ -306,8 +422,8 @@ |
307 | 423 | } |
308 | 424 | /* }}} */ |
309 | 425 | |
310 | | -/** {{{ getLuaState */ |
311 | | -static lua_State * getLuaState(zval * this_ptr) |
| 426 | +/** {{{ luasandbox_state_from_zval */ |
| 427 | +static lua_State * luasandbox_state_from_zval(zval * this_ptr TSRMLS_DC) |
312 | 428 | { |
313 | 429 | php_luasandbox_obj * intern = (php_luasandbox_obj*) |
314 | 430 | zend_object_store_get_object(this_ptr TSRMLS_CC); |
— | — | @@ -318,27 +434,41 @@ |
319 | 435 | /** {{{ proto int LuaSandbox::loadString(string code, string chunkName) */ |
320 | 436 | PHP_METHOD(LuaSandbox, loadString) |
321 | 437 | { |
322 | | - char *code, *chunkName; |
| 438 | + char *code, *chunkName = NULL; |
323 | 439 | int codeLength, chunkNameLength; |
324 | 440 | int status; |
325 | | - lua_State * L = getLuaState(getThis()); |
| 441 | + lua_State * L = luasandbox_state_from_zval(getThis() TSRMLS_CC); |
| 442 | + size_t index; |
| 443 | + php_luasandboxfunction_obj * func_obj; |
326 | 444 | |
327 | | - if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", |
| 445 | + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|s", |
328 | 446 | &code, &codeLength, &chunkName, &chunkNameLength) == FAILURE) { |
329 | 447 | RETURN_FALSE; |
330 | 448 | } |
331 | 449 | |
332 | | - // Check chunkName for nulls |
333 | | - if (strlen(chunkName) != chunkNameLength) { |
334 | | - php_error_docref(NULL TSRMLS_CC, E_WARNING, |
335 | | - "chunk name may not contain null characters"); |
| 450 | + if (chunkName == NULL) { |
| 451 | + chunkName = ""; |
| 452 | + } else { |
| 453 | + // Check chunkName for nulls |
| 454 | + if (strlen(chunkName) != chunkNameLength) { |
| 455 | + php_error_docref(NULL TSRMLS_CC, E_WARNING, |
| 456 | + "chunk name may not contain null characters"); |
| 457 | + RETURN_FALSE; |
| 458 | + } |
| 459 | + } |
| 460 | + |
| 461 | + // Get the chunks table |
| 462 | + lua_getfield(L, LUA_REGISTRYINDEX, "php_luasandbox_chunks"); |
| 463 | + |
| 464 | + // Get the next free index |
| 465 | + index = lua_objlen(L, -1); |
| 466 | + if (index >= INT_MAX) { |
| 467 | + php_error_docref(NULL TSRMLS_CC, E_WARNING, |
| 468 | + "too many chunks loaded already"); |
336 | 469 | RETURN_FALSE; |
337 | 470 | } |
| 471 | + index++; |
338 | 472 | |
339 | | - // Find sandbox.scripts |
340 | | - lua_getglobal(L, "sandbox"); |
341 | | - lua_getfield(L, -1, "scripts"); |
342 | | - |
343 | 473 | // Parse the string into a function on the stack |
344 | 474 | status = luaL_loadbuffer(L, code, codeLength, chunkName); |
345 | 475 | if (status != 0) { |
— | — | @@ -346,12 +476,18 @@ |
347 | 477 | return; |
348 | 478 | } |
349 | 479 | |
350 | | - // Store the resulting function as a member of sandbox.scripts |
351 | | - lua_setfield(L, -2, chunkName); |
| 480 | + // Store the resulting function to the chunks table |
| 481 | + lua_rawseti(L, -2, (int)index); |
352 | 482 | |
| 483 | + // Create a LuaSandboxFunction object to hold a reference to the function |
| 484 | + object_init_ex(return_value, luasandboxfunction_ce); |
| 485 | + func_obj = (php_luasandboxfunction_obj*)zend_object_store_get_object(return_value); |
| 486 | + func_obj->index = index; |
| 487 | + func_obj->sandbox = getThis(); |
| 488 | + Z_ADDREF_P(getThis()); |
| 489 | + |
353 | 490 | // Balance the stack |
354 | | - lua_pop(L, 2); |
355 | | - RETURN_TRUE; |
| 491 | + lua_pop(L, 1); |
356 | 492 | } |
357 | 493 | |
358 | 494 | /* }}} */ |
— | — | @@ -387,92 +523,241 @@ |
388 | 524 | } |
389 | 525 | /* }}} */ |
390 | 526 | |
391 | | -/*** {{{ proto array LuaSandbox::callFunction(string name, ...) |
392 | | - */ |
393 | | -PHP_METHOD(LuaSandbox, callFunction) |
| 527 | + |
| 528 | +/** {{{ proto void LuaSandbox::setCPULimit(mixed normal_limit, mixed emergency_limit = false) */ |
| 529 | +PHP_METHOD(LuaSandbox, setCPULimit) |
394 | 530 | { |
395 | | - luasandbox_call_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, LUA_GLOBALSINDEX); |
| 531 | + zval **zpp_normal = NULL, **zpp_emergency = NULL; |
| 532 | + |
| 533 | + php_luasandbox_obj * sandbox = (php_luasandbox_obj*) |
| 534 | + zend_object_store_get_object(getThis() TSRMLS_CC); |
| 535 | + |
| 536 | + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Z|Z", |
| 537 | + &zpp_normal, &zpp_emergency) == FAILURE) |
| 538 | + { |
| 539 | + RETURN_FALSE; |
| 540 | + } |
| 541 | + |
| 542 | + if (!zpp_normal |
| 543 | + || (Z_TYPE_PP(zpp_normal) == IS_BOOL && Z_BVAL_PP(zpp_normal) == 0)) |
| 544 | + { |
| 545 | + // No limit |
| 546 | + sandbox->is_cpu_limited = 0; |
| 547 | + } else { |
| 548 | + convert_scalar_to_number_ex(zpp_normal); |
| 549 | + if (Z_TYPE_PP(zpp_normal) == IS_LONG) { |
| 550 | + convert_to_double_ex(zpp_normal); |
| 551 | + } |
| 552 | + if (Z_TYPE_PP(zpp_normal) == IS_DOUBLE) { |
| 553 | + luasandbox_set_timespec(&sandbox->cpu_normal_limit, Z_DVAL_PP(zpp_normal)); |
| 554 | + sandbox->is_cpu_limited = 1; |
| 555 | + } else { |
| 556 | + sandbox->is_cpu_limited = 0; |
| 557 | + } |
| 558 | + } |
| 559 | + |
| 560 | + if (!zpp_emergency |
| 561 | + || (Z_TYPE_PP(zpp_emergency) == IS_BOOL && Z_BVAL_PP(zpp_emergency) == 0)) |
| 562 | + { |
| 563 | + // No emergency limit |
| 564 | + sandbox->cpu_emergency_limit.tv_sec = 0; |
| 565 | + sandbox->cpu_emergency_limit.tv_nsec = 0; |
| 566 | + } else { |
| 567 | + convert_scalar_to_number_ex(zpp_emergency); |
| 568 | + if (Z_TYPE_PP(zpp_emergency) == IS_LONG) { |
| 569 | + convert_to_double_ex(zpp_emergency); |
| 570 | + } |
| 571 | + if (Z_TYPE_PP(zpp_normal) == IS_DOUBLE) { |
| 572 | + luasandbox_set_timespec(&sandbox->cpu_emergency_limit, Z_DVAL_PP(zpp_emergency)); |
| 573 | + } else { |
| 574 | + sandbox->cpu_emergency_limit.tv_sec = 0; |
| 575 | + sandbox->cpu_emergency_limit.tv_nsec = 0; |
| 576 | + } |
| 577 | + } |
396 | 578 | } |
397 | 579 | /* }}} */ |
398 | 580 | |
399 | | -/*** {{{ proto array LuaSandbox::callScript(string name, ...) |
400 | | - */ |
401 | | -PHP_METHOD(LuaSandbox, callScript) |
| 581 | +/** {{{ luasandbox_set_timespec */ |
| 582 | +static void luasandbox_set_timespec(struct timespec * dest, double source) |
402 | 583 | { |
403 | | - lua_State * L = getLuaState(getThis()); |
404 | | - lua_getglobal(L, "sandbox"); |
405 | | - lua_getfield(L, -1, "scripts"); |
406 | | - luasandbox_call_helper(INTERNAL_FUNCTION_PARAM_PASSTHRU, -1); |
407 | | - lua_pop(L, 2); |
| 584 | + double fractional, integral; |
| 585 | + if (source < 0) { |
| 586 | + dest->tv_sec = dest->tv_nsec = 0; |
| 587 | + return; |
| 588 | + } |
| 589 | + |
| 590 | + fractional = modf(source, &integral); |
| 591 | + dest->tv_sec = (time_t)integral; |
| 592 | + dest->tv_nsec = (long)(fractional * 1000000000.0); |
| 593 | + if (dest->tv_nsec >= 1000000000L) { |
| 594 | + dest->tv_nsec -= 1000000000L; |
| 595 | + dest->tv_sec ++; |
| 596 | + } |
408 | 597 | } |
409 | | -/** }}} */ |
410 | 598 | |
411 | | -/** {{{ luasandbox_call_helper |
412 | | - * Call a field or subfield of the table at the given index. Set return_value |
413 | | - * to an array containing all the results. |
| 599 | +/* }}} */ |
| 600 | + |
| 601 | +/*** {{{ proto array LuaSandbox::callFunction(string name, ...) |
414 | 602 | */ |
415 | | -static void luasandbox_call_helper(INTERNAL_FUNCTION_PARAMETERS, int index) |
| 603 | +PHP_METHOD(LuaSandbox, callFunction) |
416 | 604 | { |
417 | 605 | char *name; |
418 | | - int nameLength = 0, status, origTop; |
419 | | - zend_uint numArgs = 0, numResults, i; |
| 606 | + int nameLength = 0; |
| 607 | + zend_uint numArgs = 0; |
420 | 608 | zval *** args = NULL; |
421 | | - lua_State * L = getLuaState(getThis()); |
422 | 609 | |
| 610 | + php_luasandbox_obj * sandbox = (php_luasandbox_obj*) |
| 611 | + zend_object_store_get_object(getThis() TSRMLS_CC); |
| 612 | + lua_State * L = sandbox->state; |
| 613 | + |
423 | 614 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s*", |
424 | 615 | &name, &nameLength, &args, &numArgs) == FAILURE) |
425 | 616 | { |
426 | 617 | RETURN_FALSE; |
427 | 618 | } |
428 | 619 | |
429 | | - // Save the top position |
430 | | - origTop = lua_gettop(L); |
431 | | - |
432 | 620 | // Find the function |
433 | | - if (!luasandbox_find_field(L, index, name, nameLength)) { |
| 621 | + if (!luasandbox_find_field(L, LUA_GLOBALSINDEX, name, nameLength)) { |
434 | 622 | php_error_docref(NULL TSRMLS_CC, E_WARNING, |
435 | 623 | "The specified lua function does not exist"); |
436 | 624 | RETVAL_FALSE; |
437 | | - goto cleanup; |
| 625 | + } else { |
| 626 | + // Call it |
| 627 | + luasandbox_call_helper(L, sandbox, args, numArgs, return_value TSRMLS_CC); |
438 | 628 | } |
439 | 629 | |
| 630 | + // Delete varargs |
| 631 | + if (numArgs) { |
| 632 | + efree(args); |
| 633 | + } |
| 634 | +} |
| 635 | +/* }}} */ |
| 636 | + |
| 637 | +/*** {{{ proto array LuaSandboxFunction::call(...) |
| 638 | + */ |
| 639 | +PHP_METHOD(LuaSandboxFunction, call) |
| 640 | +{ |
| 641 | + zend_uint numArgs = 0; |
| 642 | + zval *** args = NULL; |
| 643 | + |
| 644 | + php_luasandboxfunction_obj * func = (php_luasandboxfunction_obj *) |
| 645 | + zend_object_store_get_object(getThis() TSRMLS_CC); |
| 646 | + lua_State * L; |
| 647 | + php_luasandbox_obj * sandbox; |
| 648 | + |
| 649 | + if (!func || !func->sandbox || !func->index) { |
| 650 | + php_error_docref(NULL TSRMLS_CC, E_WARNING, |
| 651 | + "attempt to call uninitialized LuaSandboxFunction object" ); |
| 652 | + RETURN_FALSE; |
| 653 | + } |
| 654 | + |
| 655 | + sandbox = (php_luasandbox_obj*) |
| 656 | + zend_object_store_get_object(func->sandbox TSRMLS_CC); |
| 657 | + L = sandbox->state; |
| 658 | + |
| 659 | + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "*", |
| 660 | + &args, &numArgs) == FAILURE) |
| 661 | + { |
| 662 | + RETURN_FALSE; |
| 663 | + } |
| 664 | + |
| 665 | + // Find the function |
| 666 | + lua_getfield(L, LUA_REGISTRYINDEX, "php_luasandbox_chunks"); |
| 667 | + lua_rawgeti(L, -1, func->index); |
| 668 | + |
| 669 | + // Call it |
| 670 | + luasandbox_call_helper(L, sandbox, args, numArgs, return_value TSRMLS_CC); |
| 671 | + |
| 672 | + // Delete varargs |
| 673 | + if (numArgs) { |
| 674 | + efree(args); |
| 675 | + } |
| 676 | +} |
| 677 | +/** }}} */ |
| 678 | + |
| 679 | +/** {{{ luasandbox_call_helper |
| 680 | + * Call the function at the top of the stack and then pop it. Set return_value |
| 681 | + * to an array containing all the results. |
| 682 | + */ |
| 683 | +static void luasandbox_call_helper(lua_State * L, php_luasandbox_obj * sandbox, |
| 684 | + zval *** args, zend_uint numArgs, zval * return_value TSRMLS_DC) |
| 685 | +{ |
| 686 | + // Save the top position |
| 687 | + int origTop = lua_gettop(L); |
| 688 | + int status; |
| 689 | + int cpu_limited = 0; |
| 690 | + luasandbox_timer_set t; |
| 691 | + int i, numResults; |
| 692 | + |
| 693 | + // Check to see if the value is a valid function |
| 694 | + if (lua_type(L, -1) != LUA_TFUNCTION) { |
| 695 | + php_error_docref(NULL TSRMLS_CC, E_WARNING, |
| 696 | + "the specified Lua value is not a valid function"); |
| 697 | + lua_settop(L, origTop - 1); |
| 698 | + RETURN_FALSE; |
| 699 | + } |
| 700 | + |
440 | 701 | // Push the arguments |
441 | 702 | for (i = 0; i < numArgs; i++) { |
442 | 703 | if (!luasandbox_push_zval(L, *(args[i]))) { |
443 | 704 | php_error_docref(NULL TSRMLS_CC, E_WARNING, |
444 | | - "Unable to convert argument %d to a lua value", i + 1); |
445 | | - RETVAL_FALSE; |
446 | | - goto cleanup; |
| 705 | + "unable to convert argument %d to a lua value", i + 1); |
| 706 | + lua_settop(L, origTop - 1); |
| 707 | + RETURN_FALSE; |
447 | 708 | } |
448 | 709 | } |
449 | 710 | |
450 | | - // Call it |
| 711 | + // Initialise the CPU limit timer |
| 712 | + if (sandbox->is_cpu_limited) { |
| 713 | + cpu_limited = 1; |
| 714 | + if (!LUASANDBOX_G(signal_handler_installed)) { |
| 715 | + luasandbox_timer_install_handler(&LUASANDBOX_G(old_handler)); |
| 716 | + } |
| 717 | + luasandbox_timer_start(&t, sandbox, &sandbox->cpu_normal_limit, |
| 718 | + &sandbox->cpu_emergency_limit); |
| 719 | + } |
| 720 | + |
| 721 | + // Call the function |
451 | 722 | status = lua_pcall(L, numArgs, LUA_MULTRET, 0); |
| 723 | + |
| 724 | + // Stop the timer |
| 725 | + if (cpu_limited) { |
| 726 | + luasandbox_timer_stop(&t); |
| 727 | + } |
| 728 | + |
| 729 | + // If there was an emergency timeout, destroy the state |
| 730 | + if (sandbox->emergency_timed_out) { |
| 731 | + lua_close(L); |
| 732 | + L = sandbox->state = luasandbox_newstate(sandbox); |
| 733 | + sandbox->emergency_timed_out = 0; |
| 734 | + zend_throw_exception(luasandboxerror_ce, |
| 735 | + "The maximum execution time was exceeded " |
| 736 | + "and the current Lua statement failed to return, leading to " |
| 737 | + "destruction of the Lua state", LUA_ERRRUN); |
| 738 | + return; |
| 739 | + } |
| 740 | + |
| 741 | + // Handle normal errors |
452 | 742 | if (status) { |
453 | 743 | luasandbox_handle_error(L, status); |
454 | | - goto cleanup; |
| 744 | + lua_settop(L, origTop - 1); |
| 745 | + RETURN_FALSE; |
455 | 746 | } |
456 | 747 | |
457 | 748 | // Calculate the number of results and create an array of that capacity |
458 | | - numResults = lua_gettop(L) - origTop; |
| 749 | + numResults = lua_gettop(L) - origTop + 1; |
459 | 750 | array_init_size(return_value, numResults); |
460 | 751 | |
461 | 752 | // Fill the array with the results |
462 | 753 | for (i = 0; i < numResults; i++) { |
463 | | - zval * element = luasandbox_lua_to_zval(L, origTop + i + 1, NULL); |
| 754 | + zval * element = luasandbox_lua_to_zval(L, origTop + i, NULL); |
464 | 755 | zend_hash_next_index_insert(Z_ARRVAL_P(return_value), |
465 | 756 | (void*)&element, |
466 | 757 | sizeof(zval*), NULL); |
467 | 758 | } |
468 | 759 | |
469 | | -cleanup: |
470 | | - |
471 | 760 | // Balance the stack |
472 | | - lua_pop(L, lua_gettop(L) - origTop); |
473 | | - // Delete varargs |
474 | | - if (numArgs) { |
475 | | - efree(args); |
476 | | - } |
| 761 | + lua_settop(L, origTop - 1); |
477 | 762 | } |
478 | 763 | /* }}} */ |
479 | 764 | |
— | — | @@ -499,7 +784,7 @@ |
500 | 785 | if (i == specLength || spec[i] == '.') { |
501 | 786 | // Put the next item into top+2 |
502 | 787 | lua_pushlstring(L, spec + tokenStart, i - tokenStart); |
503 | | - lua_gettable(L, top + 1); |
| 788 | + lua_rawget(L, top + 1); |
504 | 789 | |
505 | 790 | // Not found? |
506 | 791 | if (lua_isnil(L, top + 2)) { |
— | — | @@ -522,7 +807,10 @@ |
523 | 808 | } |
524 | 809 | /* }}} */ |
525 | 810 | |
526 | | -/** {{{ luasandbox_push_zval */ |
| 811 | +/** {{{ luasandbox_push_zval |
| 812 | + * Convert a zval to an appropriate Lua type and push the resulting value on to |
| 813 | + * the stack. |
| 814 | + */ |
527 | 815 | static int luasandbox_push_zval(lua_State * L, zval * z) |
528 | 816 | { |
529 | 817 | switch (Z_TYPE_P(z)) { |
— | — | @@ -561,6 +849,23 @@ |
562 | 850 | } |
563 | 851 | /* }}} */ |
564 | 852 | |
| 853 | +/** {{{ luasandbox_push_zval_userdata |
| 854 | + * Push a full userdata on to the stack, which stores a zval* in its block. |
| 855 | + * Increment its reference count and set its metatable so that it will be freed |
| 856 | + * at the appropriate time. |
| 857 | + */ |
| 858 | +static void luasandbox_push_zval_userdata(lua_State * L, zval * z) |
| 859 | +{ |
| 860 | + zval ** ud; |
| 861 | + ud = (zval**)lua_newuserdata(L, sizeof(zval*)); |
| 862 | + *ud = z; |
| 863 | + Z_ADDREF_P(z); |
| 864 | + |
| 865 | + lua_getfield(L, LUA_REGISTRYINDEX, "php_luasandbox_zval_metatable"); |
| 866 | + lua_setmetatable(L, -2); |
| 867 | +} |
| 868 | +/* }}} */ |
| 869 | + |
565 | 870 | /** {{{ luasandbox_push_hashtable */ |
566 | 871 | static int luasandbox_push_hashtable(lua_State * L, HashTable * ht) |
567 | 872 | { |
— | — | @@ -581,7 +886,7 @@ |
582 | 887 | ht->nApplyCount++; |
583 | 888 | for (p = ht->pListHead; p; p = p->pListNext) { |
584 | 889 | if (p->nKeyLength) { |
585 | | - lua_pushlstring(L, p->arKey, p->nKeyLength); |
| 890 | + lua_pushlstring(L, p->arKey, p->nKeyLength - 1); |
586 | 891 | } else { |
587 | 892 | lua_pushinteger(L, p->h); |
588 | 893 | } |
— | — | @@ -746,19 +1051,136 @@ |
747 | 1052 | } |
748 | 1053 | /* }}} */ |
749 | 1054 | |
750 | | -/** {{{ luasandbox_method_getMemoryUsage */ |
751 | | -static int luasandbox_method_getMemoryUsage(lua_State * L) |
| 1055 | +/** {{{ proto void LuaSandbox::register(string libname, array functions) |
| 1056 | + */ |
| 1057 | +PHP_METHOD(LuaSandbox, register) |
752 | 1058 | { |
| 1059 | + lua_State * L = luasandbox_state_from_zval(getThis() TSRMLS_CC); |
| 1060 | + char * libname = NULL; |
| 1061 | + int libname_len = 0; |
| 1062 | + zval * zfunctions = NULL; |
| 1063 | + HashTable * functions; |
| 1064 | + Bucket * p; |
| 1065 | + |
| 1066 | + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sa", |
| 1067 | + &libname, &libname_len, &zfunctions) == FAILURE) |
| 1068 | + { |
| 1069 | + RETURN_FALSE; |
| 1070 | + } |
| 1071 | + |
| 1072 | + functions = Z_ARRVAL_P(zfunctions); |
| 1073 | + |
| 1074 | + // Determine if the library exists already |
| 1075 | + // Make a copy of the library name on the stack for rawset later |
| 1076 | + lua_pushlstring(L, libname, libname_len); |
| 1077 | + lua_pushvalue(L, -1); |
| 1078 | + lua_rawget(L, LUA_GLOBALSINDEX); |
| 1079 | + if (lua_type(L, -1) != LUA_TNIL) { |
| 1080 | + php_error_docref(NULL TSRMLS_CC, E_WARNING, |
| 1081 | + "library \"%s\" already exists", libname); |
| 1082 | + RETURN_FALSE; |
| 1083 | + } |
| 1084 | + // Remove the nil |
| 1085 | + lua_pop(L, 1); |
| 1086 | + |
| 1087 | + // Create the new table |
| 1088 | + lua_createtable(L, 0, functions->nNumOfElements); |
| 1089 | + |
| 1090 | + for (p = functions->pListHead; p; p = p->pListNext) { |
| 1091 | + // Push the key |
| 1092 | + if (p->nKeyLength) { |
| 1093 | + lua_pushlstring(L, p->arKey, p->nKeyLength - 1); |
| 1094 | + } else { |
| 1095 | + lua_pushinteger(L, p->h); |
| 1096 | + } |
| 1097 | + |
| 1098 | + // Push the callback zval and create the closure |
| 1099 | + luasandbox_push_zval_userdata(L, *(zval**)p->pData); |
| 1100 | + lua_pushcclosure(L, luasandbox_call_php, 1); |
| 1101 | + |
| 1102 | + // Add it to the table |
| 1103 | + lua_rawset(L, -3); |
| 1104 | + } |
| 1105 | + |
| 1106 | + // Move the new table to the global namespace |
| 1107 | + // The key is on the stack already |
| 1108 | + lua_rawset(L, LUA_GLOBALSINDEX); |
| 1109 | +} |
| 1110 | +/* }}} */ |
| 1111 | + |
| 1112 | +/** {{{ luasandbox_call_php |
| 1113 | + */ |
| 1114 | +static int luasandbox_call_php(lua_State * L) |
| 1115 | +{ |
753 | 1116 | php_luasandbox_obj * intern = luasandbox_get_php_obj(L); |
754 | | - if ((size_t)(lua_Integer)intern->memory_usage != intern->memory_usage) { |
755 | | - lua_pushnumber(L, (lua_Number)intern->memory_usage); |
756 | | - } else { |
757 | | - lua_pushinteger(L, (lua_Integer)intern->memory_usage); |
| 1117 | + |
| 1118 | + luasandbox_enter_php(L, intern); |
| 1119 | + |
| 1120 | + zval ** callback_pp = lua_touserdata(L, lua_upvalueindex(1)); |
| 1121 | + zval *retval_ptr = NULL; |
| 1122 | + zend_fcall_info fci; |
| 1123 | + zend_fcall_info_cache fcc; |
| 1124 | + char *is_callable_error = NULL; |
| 1125 | + int top = lua_gettop(L); |
| 1126 | + int i; |
| 1127 | + void **temp; |
| 1128 | + zval **pointers; |
| 1129 | + zval ***double_pointers; |
| 1130 | + int num_results = 0; |
| 1131 | + |
| 1132 | + // Based on zend_parse_arg_impl() |
| 1133 | + if (zend_fcall_info_init(*callback_pp, 0, &fci, &fcc, NULL, |
| 1134 | + &is_callable_error TSRMLS_CC) != SUCCESS) |
| 1135 | + { |
| 1136 | + // Handle errors similar to the way PHP does it: show a warning and |
| 1137 | + // return nil |
| 1138 | + php_error_docref(NULL TSRMLS_CC, E_WARNING, |
| 1139 | + "to be a valid callback, %s", is_callable_error); |
| 1140 | + efree(is_callable_error); |
| 1141 | + lua_pushnil(L); |
| 1142 | + luasandbox_leave_php(L, intern); |
| 1143 | + return 1; |
758 | 1144 | } |
759 | | - return 1; |
| 1145 | + |
| 1146 | + fci.retval_ptr_ptr = &retval_ptr; |
| 1147 | + |
| 1148 | + // Make an array of zval double-pointers to hold the arguments |
| 1149 | + temp = ecalloc(top, sizeof(void*) * 2); |
| 1150 | + double_pointers = (zval***)temp; |
| 1151 | + pointers = (zval**)(temp + top); |
| 1152 | + for (i = 0; i < top; i++ ) { |
| 1153 | + pointers[i] = luasandbox_lua_to_zval(L, i + 1, NULL); |
| 1154 | + double_pointers[i] = &(pointers[i]); |
| 1155 | + } |
| 1156 | + |
| 1157 | + // Initialise the fci. Use zend_fcall_info_args_restore() since that's an |
| 1158 | + // almost-legitimate way to avoid the extra malloc that we'd get from |
| 1159 | + // zend_fcall_info_argp() |
| 1160 | + zend_fcall_info_args_restore(&fci, top, double_pointers); |
| 1161 | + |
| 1162 | + // Call the function |
| 1163 | + if (zend_call_function(&fci, &fcc TSRMLS_CC) == SUCCESS |
| 1164 | + && fci.retval_ptr_ptr && *fci.retval_ptr_ptr) |
| 1165 | + { |
| 1166 | + // Push the return value back to Lua |
| 1167 | + luasandbox_push_zval(L, *fci.retval_ptr_ptr); |
| 1168 | + num_results = 1; |
| 1169 | + } |
| 1170 | + |
| 1171 | + // Free the zvals |
| 1172 | + for (i = 0; i < top; i++) { |
| 1173 | + zval_ptr_dtor(&(pointers[i])); |
| 1174 | + } |
| 1175 | + zval_ptr_dtor(&retval_ptr); |
| 1176 | + |
| 1177 | + // Free the pointer arrays |
| 1178 | + efree(temp); |
| 1179 | + luasandbox_leave_php(L, intern); |
| 1180 | + return num_results; |
760 | 1181 | } |
761 | 1182 | /* }}} */ |
762 | 1183 | |
| 1184 | +/* }}} */ |
763 | 1185 | /* |
764 | 1186 | * Local variables: |
765 | 1187 | * tab-width: 4 |
Index: trunk/php/luasandbox/timer.c |
— | — | @@ -0,0 +1,143 @@ |
| 2 | +#ifdef HAVE_CONFIG_H |
| 3 | +#include "config.h" |
| 4 | +#endif |
| 5 | + |
| 6 | +#include <signal.h> |
| 7 | +#include <time.h> |
| 8 | +#include <lua.h> |
| 9 | +#include <lauxlib.h> |
| 10 | + |
| 11 | +#include "php.h" |
| 12 | +#include "php_luasandbox.h" |
| 13 | +#include "luasandbox_timer.h" |
| 14 | + |
| 15 | +#ifdef LUASANDBOX_NO_CLOCK |
| 16 | + |
| 17 | +void luasandbox_timer_install_handler(struct sigaction * oldact) {} |
| 18 | +void luasandbox_timer_remove_handler(struct sigaction * oldact) {} |
| 19 | +void luasandbox_timer_start(luasandbox_timer_set * lts, |
| 20 | + php_luasandbox_obj * sandbox, |
| 21 | + struct timespec * normal_timeout, |
| 22 | + struct timespec * emergency_timeout) {} |
| 23 | +void luasandbox_timer_stop(luasandbox_timer_set * lts) {} |
| 24 | + |
| 25 | +#else |
| 26 | + |
| 27 | +static void luasandbox_timer_handle(int signo, siginfo_t * info, void * context); |
| 28 | +static void luasandbox_timer_create(luasandbox_timer * lt, php_luasandbox_obj * sandbox, |
| 29 | + int emergency); |
| 30 | +static void luasandbox_timer_timeout_hook(lua_State *L, lua_Debug *ar); |
| 31 | +static void luasandbox_timer_settime(luasandbox_timer * lt, struct timespec * ts); |
| 32 | + |
| 33 | +void luasandbox_timer_install_handler(struct sigaction * oldact) |
| 34 | +{ |
| 35 | + struct sigaction newact; |
| 36 | + newact.sa_sigaction = luasandbox_timer_handle; |
| 37 | + newact.sa_flags = SA_SIGINFO; |
| 38 | + sigprocmask(SIG_BLOCK, NULL, &newact.sa_mask); |
| 39 | + sigaction(LUASANDBOX_SIGNAL, &newact, oldact); |
| 40 | +} |
| 41 | + |
| 42 | +void luasandbox_timer_remove_handler(struct sigaction * oldact) |
| 43 | +{ |
| 44 | + sigaction(LUASANDBOX_SIGNAL, oldact, NULL); |
| 45 | +} |
| 46 | + |
| 47 | +static void luasandbox_timer_handle(int signo, siginfo_t * info, void * context) |
| 48 | +{ |
| 49 | + luasandbox_timer_callback_data * data; |
| 50 | + |
| 51 | + if (signo != LUASANDBOX_SIGNAL |
| 52 | + || info->si_code != SI_TIMER |
| 53 | + || !info->si_value.sival_ptr) |
| 54 | + { |
| 55 | + return; |
| 56 | + } |
| 57 | + |
| 58 | + data = (luasandbox_timer_callback_data*)info->si_value.sival_ptr; |
| 59 | + data->sandbox->timed_out = 1; |
| 60 | + if (data->emergency) { |
| 61 | + sigset_t set; |
| 62 | + sigemptyset(&set); |
| 63 | + sigprocmask(SIG_SETMASK, &set, NULL); |
| 64 | + data->sandbox->emergency_timed_out = 1; |
| 65 | + if (data->sandbox->in_php) { |
| 66 | + // The whole PHP request context is dirty now. We need to kill it, |
| 67 | + // like what happens if there is a max_execution_time timeout. |
| 68 | + zend_error(E_ERROR, "The maximum execution time for a Lua sandbox script " |
| 69 | + "was exceeded and a PHP callback failed to return"); |
| 70 | + } else { |
| 71 | + // The Lua state is dirty now and can't be used again. |
| 72 | + luaL_error(data->sandbox->state, "emergency timeout"); |
| 73 | + } |
| 74 | + } else { |
| 75 | + // Set a hook which will terminate the script execution in a graceful way |
| 76 | + lua_sethook(data->sandbox->state, luasandbox_timer_timeout_hook, |
| 77 | + LUA_MASKCALL | LUA_MASKRET | LUA_MASKLINE, 0); |
| 78 | + } |
| 79 | +} |
| 80 | + |
| 81 | +static void luasandbox_timer_timeout_hook(lua_State *L, lua_Debug *ar) |
| 82 | +{ |
| 83 | + // Avoid infinite recursion |
| 84 | + lua_sethook(L, luasandbox_timer_timeout_hook, 0, 0); |
| 85 | + // Do a longjmp to report the timeout error |
| 86 | + luaL_error(L, luasandbox_timeout_message); |
| 87 | +} |
| 88 | + |
| 89 | +void luasandbox_timer_start(luasandbox_timer_set * lts, |
| 90 | + php_luasandbox_obj * sandbox, |
| 91 | + struct timespec * normal_timeout, |
| 92 | + struct timespec * emergency_timeout) |
| 93 | +{ |
| 94 | + // Create normal timer |
| 95 | + luasandbox_timer_create(<s->normal_timer, sandbox, 0); |
| 96 | + luasandbox_timer_settime(<s->normal_timer, normal_timeout); |
| 97 | + |
| 98 | + // Create emergency timer if requested |
| 99 | + if (emergency_timeout->tv_sec || emergency_timeout->tv_nsec) { |
| 100 | + lts->use_emergency = 1; |
| 101 | + luasandbox_timer_create(<s->emergency_timer, sandbox, 1); |
| 102 | + luasandbox_timer_settime(<s->emergency_timer, emergency_timeout); |
| 103 | + } else { |
| 104 | + lts->use_emergency = 0; |
| 105 | + } |
| 106 | +} |
| 107 | + |
| 108 | +static void luasandbox_timer_create(luasandbox_timer * lt, php_luasandbox_obj * sandbox, |
| 109 | + int emergency) |
| 110 | +{ |
| 111 | + struct sigevent ev; |
| 112 | + |
| 113 | + lt->cbdata.emergency = emergency; |
| 114 | + lt->cbdata.sandbox = sandbox; |
| 115 | + |
| 116 | + ev.sigev_notify = SIGEV_SIGNAL; |
| 117 | + ev.sigev_signo = LUASANDBOX_SIGNAL; |
| 118 | + ev.sigev_value.sival_ptr = (void*)<->cbdata; |
| 119 | + |
| 120 | + timer_create(LUASANDBOX_CLOCK_ID, &ev, <->timer); |
| 121 | +} |
| 122 | + |
| 123 | +static void luasandbox_timer_settime(luasandbox_timer * lt, struct timespec * ts) |
| 124 | +{ |
| 125 | + struct itimerspec its; |
| 126 | + its.it_interval.tv_sec = 0; |
| 127 | + its.it_interval.tv_nsec = 0; |
| 128 | + its.it_value = *ts; |
| 129 | + timer_settime(lt->timer, 0, &its, NULL); |
| 130 | +} |
| 131 | + |
| 132 | +void luasandbox_timer_stop(luasandbox_timer_set * lts) |
| 133 | +{ |
| 134 | + struct timespec zero = {0, 0}; |
| 135 | + if (lts->use_emergency) { |
| 136 | + luasandbox_timer_settime(<s->emergency_timer, &zero); |
| 137 | + timer_delete(lts->emergency_timer.timer); |
| 138 | + } |
| 139 | + |
| 140 | + luasandbox_timer_settime(<s->normal_timer, &zero); |
| 141 | + timer_delete(lts->normal_timer.timer); |
| 142 | +} |
| 143 | + |
| 144 | +#endif |
Property changes on: trunk/php/luasandbox/timer.c |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 145 | + native |
Index: trunk/php/luasandbox/config.m4 |
— | — | @@ -26,5 +26,5 @@ |
27 | 27 | AC_MSG_NOTICE(LUA: found LUA_LIBS: $LUA_LIBS) |
28 | 28 | |
29 | 29 | PHP_SUBST(LUASANDBOX_SHARED_LIBADD) |
30 | | - PHP_NEW_EXTENSION(luasandbox, luasandbox.c, $ext_shared) |
| 30 | + PHP_NEW_EXTENSION(luasandbox, luasandbox.c timer.c, $ext_shared) |
31 | 31 | fi |
Index: trunk/php/luasandbox/luasandbox_timer.h |
— | — | @@ -0,0 +1,53 @@ |
| 2 | +#ifndef LUASANDBOX_TIMER_H |
| 3 | +#define LUASANDBOX_TIMER_H |
| 4 | + |
| 5 | +#ifdef CLOCK_REALTIME |
| 6 | + |
| 7 | +#ifdef CLOCK_PROCESS_CPUTIME_ID |
| 8 | +#define LUASANDBOX_CLOCK_ID CLOCK_PROCESS_CPUTIME_ID |
| 9 | +#else |
| 10 | +#define LUASANDBOX_CLOCK_ID CLOCK_REALTIME |
| 11 | +#endif |
| 12 | + |
| 13 | +#ifdef SIGRTMIN |
| 14 | +#define LUASANDBOX_SIGNAL (SIGRTMIN+1) |
| 15 | +#else |
| 16 | +#define LUASANDBOX_SIGNAL SIGUSR2 |
| 17 | +#endif |
| 18 | + |
| 19 | +typedef struct { |
| 20 | + int emergency; |
| 21 | + php_luasandbox_obj * sandbox; |
| 22 | +} luasandbox_timer_callback_data; |
| 23 | + |
| 24 | +typedef struct { |
| 25 | + timer_t timer; |
| 26 | + luasandbox_timer_callback_data cbdata; |
| 27 | +} luasandbox_timer; |
| 28 | + |
| 29 | +typedef struct { |
| 30 | + luasandbox_timer normal_timer; |
| 31 | + luasandbox_timer emergency_timer; |
| 32 | + int use_emergency; |
| 33 | + php_luasandbox_obj * sandbox; |
| 34 | +} luasandbox_timer_set; |
| 35 | + |
| 36 | + |
| 37 | +#else |
| 38 | + |
| 39 | +#define LUASANDBOX_NO_CLOCK |
| 40 | + |
| 41 | +typedef struct {} luasandbox_timer; |
| 42 | + |
| 43 | +#endif |
| 44 | + |
| 45 | +void luasandbox_timer_install_handler(struct sigaction * oldact); |
| 46 | +void luasandbox_timer_remove_handler(struct sigaction * oldact); |
| 47 | +void luasandbox_timer_start(luasandbox_timer_set * lts, |
| 48 | + php_luasandbox_obj * sandbox, |
| 49 | + struct timespec * normal_timeout, |
| 50 | + struct timespec * emergency_timeout); |
| 51 | +void luasandbox_timer_stop(luasandbox_timer_set * lts); |
| 52 | + |
| 53 | + |
| 54 | +#endif |
Property changes on: trunk/php/luasandbox/luasandbox_timer.h |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 55 | + native |
Index: trunk/php/luasandbox/php_luasandbox.h |
— | — | @@ -5,6 +5,8 @@ |
6 | 6 | #include <lua.h> |
7 | 7 | |
8 | 8 | extern zend_module_entry luasandbox_module_entry; |
| 9 | +extern char luasandbox_timeout_message[]; |
| 10 | + |
9 | 11 | #define phpext_luasandbox_ptr &luasandbox_module_entry |
10 | 12 | |
11 | 13 | #ifdef PHP_WIN32 |
— | — | @@ -21,34 +23,23 @@ |
22 | 24 | |
23 | 25 | PHP_MINIT_FUNCTION(luasandbox); |
24 | 26 | PHP_MSHUTDOWN_FUNCTION(luasandbox); |
| 27 | +PHP_RSHUTDOWN_FUNCTION(luasandbox); |
25 | 28 | PHP_MINFO_FUNCTION(luasandbox); |
26 | 29 | |
27 | 30 | PHP_METHOD(LuaSandbox, loadString); |
| 31 | +PHP_METHOD(LuaSandbox, doString); |
28 | 32 | PHP_METHOD(LuaSandbox, setMemoryLimit); |
| 33 | +PHP_METHOD(LuaSandbox, setCPULimit); |
29 | 34 | PHP_METHOD(LuaSandbox, callFunction); |
30 | | -PHP_METHOD(LuaSandbox, callScript); |
31 | | -PHP_METHOD(LuaSandbox, createFunction); |
| 35 | +PHP_METHOD(LuaSandbox, register); |
32 | 36 | |
33 | | -/* |
34 | | - Declare any global variables you may need between the BEGIN |
35 | | - and END macros here: |
| 37 | +PHP_METHOD(LuaSandboxFunction, call); |
36 | 38 | |
37 | 39 | ZEND_BEGIN_MODULE_GLOBALS(luasandbox) |
38 | | - long global_value; |
39 | | - char *global_string; |
| 40 | + int signal_handler_installed; |
| 41 | + struct sigaction old_handler; |
40 | 42 | ZEND_END_MODULE_GLOBALS(luasandbox) |
41 | | -*/ |
42 | 43 | |
43 | | -/* In every utility function you add that needs to use variables |
44 | | - in php_luasandbox_globals, call TSRMLS_FETCH(); after declaring other |
45 | | - variables used by that function, or better yet, pass in TSRMLS_CC |
46 | | - after the last function argument and declare your utility function |
47 | | - with TSRMLS_DC after the last declared argument. Always refer to |
48 | | - the globals in your function as LUASANDBOX_G(variable). You are |
49 | | - encouraged to rename these macros something shorter, see |
50 | | - examples in any other php module directory. |
51 | | -*/ |
52 | | - |
53 | 44 | #ifdef ZTS |
54 | 45 | #define LUASANDBOX_G(v) TSRMG(luasandbox_globals_id, zend_luasandbox_globals *, v) |
55 | 46 | #else |
— | — | @@ -60,8 +51,21 @@ |
61 | 52 | lua_State * state; |
62 | 53 | size_t memory_limit; |
63 | 54 | size_t memory_usage; |
| 55 | + int in_php; |
| 56 | + volatile int timed_out; |
| 57 | + volatile int emergency_timed_out; |
| 58 | + int is_cpu_limited; |
| 59 | + struct timespec cpu_normal_limit; |
| 60 | + struct timespec cpu_emergency_limit; |
64 | 61 | }; |
65 | 62 | typedef struct _php_luasandbox_obj php_luasandbox_obj; |
66 | 63 | |
| 64 | +struct _php_luasandboxfunction_obj { |
| 65 | + zend_object std; |
| 66 | + zval * sandbox; |
| 67 | + int index; |
| 68 | +}; |
| 69 | +typedef struct _php_luasandboxfunction_obj php_luasandboxfunction_obj; |
| 70 | + |
67 | 71 | #endif /* PHP_LUASANDBOX_H */ |
68 | 72 | |