yiisoft / yii2

Yii 2: The Fast, Secure and Professional PHP Framework
http://www.yiiframework.com
BSD 3-Clause "New" or "Revised" License
14.24k stars 6.91k forks source link

session_start(): Unexpected end of serialized data - Exception gets suppressed by Yii2 and breaks the application #13400

Open themroc5 opened 7 years ago

themroc5 commented 7 years ago

What steps will reproduce the problem?

Replace a running session file content with this one. This content might/propably is wrong already. But this is not the point here. Try to stay logged in.

What is the expected result?

yii\web\Session::open() and in particular @session_start() should somehow recognize if the following exception gets thrown: session_start(): Unexpected end of serialized data. We actually don't know why the session data is malformed and we debugged quite some time to realize that this is the issue. The @session_start() error suppression works against this. The function actually runs successful since $this->getIsActive() returns true. However, the next session upkeep trial fails due to the missing identity id parameter in the session and the user has to login again.

What do you get instead?

No exception :)

Additional info

Q A
Yii version 2.0.10
PHP version Tested in 5.6 and 7.0
Operating system Ubuntu
samdark commented 7 years ago

Have you tried with code from master?

cebe commented 7 years ago

related to #10583

robsch commented 7 years ago

Encountered this problem as well. Did you find out what the reason was for the malformed session data?

When I try to open the session file in an editor I get the warning that the file contains some invalid characters and that it is not an UTF-8 content.

__flash|a:0:{}processes|a:1:{s:32:"682a64bf68f73e16ce8691f2a56c796b";a:2:{s:11:"processConf";O:39:"integration\models\ProcessConfiguration":13:{s:9:"productId";s:1:"1";s:8:"language";s:5:"en-US";s:9:"returnUrl";s:70:"http://integration.c4-application-template.localhost/site/dummy-target";s:8:"padColor";s:3:"red";s:12:"returnTarget";s:5:"_self";s:8:"referrer";s:52:"http://integration.c4-application-template.localhost";s:9:"templates";N;s:13:"blankTemplate";N;s:23:"\00yii\base\Model\00_errors";a:0:{}s:27:"\00yii\base\Model\00_validators";N;s:25:"\00yii\base\Model\00_scenario";s:27:"\00yii\base\Component\00_events";s:30:"\00yii\base\Component\00_behaviors";}s:10:"workspaces";}}

I have put an object (class derived from class Yii Model) into the session and as it seems that namespace declarations causing troubles (first and last backspace is suffixed with "00"). I'm wondering if I have done anything wrong when putting the object to the session (array). It was just a normal assignment, also the Model is quite regular.

(Putting the object into the session is probably not best practise or needs some further considerations. I have solved my problem with just storing $model->attributes into session and then re-creating the object with new ProcessConfiguration(Yii::$app->session['processConf']) )

In my case the session file was also deleted. Another session_start() then just created the file again, but just with the __flash entry, so the actual data was lost.

PHP 7.0.19, Yii 2.0.11.2, Fedora

raraworks commented 5 years ago

The exception is only suppressed if YII_DEBUG is not true. To me that is not the main issue, the main issue is that: i can confirm this, that storing object instances extending AR in an array within session, throws this exception when serializing it.

Yii 2.0.15.1, PHP 7.2.10-0ubuntu0.18.04.1

flaviovs commented 5 years ago

I managed to track down this to what it seems a bug in yii\base\Model, which is triggered when your try to serialize a model with rules() returning anonymous functions (e.g. in when config), and you validate the model prior serialization.

This still applies to 2.0.16.

How to replicate

Given

class TestModel extends \yii\base\Model
{
    public $attr;
    public function rules()
    {
        return [['attr', 'required', 'when' => function() { return true; }]];
    }
}

$model = new TestModel();

Then everything is fine If you do:

var_dump(unserialize(serialize($model));

But you get an error in this case:

$model->validate();
var_dump(unserialize(serialize($model));

PHP error is Serialization of 'Closure' is not allowed. This is the same you get when you try to store the (serialized) model in the session (check your web server logs for that).

Apparently this is caused by validators being cached in $model->_validators -- and PHP choking when trying to serialize the object if those validators contain anonymous functions.

Suggested fixes

  1. Stop caching validation rules, because they usually contain anonymous functions.
  2. Make $_validators method-static inside getValidators() (that's the only place it is used anyway).
  3. Add a __sleep() method to yii\base\Model that remove $_validators from the set of attributes to serialize. This might be tricky due to interactions with properties, behaviours, etc.

IMHO, 2 is the simplest one.

Workarounds

  1. Do not use anonymous function in rules(). Move your logic to a method and use [$this, 'methodName'] as callback.
  2. Override getValidators() in your model. Original implementation only checks for the validator cache in $_validators, setting it up when empty. So you can simply do:
public function getValidators()
{
    return $this->createValidators();
}
bianchi commented 5 years ago

Is it not solved? Still having problems using when in validators. I'm using version 2.0.27

I managed to track down this to what it seems a bug in yii\base\Model, which is triggered when your try to serialize a model with rules() returning anonymous functions (e.g. in when config), and you validate the model prior serialization.

This still applies to 2.0.16.

How to replicate

Given

class TestModel extends \yii\base\Model
{
    public $attr;
    public function rules()
    {
        return [['attr', 'required', 'when' => function() { return true; }]];
    }
}

$model = new TestModel();

Then everything is fine If you do:

var_dump(unserialize(serialize($model));

But you get an error in this case:

$model->validate();
var_dump(unserialize(serialize($model));

PHP error is Serialization of 'Closure' is not allowed. This is the same you get when you try to store the (serialized) model in the session (check your web server logs for that).

Apparently this is caused by validators being cached in $model->_validators -- and PHP choking when trying to serialize the object if those validators contain anonymous functions.

Suggested fixes

  1. Stop caching validation rules, because they usually contain anonymous functions.
  2. Make $_validators method-static inside getValidators() (that's the only place it is used anyway).
  3. Add a __sleep() method to yii\base\Model that remove $_validators from the set of attributes to serialize. This might be tricky due to interactions with properties, behaviours, etc.

IMHO, 2 is the simplest one.

Workarounds

  1. Do not use anonymous function in rules(). Move your logic to a method and use [$this, 'methodName'] as callback.
  2. Override getValidators() in your model. Original implementation only checks for the validator cache in $_validators, setting it up when empty. So you can simply do:
public function getValidators()
{
    return $this->createValidators();
}
samdark commented 5 years ago

@bianchi issue status suggests that it is not.