zendframework / zend-session

Manage and preserve session data, a logical complement of cookie data, across multiple page requests by the same client.
BSD 3-Clause "New" or "Revised" License
42 stars 64 forks source link

Session unserialization does not work with shared object between Serializable objects with new PHP 7.4 #126

Closed syberon closed 4 years ago

syberon commented 4 years ago

After updating PHP to version 7.4 authorization stop working. On next page loading after success authorization i get this error:

Notice: unserialize(): Error at offset 43 of 224 bytes in C:\SERVER\projects\zf3\mt4\vendor\zendframework\zend-stdlib\src\ArrayObject.php on line 409

Notice: Trying to access array offset on value of type bool in C:\SERVER\projects\zf3\mt4\vendor\zendframework\zend-stdlib\src\ArrayObject.php on line 412

Notice: Trying to access array offset on value of type bool in C:\SERVER\projects\zf3\mt4\vendor\zendframework\zend-stdlib\src\ArrayObject.php on line 413

Warning: session_start(): Failed to decode session object. Session has been destroyed in C:\SERVER\projects\zf3\mt4\vendor\zendframework\zend-session\src\SessionManager.php on line 138

Fatal error: Uncaught Zend\Stdlib\Exception\InvalidArgumentException: Passed variable is not an array or object, using empty array instead in C:\SERVER\projects\zf3\mt4\vendor\zendframework\zend-stdlib\src\ArrayObject.php:184 Stack trace: #0 C:\SERVER\projects\zf3\mt4\vendor\zendframework\zend-stdlib\src\ArrayObject.php(413): Zend\Stdlib\ArrayObject->exchangeArray(NULL) zendframework/zend-authentication#1 [internal function]: Zend\Stdlib\ArrayObject->unserialize('a:4:{s:7:"stora...') zendframework/zend-authentication#2 C:\SERVER\projects\zf3\mt4\vendor\zendframework\zend-session\src\SessionManager.php(138): session_start() zendframework/zend-authentication#3 C:\SERVER\projects\zf3\mt4\vendor\zendframework\zend-session\src\AbstractContainer.php(81): Zend\Session\SessionManager->start() zendframework/zend-authentication#4 C:\SERVER\projects\zf3\mt4\vendor\zendframework\zend-authentication\src\Storage\Session.php(63): Zend\Session\AbstractContainer->__construct('Zend_Auth', NULL) zendframework/zend-authentication#5 C:\SERVER\projects\zf3\mt4\vendor\zendframework\zend-authentication\src\AuthenticationService.php(78): Zend\Authentication\Storage\Session->__construct() zendframework/zend-authentication#6 C:\SERVER\project in C:\SERVER\projects\zf3\mt4\vendor\zendframework\zend-stdlib\src\ArrayObject.php on line 184

After this error PHP destroys current session with authorized storage data and creates new. I found that error is appeared when PHP trying to unserialize data stored in session that related to 'Zend_Auth' namespace:

Zend_Auth|C:23:"Zend\Stdlib\ArrayObject":224:{a:4:{s:7:"storage";a:1:{s:7:"storage";r:20;}s:4:"flag";i:2;s:13:"iteratorClass";s:13:"ArrayIterator";s:19:"protectedProperties";a:4:{i:0;s:7:"storage";i:1;s:4:"flag";i:2;s:13:"iteratorClass";i:3;s:19:"protectedProperties";}}}

Error says that Error at offset 43 of 224 bytes and at this position is r:20 (reference to object in another session namespace)

michalbundyra commented 4 years ago

@syberon it looks like problem with serialising identity. What identity provider are you using? I've tried unserialise the string you have provided and I am getting the error on any PHP version.

syberon commented 4 years ago

I found that this problem is related to PHP 7.4 session unserializing. I made some tests and found that if some class (Zend\Stdlib\ArrayObject in this case) implements Serializable interface and has some property (object of stdClass for example) it failed to decode if stored in session.

I made a minimal reproducible example:

<?php
class SerializableClass implements Serializable {
    public $sharedProp;
    public function __construct($prop)
    {
        $this->sharedProp = $prop;
    }
    public function __set($key, $value)
    {
        $this->$key = $value;
    }
    public function serialize()
    {
        return serialize(get_object_vars($this));
    }
    public function unserialize($data)
    {
        $ar = unserialize($data);
        foreach ($ar as $k => $v) {
            $this->__set($k, $v);
        }
    }
}

// Shared object that acts as property of two another objects stored in session
$testPropertyObj = new stdClass();
$testPropertyObj->name = 'test';

// Two instances of \SerializableClass that shares property
$sessionObject = [
    'obj1' => new SerializableClass($testPropertyObj),
    'obj2' => new SerializableClass($testPropertyObj),
];
session_start();
$_SESSION = $sessionObject;

On first run it creates array with two instances of object with one shared property and store it in session. On second run it tries to start session and fail to parse the second object stored in session because the it loses reference to property name.

This script runs without any problems on PHP 7.3, and rises the parse errors on PHP 7.4. This makes impossible to use Zend_Authentication (and other components that uses ArrayObject to store some data in session) with PHP 7.4 because in stores identity as Zend ArrayObject.

Xerkus commented 4 years ago

Sample code you provided serialize and unserialize just fine on 7.2 to latest 7.4.

Problem can occur if nested serialize or unserialize calls happen in different order. Your setup is broken on any version but prior to 7.3(?) it produced no error, giving partially invalid result.

I will need to know more about your session management configuration to investigate further.

syberon commented 4 years ago

Sample code you provided serialize and unserialize just fine on 7.2 to latest 7.4.

I tried to run it on: PHP 7.3.13 (and all prior versions) - no problems PHP 7.4.0 - error parsing PHP 7.4.1 - error parsing

Problem can occur if nested serialize or unserialize calls happen in different order. Your setup is broken on any version but prior to 7.3(?) it produced no error, giving partially invalid result.

On any version prior to 7.4 it work fine - gives normally unserialized objects and produces no errors.

I will need to know more about your session management configuration to investigate further.

That exactly i need to provide to you? For this testing i used a fully clean default php 7.4 configuration.

Xerkus commented 4 years ago

Sample code you provided serialize and unserialize just fine on 7.2 to latest 7.4.

Disregard my previous statement. This looks like a php 7.4 bug around serialization in default php session save handler

Xerkus commented 4 years ago

From observable behavior it appears that unserialize implementation when called by session extension C code does not set context for unserialize, resulting in relative numbered references being wrong when used in nested unserialize calls. It is not something that we can fix. Php bug report must be opened if there is none yet.

May be we can workaround it in zend-session. I am going to transfer this issue there.

syberon commented 4 years ago

It is not something that we can fix. Php bug report must be opened if there is none yet.

I'm already opened a report in bugs.php.net 2 days ago, but there is still no response.

Xerkus commented 4 years ago

Php bugtracker link for reference https://bugs.php.net/bug.php?id=79031

syberon commented 4 years ago

Thank for help with problem description. I hope developers will fix it asap ) I think this problem is related to last serialize improvement (introduces the new magic methods) in PHP 7.4. The quick workaround to fix Zend_Authentication to work with 7.4 it to remove Serializable implementation from ArrayObject class declaration (not good solution but as temporary...)

michalbundyra commented 4 years ago

Closing, as it is a bug in PHP 7.4 not in the library itself.