php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #52879 Objects unreferenced in __get, __set, __isset or __unset can be freed too early
Submitted: 2010-09-18 07:17 UTC Modified: 2010-10-01 11:50 UTC
From: mail_ben_schmidt at yahoo dot com dot au Assigned: dmitry (profile)
Status: Closed Package: Scripting Engine problem
PHP Version: 5.3.3 OS: Mac OS X 10.6.4
Private report: No CVE-ID: None
 [2010-09-18 07:17 UTC] mail_ben_schmidt at yahoo dot com dot au
Description:
------------
When a __set(), __get(), __isset() or __unset() method is called on an object accessed through a reference, the refcount can fall to zero (causing the object to be destroyed) before the mechanism to avoid recursion has finished with the object and memory corruption results. These situations are rare, but possible and useful.

More specifically, with respect to __set(). Consider this php code:

class MyClass {
	private $myRef;
	public function __construct(&$myReferent) {
		$this->myRef=&$myReferent;
	}
	public function __set($property,$value) {
		$this->myRef=null;
	}
}
$myGlobal=new MyClass($myGlobal);
$myGlobal->myNonExistentProperty="triggers __set";

After the object is constructed, $myGlobal and $myGlobal->myRef are both references to the same variable (and thus both refer to the object). When __set() is called, myRef, and thus myGlobal, is changed. As the method exits, $this also goes out of scope. There are no references remaining to the object, so it is destroyed.

However, this happens slightly too soon in the Zend engine. In zend_std_write_property, `object' is $myGlobal through which the object is accessed. Since this is a reference, the call to zend_std_call_setter results in `object' being changed to a null zval, and the object being freed, but `guard' is then accessed, which was part of the now-freed object.

The (a?) solution is to separate `object' so that it is not changed by zend_std_call_setter, and we therefore retain a reference to the object which prevents it from being freed until we have finished with the guard. It is freed when zval_ptr_dtor is called on `object'.

Patch attached. I have debugged and tested fairly carefully for the __set() case, which was what led me to find the bug. The problem exists in this case and is fixed by the patch. Larger test cases I have which exhibited heap corruption and segfaults or bus errors as side-effects of the bug are also fixed by the patch. I have neither confirmed the same problem exists, nor that the patch fixes it, for the __get(), isset() and unset() cases, but see no reason they would be any different. The test suite runs just as well with the patch as without.

If it helps, I found the easiest way to explore this is to set ZEND_DEBUG_OBJECTS to 1 and DEBUG_ZEND to 2, break on zend_std_call_setter, step up a stack frame to zend_std_write_property and print *object, etc., step back down again, finish, so you will now be back in zend_std_write_property but after the call to zend_std_call_setter has completed, you can see the object deallocation on the console, and can again examine *object, etc..



Patches

zend_fix.diff (last revision 2010-09-18 05:17 UTC by mail_ben_schmidt at yahoo dot com dot au)

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2010-09-21 23:38 UTC] felipe@php.net
-Status: Open +Status: Assigned -Assigned To: +Assigned To: dmitry
 [2010-10-01 11:49 UTC] dmitry@php.net
Automatic comment from SVN on behalf of dmitry
Revision: http://svn.php.net/viewvc/?view=revision&revision=303913
Log: Fixed bug #52879 (Objects unreferenced in __get, __set, __isset or __unset can be freed too early). (mail_ben_schmidt at yahoo dot com dot au, Dmitry)
 [2010-10-01 11:50 UTC] dmitry@php.net
-Status: Assigned +Status: Closed
 [2010-10-01 11:50 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: Mon Dec 30 14:01:28 2024 UTC