[php-src] Issue #20854: OSS-Fuzz #471993725: Creation of dynamic property on lazy object via return-by-ref
[email protected] (iluuu1994)
| Newsgroups | php.bugs |
|---|---|
| Message-ID | <[email protected]> |
Issue: https://github.com/php/php-src/issues/20854
Author: iluuu1994
### Description
The following code:
```php
<?php
class C {
public $prop;
function &__get($name) {
return $this->x;
}
}
$rc = new ReflectionClass(C::class);
$obj = $rc->newLazyProxy(function () {
return new C;
});
$obj->x;
```
Resulted in this output:
```
Deprecated: Creation of dynamic property C::$x is deprecated in oss-fuzz-471993725.php on line 7
Warning: Undefined property: C::$x in Zend/tests/oss-fuzz-471993725.php on line 7
php: Zend/zend_vm_execute.h:23271: ZEND_RETURN_BY_REF_SPEC_VAR_HANDLER: Assertion `retval_ptr != &(executor_globals.uninitialized_zval)' failed.
Termsig=6
```
- The first `FETCH_OBJ_R` will call `__get`, `x` is guarded on the proxy.
- The access in `__get` compiles to `FETCH_OBJ_W`, which will call `get_property_ptr_ptr()` on the proxy.
- `x` is guarded, so the property access is attempted.
- Because the proxy is lazy, the object is initialized, `zobj` is replaced.
- `get_property_ptr_ptr()` is repeated, but this time on the underlying object.
- This time it fails, because `x` is unguarded on the underlying, so the code assumes `read_property()` will be called.
- However, we're inside the nested `get_property_ptr_ptr()` call here, and will just propagate `NULL`.
- We'll now return to the outer `FETCH_OBJ_W` call on the proxy `get_property_ptr_ptr()` has returned `NULL`, and `read_property()` `&EG(uninitialized_zval)`, even though the offset is guarded, which normally doesn't happen.
I don't know exactly what the best approach is to fix this. Intuitively I'd expect the guards for both the proxy and underlying object to be shared, which should make this problem go away. But that's not completely straight-forward to implement, because the lazy object might use guards before the underlying object even exists, and the underlying object doesn't have a fast, direct reference to the proxy (only through `EG(lazy_objects_store)`. This would be ok if we knew the object is an underlying object of a proxy, but there's no such flag either, and performing the lookup on any object is obviously bad.
The other approach would be to copy the guard to the underlying object before calling `get_property_ptr_ptr()` on it again. There are multiple places where this would need to be adjusted, and this still wouldn't solve the expectation when handling the underlying object directly elsewhere.
/cc @arnaud-lb I hope my description was understandable. Do you have any suggestions?
### PHP Version
```plain
PHP 8.4+
```
### Operating System
_No response_