Skip to content

OSS-Fuzz #471993725: Creation of dynamic property on lazy object via return-by-ref #20854

@iluuu1994

Description

@iluuu1994

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

PHP 8.4+

Operating System

No response

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions