Description
The following code:
<?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
Operating System
No response
Description
The following code:
Resulted in this output:
FETCH_OBJ_Rwill call__get,xis guarded on the proxy.__getcompiles toFETCH_OBJ_W, which will callget_property_ptr_ptr()on the proxy.xis guarded, so the property access is attempted.zobjis replaced.get_property_ptr_ptr()is repeated, but this time on the underlying object.xis unguarded on the underlying, so the code assumesread_property()will be called.get_property_ptr_ptr()call here, and will just propagateNULL.FETCH_OBJ_Wcall on the proxyget_property_ptr_ptr()has returnedNULL, andread_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
Operating System
No response