Line data Source code
1 : /**
2 : * This file holds the library of functions which are written in C and exposed
3 : * to Lua code, and the code which manages registration of both the custom
4 : * library and the parts of the standard Lua library which we allow.
5 : */
6 :
7 : #ifdef HAVE_CONFIG_H
8 : #include "config.h"
9 : #endif
10 :
11 : #include <lua.h>
12 : #include <lauxlib.h>
13 : #include <lualib.h>
14 : #include <math.h>
15 :
16 : #include "php.h"
17 : #include "php_luasandbox.h"
18 :
19 : #ifdef LUASANDBOX_NO_CLOCK
20 : #include <time.h>
21 : #endif
22 :
23 : static void luasandbox_lib_filter_table(lua_State * L, char ** member_names);
24 : static HashTable * luasandbox_lib_get_allowed_globals();
25 :
26 : static int luasandbox_base_tostring(lua_State * L);
27 : static int luasandbox_math_random(lua_State * L);
28 : static int luasandbox_math_randomseed(lua_State * L);
29 : static int luasandbox_base_pcall(lua_State * L);
30 : static int luasandbox_base_xpcall(lua_State *L);
31 : static int luasandbox_os_clock(lua_State * L);
32 :
33 : #if LUA_VERSION_NUM < 502
34 : static int luasandbox_base_pairs(lua_State *L);
35 : static int luasandbox_base_ipairs(lua_State *L);
36 : #endif
37 :
38 : /**
39 : * Allowed global variables. Omissions are:
40 : * * pcall, xpcall: We have our own versions which don't allow interception of
41 : * timeout etc. errors.
42 : * * loadfile: insecure.
43 : * * load, loadstring: Probably creates a protected environment so has
44 : * the same problem as pcall. Also omitting these makes analysis of the
45 : * code for runtime etc. feasible.
46 : * * print: Not compatible with a sandbox environment
47 : * * tostring: Provides addresses of tables and functions, which provides an
48 : * easy ASLR workaround or heap address discovery mechanism for a memory
49 : * corruption exploit. We have our own version.
50 : * * Any new or undocumented functions like newproxy.
51 : * * package: cpath, loadlib etc. are insecure.
52 : * * coroutine: Not useful for our application so unreviewed at present.
53 : * * io, file, os: insecure
54 : * * debug: Provides various ways to break the sandbox, such as setupvalue()
55 : * and getregistry().
56 : */
57 : char * luasandbox_allowed_globals[] = {
58 : // base
59 : "assert",
60 : "error",
61 : "getfenv",
62 : "getmetatable",
63 : "ipairs",
64 : "next",
65 : "pairs",
66 : "rawequal",
67 : "rawget",
68 : "rawset",
69 : "select",
70 : "setfenv",
71 : "setmetatable",
72 : "tonumber",
73 : "type",
74 : "unpack",
75 : "_G",
76 : "_VERSION",
77 : // libs
78 : "string",
79 : "table",
80 : "math",
81 : "os",
82 : "debug",
83 : NULL
84 : };
85 :
86 : char * luasandbox_allowed_os_members[] = {
87 : "date",
88 : "difftime",
89 : "time",
90 : NULL
91 : };
92 :
93 : char * luasandbox_allowed_debug_members[] = {
94 : "traceback",
95 : NULL
96 : };
97 :
98 :
99 :
100 : ZEND_EXTERN_MODULE_GLOBALS(luasandbox);
101 :
102 : /** {{{ luasandbox_lib_register
103 : */
104 126 : void luasandbox_lib_register(lua_State * L)
105 : {
106 : // Load the standard libraries that we need
107 126 : lua_pushcfunction(L, luaopen_base);
108 126 : lua_call(L, 0, 0);
109 126 : lua_pushcfunction(L, luaopen_table);
110 126 : lua_call(L, 0, 0);
111 126 : lua_pushcfunction(L, luaopen_math);
112 126 : lua_call(L, 0, 0);
113 126 : lua_pushcfunction(L, luaopen_debug);
114 126 : lua_call(L, 0, 0);
115 125 : lua_pushcfunction(L, luaopen_os);
116 125 : lua_call(L, 0, 0);
117 :
118 : // Install our own string library
119 125 : lua_pushcfunction(L, luasandbox_open_string);
120 125 : lua_call(L, 0, 0);
121 :
122 : // Filter the os library
123 125 : lua_getglobal(L, "os");
124 125 : luasandbox_lib_filter_table(L, luasandbox_allowed_os_members);
125 125 : lua_setglobal(L, "os");
126 :
127 : // Filter the debug library
128 125 : lua_getglobal(L, "debug");
129 125 : luasandbox_lib_filter_table(L, luasandbox_allowed_debug_members);
130 125 : lua_setglobal(L, "debug");
131 :
132 : // Remove any globals that aren't in a whitelist. This is mostly to remove
133 : // unsafe functions from the base library.
134 125 : lua_pushnil(L);
135 4500 : while (lua_next(L, LUA_GLOBALSINDEX) != 0) {
136 : const char * key;
137 : size_t key_len;
138 4375 : lua_pop(L, 1);
139 4375 : if (lua_type(L, -1) != LUA_TSTRING) {
140 0 : continue;
141 : }
142 4375 : key = lua_tolstring(L, -1, &key_len);
143 8750 : if (!zend_hash_str_exists(luasandbox_lib_get_allowed_globals(), key, key_len)) {
144 : // Not allowed, delete it
145 1500 : lua_pushnil(L);
146 1500 : lua_setglobal(L, key);
147 : }
148 : }
149 :
150 : // Install our own versions of tostring, pcall, xpcall
151 125 : lua_pushcfunction(L, luasandbox_base_tostring);
152 125 : lua_setglobal(L, "tostring");
153 125 : lua_pushcfunction(L, luasandbox_base_pcall);
154 125 : lua_setglobal(L, "pcall");
155 125 : lua_pushcfunction(L, luasandbox_base_xpcall);
156 125 : lua_setglobal(L, "xpcall");
157 :
158 : // Remove string.dump: may expose private data
159 125 : lua_getglobal(L, "string");
160 125 : lua_pushnil(L);
161 125 : lua_setfield(L, -2, "dump");
162 125 : lua_pop(L, 1);
163 :
164 : // Install our own versions of math.random and math.randomseed
165 125 : lua_getglobal(L, "math");
166 125 : lua_pushcfunction(L, luasandbox_math_random);
167 125 : lua_setfield(L, -2, "random");
168 125 : lua_pushcfunction(L, luasandbox_math_randomseed);
169 125 : lua_setfield(L, -2, "randomseed");
170 125 : lua_pop(L, 1);
171 :
172 : // Install our own version of os.clock(), which uses our high-resolution
173 : // usage timer
174 125 : lua_getglobal(L, "os");
175 125 : lua_pushcfunction(L, luasandbox_os_clock);
176 125 : lua_setfield(L, -2, "clock");
177 125 : lua_pop(L, 1);
178 :
179 : // Install our own versions of pairs and ipairs, if necessary
180 : #if LUA_VERSION_NUM < 502
181 125 : lua_getfield(L, LUA_GLOBALSINDEX, "pairs");
182 125 : lua_setfield(L, LUA_REGISTRYINDEX, "luasandbox_old_pairs");
183 125 : lua_getfield(L, LUA_GLOBALSINDEX, "ipairs");
184 125 : lua_setfield(L, LUA_REGISTRYINDEX, "luasandbox_old_ipairs");
185 125 : lua_pushcfunction(L, luasandbox_base_pairs);
186 125 : lua_setglobal(L, "pairs");
187 125 : lua_pushcfunction(L, luasandbox_base_ipairs);
188 125 : lua_setglobal(L, "ipairs");
189 : #endif
190 125 : }
191 : /* }}} */
192 :
193 : /** {{{ luasandbox_lib_filter_table
194 : *
195 : * Make a copy of the table at the top of the stack, and remove any members
196 : * from it that aren't in the given whitelist.
197 : */
198 250 : static void luasandbox_lib_filter_table(lua_State * L, char ** member_names)
199 : {
200 : int i, n;
201 250 : int si = lua_gettop(L);
202 750 : for (n = 0; member_names[n]; n++);
203 250 : lua_createtable(L, 0, n);
204 750 : for (i = 0; member_names[i]; i++) {
205 500 : lua_getfield(L, si, member_names[i]);
206 500 : lua_setfield(L, si+1, member_names[i]);
207 : }
208 250 : lua_replace(L, si);
209 250 : }
210 : /* }}} */
211 :
212 : /** {{{ luasandbox_lib_destroy_globals */
213 23 : void luasandbox_lib_destroy_globals()
214 : {
215 23 : if (LUASANDBOX_G(allowed_globals)) {
216 19 : zend_hash_destroy(LUASANDBOX_G(allowed_globals));
217 19 : FREE_HASHTABLE(LUASANDBOX_G(allowed_globals));
218 19 : LUASANDBOX_G(allowed_globals) = NULL;
219 : }
220 23 : }
221 : /* }}} */
222 :
223 : /** {{{ luasandbox_lib_get_allowed_globals
224 : *
225 : * Get a HashTable of allowed global variables
226 : */
227 4375 : static HashTable * luasandbox_lib_get_allowed_globals()
228 : {
229 : int i, n;
230 4375 : if (LUASANDBOX_G(allowed_globals)) {
231 4356 : return LUASANDBOX_G(allowed_globals);
232 : }
233 :
234 456 : for (n = 0; luasandbox_allowed_globals[n]; n++);
235 :
236 19 : ALLOC_HASHTABLE(LUASANDBOX_G(allowed_globals));
237 19 : zend_hash_init(LUASANDBOX_G(allowed_globals), n, NULL, NULL, 0);
238 :
239 : zval zv;
240 19 : ZVAL_TRUE(&zv);
241 :
242 456 : for (i = 0; luasandbox_allowed_globals[i]; i++) {
243 874 : zend_hash_str_update(LUASANDBOX_G(allowed_globals),
244 437 : luasandbox_allowed_globals[i], strlen(luasandbox_allowed_globals[i]), &zv);
245 : }
246 :
247 19 : return LUASANDBOX_G(allowed_globals);
248 : }
249 : /* }}} */
250 :
251 : /** {{{ luasandbox_base_tostring
252 : *
253 : * This is identical to luaB_tostring except that it does not call lua_topointer().
254 : */
255 0 : static int luasandbox_base_tostring(lua_State * L)
256 : {
257 0 : luaL_checkany(L, 1);
258 0 : if (luaL_callmeta(L, 1, "__tostring")) /* is there a metafield? */
259 0 : return 1; /* use its value */
260 0 : switch (lua_type(L, 1)) {
261 0 : case LUA_TNUMBER:
262 0 : lua_pushstring(L, lua_tostring(L, 1));
263 0 : break;
264 0 : case LUA_TSTRING:
265 0 : lua_pushvalue(L, 1);
266 0 : break;
267 0 : case LUA_TBOOLEAN:
268 0 : lua_pushstring(L, (lua_toboolean(L, 1) ? "true" : "false"));
269 0 : break;
270 0 : case LUA_TNIL:
271 0 : lua_pushliteral(L, "nil");
272 0 : break;
273 0 : default:
274 0 : lua_pushfstring(L, "%s", luaL_typename(L, 1));
275 0 : break;
276 : }
277 0 : return 1;
278 : }
279 : /* }}} */
280 :
281 : /** {{{ luasandbox_math_random
282 : *
283 : * A math.random implementation that doesn't share state with PHP's rand()
284 : */
285 0 : static int luasandbox_math_random(lua_State * L)
286 : {
287 0 : php_luasandbox_obj * sandbox = luasandbox_get_php_obj(L);
288 :
289 : #ifdef PHP_WIN32
290 : // MSVC does not provide rand_r(). Note that srand/rand still does not
291 : // share state with PHP's rand() because PHP's rand() is an alias for mt_rand()
292 : // (and does not use C srand/rand) as of PHP 7.1.
293 : int i = rand();
294 : #else
295 0 : int i = rand_r(&sandbox->random_seed);
296 : #endif
297 :
298 0 : if (i >= RAND_MAX) {
299 0 : i -= RAND_MAX;
300 : }
301 0 : lua_Number r = (lua_Number)i / (lua_Number)RAND_MAX;
302 0 : switch (lua_gettop(L)) { /* check number of arguments */
303 0 : case 0: { /* no arguments */
304 0 : lua_pushnumber(L, r); /* Number between 0 and 1 */
305 0 : break;
306 : }
307 0 : case 1: { /* only upper limit */
308 0 : int u = luaL_checkint(L, 1);
309 0 : luaL_argcheck(L, 1<=u, 1, "interval is empty");
310 0 : lua_pushnumber(L, floor(r*u)+1); /* int between 1 and `u' */
311 0 : break;
312 : }
313 0 : case 2: { /* lower and upper limits */
314 0 : int l = luaL_checkint(L, 1);
315 0 : int u = luaL_checkint(L, 2);
316 0 : luaL_argcheck(L, l<=u, 2, "interval is empty");
317 0 : lua_pushnumber(L, floor(r*(u-l+1))+l); /* int between `l' and `u' */
318 0 : break;
319 : }
320 0 : default: return luaL_error(L, "wrong number of arguments");
321 : }
322 0 : return 1;
323 : }
324 : /* }}} */
325 :
326 : /** {{{ luasandbox_math_randomseed
327 : *
328 : * Set the seed for the custom math.random.
329 : */
330 0 : static int luasandbox_math_randomseed(lua_State * L)
331 : {
332 0 : php_luasandbox_obj * sandbox = luasandbox_get_php_obj(L);
333 0 : sandbox->random_seed = (unsigned int)luaL_checkint(L, 1);
334 : #ifdef PHP_WIN32
335 : srand(sandbox->random_seed);
336 : #endif
337 0 : return 0;
338 : }
339 : /* }}} */
340 :
341 : /** {{{ luasandbox_lib_rethrow_fatal
342 : *
343 : * If the error on the top of the stack with the error return code given as a
344 : * parameter is a fatal, rethrow the error. If the error is rethrown, the
345 : * function does not return.
346 : */
347 26 : static void luasandbox_lib_rethrow_fatal(lua_State * L, int status)
348 : {
349 26 : switch (status) {
350 2 : case 0:
351 : // No error
352 2 : return;
353 15 : case LUA_ERRRUN:
354 : // A runtime error which we can rethrow in a normal way
355 15 : if (luasandbox_is_fatal(L, -1)) {
356 10 : lua_error(L);
357 : }
358 5 : break;
359 9 : case LUA_ERRMEM:
360 : case LUA_ERRERR:
361 : // Lua doesn't provide a public API for rethrowing these, so we
362 : // have to convert them to our own fatal error type
363 9 : if (!luasandbox_is_fatal(L, -1)) {
364 8 : luasandbox_wrap_fatal(L);
365 : }
366 9 : lua_error(L);
367 0 : break; // not reached
368 : }
369 : }
370 : /* }}} */
371 :
372 : /** {{{ luasandbox_lib_error_wrapper
373 : *
374 : * Wrapper for the xpcall error function
375 : */
376 15 : static int luasandbox_lib_error_wrapper(lua_State * L)
377 : {
378 : int status;
379 15 : luaL_checkany(L, 1);
380 :
381 : // This function is only called from luaG_errormsg(), which will later
382 : // unconditionally set the status code to LUA_ERRRUN, so we can assume
383 : // that the error type is equivalent to LUA_ERRRUN.
384 15 : if (luasandbox_is_fatal(L, 1)) {
385 : // Just return to whatever called lua_pcall(), don't call the user
386 : // function
387 7 : return lua_gettop(L);
388 : }
389 :
390 : // Put the user error function at the bottom of the stack
391 8 : lua_pushvalue(L, lua_upvalueindex(1));
392 8 : lua_insert(L, 1);
393 : // Call it, passing through the arguments to this function
394 8 : status = lua_pcall(L, lua_gettop(L) - 1, LUA_MULTRET, 0);
395 8 : if (status != 0) {
396 6 : luasandbox_lib_rethrow_fatal(L, LUA_ERRERR);
397 : }
398 2 : return lua_gettop(L);
399 : }
400 : /* }}} */
401 :
402 : /** {{{ luasandbox_base_pcall
403 : *
404 : * This is our implementation of the Lua function pcall(). It allows Lua code
405 : * to handle its own errors, but forces internal errors to be rethrown.
406 : */
407 9 : static int luasandbox_base_pcall(lua_State * L)
408 : {
409 : int status;
410 9 : luaL_checkany(L, 1);
411 9 : status = lua_pcall(L, lua_gettop(L) - 1, LUA_MULTRET, 0);
412 9 : luasandbox_lib_rethrow_fatal(L, status);
413 4 : lua_pushboolean(L, (status == 0));
414 4 : lua_insert(L, 1);
415 4 : return lua_gettop(L); // return status + all results
416 : }
417 : /* }}} */
418 :
419 : /** {{{ luasandbox_base_xpcall
420 : *
421 : * This is our implementation of the Lua function xpcall(). It allows Lua code
422 : * to handle its own errors, but forces internal errors to be rethrown.
423 : */
424 11 : static int luasandbox_base_xpcall (lua_State *L)
425 : {
426 : int status;
427 11 : luaL_checkany(L, 2);
428 11 : lua_settop(L, 2);
429 :
430 : // We wrap the error function in a C closure. The error function already
431 : // happens to be at the top of the stack, so we don't need to push it before
432 : // calling lua_pushcfunction to make it an upvalue
433 11 : lua_pushcclosure(L, luasandbox_lib_error_wrapper, 1);
434 11 : lua_insert(L, 1); // put error function under function to be called
435 :
436 11 : status = lua_pcall(L, 0, LUA_MULTRET, 1);
437 11 : luasandbox_lib_rethrow_fatal(L, status);
438 3 : lua_pushboolean(L, (status == 0));
439 3 : lua_replace(L, 1);
440 3 : return lua_gettop(L); // return status + all results
441 : }
442 : /* }}} */
443 :
444 : /** {{{ luasandbox_os_clock
445 : *
446 : * Implementation of os.clock() which uses our high-resolution usage timer,
447 : * if available, to provide an accurate measure of Lua CPU usage since a
448 : * particular LuaSandbox object was created.
449 : */
450 1386474 : static int luasandbox_os_clock(lua_State * L)
451 : {
452 : double clk;
453 :
454 : #ifdef LUASANDBOX_NO_CLOCK
455 : clk = ((double)clock())/(double)CLOCKS_PER_SEC;
456 : #else
457 : struct timespec ts;
458 1386474 : php_luasandbox_obj * sandbox = luasandbox_get_php_obj(L);
459 1386474 : luasandbox_timer_get_usage(&sandbox->timer, &ts);
460 1386474 : clk = ts.tv_sec + 1e-9 * ts.tv_nsec;
461 : #endif
462 :
463 : // Reduce precision to 20μs to mitigate timing attacks
464 1386474 : clk = round(clk * 50000) / 50000;
465 :
466 1386474 : lua_pushnumber(L, (lua_Number)clk);
467 1386474 : return 1;
468 : }
469 :
470 : /* }}} */
471 :
472 : #if LUA_VERSION_NUM < 502
473 : /** {{{ luasandbox_base_pairs
474 : *
475 : * This is our implementation of the Lua function pairs(). It allows the Lua
476 : * 5.2 __pairs metamethod to override the standard behavior.
477 : */
478 8 : static int luasandbox_base_pairs (lua_State *L)
479 : {
480 8 : if (!luaL_getmetafield(L, 1, "__pairs")) {
481 5 : luaL_checktype(L, 1, LUA_TTABLE);
482 4 : lua_getfield(L, LUA_REGISTRYINDEX, "luasandbox_old_pairs");
483 : }
484 7 : lua_pushvalue(L, 1);
485 7 : lua_call(L, 1, 3);
486 6 : return 3;
487 : }
488 : /* }}} */
489 :
490 : /** {{{ luasandbox_base_ipairs
491 : *
492 : * This is our implementation of the Lua function ipairs(). It allows the Lua
493 : * 5.2 __ipairs metamethod to override the standard behavior.
494 : */
495 3 : static int luasandbox_base_ipairs (lua_State *L)
496 : {
497 3 : if (!luaL_getmetafield(L, 1, "__ipairs")) {
498 2 : luaL_checktype(L, 1, LUA_TTABLE);
499 1 : lua_getfield(L, LUA_REGISTRYINDEX, "luasandbox_old_ipairs");
500 : }
501 2 : lua_pushvalue(L, 1);
502 2 : lua_call(L, 1, 3);
503 2 : return 3;
504 : }
505 : /* }}} */
506 : #endif
|