php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Doc Bug #70520 session_regenerate_id() "Failed to create session ID" with custom SessionHandler
Submitted: 2015-09-17 14:53 UTC Modified: 2015-10-20 22:34 UTC
Votes:1
Avg. Score:5.0 ± 0.0
Reproduced:1 of 1 (100.0%)
Same Version:1 (100.0%)
Same OS:0 (0.0%)
From: hpdl at oscommerce dot com Assigned: yohgaki (profile)
Status: Closed Package: Session related
PHP Version: 7.0.0RC3 OS: Win10
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: hpdl at oscommerce dot com
New email:
PHP Version: OS:

 

 [2015-09-17 14:53 UTC] hpdl at oscommerce dot com
Description:
------------
session_regenerate_id() ignores a defined session path (via session_save_path()) and uses the windows\temp directory instead.

Did not occur in earlier RC releases.

Test script:
---------------
session_regenerate_id();

Expected result:
----------------
true

Actual result:
--------------
session_regenerate_id(): Failed to create session ID: user (path: C:\WINDOWS\Temp\) in C:\Users\blahblah\blah.php

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2015-09-17 19:56 UTC] requinix@php.net
-Status: Open +Status: Feedback
 [2015-09-17 19:56 UTC] requinix@php.net
Seems to be working for me.

<?php

session_save_path(".");
session_start();

$sid1 = session_id();
$exists1 = file_exists("./sess_{$sid1}");

session_regenerate_id();
$sid2 = session_id();
$exists2 = file_exists("./sess_{$sid2}");

var_dump($exists1); // true
var_dump($sid1 != $sid2); // true
var_dump($exists2); // true

?>

What's your test script? Can you dump session_save_path() before the session_regenerate_id() to make sure it's set appropriately?
 [2015-09-17 23:11 UTC] hpdl at oscommerce dot com
-Status: Feedback +Status: Open
 [2015-09-17 23:11 UTC] hpdl at oscommerce dot com
Sorry, the problem seems to be with custom session handlers.

Below is a snippet that reproduces the problem. The handler used below is taken from the PHP documentation:

http://php.net/manual/en/class.sessionhandler.php

<?php
error_reporting(E_ALL | E_STRICT);
ini_set('display_errors', true);

class EncryptedSessionHandler extends SessionHandler
{
    private $key;

    public function __construct($key)
    {
        $this->key = $key;
    }

    public function read($id)
    {
        $data = parent::read($id);

        return @mcrypt_decrypt(MCRYPT_3DES, $this->key, $data, MCRYPT_MODE_ECB);
    }

    public function write($id, $data)
    {
        $data = @mcrypt_encrypt(MCRYPT_3DES, $this->key, $data, MCRYPT_MODE_ECB);

        return parent::write($id, $data);
    }
}

ini_set('session.save_handler', 'files');
$handler = new EncryptedSessionHandler('mykey');
session_set_save_handler($handler, true);

session_start();
echo session_id() . '<br>';
session_regenerate_id(true);
echo session_id();
 [2015-09-18 00:06 UTC] requinix@php.net
-Summary: session_regenerate_id ignores defined session path +Summary: session_regenerate_id() "Failed to create session ID" with custom SessionHandler -Status: Open +Status: Verified -Assigned To: +Assigned To: yohgaki
 [2015-09-18 00:06 UTC] requinix@php.net
Okay, I see it. So rather it's a general failure when overriding SessionHandler::write(), not about the save path.
(Note that the first echo needs to be commented out as it creates output which will interfere.)

Tentatively assigning to @yohgaki as he did some work with session_regenerate_id() before the RC3 release.
 [2015-09-18 08:58 UTC] yohgaki@php.net
It seems Windows only, doesn't it?
I tried the save handler with php-7.0.0RC3 tag and PHP-7.0 branch on my linux box and it works.

Is your session.save_path a local filesystem? i.e. It's not Windows share or like. I know some users are having problem with shared file systems.
 [2015-09-18 09:03 UTC] yohgaki@php.net
I got it right. I'll check it soon.
 [2015-09-18 09:05 UTC] requinix@php.net
I tried it with a session_save_path(".") immediately before the session_start(). CWD was a local directory, and when I ran the code I posted earlier (in the same location) it worked correctly.
 [2015-09-18 09:18 UTC] yohgaki@php.net
-Status: Verified +Status: Analyzed -Type: Bug +Type: Documentation Problem
 [2015-09-18 09:18 UTC] yohgaki@php.net
Found what's wrong. 
Current session save handler expects session read function returns STRING type when there is no error.

However, the code 

    public function read($id)
    {
        $data = parent::read($id);

        return @mcrypt_decrypt(MCRYPT_3DES, $this->key, $data, MCRYPT_MODE_ECB);
    }


returns non string because $this->key is not valid key length.

Apparently, the document has wrong size of key which results mcrypt_decrypt() error. This makes fail to write session data.

Making documentation problem.
 [2015-09-18 09:19 UTC] yohgaki@php.net
s/write/read/
 [2015-09-18 09:21 UTC] yohgaki@php.net
I see error for read, but it will fail on both read/write due to wrong key size.
 [2015-09-18 09:47 UTC] hpdl at oscommerce dot com
Thanks for the pointers here. The error occurred with my database session handler read() method returning a bool false instead of a string. This was only caught in RC3 as earlier 7dev releases and previous PHP versions did not produce any kind of error.

Thanks for the time spent on this.
 [2015-09-18 22:41 UTC] yohgaki@php.net
Since mcrypt seems to have problems https://bugs.php.net/bug.php?id=70529 , I've made new example using OpenSSL AES. Any comments on this new sample code? 

There are too many ini_set() etc, I'll get rid of irrelevant code for the doc later. 

<?php
ob_start();
error_reporting(E_ALL | E_STRICT);
ini_set('session.save_path', '/tmp');
ini_set('display_errors', true);

 /**
  * decrypt AES 256
  *
  * @param data $edata
  * @param string $password
  * @return dencrypted data
  */
function decrypt($edata, $password) {
    $data = base64_decode($edata);
    $salt = substr($data, 0, 16);
    $ct = substr($data, 16);

    $rounds = 3; // depends on key length
    $data00 = $password.$salt;
    $hash = array();
    $hash[0] = hash('sha256', $data00, true);
    $result = $hash[0];
    for ($i = 1; $i < $rounds; $i++) {
        $hash[$i] = hash('sha256', $hash[$i - 1].$data00, true);
        $result .= $hash[$i];
    }
    $key = substr($result, 0, 32);
    $iv  = substr($result, 32,16);

    return openssl_decrypt($ct, 'AES-256-CBC', $key, true, $iv);
  }

/**
 * crypt AES 256
 *
 * @param data $data
 * @param string $password
 * @return base64 encrypted data
 */
function encrypt($data, $password) {
    // Set a random salt
    $salt = openssl_random_pseudo_bytes(16);

    $salted = '';
    $dx = '';
    // Salt the key(32) and iv(16) = 48
    while (strlen($salted) < 48) {
      $dx = hash('sha256', $dx.$password.$salt, true);
      $salted .= $dx;
    }

    $key = substr($salted, 0, 32);
    $iv  = substr($salted, 32,16);

    $encrypted_data = openssl_encrypt($data, 'AES-256-CBC', $key, true, $iv);
    return base64_encode($salt . $encrypted_data);
}

class EncryptedSessionHandler extends SessionHandler
{
    private $key;

    public function __construct($key)
    {
        $this->key = $key;
    }

    public function read($id)
    {
        $data = parent::read($id);

        if ($data === "") {
            return "";
        } else {
            return decrypt($data, $this->key);
        }
    }

    public function write($id, $data)
    {
        $data = encrypt($data, $this->key);

        return parent::write($id, $data);
    }
}

ini_set('session.save_handler', 'files');
$key = substr(sha1(random_bytes(24)), 0, 24);
$handler = new EncryptedSessionHandler($key);
session_set_save_handler($handler, true);

echo '<pre>';
session_start();
$_SESSION['key'] = 1234;
var_dump($_SESSION);
echo session_id() . PHP_EOL;
session_regenerate_id(true);
echo session_id() . PHP_EOL;
 [2015-09-18 22:45 UTC] yohgaki@php.net
Oops. I forgot to modify key string to be static string.
$key = substr(sha1(random_bytes(24)), 0, 24);
should be something like
$key = 'secret_string';
 [2015-09-19 01:41 UTC] yohgaki@php.net
Automatic comment from SVN on behalf of yohgaki
Revision: http://svn.php.net/viewvc/?view=revision&amp;revision=337850
Log: Fixed bug #70520 - Update and improve example code
 [2015-09-19 01:42 UTC] yohgaki@php.net
-Status: Analyzed +Status: Closed
 [2015-09-19 01:42 UTC] yohgaki@php.net
New example code is in svn.
 [2015-09-19 06:44 UTC] alec at alec dot pl
I've got the same issue. Don't you think the error is misleading or at least gives no clue about where the issue is?
 [2015-09-19 08:13 UTC] yohgaki@php.net
@alec

When new Zend Engine is introduced, a bug in session read is introduced.

https://bugs.php.net/bug.php?id=70529

I've fixed this today. If you would like to use "binary" session data like mcrypt encrypted data, you'll need most recent PHP7 build.
 [2015-10-19 05:11 UTC] jacky at xsteach dot com
i met the same problem on mac osx with php7RC5. I use yii2 framework with redis as session storage.It is ok when i install php7RC2, but get error session_regenerate_id(): Failed to create session ID: user (path: ). Is this issue fixed after RC3?
 [2015-10-20 22:34 UTC] yohgaki@php.net
@jacky
If you have problem with Redis save handler, please file a new bug with short reproducible code.
 [2020-02-07 06:07 UTC] phpdocbot@php.net
Automatic comment on behalf of yohgaki
Revision: http://git.php.net/?p=doc/en.git;a=commit;h=c63fbfff619bce8eefbbbd93be2e52c2d14311d6
Log: Fixed bug #70520 - Update and improve example code
 
PHP Copyright © 2001-2022 The PHP Group
All rights reserved.
Last updated: Thu Oct 06 19:05:52 2022 UTC