jenkins-bot has submitted this change and it was merged.

Change subject: Invalid data should cause an error, not a silent placeholder
......................................................................


Invalid data should cause an error, not a silent placeholder

The behavior is inconsistent between LuaStandalone and LuaSandbox: in
the former, circular data structures and unsupported types cause an
error, while in the latter they are silently replaced with a
"placeholder" object.

The former behavior seems more logical, so let's do that.

Bug: 48393
Change-Id: I99c80549aebbd2ae1b7fbf8ab11f8588b40855fd
---
M data_conversion.c
M luasandbox.c
M php_luasandbox.h
M tests/call.phpt
4 files changed, 135 insertions(+), 49 deletions(-)

Approvals:
  Tim Starling: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/data_conversion.c b/data_conversion.c
index 85f62df..be7af4e 100644
--- a/data_conversion.c
+++ b/data_conversion.c
@@ -11,15 +11,16 @@
 
 #include "php.h"
 #include "php_luasandbox.h"
+#include "zend_exceptions.h"
 
-static void luasandbox_lua_to_array(HashTable *ht, lua_State *L, int index,
+static int luasandbox_lua_to_array(HashTable *ht, lua_State *L, int index,
        zval * sandbox_zval, HashTable * recursionGuard TSRMLS_DC);
 static int luasandbox_free_zval_userdata(lua_State * L);
 static int luasandbox_push_hashtable(lua_State * L, HashTable * ht);
 static int luasandbox_has_error_marker(lua_State * L, int index, void * 
marker);
 
 extern zend_class_entry *luasandboxfunction_ce;
-extern zend_class_entry *luasandboxplaceholder_ce;
+extern zend_class_entry *luasandboxruntimeerror_ce;
 
 /**
  * An int, the address of which is used as a fatal error marker. The value is 
@@ -193,8 +194,8 @@
  *
  * Convert a lua value to a zval.
  *
- * If a value is encountered that can't be converted to a zval, a 
LuaPlaceholder
- * object is returned instead.
+ * If a value is encountered that can't be converted to a zval, an exception is
+ * thrown.
  *
  * @param z A pointer to the destination zval
  * @param L The lua state
@@ -204,8 +205,9 @@
  * @param recursionGuard A hashtable for keeping track of tables that have 
been 
  *     processed, to allow infinite recursion to be avoided. External callers 
  *     should set this to NULL.
+ * @return int 0 (and a PHP exception) on failure
  */
-void luasandbox_lua_to_zval(zval * z, lua_State * L, int index, 
+int luasandbox_lua_to_zval(zval * z, lua_State * L, int index,
        zval * sandbox_zval, HashTable * recursionGuard TSRMLS_DC)
 {
        switch (lua_type(L, index)) {
@@ -251,12 +253,29 @@
                        const void * ptr = lua_topointer(L, index);
                        void * data = NULL;
                        int allocated = 0;
+                       int success = 1;
                        if (recursionGuard) {
                                // Check for circular reference (infinite 
recursion)
                                if (zend_hash_find(recursionGuard, (char*)&ptr, 
sizeof(void*), &data) == SUCCESS) {
                                        // Found circular reference!
-                                       object_init_ex(z, 
luasandboxplaceholder_ce);
-                                       break;
+                                       zval *zex, *ztrace;
+                                       MAKE_STD_ZVAL(zex);
+                                       object_init_ex(zex, 
luasandboxruntimeerror_ce);
+
+                                       MAKE_STD_ZVAL(ztrace);
+                                       luasandbox_push_structured_trace(L, 1);
+                                       luasandbox_lua_to_zval(ztrace, L, -1, 
sandbox_zval, NULL TSRMLS_CC);
+                                       
zend_update_property(luasandboxruntimeerror_ce, zex, "luaTrace", 
sizeof("luaTrace")-1, ztrace TSRMLS_CC);
+                                       zval_ptr_dtor(&ztrace);
+                                       lua_pop(L, 1);
+
+                                       
zend_update_property_string(luasandboxruntimeerror_ce, zex,
+                                               "message", sizeof("message")-1, 
"Cannot pass circular reference to PHP" TSRMLS_CC);
+                                       
zend_update_property_long(luasandboxruntimeerror_ce, zex, "code", 
sizeof("code")-1, -1 TSRMLS_CC);
+                                       zend_throw_exception_object(zex 
TSRMLS_CC);
+
+                                       ZVAL_NULL(z); // Need to set something 
to prevent a segfault
+                                       return 0;
                                }
                        } else {
                                ALLOC_HASHTABLE(recursionGuard);
@@ -270,11 +289,15 @@
 
                        // Process the array
                        array_init(z);
-                       luasandbox_lua_to_array(Z_ARRVAL_P(z), L, index, 
sandbox_zval, recursionGuard TSRMLS_CC);
+                       success = luasandbox_lua_to_array(Z_ARRVAL_P(z), L, 
index, sandbox_zval, recursionGuard TSRMLS_CC);
 
                        if (allocated) {
                                zend_hash_destroy(recursionGuard);
                                FREE_HASHTABLE(recursionGuard);
+                       }
+
+                       if (!success) {
+                               return 0;
                        }
                        break;
                }
@@ -318,10 +341,35 @@
                case LUA_TUSERDATA:
                case LUA_TTHREAD:
                case LUA_TLIGHTUSERDATA:
-               default:
                        // TODO: provide derived classes for each type
-                       object_init_ex(z, luasandboxplaceholder_ce);
+               default: {
+                       zval *zex, *ztrace;
+                       char *message;
+
+                       spprintf(&message, 0, "Cannot pass %s to PHP", 
lua_typename(L, lua_type(L, index)));
+
+                       MAKE_STD_ZVAL(zex);
+                       object_init_ex(zex, luasandboxruntimeerror_ce);
+
+                       MAKE_STD_ZVAL(ztrace);
+                       luasandbox_push_structured_trace(L, 1);
+                       luasandbox_lua_to_zval(ztrace, L, -1, sandbox_zval, 
NULL TSRMLS_CC);
+                       zend_update_property(luasandboxruntimeerror_ce, zex, 
"luaTrace", sizeof("luaTrace")-1, ztrace TSRMLS_CC);
+                       zval_ptr_dtor(&ztrace);
+                       lua_pop(L, 1);
+
+                       zend_update_property_string(luasandboxruntimeerror_ce, 
zex,
+                               "message", sizeof("message")-1, message 
TSRMLS_CC);
+                       zend_update_property_long(luasandboxruntimeerror_ce, 
zex, "code", sizeof("code")-1, -1 TSRMLS_CC);
+                       zend_throw_exception_object(zex TSRMLS_CC);
+
+                       efree(message);
+
+                       ZVAL_NULL(z); // Need to set something to prevent a 
segfault
+                       return 0;
+               }
        }
+       return 1;
 }
 /* }}} */
 
@@ -329,7 +377,7 @@
  *
  * Append the elements of the table in the specified index to the given 
HashTable.
  */
-static void luasandbox_lua_to_array(HashTable *ht, lua_State *L, int index,
+static int luasandbox_lua_to_array(HashTable *ht, lua_State *L, int index,
        zval * sandbox_zval, HashTable * recursionGuard TSRMLS_DC)
 {
        const char * str;
@@ -346,8 +394,12 @@
        lua_pushnil(L);
        while (lua_next(L, index) != 0) {
                MAKE_STD_ZVAL(value);
-               luasandbox_lua_to_zval(value, L, -1, sandbox_zval, 
recursionGuard TSRMLS_CC);
-               
+               if (!luasandbox_lua_to_zval(value, L, -1, sandbox_zval, 
recursionGuard TSRMLS_CC)) {
+                       // Conversion failed, fix stack and bail
+                       lua_settop(L, top);
+                       return 0;
+               }
+
                if (lua_type(L, -2) == LUA_TNUMBER) {
                        n = lua_tonumber(L, -2);
                        if (n == floor(n)) {
@@ -366,6 +418,7 @@
                // Pop temporary values off the stack
                lua_settop(L, top + 1);
        }
+       return 1;
 }
 /* }}} */
 
diff --git a/luasandbox.c b/luasandbox.c
index 936b3b0..d353f06 100644
--- a/luasandbox.c
+++ b/luasandbox.c
@@ -72,7 +72,6 @@
 zend_class_entry *luasandboxerrorerror_ce;
 zend_class_entry *luasandboxtimeouterror_ce;
 zend_class_entry *luasandboxemergencytimeouterror_ce;
-zend_class_entry *luasandboxplaceholder_ce;
 zend_class_entry *luasandboxfunction_ce;
 
 ZEND_DECLARE_MODULE_GLOBALS(luasandbox);
@@ -276,9 +275,6 @@
        INIT_CLASS_ENTRY(ce, "LuaSandboxEmergencyTimeoutError", 
luasandbox_empty_methods);
        luasandboxemergencytimeouterror_ce = zend_register_internal_class_ex(
                &ce, luasandboxfatalerror_ce, NULL TSRMLS_CC);
-
-       INIT_CLASS_ENTRY(ce, "LuaSandboxPlaceholder", luasandbox_empty_methods);
-       luasandboxplaceholder_ce = zend_register_internal_class(&ce TSRMLS_CC);
 
        INIT_CLASS_ENTRY(ce, "LuaSandboxFunction", luasandboxfunction_methods);
        luasandboxfunction_ce = zend_register_internal_class(&ce TSRMLS_CC);
@@ -598,8 +594,9 @@
        }
 
        // Make a zval out of it, and return false on error
-       luasandbox_lua_to_zval(return_value, L, lua_gettop(L), this_ptr, NULL 
TSRMLS_CC);
-       if (Z_TYPE_P(return_value) == IS_NULL) {
+       if (!luasandbox_lua_to_zval(return_value, L, lua_gettop(L), this_ptr, 
NULL TSRMLS_CC) ||
+               Z_TYPE_P(return_value) == IS_NULL
+       ) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING,
                                "too many chunks loaded already");
                RETVAL_FALSE;
@@ -1169,8 +1166,9 @@
        luasandbox_push_zval_userdata(L, z);
        lua_pushcclosure(L, luasandbox_call_php, 1);
 
-       luasandbox_lua_to_zval(return_value, L, lua_gettop(L), this_ptr, NULL 
TSRMLS_CC);
-       if (Z_TYPE_P(return_value) == IS_NULL) {
+       if (!luasandbox_lua_to_zval(return_value, L, lua_gettop(L), this_ptr, 
NULL TSRMLS_CC) ||
+               Z_TYPE_P(return_value) == IS_NULL
+       ) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING,
                                "too many chunks loaded already");
                RETVAL_FALSE;
@@ -1378,7 +1376,10 @@
        for (i = 0; i < numResults; i++) {
                zval * element;
                MAKE_STD_ZVAL(element);
-               luasandbox_lua_to_zval(element, L, retIndex + i, sandbox_zval, 
NULL TSRMLS_CC);
+               if (!luasandbox_lua_to_zval(element, L, retIndex + i, 
sandbox_zval, NULL TSRMLS_CC)) {
+                       // Convert failed (which means an exception), so bail.
+                       break;
+               }
                zend_hash_next_index_insert(Z_ARRVAL_P(return_value), 
                        (void*)&element,
                        sizeof(zval*), NULL);
@@ -1585,46 +1586,55 @@
        fci.retval_ptr_ptr = &retval_ptr;
 
        // Make an array of zval double-pointers to hold the arguments
+       int args_failed = 0;
        temp = ecalloc(top, sizeof(void*) * 2);
        double_pointers = (zval***)temp;
        pointers = (zval**)(temp + top);
        for (i = 0; i < top; i++ ) {
                MAKE_STD_ZVAL(pointers[i]);
-               luasandbox_lua_to_zval(pointers[i], L, i + 1, 
intern->current_zval, NULL TSRMLS_CC);
+               if (!luasandbox_lua_to_zval(pointers[i], L, i + 1, 
intern->current_zval, NULL TSRMLS_CC)) {
+                       // Argument conversion failed, so skip the call. The 
PHP exception
+                       // from the conversion will be handled below.
+                       args_failed = 1;
+                       top = i + 1;
+                       break;
+               }
                double_pointers[i] = &(pointers[i]);
        }
 
-       // Initialise the fci. Use zend_fcall_info_args_restore() since that's 
an
-       // almost-legitimate way to avoid the extra malloc that we'd get from
-       // zend_fcall_info_argp()
-       zend_fcall_info_args_restore(&fci, top, double_pointers);
+       if (!args_failed) {
+               // Initialise the fci. Use zend_fcall_info_args_restore() since 
that's an
+               // almost-legitimate way to avoid the extra malloc that we'd 
get from
+               // zend_fcall_info_argp()
+               zend_fcall_info_args_restore(&fci, top, double_pointers);
 
-       // Sanity check, timers should never be paused at this point
-       assert(!luasandbox_timer_is_paused(&intern->timer));
+               // Sanity check, timers should never be paused at this point
+               assert(!luasandbox_timer_is_paused(&intern->timer));
 
-       // Call the function
-       status = zend_call_function(&fci, &fcc TSRMLS_CC);
+               // Call the function
+               status = zend_call_function(&fci, &fcc TSRMLS_CC);
 
-       // Automatically unpause now that PHP has returned
-       luasandbox_timer_unpause(&intern->timer);
+               // Automatically unpause now that PHP has returned
+               luasandbox_timer_unpause(&intern->timer);
 
-       if (status == SUCCESS && fci.retval_ptr_ptr && *fci.retval_ptr_ptr)
-       {
-               // Push the return values back to Lua
-               if (Z_TYPE_PP(fci.retval_ptr_ptr) == IS_NULL) {
-                       // No action
-               } else if (Z_TYPE_PP(fci.retval_ptr_ptr) == IS_ARRAY) {
-                       bucket = Z_ARRVAL_PP(fci.retval_ptr_ptr)->pListHead;
-                       while (bucket) {
-                               luasandbox_push_zval(L, *((zval 
**)bucket->pData));
-                               bucket = bucket->pListNext;
-                               num_results++;
+               if (status == SUCCESS && fci.retval_ptr_ptr && 
*fci.retval_ptr_ptr)
+               {
+                       // Push the return values back to Lua
+                       if (Z_TYPE_PP(fci.retval_ptr_ptr) == IS_NULL) {
+                               // No action
+                       } else if (Z_TYPE_PP(fci.retval_ptr_ptr) == IS_ARRAY) {
+                               bucket = 
Z_ARRVAL_PP(fci.retval_ptr_ptr)->pListHead;
+                               while (bucket) {
+                                       luasandbox_push_zval(L, *((zval 
**)bucket->pData));
+                                       bucket = bucket->pListNext;
+                                       num_results++;
+                               }
+                       } else {
+                               php_error_docref(NULL TSRMLS_CC, E_WARNING, 
+                                       "function tried to return a single 
value to Lua without wrapping it in an array");
                        }
-               } else {
-                       php_error_docref(NULL TSRMLS_CC, E_WARNING, 
-                               "function tried to return a single value to Lua 
without wrapping it in an array");
+                       zval_ptr_dtor(&retval_ptr);
                }
-               zval_ptr_dtor(&retval_ptr);
        }
 
        // Free the argument zvals
diff --git a/php_luasandbox.h b/php_luasandbox.h
index 82b6bdf..5289126 100644
--- a/php_luasandbox.h
+++ b/php_luasandbox.h
@@ -129,7 +129,7 @@
 
 int luasandbox_push_zval(lua_State * L, zval * z);
 void luasandbox_push_zval_userdata(lua_State * L, zval * z);
-void luasandbox_lua_to_zval(zval * z, lua_State * L, int index, 
+int luasandbox_lua_to_zval(zval * z, lua_State * L, int index,
        zval * sandbox_zval, HashTable * recursionGuard TSRMLS_DC);
 void luasandbox_wrap_fatal(lua_State * L);
 int luasandbox_is_fatal(lua_State * L, int index);
diff --git a/tests/call.phpt b/tests/call.phpt
index bc1e97a..9fb1ff8 100644
--- a/tests/call.phpt
+++ b/tests/call.phpt
@@ -4,8 +4,31 @@
 <?php
 $sandbox = new LuaSandbox;
 var_dump( $sandbox->loadString( 'return 1' )->call() );
+
+echo "Proper handling of circular tables returned by Lua: ";
+$sandbox = new LuaSandbox;
+try {
+       $ret = $sandbox->loadString( 'local t = {}; t.t = t; return t' 
)->call();
+       echo var_export( $ret, 1 ) . "\n";
+} catch ( Exception $ex ) {
+       echo "Exception: " . $ex->getMessage() . "\n";
+}
+
+echo "Proper handling of circular tables in Lua→PHP call: ";
+$sandbox = new LuaSandbox;
+$f = $sandbox->wrapPhpFunction( function () {
+       echo func_num_args() . " args ok\n";
+} );
+try {
+       $sandbox->loadString( 'local f = ...; local t = {}; t.t = t; f( t )' 
)->call( $f );
+} catch ( Exception $ex ) {
+       echo "Exception: " . $ex->getMessage() . "\n";
+}
+
 --EXPECT--
 array(1) {
   [0]=>
   int(1)
 }
+Proper handling of circular tables returned by Lua: Exception: Cannot pass 
circular reference to PHP
+Proper handling of circular tables in Lua→PHP call: Exception: Cannot pass 
circular reference to PHP

-- 
To view, visit https://gerrit.wikimedia.org/r/63565
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I99c80549aebbd2ae1b7fbf8ab11f8588b40855fd
Gerrit-PatchSet: 4
Gerrit-Project: mediawiki/php/luasandbox
Gerrit-Branch: master
Gerrit-Owner: Anomie <[email protected]>
Gerrit-Reviewer: Aaron Schulz <[email protected]>
Gerrit-Reviewer: Anomie <[email protected]>
Gerrit-Reviewer: Tim Starling <[email protected]>
Gerrit-Reviewer: jenkins-bot

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to