-
Notifications
You must be signed in to change notification settings - Fork 11.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Race condition on session engine causing unexpected behavior on concurrent requests #5416
Comments
Bump |
Bump again. This is a pretty serious issue with ajax applications. |
Bump. This is causing huge issues for our clients |
Can anyone provide a pull request to fix this? |
Taylor has pushed a fix in d6648e2. Feedback would be appreciated. Ping @PhilipChen, @avi123, @Boogydown. |
Assuming this to be fixed. Ping me if this is not the case. |
@GrahamCampbell The fix is not complete. While it fixes concurrent writes, it doesn't fix the issue where a read can occur during the write process. More specifically, reads must explicitly obtain a lock before opening the file. |
No, we've totally locked it while we're writing to it. Php will wait to read the file while another thread is writing. |
Could you try this with the redis driver and tell me if you observe the same behaviour please? |
When you say "The fix is not complete", have you actually tested it. That would be really helpful to get to the bottom of this. |
@PhilipChen is correct. I just simulated locally with two concurrent PHP scripts. The issue is:
|
Ok, thanks for investigating. Could you propose a fix in a PR to 4.2 please. |
We fixed this locally by creating a custom session handler. Our goal was to mimic standard PHP session behavior, whereby a session is blocking until session_write_close (or in Lavarel's case, Session::save()) is called. We can do a PR of this if that behavior is of interest. |
Yes please. That was what we intended the behaviour to be. |
Can't we use the Symfony NativeFile Driver, which uses the php session driver, but different location? |
I think Laravel removed support for the NativeFile Driver via their interface |
Could we not wrap it? |
This would be a good idea in laravel 5. |
Probably be better than the proposed solution i've just had a look at. It seems a bit over kill. |
Aren't they both just implementing the SessionHandlerInterface from php? |
It's possible you could wrap it. We developed this for our own needs, where we have multiple concurrent api requests that create all sorts of race conditions if not properly handled. I've stripped out some applicatoin specifics, but this works for our needs. If there's something lighter weight, go for it. |
Yeh, they are. Switching to symfony's is maybe the best solution. |
@barryvdh we don't write the Previous URL on AJAX requests, which seems to be the most common issue here (main page requests followed by a lot of concurrent AJAX requests). |
First, I believe the Laravel request lifecycle calls a Session::start at the beginning of every request and a Session::save at the end of every request: https://github.com/laravel/framework/blob/4.2/src/Illuminate/Session/Middleware.php#L58 So regardless of the HTTP method, you are ALWAYS forcing a session read and a session write. Now, there are two separate issues here:
The first issue occurs if you have multiple concurrent POSTs/PUTs, or even sequential asynchronous requests that happen in close succession such that the second request starts prior to the first request completing. The issue here is that changes to the session in the second request can be lost if the first request doesn't lock the session, but instead does a read, and then a full write at the completion of the request (I believe the method that the Session Store takes). This is the impetus for using a locking session driver in general. The second issue is specific to the FileSessionHandler implemented by Laravel. Because session reads DO NOT respect file write locks, a request calling Session::start that HAPPENS to read the session file at the exact moment that the write call in the other request is between ftruncate and fwrite will receive NO DATA, thereby assuming that NO session exists and creating a new one, hence the new token. Because this has to occur at that exact moment, it does not always occur, and is more likely to occur when many short requests happen concurrently. If you were to want to recreate, I would suggest manually replacing file_put_contents with a fopen (mode c), ftruncate, and fwrite (as per https://github.com/php/php-src/blob/PHP-5.5.12/ext/standard/file.c#L567) and place a sleep between ftruncate and fwrite - you should see the same behavior. Unfortunately, I do not have the bandwidth to test other drivers, however, I imagine if they support atomic updates you should be good to go. |
I'm mainly interested in fixing #2 at the moment. Forcing a sequential execution of requests by implementing locking on for example a Memcached driver is perhaps fixing a "problem" that shouldn't exist in the first place based on your application structure. |
If you add a call to $this->close() to the end of the read() method in #6848, then requests will no longer be blocking, but the CSRF issue should be fixed. I can add if you guys are planning on merging that PR. |
@taylorotwell We are experiencing the same issue on Redis, I don't think it would be lock issue because redis is single thread and atomic, I used the method suggested in #6777 to debug and here is the log: [2015-03-26 13:12:06] production.INFO: ["k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","1a85f2d0adb856b874dda75f05c75ea8a7ad0c43"] [] []
[2015-03-26 13:12:06] production.INFO: ["k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","1a85f2d0adb856b874dda75f05c75ea8a7ad0c43"] [] []
[2015-03-26 13:12:06] production.INFO: ["k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","1a85f2d0adb856b874dda75f05c75ea8a7ad0c43"] [] []
[2015-03-26 13:12:06] production.INFO: ["k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","1a85f2d0adb856b874dda75f05c75ea8a7ad0c43"] [] []
[2015-03-26 13:12:06] production.INFO: ["k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","1a85f2d0adb856b874dda75f05c75ea8a7ad0c43"] [] []
[2015-03-26 13:12:06] production.INFO: ["k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","1a85f2d0adb856b874dda75f05c75ea8a7ad0c43"] [] []
[2015-03-26 13:12:07] production.INFO: ["k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","1a85f2d0adb856b874dda75f05c75ea8a7ad0c43"] [] []
[2015-03-26 13:12:07] production.INFO: ["k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","1a85f2d0adb856b874dda75f05c75ea8a7ad0c43"] [] []
[2015-03-26 13:12:07] production.INFO: ["k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","1a85f2d0adb856b874dda75f05c75ea8a7ad0c43"] [] []
[2015-03-26 13:12:07] production.INFO: ["k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","1a85f2d0adb856b874dda75f05c75ea8a7ad0c43"] [] []
[2015-03-26 13:12:07] production.INFO: ["k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","1a85f2d0adb856b874dda75f05c75ea8a7ad0c43"] [] []
[2015-03-26 13:12:08] production.INFO: ["k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","1a85f2d0adb856b874dda75f05c75ea8a7ad0c43"] [] []
[2015-03-26 13:12:08] production.INFO: ["k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","9uIpGse9rdQlZJGs8NYMFH4s8KsWkvDsZ2efk5Fv","1a85f2d0adb856b874dda75f05c75ea8a7ad0c43"] [] []
[2015-03-26 13:12:08] production.INFO: ["k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","9uIpGse9rdQlZJGs8NYMFH4s8KsWkvDsZ2efk5Fv","1a85f2d0adb856b874dda75f05c75ea8a7ad0c43"] [] []
[2015-03-26 13:12:08] production.INFO: ["k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","9uIpGse9rdQlZJGs8NYMFH4s8KsWkvDsZ2efk5Fv","1a85f2d0adb856b874dda75f05c75ea8a7ad0c43"] [] []
[2015-03-26 13:12:08] production.INFO: ["k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","9uIpGse9rdQlZJGs8NYMFH4s8KsWkvDsZ2efk5Fv","1a85f2d0adb856b874dda75f05c75ea8a7ad0c43"] [] []
[2015-03-26 13:12:09] production.INFO: ["k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","9uIpGse9rdQlZJGs8NYMFH4s8KsWkvDsZ2efk5Fv","1a85f2d0adb856b874dda75f05c75ea8a7ad0c43"] [] []
[2015-03-26 13:12:09] production.INFO: ["k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","9uIpGse9rdQlZJGs8NYMFH4s8KsWkvDsZ2efk5Fv","1a85f2d0adb856b874dda75f05c75ea8a7ad0c43"] [] []
[2015-03-26 13:12:10] production.INFO: ["k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","9uIpGse9rdQlZJGs8NYMFH4s8KsWkvDsZ2efk5Fv","1a85f2d0adb856b874dda75f05c75ea8a7ad0c43"] [] []
[2015-03-26 13:12:10] production.INFO: ["k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","9uIpGse9rdQlZJGs8NYMFH4s8KsWkvDsZ2efk5Fv","1a85f2d0adb856b874dda75f05c75ea8a7ad0c43"] [] []
[2015-03-26 13:12:10] production.INFO: ["k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","9uIpGse9rdQlZJGs8NYMFH4s8KsWkvDsZ2efk5Fv","1a85f2d0adb856b874dda75f05c75ea8a7ad0c43"] [] []
[2015-03-26 13:12:10] production.INFO: ["k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","9uIpGse9rdQlZJGs8NYMFH4s8KsWkvDsZ2efk5Fv","1a85f2d0adb856b874dda75f05c75ea8a7ad0c43"] [] []
[2015-03-26 13:12:10] production.INFO: ["k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","9uIpGse9rdQlZJGs8NYMFH4s8KsWkvDsZ2efk5Fv","1a85f2d0adb856b874dda75f05c75ea8a7ad0c43"] [] []
[2015-03-26 13:12:11] production.INFO: ["k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","9uIpGse9rdQlZJGs8NYMFH4s8KsWkvDsZ2efk5Fv","1a85f2d0adb856b874dda75f05c75ea8a7ad0c43"] [] []
[2015-03-26 13:12:11] production.INFO: ["k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","9uIpGse9rdQlZJGs8NYMFH4s8KsWkvDsZ2efk5Fv","1a85f2d0adb856b874dda75f05c75ea8a7ad0c43"] [] []
[2015-03-26 13:12:11] production.INFO: ["k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","9uIpGse9rdQlZJGs8NYMFH4s8KsWkvDsZ2efk5Fv","1a85f2d0adb856b874dda75f05c75ea8a7ad0c43"] [] []
[2015-03-26 13:12:11] production.INFO: ["k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","9uIpGse9rdQlZJGs8NYMFH4s8KsWkvDsZ2efk5Fv","1a85f2d0adb856b874dda75f05c75ea8a7ad0c43"] [] []
[2015-03-26 13:12:11] production.INFO: ["k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","9uIpGse9rdQlZJGs8NYMFH4s8KsWkvDsZ2efk5Fv","1a85f2d0adb856b874dda75f05c75ea8a7ad0c43"] [] []
[2015-03-26 13:12:11] production.INFO: ["k9OqsVTnE0QkphMjGLxfF8nnClidNXsDZo7i9H8C","9uIpGse9rdQlZJGs8NYMFH4s8KsWkvDsZ2efk5Fv","1a85f2d0adb856b874dda75f05c75ea8a7ad0c43"] [] [] you can see that the token is regenerated out of nowhere. |
The problem is still relevant for me on Laravel 5. Even more, sometimes .env file contents can be lost for AJAX-intensive applications: |
This is known, and has been discussed here. You MUST run the |
Thanks, I thought that Dotenv issue is also related to file locking issues but it's not. I managed to fix Dotenv by using an array as a fallback as I described here: #8187 (comment) Regarding the session issues, I guess, I'll have to look for an alternative or patched session drivers. |
I just tried this approach. Modify Store.php: add
modify constructor:
modify readFromHandler():
modify save():
Of course, there should be cleaner way to check if objects are dirty than comparing strings, but that would require much more code. These fixes do not solve the issue, but at least reduce concurrent write/read scenarios if your AJAX requests are just reading. I now am running my stress test with insane amount of AJAX requests (some of them fail on Chrome with net:ERR_INSUFFICIENT_RESOURCES) but for an hour already I don't have any session failures at all. And as a bonus, performance might also increase a bit - less redundant writing to disk. |
@progmars are your concurrent AJAX stress tests all read only for the session data? I'm just making sure that I understand that this fix is for read only sessions to prevent partial files from coming in but will presumably still break if you're changing things. |
@loren138 yes, they are read only; and things still will break the same as before if session data is changed in concurrent requests. |
So, here is my quick patch to make this issue much less frequent (or fix it completely, if you don't write to session yourself on every request): |
Does anyone got a solution to this problem? And why the issue occur only on Chrome browser? |
@sebastianvirlan In my experience, this occurs not only on Chrome but also in other apps which create lots of parallel requests to a Laravel web application. I have seen the same issue also when running Apache Bench The best solution would be to use some other, more concurrency-safe session driver. In my case it was enough to have my workarounds which at least prevent Laravel from rewriting session on every request, even when no session data has actually changed. If anyone wants to better understand the core of the issue, I suggest this old and in-depth article: |
@progmars even if I use driver file, database, redis I get same. https://www.youtube.com/watch?v=P_M9qf3W1CI Look at chrome requests. I also see that if I start the server from a ubuntu and access the ip from a windows chrome the problem will not occur anymore. |
@sebastianvirlan We had the same issue, here is my complete explanation about the issue: |
I read your answer. Very sad that this happen. So from the developers of laravel what we have? We will have any fix? Because this is a very serious problem, I can make a clean laravel project with this script:
and a POST route
And the issue still occur random (but as I discover last days only if I start the server on windows). |
I have tried several suggestions for fix this issue, but in my case, using a route outside from the web, auth groups solved the 401 error on multiple ajax request. May be obvious for the experts here, but this workaround present some potencial risk for sensitive data. Hope this comment helps to find a definitive solution |
Did not found a solution yet :( @Angel-Gonzalez your solution does not solve the issue in my case. |
The FileSessionHandler introduced as part of the new Session Engine in Laravel 4.1 doesn't perform any file locking during read and write operations on the underlying session file. This creates the opportunity for a race condition, which is more likely to occur when a large number of single session concurrent requests are made. This bug is present in Laravel versions 4.1 and newer.
The race condition occurs when a session is being written to the file. Currently, the write process causes the session file to be truncated before it is written to. As there is no lock on the file, if a read session file operation were to occur after the truncation but before the write, the data read will be empty. This will cause the session store to replace the existing session data with a new, empty session.
The solution is twofold:
If it were desired to continue to use the Filesystem class, one could add an optional argument to get() and put(), $lock=false. If $lock is set to true, then Laravel will obtain a lock before reading and writing. FileSessionHandler could then be modified to pass $lock = true.
The text was updated successfully, but these errors were encountered: