php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #60701 __toString() which stores $this reference triggers segfault (with fix!)
Submitted: 2012-01-10 16:43 UTC Modified: 2012-04-26 12:53 UTC
Votes:10
Avg. Score:4.8 ± 0.4
Reproduced:10 of 10 (100.0%)
Same Version:3 (30.0%)
Same OS:4 (40.0%)
From: daan at react dot com Assigned:
Status: Duplicate Package: Reproducible crash
PHP Version: 5.3.8 OS: CentOS
Private report: No CVE-ID: None
 [2012-01-10 16:43 UTC] daan at react dot com
Description:
------------
A simple object construction where a __toString() stores $this, will trigger a 
segfault during garbage collection at the end of the request.

Probably related bug: https://bugs.php.net/bug.php?id=60598
Is a distilled version of this bug: https://bugs.php.net/bug.php?id=60457

Test script:
---------------
<?php
class Container
{
	public function getObject()
	{
		$this->object = new StringableObject();

		return $this->object;
	}

	// This destructor is required to exist to trigger the segfault
	public function __destruct()
	{
	}
}

class StringableObject
{
	public function __toString()
	{
		$this->test = $this;

		return '';
	}
}

$container = new Container();
$object = $container->getObject();

// Any kind of function which triggers a 'to string' object conversion
// Casting $object with (string) will circumvent the problem
echo trim($object);
// Another call is required to corrupt heap
echo trim('test');


Expected result:
----------------
test

Actual result:
--------------
Segfault

gdb backtrace (with commandline PHP with build tag ZEND_DEBUG_OBJECTS)

[Thread debugging using libthread_db enabled]
Allocated object id #1
Allocated object id #2
Increased refcount of object id #2
Decreased refcount of object id #2
testIncreased refcount of object id #1
Decreased refcount of object id #1
Deallocated object id #1

Program received signal SIGSEGV, Segmentation fault.
0x00000000006d4c69 in gc_zval_possible_root (zv=0x1023e40) at /home/sjon/php-
debug/php-5.3.8/Zend/zend_gc.c:143
143            GC_ZOBJ_CHECK_POSSIBLE_ROOT(zv);
(gdb) bt
#0  0x00000000006d4c69 in gc_zval_possible_root (zv=0x1023e40) at 
/home/sjon/php-debug/php-5.3.8/Zend/zend_gc.c:143
#1  0x00000000006c4ad8 in zend_hash_destroy (ht=0x10266d0) at /home/sjon/php-
debug/php-5.3.8/Zend/zend_hash.c:529
#2  0x00000000006d6009 in zend_object_std_dtor (object=0x1023dc8) at 
/home/sjon/php-debug/php-5.3.8/Zend/zend_objects.c:45
#3  0x00000000006d6029 in zend_objects_free_object_storage (object=0x1023dc8) at 
/home/sjon/php-debug/php-5.3.8/Zend/zend_objects.c:126
#4  0x00000000006da037 in zend_objects_store_del_ref_by_handle_ex (handle=2, 
handlers=<optimized out>) at /home/sjon/php-debug/php-
5.3.8/Zend/zend_objects_API.c:220
#5  0x00000000006da053 in zend_objects_store_del_ref (zobject=0x1022350) at 
/home/sjon/php-debug/php-5.3.8/Zend/zend_objects_API.c:172
#6  0x00000000006a9571 in _zval_dtor (zvalue=0x1022350) at /home/sjon/php-
debug/php-5.3.8/Zend/zend_variables.h:35
#7  _zval_ptr_dtor (zval_ptr=<optimized out>) at /home/sjon/php-debug/php-
5.3.8/Zend/zend_execute_API.c:447
#8  0x00000000006c3645 in zend_hash_apply_deleter (ht=0xe33188, p=0x1026728) at 
/home/sjon/php-debug/php-5.3.8/Zend/zend_hash.c:612
#9  0x00000000006c4f81 in zend_hash_reverse_apply (ht=0xe33188, 
apply_func=0x6a9430 <zval_call_destructor>) at /home/sjon/php-debug/php-
5.3.8/Zend/zend_hash.c:762
#10 0x00000000006a9921 in shutdown_destructors () at /home/sjon/php-debug/php-
5.3.8/Zend/zend_execute_API.c:226
#11 0x00000000006b7747 in zend_call_destructors () at /home/sjon/php-debug/php-
5.3.8/Zend/zend.c:875
#12 0x00000000006651fd in php_request_shutdown (dummy=<optimized out>) at 
/home/sjon/php-debug/php-5.3.8/main/main.c:1594
#13 0x000000000042d105 in main (argc=2, argv=0x7fffffffebb8) at /home/sjon/php-
debug/php-5.3.8/sapi/cli/php_cli.c:1363
(gdb) frame 2
#2  0x00000000006d6009 in zend_object_std_dtor (object=0x1023dc8) at 
/home/sjon/php-debug/php-5.3.8/Zend/zend_objects.c:45
45            zend_hash_destroy(object->properties);
(gdb) print object->ce->name
$1 = 0x1025af0 "StringableObject" 
(gdb) frame 1
#1  0x00000000006c4ad8 in zend_hash_destroy (ht=0x10266d0) at /home/sjon/php-
debug/php-5.3.8/Zend/zend_hash.c:529
529                ht->pDestructor(q->pData);
(gdb) print_ht ht
[0x010266d0] {
  "test\0" => [0x01023e40] (refcount=-1) object
Program received signal SIGSEGV, Segmentation fault.
0x00000000006da0a4 in zend_object_store_get_object (zobject=0x1023e40) at 
/home/sjon/php-debug/php-5.3.8/Zend/zend_objects_API.c:272
272        return EG(objects_store).object_buckets[handle].bucket.obj.object;
The program being debugged was signaled while in a function called from GDB.
GDB remains in the frame where the signal was received.
To change this behavior use "set unwindonsignal on".
Evaluation of the expression containing the function
(zend_objects_get_address) will be abandoned.
When the function is done executing, GDB will silently stop.
(gdb) 


Patches

bug60701.patch (last revision 2012-01-20 13:30 UTC by hans at rakers dot org)

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2012-01-10 19:22 UTC] sjon at hortensius dot net
This bug is not reproducible when php is compiled with enable-debug. It is 
however reproducible in other PHP versions, such as 5.3.7/5.3.6/5.3.5
 [2012-01-20 13:28 UTC] hans at rakers dot org
This bug is caused by zend_std_cast_object_tostring() not checking the refcount of readobj when readobj==writeobj. It calls INIT_PZVAL(writeobj) without checking the refcount first, causing any further references to this zval to get corrupted (in this case, the 'test' property of StringableObject).

My patch against 5.3 is attached.
 [2012-01-25 09:20 UTC] daan at react dot com
Working patch fix now included! (tm)
 [2012-01-25 09:20 UTC] daan at react dot com
-Summary: __toString() which stores $this reference triggers segfault +Summary: __toString() which stores $this reference triggers segfault (with fix!) -Package: Class/Object related +Package: Reproducible crash
 [2012-01-30 21:58 UTC] felipe@php.net
-Status: Open +Status: Assigned -Assigned To: +Assigned To: dmitry
 [2012-02-11 00:49 UTC] andrew at localcoast dot net
I can produce a similar issue on PHP 5.3.10 on Ubuntu 10.04 LTS x86_64 with the patch applied. However, the initial test script provided in the first comment runs without trouble.


Here's the backtrace for the issue I am having:

http://paste2.org/p/1900387
#0  0x00007f71fa9b8d11 in gc_zval_possible_root (zv=0x7f7201483740) at /home/andrew/.Applications/build/php-5.3.10-patched/Zend/zend_gc.c:143
#1  0x00007f71fa9a777b in zend_hash_destroy (ht=0x7f7201496908) at /home/andrew/.Applications/build/php-5.3.10-patched/Zend/zend_hash.c:529
        p = 0x7f7201497c58
#2  0x00007f71fa9ba379 in zend_object_std_dtor (object=0x7f7201497428) at /home/andrew/.Applications/build/php-5.3.10-patched/Zend/zend_objects.c:45
#3  0x00007f71fa9ba399 in zend_objects_free_object_storage (object=0x7f7201483740) at /home/andrew/.Applications/build/php-5.3.10-patched/Zend/zend_objects.c:126
#4  0x00007f71fa9bdba8 in zend_objects_store_free_object_storage (objects=0x7f71fb162a18) at /home/andrew/.Applications/build/php-5.3.10-patched/Zend/zend_objects_API.c:92
        i = 626
#5  0x00007f71fa98ebfb in shutdown_executor () at /home/andrew/.Applications/build/php-5.3.10-patched/Zend/zend_execute_API.c:304
        __bailout = {{__jmpbuf = {140127520564832, -3282099667358606386, 140127614418320, 0, -4294967295, 140127589456664, -3211606770996110386, -3282099660654535730}, __mask_was_saved = 0, __saved_mask = {__val = {140127612053568, 96, 140127512287676, 
                140127629890216, 140127638595144, 88, 140127512287676, 592, 140127512287676, 140127520566336, 140127520563352, 140127520564648, 0, 18446744069414584321, 140127512403989, 140127520566336}}}}
#6  0x00007f71fa99b612 in zend_deactivate () at /home/andrew/.Applications/build/php-5.3.10-patched/Zend/zend.c:891
#7  0x00007f71fa947ad5 in php_request_shutdown (dummy=<value optimized out>) at /home/andrew/.Applications/build/php-5.3.10-patched/main/main.c:1661
        report_memleaks = 1 '\001'
#8  0x00007f71faa24a97 in php_apache_request_dtor (r=<value optimized out>) at /home/andrew/.Applications/build/php-5.3.10-patched/sapi/apache2handler/sapi_apache2.c:509
#9  php_handler (r=<value optimized out>) at /home/andrew/.Applications/build/php-5.3.10-patched/sapi/apache2handler/sapi_apache2.c:681
        ctx = 0x7f7200ae5840
        conf = 0x7f7200689c98
        brigade = 0x7f7200ae6658
        bucket = <value optimized out>
        rv = <value optimized out>
        parent_req = 0x0
#10 0x00007f71ff0e3280 in ap_run_handler (r=0x7f7200ae3d90) at /build/buildd/apache2-2.2.14/server/config.c:159
        n = 6
        rv = -2039876096
#11 0x00007f71ff0e6be8 in ap_invoke_handler (r=0x7f7200ae3d90) at /build/buildd/apache2-2.2.14/server/config.c:373
        handler = 0x7f7200ad61d8 "Xa\255"
        result = 11362776
        old_handler = 0x7f7200792ec8 "application/x-httpd-php"
        ignore = <value optimized out>
#12 0x00007f71ff0f45fc in ap_internal_redirect (new_uri=<value optimized out>, r=<value optimized out>) at /build/buildd/apache2-2.2.14/modules/http/http_request.c:501
        new = 0x7f7200ae3d90
        access_status = -2039876096
#13 0x00007f71f664dc95 in ?? () from /usr/lib/apache2/modules/mod_rewrite.so
No symbol table info available.
#14 0x00007f71ff0e3280 in ap_run_handler (r=0x7f7200ad61d8) at /build/buildd/apache2-2.2.14/server/config.c:159
        n = 7
        rv = -2039876096
#15 0x00007f71ff0e6be8 in ap_invoke_handler (r=0x7f7200ad61d8) at /build/buildd/apache2-2.2.14/server/config.c:373
        handler = 0x0
        result = 0
        old_handler = 0x7f71f6651e58 "redirect-handler"
        ignore = <value optimized out>
#16 0x00007f71ff0f47d8 in ap_process_request (r=0x7f7200ad61d8) at /build/buildd/apache2-2.2.14/modules/http/http_request.c:282
        access_status = -2039876096
#17 0x00007f71ff0f1688 in ap_process_http_connection (c=0x7f7200ad0118) at /build/buildd/apache2-2.2.14/modules/http/http_core.c:190
        r = 0x7f7200ad61d8
        csd = 0x0
#18 0x00007f71ff0eae38 in ap_run_process_connection (c=0x7f7200ad0118) at /build/buildd/apache2-2.2.14/server/connection.c:43
        n = 2
        rv = -2039876096
#19 0x00007f71ff0f97a7 in child_main (child_num_arg=<value optimized out>) at /build/buildd/apache2-2.2.14/server/mpm/prefork/prefork.c:662
        current_conn = <value optimized out>
        csd = 0x7f7200acff28
        ptrans = 0x7f7200acfea8
        allocator = 0x7f7200acdda0
        status = <value optimized out>
        i = <value optimized out>
        lr = <value optimized out>
        pollset = 0x7f7200acdfc8
        sbh = 0x7f7200acdfc0
        bucket_alloc = 0x7f7200ad4148
        last_poll_idx = 0
#20 0x00007f71ff0f9a76 in make_child (s=0x7f72005dc938, slot=0) at /build/buildd/apache2-2.2.14/server/mpm/prefork/prefork.c:702
#21 0x00007f71ff0fa0c3 in ap_mpm_run (_pconf=<value optimized out>, plog=<value optimized out>, s=<value optimized out>) at /build/buildd/apache2-2.2.14/server/mpm/prefork/prefork.c:978
        index = <value optimized out>
        remaining_children_to_start = <value optimized out>
        rv = <value optimized out>
#22 0x00007f71ff0cf350 in main (argc=2, argv=0x7fff211385d8) at /build/buildd/apache2-2.2.14/server/main.c:742
        c = 88 'X'
        configtestonly = <value optimized out>
        confname = 0x7f71ff0fc08b "/etc/apache2/apache2.conf"
        def_server_root = 0x7f71ff0ffca3 ""
        temp_error_log = 0x0
        error = <value optimized out>
        process = 0x7f72005d4220
        server_conf = 0x7f72005dc938
        pglobal = 0x7f72005d4128
        pconf = 0x7f72005d6138
        plog = 0x7f720060a2d8
        ptemp = 0x7f72005de178
        pcommands = 0x7f72005d8148
        opt = 0x7f72005d8240
        rv = <value optimized out>
        mod = <value optimized out>
        optarg = 0x0

And my PHP compile time options:

./configure --prefix=/opt/php5.3.10 --with-apxs2=/usr/bin/apxs2  --with-config-file-scan-dir=/etc/php5/apache2/ --with-config-file-path=/etc/php5/apache2/ --with-mysql --with-pdo-mysql --enable-mbstring --with-mcrypt --with-mysqli --with-gd --with-curl --enable-exif --enable-ftp --enable-sockets --with-openssl

Using Xdebug's tracing features, I found that the last series of calls that were made were to ->__destruct() on our abstract model class. The code within our __destruct() sets instance variable arrays to new arrays, and sets object references to null.

When PHP is compiled with debug mode (--enable-debug) enabled, the issue can not be reproduced.
 [2012-02-13 18:00 UTC] pada at hrz dot tu-chemnitz dot de
This patch does not work for me. I'm still experiencing SegFaults with the following code on CentOS 6.0 with php 5.3.3 and https://bugs.php.net/patch-display.php?bug_id=60701&patch=bug60701.patch&revision=1327066212 applied.

Test-Script:

<?php
class C{function f(){$this->o=new O();return$this->o;}function __destruct(){}}class O{function __toString(){$this->$this;}}$c=new C();$o=$c->f();trim($o);
?>

With the patch applied, I'm still getting SegFaults in /var/log/httpd/error_log, but no coredumps any more. This is very strange, since coredumping is correctly configured and with other reproducer scripts from other bugs I'm getting coredumps.
 [2012-02-13 19:16 UTC] sjon at hortensius dot net
@andrew at localcoast dot net
Did you try to remove all __toString methods from your application? If that 
didn't fix it you are experiencing another bug and will probably need to generate 
a small reproducing script yourself

@pada at hrz dot tu-chemnitz dot de

your problem has nothing to do with this bug, You are simply demonstrating a 
recursive loop.
 [2012-02-13 19:48 UTC] pada at hrz dot tu-chemnitz dot de
@sjon: Now, I retried with the original Test script from daan.
This patch works for me too, thanks :)
 [2012-03-26 07:21 UTC] stas@php.net
I'm not sure I understand the patch, especially this part: 

 					if (readobj == writeobj) {
+						if (Z_REFCOUNT_P(readobj) <= 1) 
{
+							INIT_PZVAL(writeobj);
+						}
 						zval_dtor(readobj);

It looks like you initializing the object and then immediately calling dtor on 
it (since readobj == writeobj). Could you explain why and what you are trying to 
do there?
 [2012-03-26 08:38 UTC] stas@php.net
I looked more into the code, and as far as I can see, the case where readobj == 
writeobj comes from parse_arg_object_to_string() - but there it is preceeded by 
SEPARATE_ZVAL_IF_NOT_REF(arg); so I don't see how you can get refcount > 1 there 
unless you have IS_REF. Something else is going on there...
 [2012-03-26 09:05 UTC] stas@php.net
OK, I think I know what may be going on here. What you're getting as $this in 
toString() is not a real $object but a copy what was generated by SEPARATE_ZVAL_IF_NOT_REF() in parse_arg_object_to_string(). If you save this 
copy, there might be trouble since it'd be destroyed by writeobj and later 
cleanup of the function arguments. However, the patch proposed doesn't seem to 
solve the problem completely since $this->test variable is still corrupted in 
this scenario...
 [2012-04-02 09:53 UTC] arjen at react dot com
5.3.10 fixed the attached testcase, a more simple test now fails.

<?php

class A {
	public $a;
	public function __toString()
	{
		$this->a = $this;
		return 'waa?';
	}
}

$a = new A;
echo trim($a);
echo trim($a->a);

Results:
5.3.0 - 5.3.9	waa?1
5.3.10 waa? Warning: trim() expects parameter 1 to be string, unknown given in 
/home/deployer/public_html/php/tmp/ba8096acaf18c52bc12e38619634c25b on line 14
5.4.0 Segmentationfault
 [2012-04-26 12:18 UTC] arjen at react dot com
5.3.10/5.4.0 fixed original testcase; see http://3v4l.org/sc0mQ
5.3.11/5.4.1 fixed modified testcase; see http://3v4l.org/iYBMZ

Please close this issue and mark as fixed!
 [2012-04-26 12:33 UTC] maarten@php.net
So this issue is apparently fixed thanks to https://bugs.php.net/bug.php?id=61165 (which is actually a dupe of 60701 all along) ?
 [2012-04-26 12:34 UTC] pajoye@php.net
-Status: Assigned +Status: Duplicate
 [2012-04-26 12:34 UTC] pajoye@php.net
Duplicate of #61165
 [2012-04-26 12:35 UTC] maarten@php.net
-Status: Duplicate +Status: Closed
 [2012-04-26 12:35 UTC] maarten@php.net
Fixed in 5.3.11 / 5.4.1 (see #61165 for revisions and tests)
 [2012-04-26 12:53 UTC] pajoye@php.net
Pls do not change the status. Duplicated means "see the other bug for any further 
info", Thanks.
 [2012-04-26 12:53 UTC] pajoye@php.net
-Status: Closed +Status: Duplicate -Assigned To: dmitry +Assigned To:
 [2012-04-26 13:18 UTC] maarten@php.net
OK, race condition, I was changing it from assigned to closed. (and yes, should've used duplicate instead)
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Thu Oct 31 23:01:28 2024 UTC