mirror of
https://github.com/php/php-src.git
synced 2026-03-24 00:02:20 +01:00
ext/standard: Improve checking of allowed_classes option (#15267)
* ext/standard: Add some unserializing tests * ext/standard: Add proper type checking for values of the allowed_classes option array * ext/standard: Check that class names are somewhat sensible for the allowed_classes option array * Indicate type of value * Add test for Stringable objects
This commit is contained in:
committed by
GitHub
parent
efe0e73c91
commit
3059adae06
@@ -0,0 +1,61 @@
|
||||
--TEST--
|
||||
Test unserialize() with array allowed_classes and nonsensical values
|
||||
--FILE--
|
||||
<?php
|
||||
class foo {
|
||||
public $x = "bar";
|
||||
}
|
||||
$z = array(new foo(), 2, "3");
|
||||
$s = serialize($z);
|
||||
|
||||
try {
|
||||
unserialize($s, ["allowed_classes" => [null]]);
|
||||
} catch (Throwable $e) {
|
||||
echo $e::class, ': ', $e->getMessage(), "\n";
|
||||
}
|
||||
try {
|
||||
unserialize($s, ["allowed_classes" => [false]]);
|
||||
} catch (Throwable $e) {
|
||||
echo $e::class, ': ', $e->getMessage(), "\n";
|
||||
}
|
||||
try {
|
||||
unserialize($s, ["allowed_classes" => [true]]);
|
||||
} catch (Throwable $e) {
|
||||
echo $e::class, ': ', $e->getMessage(), "\n";
|
||||
}
|
||||
try {
|
||||
unserialize($s, ["allowed_classes" => [42]]);
|
||||
} catch (Throwable $e) {
|
||||
echo $e::class, ': ', $e->getMessage(), "\n";
|
||||
}
|
||||
try {
|
||||
unserialize($s, ["allowed_classes" => [15.2]]);
|
||||
} catch (Throwable $e) {
|
||||
echo $e::class, ': ', $e->getMessage(), "\n";
|
||||
}
|
||||
try {
|
||||
unserialize($s, ["allowed_classes" => [[]]]);
|
||||
} catch (Throwable $e) {
|
||||
echo $e::class, ': ', $e->getMessage(), "\n";
|
||||
}
|
||||
try {
|
||||
unserialize($s, ["allowed_classes" => [STDERR]]);
|
||||
} catch (Throwable $e) {
|
||||
echo $e::class, ': ', $e->getMessage(), "\n";
|
||||
}
|
||||
try {
|
||||
unserialize($s, ["allowed_classes" => [new stdClass]]);
|
||||
} catch (Throwable $e) {
|
||||
echo $e::class, ': ', $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
TypeError: unserialize(): Option "allowed_classes" must be an array of class names, null given
|
||||
TypeError: unserialize(): Option "allowed_classes" must be an array of class names, false given
|
||||
TypeError: unserialize(): Option "allowed_classes" must be an array of class names, true given
|
||||
TypeError: unserialize(): Option "allowed_classes" must be an array of class names, int given
|
||||
TypeError: unserialize(): Option "allowed_classes" must be an array of class names, float given
|
||||
TypeError: unserialize(): Option "allowed_classes" must be an array of class names, array given
|
||||
TypeError: unserialize(): Option "allowed_classes" must be an array of class names, resource given
|
||||
Error: Object of class stdClass could not be converted to string
|
||||
@@ -0,0 +1,48 @@
|
||||
--TEST--
|
||||
Test unserialize() with array allowed_classes and nonsensical class names
|
||||
--FILE--
|
||||
<?php
|
||||
class foo {
|
||||
public $x = "bar";
|
||||
}
|
||||
$z = array(new foo(), 2, "3");
|
||||
$s = serialize($z);
|
||||
|
||||
try {
|
||||
unserialize($s, ["allowed_classes" => [""]]);
|
||||
} catch (Throwable $e) {
|
||||
echo $e::class, ': ', $e->getMessage(), "\n";
|
||||
}
|
||||
try {
|
||||
unserialize($s, ["allowed_classes" => ["245blerg"]]);
|
||||
} catch (Throwable $e) {
|
||||
echo $e::class, ': ', $e->getMessage(), "\n";
|
||||
}
|
||||
try {
|
||||
unserialize($s, ["allowed_classes" => [" whitespace "]]);
|
||||
} catch (Throwable $e) {
|
||||
echo $e::class, ': ', $e->getMessage(), "\n";
|
||||
}
|
||||
try {
|
||||
unserialize($s, ["allowed_classes" => ["name\nwith whitespace"]]);
|
||||
} catch (Throwable $e) {
|
||||
echo $e::class, ': ', $e->getMessage(), "\n";
|
||||
}
|
||||
try {
|
||||
unserialize($s, ["allowed_classes" => ['$dollars']]);
|
||||
} catch (Throwable $e) {
|
||||
echo $e::class, ': ', $e->getMessage(), "\n";
|
||||
}
|
||||
try {
|
||||
unserialize($s, ["allowed_classes" => ["have\0nul_byte"]]);
|
||||
} catch (Throwable $e) {
|
||||
echo $e::class, ': ', $e->getMessage(), "\n";
|
||||
}
|
||||
|
||||
?>
|
||||
--EXPECT--
|
||||
ValueError: unserialize(): Option "allowed_classes" must be an array of class names, " whitespace " given
|
||||
ValueError: unserialize(): Option "allowed_classes" must be an array of class names, "name
|
||||
with whitespace" given
|
||||
ValueError: unserialize(): Option "allowed_classes" must be an array of class names, "$dollars" given
|
||||
ValueError: unserialize(): Option "allowed_classes" must be an array of class names, "have" given
|
||||
@@ -0,0 +1,32 @@
|
||||
--TEST--
|
||||
Test unserialize() with Stringable object in allowed_classes
|
||||
--FILE--
|
||||
<?php
|
||||
class foo {
|
||||
public $x = "bar";
|
||||
}
|
||||
$z = array(new foo(), 2, "3");
|
||||
$s = serialize($z);
|
||||
|
||||
class Name {
|
||||
public function __toString(): string {
|
||||
return 'Foo';
|
||||
}
|
||||
}
|
||||
|
||||
$o = new Name();
|
||||
|
||||
var_dump(unserialize($s, ["allowed_classes" => [$o]]));
|
||||
?>
|
||||
--EXPECT--
|
||||
array(3) {
|
||||
[0]=>
|
||||
object(foo)#3 (1) {
|
||||
["x"]=>
|
||||
string(3) "bar"
|
||||
}
|
||||
[1]=>
|
||||
int(2)
|
||||
[2]=>
|
||||
string(1) "3"
|
||||
}
|
||||
@@ -1370,25 +1370,34 @@ PHPAPI void php_unserialize_with_options(zval *return_value, const char *buf, co
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if(classes && (Z_TYPE_P(classes) == IS_ARRAY || !zend_is_true(classes))) {
|
||||
if (classes && (Z_TYPE_P(classes) == IS_ARRAY || !zend_is_true(classes))) {
|
||||
ALLOC_HASHTABLE(class_hash);
|
||||
zend_hash_init(class_hash, (Z_TYPE_P(classes) == IS_ARRAY)?zend_hash_num_elements(Z_ARRVAL_P(classes)):0, NULL, NULL, 0);
|
||||
}
|
||||
if(class_hash && Z_TYPE_P(classes) == IS_ARRAY) {
|
||||
if (class_hash && Z_TYPE_P(classes) == IS_ARRAY) {
|
||||
zval *entry;
|
||||
zend_string *lcname;
|
||||
|
||||
ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(classes), entry) {
|
||||
convert_to_string(entry);
|
||||
lcname = zend_string_tolower(Z_STR_P(entry));
|
||||
ZVAL_DEREF(entry);
|
||||
if (UNEXPECTED(Z_TYPE_P(entry) != IS_STRING && Z_TYPE_P(entry) != IS_OBJECT)) {
|
||||
zend_type_error("%s(): Option \"allowed_classes\" must be an array of class names, %s given",
|
||||
function_name, zend_zval_value_name(entry));
|
||||
goto cleanup;
|
||||
}
|
||||
zend_string *name = zval_try_get_string(entry);
|
||||
if (UNEXPECTED(name == NULL)) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (UNEXPECTED(!zend_is_valid_class_name(name))) {
|
||||
zend_value_error("%s(): Option \"allowed_classes\" must be an array of class names, \"%s\" given", function_name, ZSTR_VAL(name));
|
||||
zend_string_release_ex(name, false);
|
||||
goto cleanup;
|
||||
}
|
||||
zend_string *lcname = zend_string_tolower(name);
|
||||
zend_hash_add_empty_element(class_hash, lcname);
|
||||
zend_string_release_ex(lcname, 0);
|
||||
zend_string_release_ex(name, false);
|
||||
zend_string_release_ex(lcname, false);
|
||||
} ZEND_HASH_FOREACH_END();
|
||||
|
||||
/* Exception during string conversion. */
|
||||
if (EG(exception)) {
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
php_var_unserialize_set_allowed_classes(var_hash, class_hash);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user