[php-src] PR #24620: Fix GH-24615: ArrayObject backing storage cycle causes unbounded recursion
| From: | Marti2203 | Date: | Fri, 23 Oct 2026 18:31:37 +0000 |
| Subject: | [php-src] PR #24620: Fix GH-24615: ArrayObject backing storage cycle causes unbounded recursion | ||
| Groups: | php.git-pulls | ||
| Request: | Send a blank email to git-pulls+get-39784@lists.php.net to get a copy of this message | ||
Pull Request: https://github.com/php/php-src/pull/24620
Author: Marti2203
spl_array_set_array() accepts another
ArrayObject/ArrayIterator as storage (SPL_ARRAY_USE_OTHER)
and only special-cases an object that uses itself (SPL_ARRAY_IS_SELF). Two objects that
use each other as storage are accepted, and the next access to the storage recurses in
spl_array_get_hash_table_ptr() until the stack overflows:
```php
$a = new ArrayObject([1]);
$b = new ArrayObject($a); // $b uses $a's storage
$a->exchangeArray($b); // $a now uses $b's storage: a cycle
var_dump(count($a)); // stack overflow
```
The same cycle can be built through __construct(),
__unserialize()/unserialize(), with ArrayIterator, and over
longer chains.
### Fix
Before accepting another ArrayObject/ArrayIterator as storage, follow its
chain of SPL_ARRAY_USE_OTHER objects and throw an InvalidArgumentException
("Cannot use ArrayObject as storage of ArrayObject, as it would create a cycle") if the
chain leads back to the object. This is done before anything is changed, so the object keeps its
previous storage. Every path that sets the storage goes through spl_array_set_array(),
so all of the above are covered. The walk needs no bound: each object has at most one storage
object, and with this check the chains stay acyclic. Using an object as its own storage and sharing
storage without a cycle still work.
### Test
ext/spl/tests/gh24615.phpt builds two-object and three-object cycles (through
exchangeArray(), __construct() and __unserialize(), with an
ArrayIterator in the chain). It checks that each attempt throws and leaves the objects
usable, and that self storage and acyclic sharing still work.
- Without the fix: the test crashes (Termsig=11, stack overflow in
spl_array_get_hash_table_ptr()).
- With the fix: passes.
- ext/spl/tests (debug build): 768 passed, 9 skipped, 0 failed.
The branch also merges cleanly into PHP-8.5 and master. On master, object storage for
ArrayObject is deprecated, so the test will need the deprecation notices in its
expected output (or E_DEPRECATED turned off) after merging up.
Fixes GH-24615
This issue was found by an LLM-Enhanced Static Analysis Engine and verified manually; the patch was
manually reviewed.
🤖 Generated with [Claude Code](https://claude.com/claude-code)