From 1ec0630262e63e395b173fad57bbca0c6e778612 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Deruss=C3=A9?= Date: Sat, 3 Oct 2020 13:52:44 +0200 Subject: [PATCH] Prevent user serializing the key --- .../Exception/UnserializableKeyException.php | 23 ++++++++++ src/Symfony/Component/Lock/Key.php | 17 ++++++++ .../Component/Lock/Store/FlockStore.php | 1 + .../Component/Lock/Store/SemaphoreStore.php | 1 + .../Component/Lock/Store/ZookeeperStore.php | 1 + src/Symfony/Component/Lock/Tests/KeyTest.php | 42 +++++++++++++++++++ .../Lock/Tests/Store/FlockStoreTest.php | 1 + .../Lock/Tests/Store/SemaphoreStoreTest.php | 1 + .../Tests/Store/UnserializableTestTrait.php | 42 +++++++++++++++++++ .../Lock/Tests/Store/ZookeeperStoreTest.php | 2 + 10 files changed, 131 insertions(+) create mode 100644 src/Symfony/Component/Lock/Exception/UnserializableKeyException.php create mode 100644 src/Symfony/Component/Lock/Tests/KeyTest.php create mode 100644 src/Symfony/Component/Lock/Tests/Store/UnserializableTestTrait.php diff --git a/src/Symfony/Component/Lock/Exception/UnserializableKeyException.php b/src/Symfony/Component/Lock/Exception/UnserializableKeyException.php new file mode 100644 index 0000000000000..287212675075a --- /dev/null +++ b/src/Symfony/Component/Lock/Exception/UnserializableKeyException.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Lock\Exception; + +/** + * UnserializableKeyException is thrown when the key contains state that can no + * be serialized and the user try to serialize it. + * ie. Connection with a database, flock, semaphore, ... + * + * @author Jérémy Derussé + */ +class UnserializableKeyException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/src/Symfony/Component/Lock/Key.php b/src/Symfony/Component/Lock/Key.php index bc09b888887b1..4d430503f5d44 100644 --- a/src/Symfony/Component/Lock/Key.php +++ b/src/Symfony/Component/Lock/Key.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Lock; +use Symfony\Component\Lock\Exception\UnserializableKeyException; + /** * Key is a container for the state of the locks in stores. * @@ -21,6 +23,7 @@ final class Key private $resource; private $expiringTime; private $state = []; + private $serializable = true; public function __construct(string $resource) { @@ -52,6 +55,11 @@ public function getState(string $stateKey) return $this->state[$stateKey]; } + public function markUnserializable(): void + { + $this->serializable = false; + } + public function resetLifetime() { $this->expiringTime = null; @@ -83,4 +91,13 @@ public function isExpired(): bool { return null !== $this->expiringTime && $this->expiringTime <= microtime(true); } + + public function __sleep(): array + { + if (!$this->serializable) { + throw new UnserializableKeyException('The key can not be serialized.'); + } + + return ['resource', 'expiringTime', 'state']; + } } diff --git a/src/Symfony/Component/Lock/Store/FlockStore.php b/src/Symfony/Component/Lock/Store/FlockStore.php index e6320b650a988..76e2b355c1854 100644 --- a/src/Symfony/Component/Lock/Store/FlockStore.php +++ b/src/Symfony/Component/Lock/Store/FlockStore.php @@ -125,6 +125,7 @@ private function lock(Key $key, bool $read, bool $blocking) } $key->setState(__CLASS__, [$read, $handle]); + $key->markUnserializable(); } /** diff --git a/src/Symfony/Component/Lock/Store/SemaphoreStore.php b/src/Symfony/Component/Lock/Store/SemaphoreStore.php index ae302ede9f6da..c4aa8e797bed8 100644 --- a/src/Symfony/Component/Lock/Store/SemaphoreStore.php +++ b/src/Symfony/Component/Lock/Store/SemaphoreStore.php @@ -76,6 +76,7 @@ private function lock(Key $key, bool $blocking) } $key->setState(__CLASS__, $resource); + $key->markUnserializable(); } /** diff --git a/src/Symfony/Component/Lock/Store/ZookeeperStore.php b/src/Symfony/Component/Lock/Store/ZookeeperStore.php index 0d86d2bf208bb..12c2386ad4160 100644 --- a/src/Symfony/Component/Lock/Store/ZookeeperStore.php +++ b/src/Symfony/Component/Lock/Store/ZookeeperStore.php @@ -65,6 +65,7 @@ public function save(Key $key) $token = $this->getUniqueToken($key); $this->createNewLock($resource, $token); + $key->markUnserializable(); $this->checkNotExpired($key); } diff --git a/src/Symfony/Component/Lock/Tests/KeyTest.php b/src/Symfony/Component/Lock/Tests/KeyTest.php new file mode 100644 index 0000000000000..77815b25d40ae --- /dev/null +++ b/src/Symfony/Component/Lock/Tests/KeyTest.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Lock\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Lock\Exception\UnserializableKeyException; +use Symfony\Component\Lock\Key; + +/** + * @author Jérémy Derussé + */ +class KeyTest extends TestCase +{ + public function testSerialize() + { + $key = new Key(__METHOD__); + $key->reduceLifetime(1); + $key->setState('foo', 'bar'); + + $copy = unserialize(serialize($key)); + $this->assertSame($key->getState('foo'), $copy->getState('foo')); + $this->assertEqualsWithDelta($key->getRemainingLifetime(), $copy->getRemainingLifetime(), 0.001); + } + + public function testUnserialize() + { + $key = new Key(__METHOD__); + $key->markUnserializable(); + + $this->expectException(UnserializableKeyException::class); + serialize($key); + } +} diff --git a/src/Symfony/Component/Lock/Tests/Store/FlockStoreTest.php b/src/Symfony/Component/Lock/Tests/Store/FlockStoreTest.php index f197a501cdd9c..d879c6ac256ff 100644 --- a/src/Symfony/Component/Lock/Tests/Store/FlockStoreTest.php +++ b/src/Symfony/Component/Lock/Tests/Store/FlockStoreTest.php @@ -22,6 +22,7 @@ class FlockStoreTest extends AbstractStoreTest { use BlockingStoreTestTrait; use SharedLockStoreTestTrait; + use UnserializableTestTrait; /** * {@inheritdoc} diff --git a/src/Symfony/Component/Lock/Tests/Store/SemaphoreStoreTest.php b/src/Symfony/Component/Lock/Tests/Store/SemaphoreStoreTest.php index 0611e2802c07c..72ffbdcad04b7 100644 --- a/src/Symfony/Component/Lock/Tests/Store/SemaphoreStoreTest.php +++ b/src/Symfony/Component/Lock/Tests/Store/SemaphoreStoreTest.php @@ -23,6 +23,7 @@ class SemaphoreStoreTest extends AbstractStoreTest { use BlockingStoreTestTrait; + use UnserializableTestTrait; /** * {@inheritdoc} diff --git a/src/Symfony/Component/Lock/Tests/Store/UnserializableTestTrait.php b/src/Symfony/Component/Lock/Tests/Store/UnserializableTestTrait.php new file mode 100644 index 0000000000000..6a9cf0bd49477 --- /dev/null +++ b/src/Symfony/Component/Lock/Tests/Store/UnserializableTestTrait.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Lock\Tests\Store; + +use Symfony\Component\Lock\Exception\UnserializableKeyException; +use Symfony\Component\Lock\Key; +use Symfony\Component\Lock\PersistingStoreInterface; + +/** + * @author Jérémy Derussé + */ +trait UnserializableTestTrait +{ + /** + * @see AbstractStoreTest::getStore() + * + * @return PersistingStoreInterface + */ + abstract protected function getStore(); + + public function testUnserializableKey() + { + $store = $this->getStore(); + + $key = new Key(uniqid(__METHOD__, true)); + + $store->save($key); + $this->assertTrue($store->exists($key)); + + $this->expectException(UnserializableKeyException::class); + serialize($key); + } +} diff --git a/src/Symfony/Component/Lock/Tests/Store/ZookeeperStoreTest.php b/src/Symfony/Component/Lock/Tests/Store/ZookeeperStoreTest.php index b3cd685a23160..7a443921f20d6 100644 --- a/src/Symfony/Component/Lock/Tests/Store/ZookeeperStoreTest.php +++ b/src/Symfony/Component/Lock/Tests/Store/ZookeeperStoreTest.php @@ -23,6 +23,8 @@ */ class ZookeeperStoreTest extends AbstractStoreTest { + use UnserializableTestTrait; + /** * @return ZookeeperStore */