php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Doc Bug #72345 session_start() is not subject to max_execution_time
Submitted: 2016-06-06 15:54 UTC Modified: 2016-10-10 03:32 UTC
From: maggus dot staab at googlemail dot com Assigned: yohgaki (profile)
Status: Closed Package: Session related
PHP Version: Irrelevant OS:
Private report: No CVE-ID: None
Welcome back! If you're the original bug submitter, here's where you can edit the bug or add additional notes.
If this is not your bug, you can add a comment by following this link.
If this is your bug, but you forgot your password, you can retrieve your password here.
Password:
Status:
Package:
Bug Type:
Summary:
From: maggus dot staab at googlemail dot com
New email:
PHP Version: OS:

 

 [2016-06-06 15:54 UTC] maggus dot staab at googlemail dot com
Description:
------------
time spent in session_start() is not subject to max_execution_time().

this can cause php to block all its worker processes by waiting for the session_lock, which finally leads to a no longer responding website.

shouldn't a process error and exit after max_execution_time-seconds when it is not able to aquire the session lock while session_start()?

running with php 5.4 (as this is the most recent version which ubuntu12lts provides)

PHP 5.4.45-3+deb.sury.org~precise+1 (cli) (built: Jan  7 2016 15:32:17)
Copyright (c) 1997-2014 The PHP Group
Zend Engine v2.4.0, Copyright (c) 1998-2014 Zend Technologies
    with Zend Debugger v6.0.0, Copyright (c) 1999-2013, by Zend Technologies
    with blackfire v1.10.6, https://blackfire.io, by Blackfireio Inc.


Test script:
---------------
<?php
// run the script 2x concurrently

set_time_limit(5);

echo "acquire session lock:". time() ."\n";
session_start();
echo "got session lock:". time()."\n";
sleep(10);

echo "finished:". time()."\n";


Expected result:
----------------
1st instance should report
>acquire session lock:1465227871
>got session lock:1465227871
>finished:1465227881 

2nd instance should error after 5 seconds because session_start() is not able to aquire a session lock within 5 seconds

Actual result:
--------------
1st instance reports
>acquire session lock:1465227871
>got session lock:1465227871 // LOCK immediately acquired
>finished:1465227881 

2nd instance reports
>acquire session lock:1465227872
>got session lock:1465227881 // LOCK acquired after 1st instance exit'ed
>finished:1465227891 

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2016-06-06 16:12 UTC] maggus dot staab at googlemail dot com
-Package: *General Issues +Package: Session related
 [2016-06-06 16:12 UTC] maggus dot staab at googlemail dot com
fixed package
 [2016-06-06 16:36 UTC] cmb@php.net
It is documented[1] that:

| The maximum execution time is not affected by system calls,
| stream operations etc. Please see the set_time_limit() function
| for more details.

So, in my opinion, this is not a bug.

[1] <http://php.net/manual/en/info.configuration.php#ini.max-execution-time>
 [2016-06-06 17:48 UTC] maggus dot staab at googlemail dot com
I agree that it is a documented/known issue.

Like https://bugs.php.net/bug.php?id=72346 it is a very fundamental problem.. The fact that there are a bunch of functions which are not subject to any timeout makes it really easy to abuse/attack php based applications.

One just needs to block all available php workers and the website will be unavailble. There are no means in php to protect against such kind of attacks (except you do everything async, but this is not something 99% of php apps will do)
 [2016-07-14 08:12 UTC] maggus dot staab at googlemail dot com
in the last 2 months we had at least 4 DDOS attacks which succeeded because php "never" returns from session_start() of parallel running requests.

A few attackers were able to DOS a apache2 (mod_php) with 400 workers, because all the workers were waiting for a session lock.

would it be possible to add a "lock_timeout" option (or similar) so one could configure that session_start() should return (or throw) after a certain amount of milliseconds if it could not acquire a session lock?
 [2016-07-14 12:56 UTC] maggus dot staab at googlemail dot com
I created a custom FileSessionHandler class which works arround this (in my eyes php bug) using a non-blocking flock() call.

https://gist.github.com/staabm/ce225d4b01d7b4e2560848646e13d6fc

As anyone can easily DDOS any web-sapi based php application because of the blocking default-file-session-handler I hope for a fix at php-src though.
 [2016-07-14 13:00 UTC] maggus dot staab at googlemail dot com
put the following code into a file called "experiment.php" and you can reproduce the issue.

with my custom session files handler (see comment above) registered
  $handler = new FileSessionHandler(200);
  session_set_save_handler($handler, true);
the queued up requests immediately fail.
with the native php-session handler 20 apache workers are are bound with just 1 request. depending on how "slow" you simulate the first 8 workers, the things get very fast a lot worse as the apache nearly needs forever to fullfill all the requests.

if (!empty($_GET['worker'])) {
    $worker = $_GET['worker'];
    echo "<xmp>";
    echo "ID: ". $worker."\n";

    $start = microtime(true);
    echo "ACQUIRE LOCK: ". $start ."\n";
    try {
        session_start();
    } catch (Exception $e) {
        var_dump($e->getMessage());
        exit();
    }

    $waited = ((microtime(true) - $start) * 1000);

    // simulate work
    $sleep = 500;
    if ($worker < 8) {
        $sleep = 5000;
    }
    usleep($sleep * 1000);

    // make sure the server needs to update the sess file after each request
    $_SESSION['worker'][$worker]++;

    $finished = ((microtime(true) - $start) * 1000);
    echo "FINISHED: ". $finished ."ms\n";

    // highlight runs which were blocked by others
    // (so session_start() couldn't return immediately)
    if ($waited > 10) {
        echo '</xmp><span style="color:red">';
    }
    echo 'BLOCKED FOR '. $waited .'ms';

    return;
}

foreach(range(1,20) as $workerId) {
    echo '<iframe src="experiment.php?worker='. $workerId .'"></iframe>';
}
 [2016-08-27 07:19 UTC] yohgaki@php.net
-Status: Open +Status: Analyzed -Assigned To: +Assigned To: yohgaki
 [2016-08-27 07:19 UTC] yohgaki@php.net
Thank you for reporting. It's possible attackers setup clients that exploit session locking mechanism to use all server processes/threads.

Counter measure is difficult because "locks" are handled by save handlers. "lock" is implemented by save handler in various ways.

Any lock mechanism in PHP could be exploited. i.e. It's not a session only issue. Can we handle timeout signal in the engine?
 [2016-09-03 20:53 UTC] yohgaki@php.net
-Type: Bug +Type: Documentation Problem
 [2016-09-03 20:53 UTC] yohgaki@php.net
We have to make this a doc issue because signal handling in PHP is problematic. I'll update doc to encourage use of readonly sessions.
 [2016-09-05 06:46 UTC] yohgaki@php.net
Automatic comment from SVN on behalf of yohgaki
Revision: http://svn.php.net/viewvc/?view=revision&amp;revision=339984
Log: Fixed Doc Bug #72345 and improve session security.xml
 [2016-09-05 07:43 UTC] yohgaki@php.net
For those who would like to mitigate DoS attacks by session lock. Minimizing locked period works. i.e. use session_start('read_and_close'=>1), use session_commit() as soon as you've finished updating $_SESSION.

Drawback is you may accidentally update $_SESSION while it's not active. Be careful for this!
 [2016-10-10 03:32 UTC] yohgaki@php.net
-Status: Analyzed +Status: Closed
 [2016-10-10 03:32 UTC] yohgaki@php.net
Document is updated.
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Thu Mar 28 17:01:29 2024 UTC