LCOV - code coverage report
Current view: top level - src - data_conversion.c (source / functions) Hit Total Coverage
Test: mediawiki/php/luasandbox test coverage report Lines: 304 321 94.7 %
Date: 2023-12-12 07:35:14 Functions: 18 18 100.0 %

          Line data    Source code
       1             : 
       2             : #ifdef HAVE_CONFIG_H
       3             : #include "config.h"
       4             : #endif
       5             : 
       6             : #include <lua.h>
       7             : #include <lauxlib.h>
       8             : #include <limits.h>
       9             : #include <float.h>
      10             : #include <math.h>
      11             : #include <inttypes.h>
      12             : 
      13             : #include "php.h"
      14             : #include "php_luasandbox.h"
      15             : #include "zend_exceptions.h"
      16             : 
      17             : #include "luasandbox_compat.h"
      18             : 
      19             : static void luasandbox_throw_runtimeerror(lua_State * L, zval * sandbox_zval, const char *message);
      20             : 
      21             : static inline int luasandbox_protect_recursion(zval * z, HashTable ** recursionGuard, int * allocated);
      22             : static inline void luasandbox_unprotect_recursion(zval * z, HashTable * recursionGuard, int allocated);
      23             : 
      24             : static int luasandbox_lua_to_array(HashTable *ht, lua_State *L, int index,
      25             :     zval * sandbox_zval, HashTable * recursionGuard);
      26             : static int luasandbox_lua_pair_to_array(HashTable *ht, lua_State *L,
      27             :     zval * sandbox_zval, HashTable * recursionGuard);
      28             : static int luasandbox_free_zval_userdata(lua_State * L);
      29             : static int luasandbox_push_hashtable(lua_State * L, HashTable * ht, HashTable * recursionGuard);
      30             : static int luasandbox_has_error_marker(lua_State * L, int index, void * marker);
      31             : 
      32             : extern zend_class_entry *luasandboxfunction_ce;
      33             : extern zend_class_entry *luasandboxruntimeerror_ce;
      34             : 
      35             : /**
      36             :  * An int, the address of which is used as a fatal error marker. The value is
      37             :  * not used.
      38             :  */
      39             : int luasandbox_fatal_error_marker = 0;
      40             : 
      41             : /**
      42             :  * Same as luasandbox_fatal_error_marker but for trace errors
      43             :  */
      44             : int luasandbox_trace_error_marker = 0;
      45             : 
      46             : /** {{{ luasandbox_data_conversion_init
      47             :  *
      48             :  * Set up a lua_State so that this module can work with it.
      49             :  */
      50         125 : void luasandbox_data_conversion_init(lua_State * L)
      51             : {
      52             :     // Create the metatable for zval destruction
      53         125 :     lua_createtable(L, 0, 1);
      54         125 :     lua_pushcfunction(L, luasandbox_free_zval_userdata);
      55         125 :     lua_setfield(L, -2, "__gc");
      56         125 :     lua_setfield(L, LUA_REGISTRYINDEX, "php_luasandbox_zval_metatable");
      57         125 : }
      58             : /* }}} */
      59             : 
      60             : /** {{{ luasandbox_push_zval
      61             :  *
      62             :  * Convert a zval to an appropriate Lua type and push the resulting value on to
      63             :  * the stack.
      64             :  */
      65        1826 : int luasandbox_push_zval(lua_State * L, zval * z, HashTable * recursionGuard)
      66             : {
      67        1826 :     switch (Z_TYPE_P(z)) {
      68             : #ifdef IS_UNDEF
      69           1 :         case IS_UNDEF: // Close enough to IS_NULL
      70             : #endif
      71             :         case IS_NULL:
      72           1 :             lua_pushnil(L);
      73           1 :             break;
      74         223 :         case IS_LONG:
      75         223 :             lua_pushinteger(L, Z_LVAL_P(z));
      76         223 :             break;
      77           3 :         case IS_DOUBLE:
      78           3 :             lua_pushnumber(L, Z_DVAL_P(z));
      79           3 :             break;
      80             : #ifdef IS_BOOL
      81             :         case IS_BOOL:
      82             :             lua_pushboolean(L, Z_BVAL_P(z));
      83             :             break;
      84             : #endif
      85             : #ifdef IS_TRUE
      86           1 :         case IS_TRUE:
      87           1 :             lua_pushboolean(L, 1);
      88           1 :             break;
      89           1 :         case IS_FALSE:
      90           1 :             lua_pushboolean(L, 0);
      91           1 :             break;
      92             : #endif
      93         509 :         case IS_ARRAY: {
      94         509 :             int ret, allocated = 0;
      95         509 :             if (!luasandbox_protect_recursion(z, &recursionGuard, &allocated)) {
      96           0 :                 return 0;
      97             :             }
      98         509 :             ret = luasandbox_push_hashtable(L, Z_ARRVAL_P(z), recursionGuard);
      99         509 :             luasandbox_unprotect_recursion(z, recursionGuard, allocated);
     100         509 :             return ret;
     101             :         }
     102          45 :         case IS_OBJECT: {
     103             :             zend_class_entry * objce;
     104             : 
     105          45 :             objce = Z_OBJCE_P(z);
     106          45 :             if (instanceof_function(objce, luasandboxfunction_ce)) {
     107             :                 php_luasandboxfunction_obj * func_obj;
     108             : 
     109          43 :                 func_obj = GET_LUASANDBOXFUNCTION_OBJ(z);
     110             : 
     111          43 :                 lua_getfield(L, LUA_REGISTRYINDEX, "php_luasandbox_chunks");
     112          43 :                 lua_rawgeti(L, -1, func_obj->index);
     113          43 :                 lua_remove(L, -2);
     114          43 :                 break;
     115             :             }
     116             : 
     117           2 :             php_error_docref(NULL, E_WARNING, "Unable to convert object of type %s",
     118           2 :                 ZSTR_VAL(objce->name)
     119             :             );
     120             : 
     121           2 :             return 0;
     122             :         }
     123        1038 :         case IS_STRING:
     124        1038 :             lua_pushlstring(L, Z_STRVAL_P(z), Z_STRLEN_P(z));
     125        1036 :             break;
     126             : #ifdef IS_REFERENCE
     127           5 :         case IS_REFERENCE: {
     128           5 :             int ret, allocated = 0;
     129           5 :             if (!luasandbox_protect_recursion(z, &recursionGuard, &allocated)) {
     130           2 :                 return 0;
     131             :             }
     132           3 :             ret = luasandbox_push_zval(L, Z_REFVAL_P(z), recursionGuard);
     133           3 :             luasandbox_unprotect_recursion(z, recursionGuard, allocated);
     134           3 :             return ret;
     135             :         }
     136             : #endif
     137             : 
     138           0 :         case IS_RESOURCE:
     139             :         default:
     140           0 :             return 0;
     141             :     }
     142        1308 :     return 1;
     143             : }
     144             : /* }}} */
     145             : 
     146             : /** {{{ luasandbox_free_zval_userdata
     147             :  *
     148             :  * Free a zval given to Lua by luasandbox_push_zval_userdata.
     149             :  */
     150         125 : static int luasandbox_free_zval_userdata(lua_State * L)
     151             : {
     152         125 :     zval * ud = (zval*)lua_touserdata(L, 1);
     153         125 :     php_luasandbox_obj * intern = luasandbox_get_php_obj(L);
     154             : 
     155             :     // Don't abort if the request has timed out, we need to be able to clean up
     156         125 :     luasandbox_enter_php_ignore_timeouts(L, intern);
     157             : 
     158         250 :     if (ud && !Z_ISUNDEF_P(ud)) {
     159         125 :         zval_ptr_dtor(ud);
     160         125 :         ZVAL_UNDEF(ud);
     161             :     }
     162             : 
     163         125 :     luasandbox_leave_php(L, intern);
     164         125 :     return 0;
     165             : }
     166             : /* }}} */
     167             : 
     168             : /** {{{ luasandbox_push_zval_userdata
     169             :  *
     170             :  * Push a full userdata on to the stack, which stores a zval* in its block.
     171             :  * Increment its reference count and set its metatable so that it will be freed
     172             :  * at the appropriate time.
     173             :  */
     174         125 : void luasandbox_push_zval_userdata(lua_State * L, zval * z)
     175             : {
     176         125 :     zval * ud = (zval*)lua_newuserdata(L, sizeof(zval));
     177         125 :     ZVAL_COPY(ud, z);
     178             : 
     179         125 :     lua_getfield(L, LUA_REGISTRYINDEX, "php_luasandbox_zval_metatable");
     180         125 :     lua_setmetatable(L, -2);
     181         125 : }
     182             : /* }}} */
     183             : 
     184             : /** {{{ luasandbox_push_hashtable
     185             :  *
     186             :  * Helper function for luasandbox_push_zval. Create a new table on the top of
     187             :  * the stack and add the zvals in the HashTable to it.
     188             :  */
     189         509 : static int luasandbox_push_hashtable(lua_State * L, HashTable * ht, HashTable * recursionGuard)
     190             : {
     191             : #if SIZEOF_LONG > 4
     192             :     char buffer[MAX_LENGTH_OF_LONG + 1];
     193             : #endif
     194             : 
     195             :     // Recursion requires an arbitrary amount of stack space so we have to
     196             :     // check the stack.
     197         509 :     luaL_checkstack(L, 10, "converting PHP array to Lua");
     198             : 
     199         509 :     lua_newtable(L);
     200         509 :     if (!ht || !zend_hash_num_elements(ht)) {
     201           0 :         return 1;
     202             :     }
     203             : 
     204             :     zend_ulong lkey;
     205             :     zend_string *key;
     206             :     zval *value;
     207        1537 :     ZEND_HASH_FOREACH_KEY_VAL(ht, lkey, key, value)
     208             :     {
     209             :         // Lua doesn't represent most integers with absolute value over 2**53,
     210             :         // so stringify them.
     211             : #if SIZEOF_LONG > 4
     212         516 :         if (!key &&
     213         511 :                 ((int64_t)lkey > INT64_C(9007199254740992) || (int64_t)lkey < INT64_C(-9007199254740992))
     214           2 :         ) {
     215           2 :             size_t len = snprintf(buffer, sizeof(buffer), "%" PRId64, (int64_t)lkey);
     216           2 :             lua_pushlstring(L, buffer, len);
     217             :         } else
     218             : #endif
     219         514 :         if (key) {
     220           5 :             lua_pushlstring(L, ZSTR_VAL(key), ZSTR_LEN(key));
     221             :         } else {
     222         509 :             lua_pushinteger(L, lkey);
     223             :         }
     224             : 
     225         516 :         if (!luasandbox_push_zval(L, value, recursionGuard)) {
     226             :             // Failed to process that data value
     227             :             // Pop the key and the half-constructed table
     228           4 :             lua_pop(L, 2);
     229           4 :             return 0;
     230             :         }
     231             : 
     232         512 :         lua_settable(L, -3);
     233             :     } ZEND_HASH_FOREACH_END();
     234             : 
     235         505 :     return 1;
     236             : }
     237             : /* }}} */
     238             : 
     239             : /** {{{ luasandbox_lua_to_zval
     240             :  *
     241             :  * Convert a lua value to a zval.
     242             :  *
     243             :  * If a value is encountered that can't be converted to a zval, an exception is
     244             :  * thrown.
     245             :  *
     246             :  * @param z A pointer to the destination zval
     247             :  * @param L The lua state
     248             :  * @param index The stack index to the input value
     249             :  * @param sandbox_zval A zval poiting to a valid LuaSandbox object which will be
     250             :  *     used for the parent object of any LuaSandboxFunction objects created.
     251             :  * @param recursionGuard A hashtable for keeping track of tables that have been
     252             :  *     processed, to allow infinite recursion to be avoided. External callers
     253             :  *     should set this to NULL.
     254             :  * @return int 0 (and a PHP exception) on failure
     255             :  */
     256      190502 : int luasandbox_lua_to_zval(zval * z, lua_State * L, int index,
     257             :     zval * sandbox_zval, HashTable * recursionGuard)
     258             : {
     259      190502 :     switch (lua_type(L, index)) {
     260           1 :         case LUA_TNIL:
     261           1 :             ZVAL_NULL(z);
     262           1 :             break;
     263       60698 :         case LUA_TNUMBER: {
     264             :             long i;
     265             :             double d, integerPart, fractionalPart;
     266             :             // Lua only provides a single number type
     267             :             // Convert it to a PHP integer if that can be done without loss
     268             :             // of precision
     269       60698 :             d = lua_tonumber(L, index);
     270       60698 :             fractionalPart = modf(d, &integerPart);
     271       60698 :             if (fractionalPart == 0.0 && integerPart >= LONG_MIN && integerPart <= LONG_MAX) {
     272             :                 // The number is small enough to fit inside an int. But has it already
     273             :                 // been truncated by squeezing it into a double? This is only relevant
     274             :                 // where the integer size is greater than the mantissa size.
     275       60695 :                 i = (long)integerPart;
     276      121390 :                 if (LONG_MAX < (1LL << DBL_MANT_DIG)
     277       60695 :                     || labs(i) < (1LL << DBL_MANT_DIG))
     278             :                 {
     279       60695 :                     ZVAL_LONG(z, i);
     280             :                 } else {
     281           0 :                     ZVAL_DOUBLE(z, d);
     282             :                 }
     283             :             } else {
     284           3 :                 ZVAL_DOUBLE(z, d);
     285             :             }
     286       60698 :             break;
     287             :         }
     288           2 :         case LUA_TBOOLEAN:
     289           2 :             ZVAL_BOOL(z, lua_toboolean(L, index));
     290           2 :             break;
     291       99163 :         case LUA_TSTRING: {
     292             :             const char * str;
     293             :             size_t length;
     294       99163 :             str = lua_tolstring(L, index, &length);
     295       99163 :             ZVAL_STRINGL(z, str, length);
     296       99163 :             break;
     297             :         }
     298       30474 :         case LUA_TTABLE: {
     299       30474 :             const void * ptr = lua_topointer(L, index);
     300       30474 :             int allocated = 0;
     301       30474 :             int success = 1;
     302       30474 :             if (recursionGuard) {
     303             :                 // Check for circular reference (infinite recursion)
     304       30234 :                 if (zend_hash_str_exists(recursionGuard, (char*)&ptr, sizeof(void*))) {
     305             :                     // Found circular reference!
     306           4 :                     luasandbox_throw_runtimeerror(L, sandbox_zval, "Cannot pass circular reference to PHP");
     307             : 
     308           4 :                     ZVAL_NULL(z); // Need to set something to prevent a segfault
     309          16 :                     return 0;
     310             :                 }
     311             :             } else {
     312         240 :                 ALLOC_HASHTABLE(recursionGuard);
     313         240 :                 zend_hash_init(recursionGuard, 1, NULL, NULL, 0);
     314         240 :                 allocated = 1;
     315             :             }
     316             : 
     317             :             // Add the current table to the recursion guard hashtable
     318             :             // Use the pointer as the key, zero-length data
     319             :             zval zv;
     320       30470 :             ZVAL_TRUE(&zv);
     321       30470 :             zend_hash_str_update(recursionGuard, (char*)&ptr, sizeof(void*), &zv);
     322             : 
     323             :             // Process the array
     324       30470 :             array_init(z);
     325       30470 :             success = luasandbox_lua_to_array(Z_ARRVAL_P(z), L, index, sandbox_zval, recursionGuard);
     326             : 
     327       30470 :             if (allocated) {
     328         240 :                 zend_hash_destroy(recursionGuard);
     329         240 :                 FREE_HASHTABLE(recursionGuard);
     330             :             }
     331             : 
     332       30470 :             if (!success) {
     333             :                 // free the array created by array_init() above.
     334             :                 zval_dtor(z);
     335          12 :                 ZVAL_NULL(z);
     336          12 :                 return 0;
     337             :             }
     338       30458 :             break;
     339             :         }
     340         164 :         case LUA_TFUNCTION: {
     341             :             int func_index;
     342             :             php_luasandboxfunction_obj * func_obj;
     343         164 :             php_luasandbox_obj * sandbox = GET_LUASANDBOX_OBJ(sandbox_zval);
     344             : 
     345             :             // Normalise the input index so that we can push without invalidating it.
     346         164 :             if (index < 0) {
     347           0 :                 index += lua_gettop(L) + 1;
     348             :             }
     349             : 
     350             :             // Get the chunks table
     351         164 :             lua_getfield(L, LUA_REGISTRYINDEX, "php_luasandbox_chunks");
     352             : 
     353             :             // Get the next free index
     354         164 :             if (sandbox->function_index >= INT_MAX) {
     355           0 :                 ZVAL_NULL(z);
     356           0 :                 lua_pop(L, 1);
     357           0 :                 break;
     358             :             }
     359         164 :             func_index = ++(sandbox->function_index);
     360             : 
     361             :             // Store it in the chunks table
     362         164 :             lua_pushvalue(L, index);
     363         164 :             lua_rawseti(L, -2, func_index);
     364             : 
     365             :             // Create a LuaSandboxFunction object to hold a reference to the function
     366         164 :             object_init_ex(z, luasandboxfunction_ce);
     367         164 :             func_obj = GET_LUASANDBOXFUNCTION_OBJ(z);
     368         164 :             func_obj->index = func_index;
     369         164 :             ZVAL_COPY(&func_obj->sandbox, sandbox_zval);
     370             : 
     371             :             // Balance the stack
     372         164 :             lua_pop(L, 1);
     373         164 :             break;
     374             :         }
     375           0 :         case LUA_TUSERDATA:
     376             :         case LUA_TTHREAD:
     377             :         case LUA_TLIGHTUSERDATA:
     378             :             // TODO: provide derived classes for each type
     379             :         default: {
     380             :             char *message;
     381           0 :             spprintf(&message, 0, "Cannot pass %s to PHP", lua_typename(L, lua_type(L, index)));
     382           0 :             luasandbox_throw_runtimeerror(L, sandbox_zval, message);
     383           0 :             efree(message);
     384             : 
     385           0 :             ZVAL_NULL(z); // Need to set something to prevent a segfault
     386           0 :             return 0;
     387             :         }
     388             :     }
     389      190486 :     return 1;
     390             : }
     391             : /* }}} */
     392             : 
     393             : /** {{{ luasandbox_lua_to_array
     394             :  *
     395             :  * Append the elements of the table in the specified index to the given HashTable.
     396             :  */
     397       30470 : static int luasandbox_lua_to_array(HashTable *ht, lua_State *L, int index,
     398             :     zval * sandbox_zval, HashTable * recursionGuard)
     399             : {
     400             :     php_luasandbox_obj * sandbox;
     401       30470 :     int top = lua_gettop(L);
     402             : 
     403             :     // Recursion requires an arbitrary amount of stack space so we have to
     404             :     // check the stack.
     405       30470 :     luaL_checkstack(L, 15, "converting Lua table to PHP");
     406             : 
     407             :     // Normalise the input index so that we can push without invalidating it.
     408       30470 :     if (index < 0) {
     409       30240 :         index += top + 1;
     410             :     }
     411             : 
     412             :     // If the input table has a __pairs function, we need to use that instead
     413             :     // of lua_next.
     414       30470 :     if (luaL_getmetafield(L, index, "__pairs")) {
     415           3 :         sandbox = luasandbox_get_php_obj(L);
     416             : 
     417             :         // Put the error handler function onto the stack
     418           3 :         lua_pushcfunction(L, luasandbox_attach_trace);
     419           3 :         lua_insert(L, top + 1);
     420             : 
     421             :         // Call __pairs
     422           3 :         lua_pushvalue(L, index);
     423           3 :         if (!luasandbox_call_lua(sandbox, sandbox_zval, 1, 3, top + 1)) {
     424             :             // Failed to call __pairs. Cleanup stack and return failure.
     425           1 :             lua_settop(L, top);
     426           1 :             return 0;
     427             :         }
     428             :         while (1) {
     429             :             // We need to copy static-data and func so we can reuse them for
     430             :             // each iteration.
     431           4 :             lua_pushvalue(L, -3);
     432           4 :             lua_insert(L, -2);
     433           4 :             lua_pushvalue(L, -3);
     434           4 :             lua_insert(L, -2);
     435             : 
     436             :             // Call the custom 'next' function from __pairs
     437           4 :             if (!luasandbox_call_lua(sandbox, sandbox_zval, 2, 2, top + 1)) {
     438             :                 // Failed. Cleanup stack and return failure.
     439           1 :                 lua_settop(L, top);
     440           1 :                 return 0;
     441             :             }
     442             : 
     443           3 :             if (lua_isnil(L, -2)) {
     444             :                 // Nil key == end. Cleanup stack and exit loop.
     445           1 :                 lua_settop(L, top);
     446           1 :                 break;
     447             :             }
     448           2 :             if (!luasandbox_lua_pair_to_array(ht, L, sandbox_zval, recursionGuard)) {
     449             :                 // Failed to convert value. Cleanup stack and return failure.
     450           0 :                 lua_settop(L, top);
     451           0 :                 return 0;
     452             :             }
     453             :         }
     454             :     } else {
     455             :         // No __pairs, we can use lua_next
     456       30467 :         lua_pushnil(L);
     457      219290 :         while (lua_next(L, index) != 0) {
     458      188833 :             if (!luasandbox_lua_pair_to_array(ht, L, sandbox_zval, recursionGuard)) {
     459             :                 // Failed to convert value. Cleanup stack and return failure.
     460          10 :                 lua_settop(L, top);
     461          10 :                 return 0;
     462             :             }
     463             :         }
     464             :     }
     465       30458 :     return 1;
     466             : }
     467             : /* }}} */
     468             : 
     469             : /** {{{ luasandbox_lua_pair_to_array
     470             :  *
     471             :  * Take the lua key-value pair at the top of the Lua stack and add it to the given HashTable.
     472             :  * On success the value is popped, but the key remains on the stack.
     473             :  */
     474      188835 : static int luasandbox_lua_pair_to_array(HashTable *ht, lua_State *L,
     475             :     zval * sandbox_zval, HashTable * recursionGuard)
     476             : {
     477             :     const char * str;
     478             :     size_t length;
     479             :     lua_Number n;
     480             :     zend_ulong zn;
     481             : 
     482      188835 :     zval value, *valp = &value;
     483      188835 :     ZVAL_NULL(&value);
     484             : 
     485             :     // Convert value, then remove it
     486      188835 :     if (!luasandbox_lua_to_zval(valp, L, -1, sandbox_zval, recursionGuard)) {
     487           4 :         zval_ptr_dtor(&value);
     488           4 :         return 0;
     489             :     }
     490      188831 :     lua_pop(L, 1);
     491             : 
     492             :     // Convert key, but leave it there
     493      188831 :     if (lua_type(L, -1) == LUA_TNUMBER) {
     494       30243 :         n = lua_tonumber(L, -1);
     495       30243 :         if (isfinite(n) && n == floor(n) && n >= LONG_MIN && n <= LONG_MAX) {
     496       30239 :             zn = (long)n;
     497       30239 :             goto add_int_key;
     498             :         }
     499             :     }
     500             : 
     501             :     // Make a copy of the key so that we can call lua_tolstring() which is destructive
     502      158592 :     lua_pushvalue(L, -1);
     503      158592 :     str = lua_tolstring(L, -1, &length);
     504      158592 :     if ( str == NULL ) {
     505             :         // Only strings and integers may be used as keys
     506             :         char *message;
     507           3 :         spprintf(&message, 0, "Cannot use %s as an array key when passing data from Lua to PHP",
     508             :             lua_typename(L, lua_type(L, -2))
     509             :         );
     510           3 :         zval_ptr_dtor(&value);
     511           3 :         luasandbox_throw_runtimeerror(L, sandbox_zval, message);
     512           3 :         efree(message);
     513           3 :         return 0;
     514             :     }
     515      158589 :     lua_pop(L, 1);
     516             : 
     517             :     // See if the string is convertable to a number
     518      317178 :     if (ZEND_HANDLE_NUMERIC_STR(str, length, zn)) {
     519           6 :         goto add_int_key;
     520             :     }
     521             : 
     522             :     // Nope, use it as a string
     523      317166 :     if (zend_hash_str_exists(ht, str, length)) {
     524             :         // Collision, probably the key is an integer-like string
     525             :         char *message;
     526           2 :         spprintf(&message, 0, "Collision for array key %s when passing data from Lua to PHP", str );
     527           2 :         zval_ptr_dtor(&value);
     528           2 :         luasandbox_throw_runtimeerror(L, sandbox_zval, message);
     529           2 :         efree(message);
     530           2 :         return 0;
     531             :     }
     532      158581 :     zend_hash_str_update(ht, str, length, valp);
     533             : 
     534      158581 :     return 1;
     535             : 
     536       30245 : add_int_key:
     537       60490 :     if (zend_hash_index_exists(ht, zn)) {
     538             :         // Collision, probably with a integer-like string
     539             :         char *message;
     540           1 :         spprintf(&message, 0, "Collision for array key %" PRId64 " when passing data from Lua to PHP",
     541             :             (int64_t)zn
     542             :         );
     543           1 :         zval_ptr_dtor(&value);
     544           1 :         luasandbox_throw_runtimeerror(L, sandbox_zval, message);
     545           1 :         efree(message);
     546           1 :         return 0;
     547             :     }
     548       30244 :     zend_hash_index_update(ht, zn, valp);
     549             : 
     550       30244 :     return 1;
     551             : }
     552             : /* }}} */
     553             : 
     554             : /** {{{ luasandbox_wrap_fatal
     555             :  *
     556             :  * Pop a value off the top of the stack, and push a fatal error wrapper
     557             :  * containing the value.
     558             :  */
     559          37 : void luasandbox_wrap_fatal(lua_State * L)
     560             : {
     561             :     // Create the table and put the marker in it as element 1
     562          37 :     lua_createtable(L, 0, 2);
     563          37 :     lua_pushlightuserdata(L, &luasandbox_fatal_error_marker);
     564          37 :     lua_rawseti(L, -2, 1);
     565             : 
     566             :     // Swap the table with the input value, so that the value is on the top,
     567             :     // then put the value in the table as element 2
     568          37 :     lua_insert(L, -2);
     569          37 :     lua_rawseti(L, -2, 2);
     570          37 : }
     571             : /* }}} */
     572             : 
     573             : /** {{{ luasandbox_is_fatal
     574             :  *
     575             :  * Check if the value at the given stack index is a fatal error wrapper
     576             :  * created by luasandbox_wrap_fatal(). Return 1 if it is, 0 otherwise.
     577             :  *
     578             :  * This function cannot raise Lua errors.
     579             :  */
     580         751 : int luasandbox_is_fatal(lua_State * L, int index)
     581             : {
     582         751 :     return luasandbox_has_error_marker(L, index, &luasandbox_fatal_error_marker);
     583             : }
     584             : /* }}} */
     585             : 
     586             : /** {{{ luasandbox_is_trace_error
     587             :  *
     588             :  * Check if the value at the given stack index is an error wrapper created by
     589             :  * luasandbox_attach_trace(). Return 1 if it is, 0 otherwise.
     590             :  *
     591             :  * This function cannot raise Lua errors.
     592             :  */
     593         450 : int luasandbox_is_trace_error(lua_State * L, int index)
     594             : {
     595         450 :     return luasandbox_has_error_marker(L, index, &luasandbox_trace_error_marker);
     596             : }
     597             : /* }}} */
     598             : 
     599             : /** {{{
     600             :  *
     601             :  * Check if the error at the given stack index has a given marker userdata
     602             :  */
     603        1201 : static int luasandbox_has_error_marker(lua_State * L, int index, void * marker)
     604             : {
     605             :     void * ud;
     606        1201 :     if (!lua_istable(L, index)) {
     607         247 :         return 0;
     608             :     }
     609         954 :     lua_rawgeti(L, index, 1);
     610         954 :     ud = lua_touserdata(L, -1);
     611         954 :     lua_pop(L, 1);
     612         954 :     return ud == marker;
     613             : }
     614             : /* }}} */
     615             : 
     616             : /** {{{
     617             :  *
     618             :  * If the value at the given stack index is a fatal error wrapper, convert
     619             :  * the error object it wraps to a string. If the value is anything else,
     620             :  * convert it directly to a string. If the error object is not convertible
     621             :  * to a string, return "unknown error".
     622             :  *
     623             :  * This calls lua_tolstring() and will corrupt the value on the stack as
     624             :  * described in that function's documentation. The string is valid until the
     625             :  * Lua value is destroyed.
     626             :  *
     627             :  * This function can raise Lua memory errors, but no other Lua errors.
     628             :  */
     629         237 : const char * luasandbox_error_to_string(lua_State * L, int index)
     630             : {
     631             :     const char * s;
     632         237 :     if (index < 0) {
     633         237 :         index += lua_gettop(L) + 1;
     634             :     }
     635         237 :     if (luasandbox_is_fatal(L, index) || luasandbox_is_trace_error(L, index)) {
     636         230 :         lua_rawgeti(L, index, 2);
     637         230 :         s = lua_tostring(L, -1);
     638         230 :         lua_pop(L, 1);
     639             :     } else {
     640           7 :         s = lua_tostring(L, index);
     641             :     }
     642         237 :     if (!s) {
     643           1 :         return "unknown error";
     644             :     } else {
     645         236 :         return s;
     646             :     }
     647             : }
     648             : /* }}} */
     649             : 
     650             : /** {{{ luasandbox_attach_trace
     651             :  *
     652             :  * Error callback function for lua_pcall(): wrap the error value in a table that
     653             :  * includes backtrace information.
     654             :  */
     655         245 : int luasandbox_attach_trace(lua_State * L)
     656             : {
     657         245 :     if (luasandbox_is_fatal(L, 1)) {
     658             :         // Pass fatals through unaltered
     659          37 :         return 1;
     660             :     }
     661             : 
     662             :     // Create the table and put the marker in it as element 1
     663         208 :     lua_createtable(L, 0, 3);
     664         208 :     lua_pushlightuserdata(L, &luasandbox_trace_error_marker);
     665         208 :     lua_rawseti(L, -2, 1);
     666             : 
     667             :     // Swap the table with the input value, so that the value is on the top,
     668             :     // then put the value in the table as element 2
     669         208 :     lua_insert(L, -2);
     670         208 :     lua_rawseti(L, -2, 2);
     671             : 
     672             :     // Put the backtrace in element 3
     673         208 :     luasandbox_push_structured_trace(L, 1);
     674         208 :     lua_rawseti(L, -2, 3);
     675             : 
     676         208 :     return 1;
     677             : }
     678             : /* }}} */
     679             : 
     680             : /** {{{ luasandbox_push_structured_trace
     681             :  *
     682             :  * Make a table representing the current backtrace and push it to the stack.
     683             :  * "level" is the call stack level to start at, 1 for the current function.
     684             :  */
     685         218 : void luasandbox_push_structured_trace(lua_State * L, int level)
     686             : {
     687             :     lua_Debug ar;
     688         218 :     lua_newtable(L);
     689             :     int i;
     690             : 
     691       30545 :     for (i = level; lua_getstack(L, i, &ar); i++) {
     692       30327 :         lua_getinfo(L, "nSl", &ar);
     693       30327 :         lua_createtable(L, 0, 8);
     694       30327 :         lua_pushstring(L, ar.short_src);
     695       30327 :         lua_setfield(L, -2, "short_src");
     696       30327 :         lua_pushstring(L, ar.what);
     697       30327 :         lua_setfield(L, -2, "what");
     698       30327 :         lua_pushnumber(L, ar.currentline);
     699       30327 :         lua_setfield(L, -2, "currentline");
     700       30327 :         lua_pushstring(L, ar.name);
     701       30327 :         lua_setfield(L, -2, "name");
     702       30327 :         lua_pushstring(L, ar.namewhat);
     703       30327 :         lua_setfield(L, -2, "namewhat");
     704       30327 :         lua_pushnumber(L, ar.linedefined);
     705       30327 :         lua_setfield(L, -2, "linedefined");
     706       30327 :         lua_rawseti(L, -2, i - level + 1);
     707             :     }
     708         218 : }
     709             : /* }}} */
     710             : 
     711             : /** {{{ luasandbox_throw_runtimeerror
     712             :  *
     713             :  * Create and throw a luasandboxruntimeerror
     714             :  */
     715          10 : void luasandbox_throw_runtimeerror(lua_State * L, zval * sandbox_zval, const char *message)
     716             : {
     717             :     zval *zex, *ztrace;
     718             : 
     719             :     zval zvex, zvtrace;
     720          10 :     zex = &zvex;
     721          10 :     ztrace = &zvtrace;
     722          10 :     ZVAL_NULL(ztrace); // IS_NULL if lua_to_zval fails.
     723             : 
     724          10 :     object_init_ex(zex, luasandboxruntimeerror_ce);
     725             : 
     726          10 :     luasandbox_push_structured_trace(L, 1);
     727          10 :     luasandbox_lua_to_zval(ztrace, L, -1, sandbox_zval, NULL);
     728          10 :     luasandbox_update_property(luasandboxruntimeerror_ce, zex, "luaTrace", sizeof("luaTrace")-1, ztrace);
     729          10 :     zval_ptr_dtor(&zvtrace);
     730             : 
     731          10 :     lua_pop(L, 1);
     732             : 
     733          10 :     luasandbox_update_property_string(luasandboxruntimeerror_ce, zex,
     734             :         "message", sizeof("message")-1, message);
     735          10 :     luasandbox_update_property_long(luasandboxruntimeerror_ce, zex, "code", sizeof("code")-1, -1);
     736          10 :     zend_throw_exception_object(zex);
     737          10 : }
     738             : /* }}} */
     739             : 
     740             : /** {{{ luasandbox_protect_recursion
     741             :  *
     742             :  * Check that the zval isn't already in recursionGuard, and if so add it. May
     743             :  * allocate recursionGuard if necessary.
     744             :  *
     745             :  * Returns 1 if recursion is not detected, 0 if it was.
     746             :  */
     747         514 : static inline int luasandbox_protect_recursion(zval * z, HashTable ** recursionGuard, int * allocated) {
     748         514 :     if (!*recursionGuard) {
     749           8 :         *allocated = 1;
     750           8 :         ALLOC_HASHTABLE(*recursionGuard);
     751           8 :         zend_hash_init(*recursionGuard, 1, NULL, NULL, 0);
     752        1012 :     } else if (zend_hash_str_exists(*recursionGuard, (char*)&z, sizeof(void*))) {
     753           2 :         php_error_docref(NULL, E_WARNING, "Cannot pass circular reference to Lua");
     754           2 :         return 0;
     755             :     }
     756             : 
     757             :     zval zv;
     758         512 :     ZVAL_TRUE(&zv);
     759         512 :     zend_hash_str_update(*recursionGuard, (char*)&z, sizeof(void*), &zv);
     760             : 
     761         512 :     return 1;
     762             : }
     763             : /* }}} */
     764             : 
     765             : /** {{{ luasandbox_unprotect_recursion
     766             :  *
     767             :  * Undoes what luasandbox_protect_recursion did.
     768             :  */
     769         512 : static inline void luasandbox_unprotect_recursion(zval * z, HashTable * recursionGuard, int allocated) {
     770         512 :     if (allocated) {
     771           8 :         zend_hash_destroy(recursionGuard);
     772           8 :         FREE_HASHTABLE(recursionGuard);
     773             :     } else {
     774         504 :         zend_hash_str_del(recursionGuard, (char*)&z, sizeof(void*));
     775             :     }
     776         512 : }
     777             : /* }}} */

Generated by: LCOV version 1.13