From 3f86adb0efab3a68d87645941dc997d1e6314050 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Mon, 9 Dec 2019 15:14:39 +0100 Subject: Fixed bug #78935: Check that all linked classes can be preloaded During preloading, check that all classes that have been included as part of the preload script itself (rather than through opcache_compile_file) can actually be preloaded, i.e. satisfy Windows restrictions, have resolved initializers and resolved property types. When resolving initializers and property types, also autoload additional classes. Because of this, the resolution runs in a loop. --- ext/opcache/ZendAccelerator.c | 106 +++++++++++++++++++++ ext/opcache/tests/preload_004.phpt | 5 +- ext/opcache/tests/preload_009.phpt | 4 +- ext/opcache/tests/preload_loadable_classes_1.inc | 18 ++++ ext/opcache/tests/preload_loadable_classes_1.phpt | 19 ++++ ext/opcache/tests/preload_loadable_classes_2.inc | 6 ++ ext/opcache/tests/preload_loadable_classes_2.phpt | 17 ++++ ext/opcache/tests/preload_loadable_classes_3.inc | 5 + ext/opcache/tests/preload_loadable_classes_3.phpt | 13 +++ ext/opcache/tests/preload_loadable_classes_4.inc | 3 + ext/opcache/tests/preload_loadable_classes_4.phpt | 16 ++++ ext/opcache/tests/preload_unresolved_prop_type.inc | 2 + .../tests/preload_unresolved_prop_type.phpt | 14 +++ .../tests/preload_unresolved_prop_type_2.inc | 5 + 14 files changed, 229 insertions(+), 4 deletions(-) create mode 100644 ext/opcache/tests/preload_loadable_classes_1.inc create mode 100644 ext/opcache/tests/preload_loadable_classes_1.phpt create mode 100644 ext/opcache/tests/preload_loadable_classes_2.inc create mode 100644 ext/opcache/tests/preload_loadable_classes_2.phpt create mode 100644 ext/opcache/tests/preload_loadable_classes_3.inc create mode 100644 ext/opcache/tests/preload_loadable_classes_3.phpt create mode 100644 ext/opcache/tests/preload_loadable_classes_4.inc create mode 100644 ext/opcache/tests/preload_loadable_classes_4.phpt create mode 100644 ext/opcache/tests/preload_unresolved_prop_type.inc create mode 100644 ext/opcache/tests/preload_unresolved_prop_type.phpt create mode 100644 ext/opcache/tests/preload_unresolved_prop_type_2.inc (limited to 'ext/opcache') diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 895bf82b45..ccd300390f 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -3850,6 +3850,108 @@ static void preload_link(void) } ZEND_HASH_FOREACH_END(); } +#ifdef ZEND_WIN32 +static void preload_check_windows_restriction(zend_class_entry *scope, zend_class_entry *ce) { + if (ce && ce->type == ZEND_INTERNAL_CLASS) { + zend_error_noreturn(E_ERROR, + "Class %s uses internal class %s during preloading, which is not supported on Windows", + ZSTR_VAL(scope->name), ZSTR_VAL(ce->name)); + } +} + +static void preload_check_windows_restrictions(zend_class_entry *scope) { + uint32_t i; + + preload_check_windows_restriction(scope, scope->parent); + + for (i = 0; i < scope->num_interfaces; i++) { + preload_check_windows_restriction(scope, scope->interfaces[i]); + } +} +#endif + +static zend_class_entry *preload_load_prop_type(zend_property_info *prop, zend_string *name) { + zend_class_entry *ce; + if (zend_string_equals_literal_ci(name, "self")) { + ce = prop->ce; + } else if (zend_string_equals_literal_ci(name, "parent")) { + ce = prop->ce->parent; + } else { + ce = zend_lookup_class(name); + } + if (ce) { + return ce; + } + + zend_error_noreturn(E_ERROR, + "Failed to load class %s used by typed property %s::$%s during preloading", + ZSTR_VAL(name), ZSTR_VAL(prop->ce->name), zend_get_unmangled_property_name(prop->name)); + return ce; +} + +static void preload_ensure_classes_loadable() { + /* Run this in a loop, because additional classes may be loaded while updating constants etc. */ + uint32_t checked_classes_idx = 0; + while (1) { + zend_class_entry *ce; + uint32_t num_classes = zend_hash_num_elements(EG(class_table)); + if (num_classes == checked_classes_idx) { + return; + } + + ZEND_HASH_REVERSE_FOREACH_PTR(EG(class_table), ce) { + if (ce->type == ZEND_INTERNAL_CLASS || _idx == checked_classes_idx) { + break; + } + + if (!(ce->ce_flags & ZEND_ACC_LINKED)) { + /* Only require that already linked classes are loadable, we'll properly check + * things when linking additional classes. */ + continue; + } + +#ifdef ZEND_WIN32 + preload_check_windows_restrictions(ce); +#endif + + if (!(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) { + int result = SUCCESS; + zend_try { + result = zend_update_class_constants(ce); + } zend_catch { + /* Provide some context for the generated error. */ + zend_error_noreturn(E_ERROR, + "Error generated while resolving initializers of class %s during preloading", + ZSTR_VAL(ce->name)); + } zend_end_try(); + if (result == FAILURE) { + /* Just present to be safe: We generally always throw some + * other fatal error as part of update_class_constants(). */ + zend_error_noreturn(E_ERROR, + "Failed to resolve initializers of class %s during preloading", + ZSTR_VAL(ce->name)); + } + ZEND_ASSERT(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED); + } + + if (!(ce->ce_flags & ZEND_ACC_PROPERTY_TYPES_RESOLVED)) { + if (ce->ce_flags & ZEND_ACC_HAS_TYPE_HINTS) { + zend_property_info *prop; + ZEND_HASH_FOREACH_PTR(&ce->properties_info, prop) { + if (ZEND_TYPE_IS_NAME(prop->type)) { + zend_class_entry *ce = + preload_load_prop_type(prop, ZEND_TYPE_NAME(prop->type)); + prop->type = ZEND_TYPE_ENCODE_CE(ce, ZEND_TYPE_ALLOW_NULL(prop->type)); + } + } ZEND_HASH_FOREACH_END(); + } + ce->ce_flags |= ZEND_ACC_PROPERTY_TYPES_RESOLVED; + } + } ZEND_HASH_FOREACH_END(); + checked_classes_idx = num_classes; + } +} + static zend_string *preload_resolve_path(zend_string *filename) { if (is_stream_path(ZSTR_VAL(filename))) { @@ -4205,6 +4307,10 @@ static int accel_preload(const char *config) CG(unclean_shutdown) = 1; ret = FAILURE; } + + if (ret == SUCCESS) { + preload_ensure_classes_loadable(); + } } zend_catch { ret = FAILURE; } zend_end_try(); diff --git a/ext/opcache/tests/preload_004.phpt b/ext/opcache/tests/preload_004.phpt index 0441c78883..df92abdc28 100644 --- a/ext/opcache/tests/preload_004.phpt +++ b/ext/opcache/tests/preload_004.phpt @@ -12,5 +12,6 @@ opcache.preload={PWD}/preload_undef_const.inc var_dump(class_exists('Foo')); ?> --EXPECTF-- -Warning: Can't preload class Foo with unresolved initializer for constant A in %spreload_undef_const.inc on line 2 -bool(false) +Fatal error: Undefined class constant 'self::DOES_NOT_EXIST' in Unknown on line 0 + +Fatal error: Error generated while resolving initializers of class Foo during preloading in Unknown on line 0 diff --git a/ext/opcache/tests/preload_009.phpt b/ext/opcache/tests/preload_009.phpt index 84f444768c..1adbf5b006 100644 --- a/ext/opcache/tests/preload_009.phpt +++ b/ext/opcache/tests/preload_009.phpt @@ -13,6 +13,6 @@ var_dump(trait_exists('T')); var_dump(class_exists('Foo')); ?> --EXPECTF-- -Warning: Can't preload class Foo with unresolved initializer for constant C in %spreload_undef_const_2.inc on line 8 +Warning: Use of undefined constant UNDEF - assumed 'UNDEF' (this will throw an Error in a future version of PHP) in Unknown on line 0 +bool(true) bool(true) -bool(false) diff --git a/ext/opcache/tests/preload_loadable_classes_1.inc b/ext/opcache/tests/preload_loadable_classes_1.inc new file mode 100644 index 0000000000..b2bdabae1b --- /dev/null +++ b/ext/opcache/tests/preload_loadable_classes_1.inc @@ -0,0 +1,18 @@ + +--FILE-- + +--EXPECT-- +bool(true) +bool(true) +bool(true) diff --git a/ext/opcache/tests/preload_loadable_classes_2.inc b/ext/opcache/tests/preload_loadable_classes_2.inc new file mode 100644 index 0000000000..d21384dcd4 --- /dev/null +++ b/ext/opcache/tests/preload_loadable_classes_2.inc @@ -0,0 +1,6 @@ + +--FILE-- +Unreachable +--EXPECTF-- +Warning: Use of undefined constant UNDEF - assumed 'UNDEF' (this will throw an Error in a future version of PHP) in Unknown on line 0 + +Fatal error: Class 'Foo' not found in Unknown on line 0 + +Fatal error: Error generated while resolving initializers of class Test during preloading in Unknown on line 0 diff --git a/ext/opcache/tests/preload_loadable_classes_3.inc b/ext/opcache/tests/preload_loadable_classes_3.inc new file mode 100644 index 0000000000..d5c550f8c4 --- /dev/null +++ b/ext/opcache/tests/preload_loadable_classes_3.inc @@ -0,0 +1,5 @@ + +--FILE-- +Unreachable +--EXPECTF-- +Fatal error: Failed to load class Foo used by typed property Test::$prop during preloading in Unknown on line 0 diff --git a/ext/opcache/tests/preload_loadable_classes_4.inc b/ext/opcache/tests/preload_loadable_classes_4.inc new file mode 100644 index 0000000000..162de6eab2 --- /dev/null +++ b/ext/opcache/tests/preload_loadable_classes_4.inc @@ -0,0 +1,3 @@ + +--FILE-- +Unreachable +--EXPECTF-- +Fatal error: Class Test uses internal class Exception during preloading, which is not supported on Windows in Unknown on line 0 diff --git a/ext/opcache/tests/preload_unresolved_prop_type.inc b/ext/opcache/tests/preload_unresolved_prop_type.inc new file mode 100644 index 0000000000..05f4ee06a3 --- /dev/null +++ b/ext/opcache/tests/preload_unresolved_prop_type.inc @@ -0,0 +1,2 @@ + +--FILE-- +===DONE=== +--EXPECTF-- +Warning: Can't preload class Test with unresolved property types in %s on line %d +===DONE=== diff --git a/ext/opcache/tests/preload_unresolved_prop_type_2.inc b/ext/opcache/tests/preload_unresolved_prop_type_2.inc new file mode 100644 index 0000000000..c5557d1117 --- /dev/null +++ b/ext/opcache/tests/preload_unresolved_prop_type_2.inc @@ -0,0 +1,5 @@ +