[GIT-PULLS] [php-src] PR #22714: Fix use-after-free serializing an array grown by an element's hook

[email protected] (iliaal)
Newsgroups php.git-pulls
Message-ID <BDXunTKkKGLhDD6ViPPl0bAZKo9TzSwKh2klz1CLtUA@main.internal.php.net>
Pull Request: https://github.com/php/php-src/pull/22714
Author: iliaal

The IS_ARRAY case of php_var_serialize_intern() walks the array's HashTable without holding a reference while php_var_serialize_nested_data() recurses into user hooks, so a __serialize() that grows the same array through a by-reference alias reallocs the backing store mid-walk and the iterator then reads freed memory. The object case already holds a ref (via zend_get_properties_for), as do var_dump and var_export; this adds the same GC_ADDREF/GC_DTOR_NO_REF around the array walk, so the mutating append separates a copy instead of reallocating in place. __sleep() and Serializable::serialize() reach the same walk, so the single test covers all three. Only an attacker-defined mutating hook can trigger this, so it is a memory-safety bug, not a security issue.

Reproducer (invalid read in php_var_serialize_nested_data under ASan/valgrind; a normal build usually reads the freed memory and passes, so the test is an ASan canary):

```php
<?php
class G {
    public $ref;
    public function __serialize(): array {
        for ($i = 0; $i < 128; $i++) $this->ref[] = 'x' . $i;
        return ['d' => 1];
    }
}
$g = new G();
$inner = [$g, 'tail'];
$g->ref = &$inner;
$top = [&$inner];
serialize($top);
```
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.