From a0f775683d289dd517781d70c5962916bc4f2113 Mon Sep 17 00:00:00 2001 From: Lee Rowlands Date: Thu, 7 Nov 2024 11:49:11 +1000 Subject: [PATCH] Fix recursion when arrays contain self-references in sandboxed mode --- src/Extension/SandboxExtension.php | 11 ++++++++++- tests/Extension/SandboxTest.php | 12 ++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/Extension/SandboxExtension.php b/src/Extension/SandboxExtension.php index 95b6295aa2..9603f8e975 100644 --- a/src/Extension/SandboxExtension.php +++ b/src/Extension/SandboxExtension.php @@ -26,11 +26,14 @@ final class SandboxExtension extends AbstractExtension private $policy; private $sourcePolicy; + static array $recursionProjection = []; + public function __construct(SecurityPolicyInterface $policy, $sandboxed = false, ?SourcePolicyInterface $sourcePolicy = null) { $this->policy = $policy; $this->sandboxedGlobally = $sandboxed; $this->sourcePolicy = $sourcePolicy; + static::$recursionProjection = []; } public function getTokenParsers(): array @@ -120,10 +123,16 @@ public function checkPropertyAllowed($obj, $property, int $lineno = -1, ?Source public function ensureToStringAllowed($obj, int $lineno = -1, ?Source $source = null) { if (\is_array($obj)) { + $hash = \hash('sha256', \serialize($obj)); + if (\array_key_exists($hash, static::$recursionProjection)) { + unset(static::$recursionProjection[$hash]); + return $obj; + } + static::$recursionProjection[$hash] = TRUE; foreach ($obj as $v) { $this->ensureToStringAllowed($v, $lineno, $source); } - + unset(static::$recursionProjection[$hash]); return $obj; } diff --git a/tests/Extension/SandboxTest.php b/tests/Extension/SandboxTest.php index cc74e8cd56..647ea29534 100644 --- a/tests/Extension/SandboxTest.php +++ b/tests/Extension/SandboxTest.php @@ -41,7 +41,10 @@ protected function setUp(): void 'some_array' => [5, 6, 7, new FooObject()], 'array_like' => new ArrayLikeObject(), 'magic' => new MagicObject(), + 'recursion' => [4], ]; + self::$params['recursion'][] = &self::$params['recursion']; + self::$params['recursion'][] = new FooObject(); self::$templates = [ '1_basic1' => '{{ obj.foo }}', @@ -240,6 +243,7 @@ public function getSandboxUnallowedToStringTests() 'context' => ['{{ _context|join(", ") }}'], 'spread_array_operator' => ['{{ [1, 2, ...[5, 6, 7, obj]]|join(",") }}'], 'spread_array_operator_var' => ['{{ [1, 2, ...some_array]|join(",") }}'], + 'recursion' => ['{{ recursion|join(", ") }}'], ]; } @@ -573,13 +577,13 @@ class ArrayLikeObject extends \ArrayObject { public function offsetExists($offset): bool { - throw new \BadMethodCallException('Should not be called'); + throw new \BadMethodCallException('Should not be called.'); } #[\ReturnTypeWillChange] public function offsetGet($offset) { - throw new \BadMethodCallException('Should not be called'); + throw new \BadMethodCallException('Should not be called.'); } public function offsetSet($offset, $value): void @@ -596,11 +600,11 @@ class MagicObject #[\ReturnTypeWillChange] public function __get($name) { - throw new \BadMethodCallException('Should not be called'); + throw new \BadMethodCallException('Should not be called.'); } public function __isset($name): bool { - throw new \BadMethodCallException('Should not be called'); + throw new \BadMethodCallException('Should not be called.'); } }