diff --git a/php_runkit.h b/php_runkit.h index e739cc6..17f52aa 100644 --- a/php_runkit.h +++ b/php_runkit.h @@ -61,6 +61,8 @@ static inline void* _debug_emalloc(void* data, int bytes, char* file, int line) #endif #define PHP_RUNKIT_VERSION "1.0.5b1" +#define PHP_RUNKIT_SANDBOX_CLASSNAME "Runkit_Sandbox" +#define PHP_RUNKIT_SANDBOX_PARENT_CLASSNAME "Runkit_Sandbox_Parent" #define PHP_RUNKIT_IMPORT_FUNCTIONS 0x0001 #define PHP_RUNKIT_IMPORT_CLASS_METHODS 0x0002 @@ -81,6 +83,13 @@ static inline void* _debug_emalloc(void* data, int bytes, char* file, int line) #endif #endif +/* The TSRM interpreter patch required by runkit_sandbox was added in 5.1, but this package includes diffs for older versions + * Those diffs include an additional #define to indicate that they've been applied + */ +#if defined(ZTS) && defined(PHP_RUNKIT_FEATURE_SANDBOX) +#define PHP_RUNKIT_SANDBOX +#endif + #ifdef PHP_RUNKIT_FEATURE_MODIFY #define PHP_RUNKIT_MANIPULATION // TODO: Enable these macros once the corresponding functions/files compile and pass some of the tests. @@ -161,15 +170,26 @@ PHP_FUNCTION(runkit_import); #endif /* PHP_RUNKIT_MANIPULATION_IMPORT */ #endif /* PHP_RUNKIT_MANIPULATION */ +#ifdef PHP_RUNKIT_SANDBOX +PHP_FUNCTION(runkit_sandbox_output_handler); +PHP_FUNCTION(runkit_lint); +PHP_FUNCTION(runkit_lint_file); + +typedef struct _php_runkit_sandbox_object php_runkit_sandbox_object; +#endif /* PHP_RUNKIT_SANDBOX */ + #ifdef PHP_RUNKIT_MANIPULATION // typedef struct _php_runkit_default_class_members_list_element php_runkit_default_class_members_list_element; #endif -#if defined(PHP_RUNKIT_SUPERGLOBALS) || defined(PHP_RUNKIT_MANIPULATION) +#if defined(PHP_RUNKIT_SUPERGLOBALS) || defined(PHP_RUNKIT_SANDBOX) || defined(PHP_RUNKIT_MANIPULATION) ZEND_BEGIN_MODULE_GLOBALS(runkit) #ifdef PHP_RUNKIT_SUPERGLOBALS HashTable *superglobals; #endif +#ifdef PHP_RUNKIT_SANDBOX + php_runkit_sandbox_object *current_sandbox; +#endif #ifdef PHP_RUNKIT_MANIPULATION HashTable *misplaced_internal_functions; HashTable *replaced_internal_functions; @@ -374,6 +394,69 @@ static inline void php_runkit_modify_function_doc_comment(zend_function *fe, zen #endif /* PHP_RUNKIT_MANIPULATION */ +#ifdef PHP_RUNKIT_SANDBOX +/* runkit_sandbox.c */ +int php_runkit_init_sandbox(INIT_FUNC_ARGS); +int php_runkit_shutdown_sandbox(SHUTDOWN_FUNC_ARGS); + +/* runkit_sandbox_parent.c */ +int php_runkit_init_sandbox_parent(INIT_FUNC_ARGS); +int php_runkit_shutdown_sandbox_parent(SHUTDOWN_FUNC_ARGS); +int php_runkit_sandbox_array_deep_copy(RUNKIT_53_TSRMLS_ARG(zval **value), int num_args, va_list args, zend_hash_key *hash_key); + +struct _php_runkit_sandbox_object { + zend_object obj; + + void *context, *parent_context; + + char *disable_functions; + char *disable_classes; + zval *output_handler; /* points to function which lives in the parent_context */ + + unsigned char bailed_out_in_eval; /* Patricide is an ugly thing. Especially when it leaves bailout address mis-set */ + + unsigned char active; /* A bailout will set this to 0 */ + unsigned char parent_access; /* May Runkit_Sandbox_Parent be instantiated/used? */ + unsigned char parent_read; /* May parent vars be read? */ + unsigned char parent_write; /* May parent vars be written to? */ + unsigned char parent_eval; /* May arbitrary code be run in the parent? */ + unsigned char parent_include; /* May arbitrary code be included in the parent? (includes require(), and *_once()) */ + unsigned char parent_echo; /* May content be echoed from the parent scope? */ + unsigned char parent_call; /* May functions in the parent scope be called? */ + unsigned char parent_die; /* Are $PARENT->die() / $PARENT->exit() enabled? */ + unsigned long parent_scope; /* 0 == Global, 1 == Active, 2 == Active->prior, 3 == Active->prior->prior, etc... */ + + char *parent_scope_name; /* Combines with parent_scope to refer to a named array as a symbol table */ + int parent_scope_namelen; +}; + + +/* TODO: It'd be nice if objects and resources could make it across... */ +#define PHP_SANDBOX_CROSS_SCOPE_ZVAL_COPY_CTOR(pzv) \ +{ \ + switch (Z_TYPE_P(pzv)) { \ + case IS_RESOURCE: \ + case IS_OBJECT: \ + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to translate resource, or object variable to current context."); \ + ZVAL_NULL(pzv); \ + break; \ + case IS_ARRAY: \ + { \ + HashTable *original_hashtable = Z_ARRVAL_P(pzv); \ + array_init(pzv); \ + zend_hash_apply_with_arguments(RUNKIT_53_TSRMLS_PARAM(original_hashtable), (apply_func_args_t)php_runkit_sandbox_array_deep_copy, 1, Z_ARRVAL_P(pzv) TSRMLS_CC); \ + break; \ + } \ + default: \ + zval_copy_ctor(pzv); \ + } \ + if (Z_REFCOUNTED_P(pzf)) \ + Z_SET_REFCOUNT(pzv, 1); \ + /*(pzv)->RUNKIT_IS_REF = 0; // I think I can get rid of that, since IS_REFERENCE is now part of Z_TYPE?*/ \ + } \ +} +#endif /* PHP_RUNKIT_SANDBOX */ + #ifdef PHP_RUNKIT_MANIPULATION // Split pnname into classname and pnname, if it contains the string "::" @@ -623,6 +706,18 @@ void php_runkit_update_reflection_object_name(zend_object* object, int handle, c } reflection_object; #endif /* PHP_RUNKIT_MANIPULATION */ +#ifdef PHP_RUNKIT_SANDBOX +// TODO: Figure out what the php7 equivalent of zend_object_store_bucket and zend_object_handle are. +/* {{{ php_runkit_zend_object_store_get_obj */ +inline static zend_object *php_runkit_zend_object_store_get(const zval *zobject TSRMLS_DC) +{ + // Note: Object handle may be removed from _zend_resource in the future. + int handle = Z_OBJ_HANDLE_P(zobject); + return EG(objects_store).object_buckets[handle]; +} +/* }}} */ +#endif + #endif /* PHP_RUNKIT_H */ /* diff --git a/php_runkit_sandbox.h b/php_runkit_sandbox.h new file mode 100644 index 0000000..6b60396 --- /dev/null +++ b/php_runkit_sandbox.h @@ -0,0 +1,188 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 7 | + +----------------------------------------------------------------------+ + | (c) 2008-2015 Dmitry Zenovich | + +----------------------------------------------------------------------+ + | This source file is subject to the new BSD license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.opensource.org/licenses/BSD-3-Clause | + | If you did not receive a copy of the license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | dzenovich@gmail.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Dmitry Zenovich | + +----------------------------------------------------------------------+ +*/ + +#ifndef PHP_RUNKIT_SANDBOX_H +#define PHP_RUNKIT_SANDBOX_H + +/* {{{ php_runkit_sandbox_has_property_int */ +inline static int php_runkit_sandbox_has_property_int(int has_set_exists, zval *member TSRMLS_DC) { + zval **tmpzval; + int result = 0; + +#if PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION < 1 + /* Map PHP 5.0 has_property flag to PHP 5.1+ flag */ + has_set_exists = (has_set_exists == 0) ? 2 : 1; +#endif + + if (zend_hash_find(&EG(symbol_table), Z_STRVAL_P(member), Z_STRLEN_P(member) + 1, (void*)&tmpzval) == SUCCESS) { + switch (has_set_exists) { + case 0: + result = (Z_TYPE_PP(tmpzval) != IS_NULL); + break; + case 1: + switch (Z_TYPE_PP(tmpzval)) { + case IS_BOOL: case IS_LONG: case IS_RESOURCE: + result = (Z_LVAL_PP(tmpzval) != 0); + break; + case IS_DOUBLE: + result = (Z_DVAL_PP(tmpzval) != 0); + break; + case IS_STRING: + result = (Z_STRLEN_PP(tmpzval) > 1 || (Z_STRLEN_PP(tmpzval) == 1 && Z_STRVAL_PP(tmpzval)[0] != '0')); + break; + case IS_ARRAY: + result = zend_hash_num_elements(Z_ARRVAL_PP(tmpzval)) > 0; + break; + case IS_OBJECT: + /* TODO: Use ZE2 logic for this rather than ZE1 logic */ + result = zend_hash_num_elements(Z_OBJPROP_PP(tmpzval)) > 0; + break; + case IS_NULL: + default: + result = 0; + } + break; + case 2: + result = 1; + break; + } + } else { + result = 0; + } + return result; +} +/* }}} */ + +/* {{{ php_runkit_sandbox_include_or_eval_int */ +inline static zend_op_array *php_runkit_sandbox_include_or_eval_int(zval *return_value, zval *zcode, int type, int once, int *already_included TSRMLS_DC) { + zend_op_array *op_array = NULL; + + if (type == ZEND_EVAL) { + /* eval() */ + char *eval_desc = zend_make_compiled_string_description("Runkit_Sandbox Eval Code" TSRMLS_CC); + op_array = compile_string(zcode, eval_desc TSRMLS_CC); + efree(eval_desc); + } else if (!once) { + /* include() & requre() */ + op_array = compile_filename(type, zcode TSRMLS_CC); + } else { + /* include_once() & require_once() */ + int dummy = 1; + zend_file_handle file_handle; + + if (SUCCESS == zend_stream_open(Z_STRVAL_P(zcode), &file_handle TSRMLS_CC)) { + if (!file_handle.opened_path) { + file_handle.opened_path = estrndup(Z_STRVAL_P(zcode), Z_STRLEN_P(zcode)); + } + if (zend_hash_add(&EG(included_files), file_handle.opened_path, strlen(file_handle.opened_path)+1, (void*)&dummy, sizeof(int), NULL)==SUCCESS) { + op_array = zend_compile_file(&file_handle, type TSRMLS_CC); + zend_destroy_file_handle(&file_handle TSRMLS_CC); + } else { + RUNKIT_FILE_HANDLE_DTOR(&file_handle); + RETVAL_TRUE; + *already_included = 1; + } + } + } + return op_array; +} +/* }}} */ + +/* {{{ php_runkit_sandbox_call_int */ +inline static void php_runkit_sandbox_call_int(zval *func_name, char **pname, zval **pretval, zval *args, zval *return_value, void *prior_context TSRMLS_DC) { + HashPosition pos; + int i; + zval **tmpzval; + int argc = zend_hash_num_elements(Z_ARRVAL_P(args)); + zval ***sandbox_args = safe_emalloc(sizeof(zval**), argc, 0); + + for(zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(args), &pos), i = 0; + (zend_hash_get_current_data_ex(Z_ARRVAL_P(args), (void*)&tmpzval, &pos) == SUCCESS) && (i < argc); + zend_hash_move_forward_ex(Z_ARRVAL_P(args), &pos), i++) { + sandbox_args[i] = emalloc(sizeof(zval*)); + MAKE_STD_ZVAL(*sandbox_args[i]); + **sandbox_args[i] = **tmpzval; + + if (Z_TYPE_P(*sandbox_args[i]) == IS_OBJECT && zend_get_class_entry(*sandbox_args[i], prior_context) == zend_ce_closure) { + zend_closure *closure; + zend_object *bucket; + bucket = php_runkit_zend_object_store_get_obj(*sandbox_args[i], prior_context); + closure = (zend_closure *) bucket->bucket.obj.object; + (*sandbox_args[i])->value.obj.handle = zend_objects_store_put(closure, NULL, NULL, bucket->bucket.obj.clone TSRMLS_CC); + } else + PHP_SANDBOX_CROSS_SCOPE_ZVAL_COPY_CTOR(*sandbox_args[i]); + } + + /* Shouldn't be necessary */ + argc = i; + + /* Note: If this function is disabled by disable_functions or disable_classes, + * The user will get a confusing error message about (null)() being disabled for security reasons on line 0 + * This will be fixable with a properly set EG(function_state_ptr)....just not yet + */ + if (call_user_function_ex(EG(function_table), NULL, func_name, pretval, argc, sandbox_args, 0, NULL TSRMLS_CC) == SUCCESS) { + if (*pretval) { + *return_value = **pretval; + } else { + RETVAL_TRUE; + } + } else { + php_error_docref1(NULL TSRMLS_CC, *pname, E_WARNING, "Unable to call function"); + RETVAL_FALSE; + } + if (*pname) { + efree(*pname); + *pname = NULL; + } + + for(i = 0; i < argc; i++) { + if (Z_TYPE_P(*sandbox_args[i]) == IS_OBJECT && zend_get_class_entry(*sandbox_args[i] TSRMLS_CC) == zend_ce_closure) { + zend_object_store_bucket *bucket = php_runkit_zend_object_store_get_obj(*sandbox_args[i] TSRMLS_CC); + zend_objects_store_del_ref(*sandbox_args[i] TSRMLS_CC); + zval_ptr_dtor(sandbox_args[i]); + bucket->bucket.obj.object = NULL; + } + zval_ptr_dtor(sandbox_args[i]); + efree(sandbox_args[i]); + } + efree(sandbox_args); +} +/* }}} */ + +/* {{{ php_runkit_sandbox_return_property_value */ +inline static zval *php_runkit_sandbox_return_property_value(int prop_found, zval *retval TSRMLS_DC) { + if (prop_found) { + zval *return_value; + + ALLOC_ZVAL(return_value); + *return_value = *retval; + + /* ZE expects refcount == 0 for unowned values */ + INIT_PZVAL(return_value); + PHP_SANDBOX_CROSS_SCOPE_ZVAL_COPY_CTOR(return_value); + return_value->RUNKIT_REFCOUNT--; + + return return_value; + } else { + return EG(uninitialized_zval_ptr); + } +} +/* }}} */ + +#endif + diff --git a/tests/runkit_sandbox_output_handler.phpt b/tests/runkit_sandbox_output_handler.phpt new file mode 100644 index 0000000..c8c3fd0 --- /dev/null +++ b/tests/runkit_sandbox_output_handler.phpt @@ -0,0 +1,29 @@ +--TEST-- +runkit_sandbox_output_handler() function +--SKIPIF-- + +--FILE-- +echo("foo\n"); +$php->echo("Barish\n"); +$php->echo("BAZimbly\n"); + +function test_handler($str) { + if (strlen($str) == 0) return NULL; /* flush() */ + /* Echoing and returning have the same effect here, both go to parent's output chain */ + echo 'Received string from sandbox: ' . strlen($str) . " bytes long.\n"; + + return strtoupper($str); +} +--EXPECTF-- +Notice: runkit_sandbox_output_handler(): Use of runkit_sandbox_output_handler() is deprecated. Use $sandbox['output_handler'] instead. in %s on line %d +Received string from sandbox: 4 bytes long. +FOO +Received string from sandbox: 7 bytes long. +BARISH +Received string from sandbox: 9 bytes long. +BAZIMBLY diff --git a/tests/runkit_sandbox_with_register_globals.phpt b/tests/runkit_sandbox_with_register_globals.phpt new file mode 100644 index 0000000..2cbc6df --- /dev/null +++ b/tests/runkit_sandbox_with_register_globals.phpt @@ -0,0 +1,15 @@ +--TEST-- +Runkit_Sandbox with register_globals +--SKIPIF-- +')) echo "skip"; +?> +--INI-- +register_globals=On +--FILE-- +