From b46aafa3f0236348a73cfa40e0981be27f5e0776 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: 2025年9月13日 17:03:03 +0100 Subject: [PATCH 1/2] Zend: Add serialization tests for exceptions --- .../exception_error_serialization.phpt | 27 +++++++++++++++++++ .../exceptions/exception_serialization.phpt | 27 +++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 Zend/tests/exceptions/exception_error_serialization.phpt create mode 100644 Zend/tests/exceptions/exception_serialization.phpt diff --git a/Zend/tests/exceptions/exception_error_serialization.phpt b/Zend/tests/exceptions/exception_error_serialization.phpt new file mode 100644 index 0000000000000..6472451e31846 --- /dev/null +++ b/Zend/tests/exceptions/exception_error_serialization.phpt @@ -0,0 +1,27 @@ +--TEST-- +Check exception Error can round trip serialization +--FILE-- +getMessage() === $e2->getMessage()); +var_dump($e->getCode() === $e2->getCode()); +var_dump($e->getFile() === $e2->getFile()); +var_dump($e->getLine() === $e2->getLine()); +var_dump($e->getTrace() === $e2->getTrace()); +var_dump($e->getPrevious()::class === $e2->getPrevious()::class); + +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) diff --git a/Zend/tests/exceptions/exception_serialization.phpt b/Zend/tests/exceptions/exception_serialization.phpt new file mode 100644 index 0000000000000..b78022a3b0982 --- /dev/null +++ b/Zend/tests/exceptions/exception_serialization.phpt @@ -0,0 +1,27 @@ +--TEST-- +Check exceptions can round trip serialization +--FILE-- +getMessage() === $e2->getMessage()); +var_dump($e->getCode() === $e2->getCode()); +var_dump($e->getFile() === $e2->getFile()); +var_dump($e->getLine() === $e2->getLine()); +var_dump($e->getTrace() === $e2->getTrace()); +var_dump($e->getPrevious()::class === $e2->getPrevious()::class); + +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) From c9186f7ceb37efefa6c16d2f27e13542102caf8f Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: 2025年9月13日 17:27:51 +0100 Subject: [PATCH 2/2] Zend: Implement __unserialize() for Exception/Error --- Zend/tests/serialize/bug70121.phpt | 5 +- Zend/zend_exceptions.c | 89 ++++++++++++++++++++++ Zend/zend_exceptions.stub.php | 7 ++ Zend/zend_exceptions_arginfo.h | 11 ++- ext/soap/tests/bugs/bug73452.phpt | 5 +- ext/standard/tests/serialize/bug69152.phpt | 30 +++++--- ext/standard/tests/serialize/bug69793.phpt | 18 ++--- ext/standard/tests/serialize/bug70963.phpt | 21 +++-- sapi/cli/tests/005.phpt | 12 ++- 9 files changed, 165 insertions(+), 33 deletions(-) diff --git a/Zend/tests/serialize/bug70121.phpt b/Zend/tests/serialize/bug70121.phpt index 5b4aaa3a7679b..73c0ddc4fada7 100644 --- a/Zend/tests/serialize/bug70121.phpt +++ b/Zend/tests/serialize/bug70121.phpt @@ -8,6 +8,7 @@ OK --EXPECTF-- Fatal error: Uncaught TypeError: Cannot assign stdClass to property Exception::$previous of type ?Throwable in %s:%d Stack trace: -#0 %s(%d): unserialize('O:12:"DateInter...') -#1 {main} +#0 [internal function]: Exception->__unserialize(Array) +#1 %s(%d): unserialize('O:12:"DateInter...') +#2 {main} thrown in %s on line %d diff --git a/Zend/zend_exceptions.c b/Zend/zend_exceptions.c index 0b0945aac0f44..c3a77650ab89b 100644 --- a/Zend/zend_exceptions.c +++ b/Zend/zend_exceptions.c @@ -396,6 +396,95 @@ ZEND_METHOD(Exception, __wakeup) } /* }}} */ +ZEND_METHOD(Exception, __unserialize) +{ + HashTable *ht; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "h", &ht) == FAILURE) { + RETURN_THROWS(); + } + + /* Fake strict_types as zend_update_property_ex() would coerce values compared to unserialize() */ + EG(current_execute_data)->func->common.fn_flags |= ZEND_ACC_STRICT_TYPES; + + zend_string *key = NULL; + zval *tmp; + ZEND_HASH_FOREACH_STR_KEY_VAL(ht, key, tmp) { + if (UNEXPECTED(key == NULL)) { + zend_throw_error(NULL, "Must have a string key"); + RETURN_THROWS(); + } + if (ZSTR_VAL(key)[0] == '0円') { + if (zend_string_equals_literal(key, "0円*0円message")) { + if (Z_TYPE_P(tmp) != IS_STRING) { + zend_type_error("Cannot assign %s to property %s::$message of type string", + zend_zval_type_name(tmp), ZSTR_VAL(Z_OBJCE_P(ZEND_THIS)->name)); + RETURN_THROWS(); + } + zend_update_property_num_checked(NULL, Z_OBJ_P(ZEND_THIS), ZEND_EXCEPTION_MESSAGE_OFF, ZSTR_KNOWN(ZEND_STR_MESSAGE), tmp); + zval_add_ref(tmp); + if (UNEXPECTED(EG(exception))) { + RETURN_THROWS(); + } + continue; + } + if (zend_string_equals_literal(key, "0円*0円code")) { + if (Z_TYPE_P(tmp) != IS_LONG) { + zend_type_error("Cannot assign %s to property %s::$code of type int", + zend_zval_type_name(tmp), ZSTR_VAL(Z_OBJCE_P(ZEND_THIS)->name)); + RETURN_THROWS(); + } + zend_update_property_num_checked(NULL, Z_OBJ_P(ZEND_THIS), ZEND_EXCEPTION_CODE_OFF, ZSTR_KNOWN(ZEND_STR_CODE), tmp); + if (UNEXPECTED(EG(exception))) { + RETURN_THROWS(); + } + continue; + } + if (zend_string_equals_literal(key, "0円Exception0円previous") || zend_string_equals_literal(key, "0円Error0円previous")) { + if (Z_TYPE_P(tmp) != IS_NULL && !instanceof_function(Z_OBJCE_P(tmp), zend_ce_throwable)) { + zend_type_error("Cannot assign %s to property %s::$previous of type ?Throwable", + zend_zval_type_name(tmp), ZSTR_VAL(Z_OBJCE_P(ZEND_THIS)->name)); + RETURN_THROWS(); + } + zend_update_property_num_checked(NULL, Z_OBJ_P(ZEND_THIS), ZEND_EXCEPTION_PREVIOUS_OFF, ZSTR_KNOWN(ZEND_STR_PREVIOUS), tmp); + zval_add_ref(tmp); + if (UNEXPECTED(EG(exception))) { + RETURN_THROWS(); + } + continue; + } + if (zend_string_equals_literal(key, "0円Exception0円trace") || zend_string_equals_literal(key, "0円Error0円trace")) { + if (Z_TYPE_P(tmp) != IS_ARRAY) { + zend_type_error("Cannot assign %s to property %s::$trace of type array", + zend_zval_type_name(tmp), ZSTR_VAL(Z_OBJCE_P(ZEND_THIS)->name)); + RETURN_THROWS(); + } + zend_update_property_num_checked(NULL, Z_OBJ_P(ZEND_THIS), ZEND_EXCEPTION_TRACE_OFF, ZSTR_KNOWN(ZEND_STR_TRACE), tmp); + zval_add_ref(tmp); + if (UNEXPECTED(EG(exception))) { + RETURN_THROWS(); + } + continue; + } + if (zend_string_starts_with_cstr(key, ZEND_STRL("0円*0円"))) { + const char *name = ZSTR_VAL(key) + sizeof("0円*0円")-1; + zend_update_property(NULL, Z_OBJ_P(ZEND_THIS), name, strlen(name), tmp); + if (UNEXPECTED(EG(exception))) { + RETURN_THROWS(); + } + continue; + } + // TODO: other private props? + } else { + zend_update_property_ex(NULL, Z_OBJ_P(ZEND_THIS), key, tmp); + if (UNEXPECTED(EG(exception))) { + RETURN_THROWS(); + } + } + } ZEND_HASH_FOREACH_END(); +} +/* }}} */ + /* {{{ ErrorException constructor */ ZEND_METHOD(ErrorException, __construct) { diff --git a/Zend/zend_exceptions.stub.php b/Zend/zend_exceptions.stub.php index 86f2838ee9123..9ccf675957ebb 100644 --- a/Zend/zend_exceptions.stub.php +++ b/Zend/zend_exceptions.stub.php @@ -47,6 +47,8 @@ public function __construct(string $message = "", int $code = 0, ?Throwable $pre /** @tentative-return-type */ public function __wakeup(): void {} + public function __unserialize(array $data): void {} + final public function getMessage(): string {} /** @return int */ @@ -111,6 +113,11 @@ public function __construct(string $message = "", int $code = 0, ?Throwable $pre */ public function __wakeup(): void {} + /** + * @implementation-alias Exception::__unserialize + */ + public function __unserialize(array $data): void {} + /** @implementation-alias Exception::getMessage */ final public function getMessage(): string {} diff --git a/Zend/zend_exceptions_arginfo.h b/Zend/zend_exceptions_arginfo.h index cef37a1f0f0b9..1ff9b210aa9b5 100644 --- a/Zend/zend_exceptions_arginfo.h +++ b/Zend/zend_exceptions_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: ba1562ca8fe2fe48c40bc52d10545aa989afd86c */ + * Stub hash: e412ae81454288565daae5ffaf9cc3941b6edbb7 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Throwable_getMessage, 0, 0, IS_STRING, 0) ZEND_END_ARG_INFO() @@ -32,6 +32,10 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_Exception___wakeup, 0, 0, IS_VOID, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Exception___unserialize, 0, 1, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, data, IS_ARRAY, 0) +ZEND_END_ARG_INFO() + #define arginfo_class_Exception_getMessage arginfo_class_Throwable_getMessage #define arginfo_class_Exception_getCode arginfo_class_Throwable_getCode @@ -65,6 +69,8 @@ ZEND_END_ARG_INFO() #define arginfo_class_Error___wakeup arginfo_class_Exception___wakeup +#define arginfo_class_Error___unserialize arginfo_class_Exception___unserialize + #define arginfo_class_Error_getMessage arginfo_class_Throwable_getMessage #define arginfo_class_Error_getCode arginfo_class_Throwable_getCode @@ -84,6 +90,7 @@ ZEND_END_ARG_INFO() ZEND_METHOD(Exception, __clone); ZEND_METHOD(Exception, __construct); ZEND_METHOD(Exception, __wakeup); +ZEND_METHOD(Exception, __unserialize); ZEND_METHOD(Exception, getMessage); ZEND_METHOD(Exception, getCode); ZEND_METHOD(Exception, getFile); @@ -110,6 +117,7 @@ static const zend_function_entry class_Exception_methods[] = { ZEND_ME(Exception, __clone, arginfo_class_Exception___clone, ZEND_ACC_PRIVATE) ZEND_ME(Exception, __construct, arginfo_class_Exception___construct, ZEND_ACC_PUBLIC) ZEND_ME(Exception, __wakeup, arginfo_class_Exception___wakeup, ZEND_ACC_PUBLIC) + ZEND_ME(Exception, __unserialize, arginfo_class_Exception___unserialize, ZEND_ACC_PUBLIC) ZEND_ME(Exception, getMessage, arginfo_class_Exception_getMessage, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) ZEND_ME(Exception, getCode, arginfo_class_Exception_getCode, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) ZEND_ME(Exception, getFile, arginfo_class_Exception_getFile, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) @@ -131,6 +139,7 @@ static const zend_function_entry class_Error_methods[] = { ZEND_RAW_FENTRY("__clone", zim_Exception___clone, arginfo_class_Error___clone, ZEND_ACC_PRIVATE, NULL, NULL) ZEND_RAW_FENTRY("__construct", zim_Exception___construct, arginfo_class_Error___construct, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_RAW_FENTRY("__wakeup", zim_Exception___wakeup, arginfo_class_Error___wakeup, ZEND_ACC_PUBLIC, NULL, NULL) + ZEND_RAW_FENTRY("__unserialize", zim_Exception___unserialize, arginfo_class_Error___unserialize, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_RAW_FENTRY("getMessage", zim_Exception_getMessage, arginfo_class_Error_getMessage, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL, NULL, NULL) ZEND_RAW_FENTRY("getCode", zim_Exception_getCode, arginfo_class_Error_getCode, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL, NULL, NULL) ZEND_RAW_FENTRY("getFile", zim_Exception_getFile, arginfo_class_Error_getFile, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL, NULL, NULL) diff --git a/ext/soap/tests/bugs/bug73452.phpt b/ext/soap/tests/bugs/bug73452.phpt index d818ba15f49e5..0a15fc8b873c5 100644 --- a/ext/soap/tests/bugs/bug73452.phpt +++ b/ext/soap/tests/bugs/bug73452.phpt @@ -13,6 +13,7 @@ echo unserialize($data); --EXPECTF-- Fatal error: Uncaught TypeError: Cannot assign %s to property SoapFault::$faultcode of type ?string in %s:%d Stack trace: -#0 %sbug73452.php(4): unserialize('O:9:"SoapFault"...') -#1 {main} +#0 [internal function]: Exception->__unserialize(Array) +#1 %s(4): unserialize('O:9:"SoapFault"...') +#2 {main} thrown in %s on line %d diff --git a/ext/standard/tests/serialize/bug69152.phpt b/ext/standard/tests/serialize/bug69152.phpt index f7f3b9ac8d33c..8ed9a0ddb2064 100644 --- a/ext/standard/tests/serialize/bug69152.phpt +++ b/ext/standard/tests/serialize/bug69152.phpt @@ -2,15 +2,25 @@ Bug #69152: Type Confusion Infoleak Vulnerability in unserialize() --FILE-- test(); +try { + $x = unserialize('O:9:"exception":1:{s:16:"'."0円".'Exception'."0円".'trace";s:4:"ryat";}'); + var_dump($x); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} +try { + $x = unserialize('O:4:"test":1:{s:27:"__PHP_Incomplete_Class_Name";R:1;}'); + var_dump($x); + $x->test(); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} ?> ---EXPECTF-- -Fatal error: Uncaught TypeError: Cannot assign string to property Exception::$trace of type array in %s:%d -Stack trace: -#0 %s(%d): unserialize('O:9:"exception"...') -#1 {main} - thrown in %s on line %d +--EXPECT-- +TypeError: Cannot assign string to property Exception::$trace of type array +object(__PHP_Incomplete_Class)#1 (1) { + ["__PHP_Incomplete_Class_Name"]=> + *RECURSION* +} +Error: The script tried to call a method on an incomplete object. Please ensure that the class definition "unknown" of the object you are trying to operate on was loaded _before_ unserialize() gets called or provide an autoloader to load the class definition diff --git a/ext/standard/tests/serialize/bug69793.phpt b/ext/standard/tests/serialize/bug69793.phpt index 1ebc831a9adf4..6599b5f0dd8dd 100644 --- a/ext/standard/tests/serialize/bug69793.phpt +++ b/ext/standard/tests/serialize/bug69793.phpt @@ -2,13 +2,13 @@ Bug #69793: Remotely triggerable stack exhaustion via recursive method calls --FILE-- getMessage(), PHP_EOL; +} ?> ---EXPECTF-- -Fatal error: Uncaught TypeError: Cannot assign int to property Exception::$previous of type ?Throwable in %s:%d -Stack trace: -#0 %s(%d): unserialize('O:9:"Exception"...') -#1 {main} - thrown in %s on line %d +--EXPECT-- +TypeError: Cannot assign null to property Exception::$message of type string diff --git a/ext/standard/tests/serialize/bug70963.phpt b/ext/standard/tests/serialize/bug70963.phpt index 63f5845ae0d8c..b677634ad9ace 100644 --- a/ext/standard/tests/serialize/bug70963.phpt +++ b/ext/standard/tests/serialize/bug70963.phpt @@ -2,12 +2,19 @@ Bug #70963 (Unserialize shows UNKNOW in result) --FILE-- getMessage(), PHP_EOL; +} +try { + var_dump(unserialize('a:2:{i:0;O:9:"exception":1:{s:16:"'."0円".'Exception'."0円".'trace";s:4:"test";}i:1;r:3;}')); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} ?> --EXPECTF-- -Fatal error: Uncaught TypeError: Cannot assign string to property Exception::$trace of type array in %s:%d -Stack trace: -#0 %s(%d): unserialize('a:2:{i:0;O:9:"e...') -#1 {main} - thrown in %s on line %d +TypeError: Cannot assign string to property Exception::$trace of type array + +Warning: unserialize(): Error at offset 72 of 73 bytes in %s on line %d +TypeError: Cannot assign string to property Exception::$trace of type array diff --git a/sapi/cli/tests/005.phpt b/sapi/cli/tests/005.phpt index ca168ecfe20e3..18fd824d37d4f 100644 --- a/sapi/cli/tests/005.phpt +++ b/sapi/cli/tests/005.phpt @@ -37,7 +37,7 @@ string(183) "Class [ class stdClass ] { } " -string(2232) "Class [ class Exception implements Stringable, Throwable ] { +string(2406) "Class [ class Exception implements Stringable, Throwable ] { - Constants [0] { } @@ -58,7 +58,7 @@ string(2232) "Class [ class Exception implements Stringable, Thr Property [ private ?Throwable $previous = NULL ] } - - Methods [11] { + - Methods [12] { Method [ private method __clone ] { - Parameters [0] { @@ -82,6 +82,14 @@ string(2232) "Class [ class Exception implements Stringable, Thr - Tentative return [ void ] } + Method [ public method __unserialize ] { + + - Parameters [1] { + Parameter #0 [ array $data ] + } + - Return [ void ] + } + Method [ final public method getMessage ] { - Parameters [0] {

AltStyle によって変換されたページ (->オリジナル) /