diff --git a/NEWS b/NEWS index 3c4d6993f46c..e1ad376f3355 100644 --- a/NEWS +++ b/NEWS @@ -21,6 +21,8 @@ PHP NEWS . Deprecate specifying a nullable return type for __debugInfo(). (timwolla) . Fixed bug GH-22142 (Assertion failure in zendi_try_get_long() on IS_UNDEF). (David Carlier) + . Fixed GH-22060 (Use-after-free when an autoloader unregisters itself + during dispatch). (iliaal) - BCMath: . Added NUL-byte validation to BCMath functions. (jorgsowa) @@ -200,6 +202,8 @@ PHP NEWS - Sqlite3: . Fix NUL byte truncation in sqlite3 TEXT column handling. (ndossche) + . Fixed GH-22122 (Use-after-free in the authorizer callback when it releases + the authorizer). (iliaal) - Standard: . Fixed bug GH-19926 (reset internal pointer earlier while splicing array diff --git a/Zend/zend_API.h b/Zend/zend_API.h index 2487c8b632f2..3ad993b278e8 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -860,7 +860,14 @@ static zend_always_inline void zend_call_known_fcc( memcpy(func, fcc->function_handler, sizeof(zend_function)); zend_string_addref(func->op_array.function_name); } + zend_object *pinned_object = fcc->object; + if (pinned_object) { + GC_ADDREF(pinned_object); + } zend_call_known_function(func, fcc->object, fcc->called_scope, retval_ptr, param_count, params, named_params); + if (pinned_object) { + OBJ_RELEASE(pinned_object); + } } /* Call the provided zend_function instance method on an object. */ diff --git a/ext/pdo_sqlite/tests/gh22122.phpt b/ext/pdo_sqlite/tests/gh22122.phpt new file mode 100644 index 000000000000..ae15d7490ed2 --- /dev/null +++ b/ext/pdo_sqlite/tests/gh22122.phpt @@ -0,0 +1,40 @@ +--TEST-- +GH-22122 (Use-after-free in Pdo\Sqlite authorizer when callback releases the authorizer) +--EXTENSIONS-- +pdo_sqlite +--FILE-- +setAuthorizer(null); + echo "method: ", $this->state, "\n"; + return Pdo\Sqlite::OK; + } +} +$auth = new Auth(); +$db->setAuthorizer([$auth, 'authorize']); +unset($auth); +$db->exec('SELECT 1'); + +$capture = "closure-alive"; +$closure = function (int $action, ...$args) use (&$capture, $db): int { + $db->setAuthorizer(null); + echo "closure: ", $capture, "\n"; + return Pdo\Sqlite::OK; +}; +$db->setAuthorizer($closure); +unset($closure); +$db->exec('SELECT 2'); + +$db->exec('SELECT 3'); +echo "post-disable query ok\n"; +?> +--EXPECT-- +method: alive +closure: closure-alive +post-disable query ok diff --git a/ext/spl/tests/autoloading/gh22060.phpt b/ext/spl/tests/autoloading/gh22060.phpt new file mode 100644 index 000000000000..50dff5d71b11 --- /dev/null +++ b/ext/spl/tests/autoloading/gh22060.phpt @@ -0,0 +1,27 @@ +--TEST-- +GH-22060 (Class autoloader $this freed via spl_autoload_unregister during dispatch) +--FILE-- +data, "\n"; + } +} + +$obj = new Loader(); +spl_autoload_register([$obj, 'load']); +unset($obj); + +try { + new NonExistentClass42(); +} catch (\Throwable $e) { + echo $e::class, ": ", $e->getMessage(), "\n"; +} +?> +--EXPECT-- +loader-data +Error: Class "NonExistentClass42" not found diff --git a/ext/sqlite3/tests/gh22122.phpt b/ext/sqlite3/tests/gh22122.phpt new file mode 100644 index 000000000000..1df1c3bc0e28 --- /dev/null +++ b/ext/sqlite3/tests/gh22122.phpt @@ -0,0 +1,40 @@ +--TEST-- +GH-22122 (Use-after-free in SQLite3 authorizer when callback releases the authorizer) +--EXTENSIONS-- +sqlite3 +--FILE-- +setAuthorizer(null); + echo "method: ", $this->state, "\n"; + return SQLite3::OK; + } +} +$auth = new Auth(); +$db->setAuthorizer([$auth, 'authorize']); +unset($auth); +$db->exec('SELECT 1'); + +$capture = "closure-alive"; +$closure = function (int $action, ...$args) use (&$capture, $db): int { + $db->setAuthorizer(null); + echo "closure: ", $capture, "\n"; + return SQLite3::OK; +}; +$db->setAuthorizer($closure); +unset($closure); +$db->exec('SELECT 2'); + +$db->exec('SELECT 3'); +echo "post-disable query ok\n"; +?> +--EXPECT-- +method: alive +closure: closure-alive +post-disable query ok