Bug #66052 [NEW]: Serialized value ids are shared between nested serialization operations

From: 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

« previous php.bugs (#182636) next »