Open
Description
Description
The following code:
<?php
error_reporting(E_ALL & ~E_DEPRECATED);
// test serialize() functionality on shutdown
register_shutdown_function('shutdown_serialize_test');
function shutdown_serialize_test() {
$bug_test = new stdClass();
$bug_test_serialized = serialize([$bug_test, $bug_test]);
$bug_test_unserialized = unserialize($bug_test_serialized);
if ($bug_test_unserialized === false) {
echo "\nserialize() / unserialize() round trip failed on shutdown\n";
}
$reference_serialized = 'a:2:{i:0;O:8:"stdClass":0:{}i:1;r:2;}';
if ($bug_test_serialized !== $reference_serialized) {
echo "serialize() on shutdown does not match reference:\n";
echo "$bug_test_serialized\n";
echo "should be:\n";
echo "$reference_serialized\n";
}
}
// records $test into trace
function crash_step_1($test) {
crash_step_2(debug_backtrace());
}
// works on the first trace and serializes it, which triggers the second trace
function crash_step_2($trace1) {
$new_trace = [];
foreach ($trace1 as $traceline) {
if (isset($traceline['args'])) {
$new_args = [];
foreach ($traceline['args'] as $traceline_arg) {
$new_args[] = $traceline_arg;
}
$traceline['args'] = $new_args;
// this fails as well
# $traceline['args'] = $traceline['args'];
}
$new_trace[] = $traceline;
}
return serialize($new_trace);
}
class class_crash_step_3 {
protected function trace2() {
$trace2 = debug_backtrace();
$var_list = [];
foreach ($trace2 as $traceline) {
if (!isset($traceline['args'])) continue;
foreach ($traceline['args'] as $tracearg) {
$var_idx = false;
foreach ($var_list as $found_idx => $var_list_item) {
// eventually, this comparison triggers the Fatal error
if ($var_list_item === $tracearg) {
$var_idx = $found_idx;
break;
}
}
if ($var_idx === false) {
$var_list[] = $tracearg;
}
}
}
}
}
// this one triggers "Fatal error: Nesting level too deep - recursive dependency?"
// and corrupts serialize() during shutdown, also affecting session handlers
class class_crash_step_3_variant_A extends class_crash_step_3 implements Serializable {
public function serialize() {
$this->trace2();
}
public function unserialize($data) { }
}
// interface Serializable is deprecated. with magic methods, corruption does not occur,
// but "Fatal error: Nesting level too deep - recursive dependency?" is still triggered
class class_crash_step_3_variant_B extends class_crash_step_3 {
public function __serialize() {
$this->trace2();
}
public function __unserialize($data) { }
}
$test = new class_crash_step_3_variant_A();
#$test = new class_crash_step_3_variant_B();
crash_step_1($test);
echo "End of script";
Resulted in this output:
Fatal error: Nesting level too deep - recursive dependency? in /in/trjSb on line 60
Warning: unserialize(): Error at offset 36 of 37 bytes in /in/trjSb on line 10
serialize() / unserialize() round trip failed on shutdown
serialize() on shutdown does not match reference:
a:2:{i:0;O:8:"stdClass":0:{}i:1;r:9;}
should be:
a:2:{i:0;O:8:"stdClass":0:{}i:1;r:2;}
But I expected this output instead:
End of script
The bug appeared in 7.3.0 and is present up to 8.3.8.
5.4.0 - 7.2.34 are not affected.
It is not synthetic - this has hit me in production (corrupted user sessions). It's a minimum example as small as I could make it.
3v4l Link: https://3v4l.org/9qWTh
PHP Version
7.3.0 - 8.3.8
Operating System
No response