php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #51768 Fallback to read_property causes subsequent crash
Submitted: 2010-05-08 04:14 UTC Modified: 2010-05-09 17:21 UTC
From: cataphract@php.net Assigned:
Status: Open Package: Reproducible crash
PHP Version: 5.3.2 OS: Windows
Private report: No CVE-ID:
Have you experienced this issue?
Rate the importance of this bug to you:

 [2010-05-08 04:14 UTC] cataphract@php.net
Description:
------------
When the get_property_ptr_ptr handler is omitted, zend_fetch_property_address falls back to read_property, but then the behavior in cases such as
$a = &$obj->prop;
deviates from that of
$a = &$obj['prop'];
which properly emits an error.
The final result is a crash.



Test script:
---------------
exttest.h:

#ifndef PHP_EXTTEST_H
# define PHP_EXTTEST_H
# ifdef HAVE_CONFIG_H
#  include<config.h>
# endif
# include<php.h>
extern zend_module_entry exttest_module_entry;
#define phpext_exttest_ptr &exttest_module_entry
#endif

exttest.c:
#include "exttest.h"

static zend_object_handlers object_handlers;

static zend_object_value ce_create_object(zend_class_entry *class_type TSRMLS_DC)
{
    zend_object_value zov;
    zend_object       *zobj;

    zobj = emalloc(sizeof *zobj);
    zend_object_std_init(zobj, class_type TSRMLS_CC);

    zend_hash_copy(zobj->properties, &(class_type->default_properties),
        (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval*));
    zov.handle = zend_objects_store_put(zobj,
        (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 = &object_handlers;
    return zov;
}

ZEND_MODULE_STARTUP_D(exttest)
{
    zend_class_entry ce;
    zend_class_entry *ce_ptr;
    zval *property;

    ALLOC_PERMANENT_ZVAL(property);
    INIT_ZVAL(*property);
    Z_TYPE_P(property) = IS_LONG;
    Z_LVAL_P(property) = 20l;

    memcpy(&object_handlers, zend_get_std_object_handlers(),
        sizeof object_handlers);
    object_handlers.get_property_ptr_ptr = NULL;

    INIT_CLASS_ENTRY(ce, "TestClass", NULL);
    ce_ptr = zend_register_internal_class(&ce TSRMLS_CC);
    ce_ptr->create_object = ce_create_object;
    zend_declare_property_ex(ce_ptr, "prop", 4, property, ZEND_ACC_PUBLIC,
        NULL, 0 TSRMLS_CC);
}

zend_module_entry exttest_module_entry = {
    STANDARD_MODULE_HEADER,
    "exttest",
    NULL, /* Functions */
    ZEND_MODULE_STARTUP_N(exttest) , /* MINIT */
    NULL, /* MSHUTDOWN */
    NULL, /* RINIT */
    NULL, /* RSHUTDOWN */
    NULL, /* MINFO */
    NO_VERSION_YET,
    STANDARD_MODULE_PROPERTIES
};

ZEND_GET_MODULE(exttest)

config.m4:
PHP_ARG_ENABLE(exttest,
  [Whether to enable the "exttest" extension],
  [  enable-exttest         Enable "exttest" extension support])

if test $PHP_EXTTEST != "no"; then
  PHP_SUBST(EXTTEST_SHARED_LIBADD)
  PHP_NEW_EXTENSION(exttest, exttest.c, $ext_shared)
fi


test.php:
<?php
$obj = new TestClass();
debug_zval_dump($obj);
$a = &$obj->prop;
debug_zval_dump($obj);
debug_zval_dump(&$a);
$a = 40;
debug_zval_dump($obj);
debug_zval_dump(&$a);
unset($a);
debug_zval_dump($obj);


Expected result:
----------------
Expected an error saying the operation is not permitted (like when attempting $a = &obj['index'] where there's no get_property_ptr_ptr equivalent).

Actual result:
--------------
When thre's get_property_ptr_ptr:

object(TestClass)#1 (1) refcount(2){
  ["prop"]=>
  long(20) refcount(2)
}
object(TestClass)#1 (1) refcount(2){
  ["prop"]=>
  &long(20) refcount(2)
}
&long(20) refcount(3)
object(TestClass)#1 (1) refcount(2){
  ["prop"]=>
  &long(40) refcount(2)
}
&long(40) refcount(3)
object(TestClass)#1 (1) refcount(2){
  ["prop"]=>
  long(40) refcount(1)
}

When there's no get_property_ptr_ptr, zend_fetch_property_address falls back to read_propert. Something happens afterwards that provokes the crash.

object(TestClass)#1 (1) refcount(2){
  ["prop"]=>
  long(20) refcount(2)
}
object(TestClass)#1 (1) refcount(2){
  ["prop"]=>
  long(20) refcount(1)
}
&long(20) refcount(3)
object(TestClass)#1 (1) refcount(2){
  ["prop"]=>
  long(20) refcount(1)
}
&long(40) refcount(3)
object(TestClass)#1 (1) refcount(2){
  ["prop"]=>
  long(20) refcount(1)
}
Segmentation fault

#0  0x081d60ae in zend_mm_check_ptr (heap=0x83881b8, ptr=0x840ca90, silent=1,
    __zend_filename=0x83633a0 "/home/glopes/php/php-5.3.2/Zend/zend_variables.c", __zend_lineno=178,
    __zend_orig_filename=0x83621b8 "/home/glopes/php/php-5.3.2/Zend/zend_execute_API.c",
    __zend_orig_lineno=440) at /home/glopes/php/php-5.3.2/Zend/zend_alloc.c:1347
#1  0x081d768d in _zend_mm_free_int (heap=0x83881b8, p=0x840ca90,
    __zend_filename=0x83633a0 "/home/glopes/php/php-5.3.2/Zend/zend_variables.c", __zend_lineno=178,
    __zend_orig_filename=0x83621b8 "/home/glopes/php/php-5.3.2/Zend/zend_execute_API.c",
    __zend_orig_lineno=440) at /home/glopes/php/php-5.3.2/Zend/zend_alloc.c:1983
#2  0x081d86bb in _efree (ptr=0x840ca90,
    __zend_filename=0x83633a0 "/home/glopes/php/php-5.3.2/Zend/zend_variables.c", __zend_lineno=178,
    __zend_orig_filename=0x83621b8 "/home/glopes/php/php-5.3.2/Zend/zend_execute_API.c",
    __zend_orig_lineno=440) at /home/glopes/php/php-5.3.2/Zend/zend_alloc.c:2351
#3  0x081e9944 in _zval_ptr_dtor (zval_ptr=0x841d584,
    __zend_filename=0x83633a0 "/home/glopes/php/php-5.3.2/Zend/zend_variables.c", __zend_lineno=178)
    at /home/glopes/php/php-5.3.2/Zend/zend_execute_API.c:440
#4  0x081f7ca8 in _zval_ptr_dtor_wrapper (zval_ptr=0x841d584)
    at /home/glopes/php/php-5.3.2/Zend/zend_variables.c:178
#5  0x08207cf7 in zend_hash_destroy (ht=0x841d520) at /home/glopes/php/php-5.3.2/Zend/zend_hash.c:526
#6  0x0821d64d in zend_object_std_dtor (object=0x841ccf8)
    at /home/glopes/php/php-5.3.2/Zend/zend_objects.c:45
#7  0x0821d9a9 in zend_objects_free_object_storage (object=0x841ccf8)
    at /home/glopes/php/php-5.3.2/Zend/zend_objects.c:114
#8  0x0822252b in zend_objects_store_del_ref_by_handle_ex (handle=1, handlers=0xb7f1bd60)
    at /home/glopes/php/php-5.3.2/Zend/zend_objects_API.c:220
#9  0x08222320 in zend_objects_store_del_ref (zobject=0x841c45c)
    at /home/glopes/php/php-5.3.2/Zend/zend_objects_API.c:172
#10 0x081f7917 in _zval_dtor_func (zvalue=0x841c45c,
    __zend_filename=0x83621b8 "/home/glopes/php/php-5.3.2/Zend/zend_execute_API.c", __zend_lineno=439)
    at /home/glopes/php/php-5.3.2/Zend/zend_variables.c:52
#11 0x081e96cf in _zval_dtor (zvalue=0x841c45c,
    __zend_filename=0x83621b8 "/home/glopes/php/php-5.3.2/Zend/zend_execute_API.c", __zend_lineno=439)
    at /home/glopes/php/php-5.3.2/Zend/zend_variables.h:35
#12 0x081e9919 in _zval_ptr_dtor (zval_ptr=0x841d5d8,
    __zend_filename=0x83633a0 "/home/glopes/php/php-5.3.2/Zend/zend_variables.c", __zend_lineno=178)
    at /home/glopes/php/php-5.3.2/Zend/zend_execute_API.c:439
#13 0x081f7ca8 in _zval_ptr_dtor_wrapper (zval_ptr=0x841d5d8)
    at /home/glopes/php/php-5.3.2/Zend/zend_variables.c:178
#14 0x0820806e in zend_hash_apply_deleter (ht=0x83879d0, p=0x841d5cc)
    at /home/glopes/php/php-5.3.2/Zend/zend_hash.c:611
#15 0x08208596 in zend_hash_reverse_apply (ht=0x83879d0, apply_func=0x81e91bd <zval_call_destructor>)
    at /home/glopes/php/php-5.3.2/Zend/zend_hash.c:760
#16 0x081e924a in shutdown_destructors () at /home/glopes/php/php-5.3.2/Zend/zend_execute_API.c:226
#17 0x081f943c in zend_call_destructors () at /home/glopes/php/php-5.3.2/Zend/zend.c:874
#18 0x08190427 in php_request_shutdown (dummy=0x0) at /home/glopes/php/php-5.3.2/main/main.c:1587
#19 0x082c0086 in main (argc=2, argv=0xbfffe674) at /home/glopes/php/php-5.3.2/sapi/cli/php_cli.c:1373


Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2010-05-08 16:01 UTC] cataphract@php.net
I got it backwards in the expected behavior and a bit in the description.
What should give a fatal error is something like:
$obj->prop = &$a;
I said that
$a = &$obj['prop']
emits an error. It doesn't. It emits a notice if the zval* returned by the read_dimension handler does not return a reference (Indirect modification of overloaded element).
In fact both read_dimension and read_property without get_property_ptr_ptr behave correctly in the $obj->prop = &$a by emitting a fatal error: (Cannot assign by reference to overloaded object)

The expected behavior should then be either mimic read_dimension (accept only references) or just throw an error.
 [2010-05-09 17:21 UTC] cataphract@php.net
Apparently, not to break backwards compatibility, 
$a = &$obj->prop
should not accept only references, like read_dimension. The current behavior is to make a reference if the refcount is one (SEPARATE_ZVAL_TO_MAKE_IS_REF).
Anyway, in the test script the refcount is 2.
 
PHP Copyright © 2001-2014 The PHP Group
All rights reserved.
Last updated: Sun Apr 20 03:02:42 2014 UTC