Bug #66052 [NEW]: Serialized value ids are shared between nested serialization operations
| From: | crog at gustavus dot edu | Date: | Thu, 07 Nov 2013 20:09:02 +0000 |
| Subject: | Bug #66052 [NEW]: Serialized value ids are shared between nested serialization operations | ||
| Groups: | php.bugs | ||
| Request: | Send a blank email to php-bugs+get-182636@lists.php.net to get a copy of this message | ||
From: crog at gustavus dot edu
Operating system:
PHP version: 5.4.21
Package: *General Issues
Bug Type: Bug
Bug description:Serialized value ids are shared between nested serialization operations
Description:
------------
The current serialization format does not specify value ids and instead
relies on a count from the beginning of the data. While this works fine
for "standard" serialization, it breaks horribly when custom
serialization (via Serializable) is thrown into the mix.
In the case in which we first discovered this issue, we have an object
which saves and loads its data to a file, database or whatever
(depending on the current implementation of the save() method). It can
then be saved/loaded via the save()/load() methods respectively, and
this works fine.
To ensure that we weren't wasting time/resources, we also implemented
the Serializable interface and a serialize method which simply calls
save() and returns a single string which can be used to identify the
location of our data (a filename if stored on the file system, row id if
in the database, etc.). Conversely, unserialize sets the source and
calls load(). This, again, works fine if used in isolation.
Now, where the problems arise is when the two are mixed. Our current
file system implementation of save() does its own serialization. If we
call save() to save our data, but then load through it through the
unserialize operation, the serialized data's reference counts are all
off by one (as the original serialization didn't go through serialize).
The same off-by-one bug shows up if this is done in reverse (saved
through serialize(), loaded through load()).
The effects of this bug are one of two things:
- The values simply point to the wrong variables. Very hard to debug and
incredibly confusing to anyone unfamiliar with the serialization
format.
- The values point to non-existent values (-1 or max +1). This causes an
error which reports the byte offset into the serialized data where the
error occurred.
The test below is rather convoluted for a test case, but demonstrates
the problem as it relates to the code in which we discovered it. I can,
if necessary, attempt to provide further explanation or additional
details.
Test script:
---------------
class ObjectWithReferences {
protected $var1;
protected $var2;
protected $var3;
public function __construct() {
$this->var1 = new StdClass();
$this->var2 = null;
$this->var3 = $this->var1;
}
}
class WrapperObject implements Serializable
{
static $tempstorage;
protected $obj;
public function setObject($obj) {
$this->obj = $obj;
}
public function getObject() {
if (is_string($this->obj)) {
$this->obj = unserialize($this->obj);
}
return $this->obj;
}
public function save() {
$archive = (object) [
'object' => is_string($this->obj) ? $this->obj :
serialize($this->obj),
];
static::$tempstorage = json_encode($archive);
var_dump("Serialized:", static::$tempstorage);
}
public function load() {
$archive = json_decode(static::$tempstorage);
$this->obj = $archive->object;
}
public function serialize() {
$this->save(false);
return "wrapper!"; // Doesn't actually matter here.
}
public function unserialize($serialized) {
$this->load();
}
}
$owr = new ObjectWithReferences();
$wrapper = new WrapperObject();
$wrapper->setObject($owr);
$wrapper->save();
$wrapper->load();
var_dump($wrapper->getObject());
$serialized = serialize($wrapper);
$wrapper = unserialize($serialized);
var_dump($wrapper->getObject());
Expected result:
----------------
string 'Serialized:' (length=11)
string
'{"object":"O:20:\"ObjectWithReferences\":3:{s:7:\"\u0000*\u0000var1\";O:8:\"stdClass\":0:{}s:7:\"\u0000*\u0000var2\";N;s:7:\"\u0000*\u0000var3\";r:2;}"}'
(length=152)
object(ObjectWithReferences)[9]
protected 'var1' =>
object(stdClass)[10]
protected 'var2' => null
protected 'var3' =>
object(stdClass)[10]
string 'Serialized:' (length=11)
string
'{"object":"O:20:\"ObjectWithReferences\":3:{s:7:\"\u0000*\u0000var1\";O:8:\"stdClass\":0:{}s:7:\"\u0000*\u0000var2\";N;s:7:\"\u0000*\u0000var3\";r:3;}"}'
(length=152)
object(ObjectWithReferences)[8]
protected 'var1' =>
object(stdClass)[9]
protected 'var2' => null
protected 'var3' =>
object(stdClass)[9]
Actual result:
--------------
string 'Serialized:' (length=11)
string
'{"object":"O:20:\"ObjectWithReferences\":3:{s:7:\"\u0000*\u0000var1\";O:8:\"stdClass\":0:{}s:7:\"\u0000*\u0000var2\";N;s:7:\"\u0000*\u0000var3\";r:2;}"}'
(length=152)
object(ObjectWithReferences)[9]
protected 'var1' =>
object(stdClass)[10]
protected 'var2' => null
protected 'var3' =>
object(stdClass)[10]
string 'Serialized:' (length=11)
string
'{"object":"O:20:\"ObjectWithReferences\":3:{s:7:\"\u0000*\u0000var1\";O:8:\"stdClass\":0:{}s:7:\"\u0000*\u0000var2\";N;s:7:\"\u0000*\u0000var3\";r:3;}"}'
(length=152)
object(ObjectWithReferences)[8]
protected 'var1' =>
object(stdClass)[9]
protected 'var2' => null
protected 'var3' => null
--
Edit bug report at https://bugs.php.net/bug.php?id=66052&edit=1
--
Try a snapshot (PHP 5.4): https://bugs.php.net/fix.php?id=66052&r=trysnapshot54
Try a snapshot (PHP 5.5): https://bugs.php.net/fix.php?id=66052&r=trysnapshot55
Try a snapshot (trunk): https://bugs.php.net/fix.php?id=66052&r=trysnapshottrunk
Fixed in SVN: https://bugs.php.net/fix.php?id=66052&r=fixed
Fixed in release: https://bugs.php.net/fix.php?id=66052&r=alreadyfixed
Need backtrace: https://bugs.php.net/fix.php?id=66052&r=needtrace
Need Reproduce Script: https://bugs.php.net/fix.php?id=66052&r=needscript
Try newer version: https://bugs.php.net/fix.php?id=66052&r=oldversion
Not developer issue: https://bugs.php.net/fix.php?id=66052&r=support
Expected behavior: https://bugs.php.net/fix.php?id=66052&r=notwrong
Not enough info: https://bugs.php.net/fix.php?id=66052&r=notenoughinfo
Submitted twice: https://bugs.php.net/fix.php?id=66052&r=submittedtwice
register_globals: https://bugs.php.net/fix.php?id=66052&r=globals
PHP 4 support discontinued: https://bugs.php.net/fix.php?id=66052&r=php4
Daylight Savings: https://bugs.php.net/fix.php?id=66052&r=dst
IIS Stability: https://bugs.php.net/fix.php?id=66052&r=isapi
Install GNU Sed: https://bugs.php.net/fix.php?id=66052&r=gnused
Floating point limitations: https://bugs.php.net/fix.php?id=66052&r=float
No Zend Extensions: https://bugs.php.net/fix.php?id=66052&r=nozend
MySQL Configuration Error: https://bugs.php.net/fix.php?id=66052&r=mysqlcfg