[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_
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.