php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #52774 Proxy object's store free callback calls zval_ptor_dtor on already freed data
Submitted: 2010-09-04 03:43 UTC Modified: 2010-10-04 09:16 UTC
From: cataphract@php.net Assigned: dmitry (profile)
Status: Closed Package: Scripting Engine problem
PHP Version: trunk-SVN-2010-09-04 (SVN) OS:
Private report: No CVE-ID: None
Welcome back! If you're the original bug submitter, here's where you can edit the bug or add additional notes.
If you forgot your password, you can retrieve your password here.
Password:
Status:
Package:
Bug Type:
Summary:
From: cataphract@php.net
New email:
PHP Version: OS:

 

 [2010-09-04 03:43 UTC] cataphract@php.net
Description:
------------
Proxy objects have this structure:

typedef struct _zend_proxy_object {
	zval *object;
	zval *property;
} zend_proxy_object;

zend_object_create_proxy does this:

ZEND_API zval *zend_object_create_proxy(zval *object, zval *member TSRMLS_DC)
{
	zend_proxy_object *pobj = emalloc(sizeof(zend_proxy_object));
        /* ... */
	pobj->property = member;
	zval_add_ref(&pobj->property);
        /* ... */
}

The property field is used to store a zval that, in the get and set handlers can the passed to zend_proxy_object.object's read_property and write_property callbacks, so that the reads and writes in the proxy object can be proxied.

The store free callback for proxy objects calls zval_ptr_tor on &pobj->property:

ZEND_API void zend_objects_proxy_free_storage(zend_proxy_object *object TSRMLS_DC)
{
	zval_ptr_dtor(&object->object);
	zval_ptr_dtor(&object->property);
	efree(object);
}

However, on script cleanup, the destruction order is wrong. The zval stored in object->property is destroyed *before* the proxy object is.

Test script:
---------------
/* Extension */


typedef struct _proxy_test {
	zend_object std;
	long value;
} proxy_test;
static zend_class_entry *pt_ce_ptr;
static zend_object_handlers p_obj_handlers;
static zend_object_value p_ce_create_object(zend_class_entry *class_type TSRMLS_DC)
{
    zend_object_value zov;
    proxy_test       *pobj;

    pobj = emalloc(sizeof *pobj);
    zend_object_std_init((zend_object *) pobj, class_type TSRMLS_CC);
	pobj->value = 7;

    object_properties_init(&pobj->std, class_type);
    zov.handle = zend_objects_store_put(pobj,
        (zend_objects_store_dtor_t) zend_objects_destroy_object,
        (zend_objects_free_object_storage_t) zend_objects_free_object_storage,
        NULL TSRMLS_CC);
	zov.handlers = &p_obj_handlers;
    return zov;
}
zval *p_read_property(zval *object, zval *member, int type, const struct _zend_literal *key TSRMLS_DC)
{
	proxy_test *iobj = zend_object_store_get_object(object TSRMLS_CC);
	if (type == BP_VAR_W || type == BP_VAR_RW || type == BP_VAR_UNSET) {
		zval *ret = zend_object_create_proxy(object, member TSRMLS_CC);
		Z_DELREF_P(ret);
		return ret;
	} else {
		zval *ret;
		MAKE_STD_ZVAL(ret);
		ZVAL_LONG(ret, iobj->value);
		Z_DELREF_P(ret);
		return ret;
	}
}

void p_write_property(zval *object, zval *member, zval *value, const struct _zend_literal *key TSRMLS_DC)
{
	proxy_test *iobj = zend_object_store_get_object(object TSRMLS_CC);
	if (Z_TYPE_P(value) == IS_LONG) {
		iobj->value = Z_LVAL_P(value);
	}
}
zval **p_get_property_ptr_ptr(zval *object, zval *member, const struct _zend_literal *key TSRMLS_DC)
{
	return NULL;
}

ZEND_MODULE_STARTUP_D(testext)
{
	zend_class_entry ce;

	INIT_CLASS_ENTRY(ce, "ProxyTestClass", NULL);
	pt_ce_ptr = zend_register_internal_class(&ce TSRMLS_CC);
	pt_ce_ptr->create_object = p_ce_create_object;
    memcpy(&p_obj_handlers, zend_get_std_object_handlers(), sizeof p_obj_handlers);
	/* could be NULL, but an empty impl is better (see bug #51768) */
	p_obj_handlers.get_property_ptr_ptr = p_get_property_ptr_ptr;
	p_obj_handlers.read_property = p_read_property;
	p_obj_handlers.write_property = p_write_property;
}

/* Script */

<?php

$n = new ProxyTestClass();
$h =& $n->whatever;

Expected result:
----------------
The proxy object would be destroyed before the property zval encapsulated in it.

Actual result:
--------------
The property zval encapsulated in the proxy object is destroyed prematurely.

Breakpoint on zend_object_create_proxy. Set data breakpoint on &member->refcount__gc

Continue. Data breakpoint is hit (zval_add_ref(&pobj->property);). The refcount is now 3.

Continue. Data breakpoint is hit. Call stack:

>	msvcr100d.dll!memset(unsigned char * dst=0x0000005a, unsigned char value='`', unsigned long count=11071032)  Line 127	Asm
 	php5ts_debug.dll!_zend_mm_free_int(_zend_mm_heap * heap=0x02426d50, void * p=0x028101a0, const char * __zend_filename=0x5d637b70, const unsigned int __zend_lineno=397, const char * __zend_orig_filename=0x00000000, const unsigned int __zend_orig_lineno=0)  Line 2019 + 0x15 bytes	C
 	php5ts_debug.dll!_efree(void * ptr=0x028101a0, const char * __zend_filename=0x5d637b70, const unsigned int __zend_lineno=397, const char * __zend_orig_filename=0x00000000, const unsigned int __zend_orig_lineno=0)  Line 2378 + 0x2b bytes	C
 	php5ts_debug.dll!destroy_op_array(_zend_op_array * op_array=0x0280f470, void * * * tsrm_ls=0x024115f8)  Line 397 + 0x21 bytes	C
 	php5ts_debug.dll!zend_execute_scripts(int type=8, void * * * tsrm_ls=0x024115f8, _zval_struct * * retval=0x00000000, int file_count=3, ...)  Line 1220 + 0x1e bytes	C
 	php5ts_debug.dll!php_execute_script(_zend_file_handle * primary_file=0x00a8f72c, void * * * tsrm_ls=0x024115f8)  Line 2330 + 0x1b bytes	C
 	php.exe!main(int argc=2, char * * argv=0x024114c0)  Line 1252 + 0x13 bytes	C
 	php.exe!__tmainCRTStartup()  Line 555 + 0x19 bytes	C
 	php.exe!mainCRTStartup()  Line 371	C


efree() is being called with the refcount still being 3.

Breakpoint on zend_objects_proxy_free_storage. The call zval_ptr_dtor(&object->property); will operate on already freed data:

object
0x028102c0 {object=0x0280db38 property=0x028101d0 }
    object: 0x0280db38 {value={...} refcount__gc=2 type='' ...}
    property: 0x028101d0 {value={...} refcount__gc=1515870810 type='Z' ...}







Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2010-09-05 23:36 UTC] felipe@php.net
-Status: Open +Status: Assigned -Assigned To: +Assigned To: dmitry
 [2010-10-04 09:15 UTC] dmitry@php.net
Automatic comment from SVN on behalf of dmitry
Revision: http://svn.php.net/viewvc/?view=revision&amp;revision=303971
Log: - Fixed bug #52773 (Proxy objects have an inadequate destroy_object store callback)
- Fixed bug #52774 (Proxy object's store free callback calls zval_ptor_dtor on already freed data)
 [2010-10-04 09:16 UTC] dmitry@php.net
-Status: Assigned +Status: Closed
 [2010-10-04 09:16 UTC] dmitry@php.net
This bug has been fixed in SVN.

Snapshots of the sources are packaged every three hours; this change
will be in the next snapshot. You can grab the snapshot at
http://snaps.php.net/.
 
Thank you for the report, and for helping us make PHP better.


 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Fri Dec 27 00:01:30 2024 UTC