diff --git a/classes/Visualizer/Module.php b/classes/Visualizer/Module.php index 99ac4a79..13d7cbfc 100644 --- a/classes/Visualizer/Module.php +++ b/classes/Visualizer/Module.php @@ -812,7 +812,36 @@ public static function decode_content( $content ) { if ( ! is_string( $content ) ) { return false; } - return self::strip_incomplete_objects( unserialize( trim( $content ), array( 'allowed_classes' => false ) ) ); + $value = unserialize( trim( $content ), array( 'allowed_classes' => false ) ); + if ( self::contains_references( $value ) ) { + return false; + } + return self::strip_incomplete_objects( $value ); + } + + /** + * Check decoded arrays for references before recursively processing them. + * + * Cyclic serialized arrays necessarily contain a reference. Rejecting all + * references also prevents shared references from becoming cycles later, + * so strip_incomplete_objects() cannot recurse without terminating. + * + * @param mixed $value The decoded value. + * @return bool Whether the value contains an array reference. + */ + private static function contains_references( $value ) { + if ( ! is_array( $value ) ) { + return false; + } + foreach ( array_keys( $value ) as $key ) { + if ( null !== ReflectionReference::fromArrayElement( $value, $key ) ) { + return true; + } + if ( is_array( $value[ $key ] ) && self::contains_references( $value[ $key ] ) ) { + return true; + } + } + return false; } /** diff --git a/tests/test-security-object-injection.php b/tests/test-security-object-injection.php index 15fdf9f6..d69ad917 100644 --- a/tests/test-security-object-injection.php +++ b/tests/test-security-object-injection.php @@ -113,6 +113,25 @@ public function test_decode_content_does_not_instantiate_objects() { ); } + /** + * The shared decoder must reject cyclic serialized arrays without recursing. + * + * The allowed_classes guard only blocks objects; a self-referential array + * (R:/r: token) survives it and would drive strip_incomplete_objects() into + * unbounded recursion. decode_content() must reject it up front. + */ + public function test_decode_content_rejects_cyclic_arrays() { + $this->assertFalse( + Visualizer_Module::decode_content( 'a:1:{i:0;R:1;}' ), + 'decode_content() must reject cyclic arrays.' + ); + $this->assertSame( + array( 'marker' => 'R:1;' ), + Visualizer_Module::decode_content( serialize( array( 'marker' => 'R:1;' ) ) ), + 'Reference-like text inside strings must remain valid.' + ); + } + /** * The Utility pie/polarArea render palette call site must not instantiate objects. *