Monday, December 5, 2016

session_regenerate_id causing fatal error in Php 7

While upgrading to Php 7, our code had an issue with session_regenerate_id().

Catchable fatal error: session_regenerate_id(): Failed to create(read) session ID: user (path: /var/lib/php/session)

Thanks to bellosthomas, I was able to determine that our DB_Sessions class was causing the issue. It was a simple fix of FORCING the return values to be strings.  I have marked the code and comments with BOLD and UNDERLINED text so you can see the strings.  Just make sure that your "sess_read" method is returning a string!


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

  [2015-11-13 05:23 UTC] bellosthomas at gmail dot com
I was also getting a fatal error (check comment above), with a custom session handler. After yohgaki's comment about the return value of the read() method, which needs to return a string, I checked my session handler and I saw I was returning boolean false in some cases. Casting the return value to string, solved it.


https://gist.github.com/Artistan/0838c95d46ffad965570afa0ec354091
<?php
/**
* Initial Setup of file
* Garbage collection sucks, we should have a time check during read and auto destroy/not recover old sessions
*/
class DB_Sessions
{
var $session_life = 300;
var $sessiontable = 'sessions';
var $recordtable = 'sessions_loginrecord';
/**
* @var DB_Wrapper
*/
var $dbhost;
var $sessionhash;
var $write_callback;
var $memcache;
function __construct(&$dbhost)
{
$this->dbhost =& $dbhost;
ini_set('session.save_handler', 'user');
session_set_save_handler(
array($this, 'sess_open'),
array($this, 'sess_close'),
array($this, 'sess_read'),
array($this, 'sess_write'),
array($this, 'sess_destroy'),
array($this, 'sess_gc')
);
if(function_exists('memcache_pconnect')) {
$this->memcache = @memcache_pconnect($_SERVER['MEMCACHE_SERVER'], $_SERVER['MEMCACHE_PORT']);
}
}
/**************************************
* sess_open
***************************************
* Notes:
***************************************
* Used In
***************************************
* @author Richard Thomas
*
* @param $save_path STRING
* @param $session_name STRING
*
* @return BOOL
*/
function sess_open($save_path, $session_name)
{
return true;
}
/**************************************
* sess_close
***************************************
* Notes:
***************************************
* Used In
***************************************
* @author Richard Thomas
* @return BOOL
*/
function sess_close()
{
return true;
}
/**************************************
* sess_read
***************************************
* Notes:
***************************************
* Used In
***************************************
* @author Richard Thomas
*
* @param $key STRING
*
* @return BOOL
*/
function sess_read($key)
{
if (!is_object($this->memcache) OR (!$value = $this->memcache->get('sess_key_' . $key)))
{
$qry = "SELECT `value` FROM " . $this->sessiontable . " WHERE `sesskey` = '$key'";
$qid = $this->dbhost->query($qry);
if (list($value) = $qid->fetch_row())
{
$this->sessionhash = md5($value);
if (is_object($this->memcache) AND $value)
{
$this->memcache->set('sess_key_' . $key, $value, COMPRESS_CACHE);
}
return (String) $value;
}
}
else
{
$this->sessionhash = md5($value);
return (String) $value;
}
return '';
}
/**************************************
* sess_write
***************************************
* Notes:
***************************************
* Used In
***************************************
* @author Richard Thomas
*
* @param $key STRING
* @param $val STRING
*
* @return BOOL
*/
function sess_write($key, $val)
{
$expiry = time() + $this->session_life;
$value = addslashes($val);
if (md5($val) == $this->sessionhash)
{
$qry = "UPDATE " . $this->sessiontable . " SET expiry = $expiry, userkey = '" . @$_SESSION['login'] . "' WHERE sesskey = '$key'";
}
else
{
if (is_object($this->memcache))
{
$this->memcache->delete('sess_key_' . $key);
}
$qry = "UPDATE " . $this->sessiontable . " SET expiry = $expiry, value = '$value', userkey = '" . @$_SESSION['login'] . "' WHERE sesskey = '$key'";
if ($this->write_callback)
{
call_user_func($this->write_callback, $val);
}
}
$qid = $this->dbhost->query($qry);
if (!$this->dbhost->affected_rows())
{
if (!empty($_SERVER['REMOTE_ADDR']))
{
$qry = "INSERT INTO " . $this->sessiontable . " VALUES ('$key', $expiry, '$value','" . @$_SESSION['login'] . "','$expiry','" . $_SERVER['REMOTE_ADDR'] . "','" . $_SERVER['HTTP_HOST'] . "')";
}
else
{
$qry = "INSERT INTO " . $this->sessiontable . " VALUES ('$key', $expiry, '$value','" . @$_SESSION['login'] . "','$expiry','\"\"','" . $_SERVER['HTTP_HOST'] . "')";
}
$qid = $this->dbhost->query($qry);
}
return $qid;
}
/**************************************
* sess_destroy
***************************************
* Notes:
***************************************
* Used In
***************************************
* @author Richard Thomas
*
* @param $key STRING
*
* @return BOOL
*/
function sess_destroy($key)
{
$temp = time();
if (is_object($this->memcache))
{
$this->memcache->delete('sess_key_' . $key);
}
$qry = "SELECT userkey,started,ip,host FROM " . $this->sessiontable . " WHERE sesskey = '$key'";
$qid = $this->dbhost->query($qry);
$row = $qid->fetch_assoc();
$userkey = $row['userkey'];
$started = $row['started'];
$ip = $row['ip'];
$host = $row['host'];
if ($userkey <> "" AND $this->recordtable)
{
$qry = "INSERT INTO " . $this->recordtable . " VALUES ('$userkey','$started','$temp','$ip','$host')";
$qid = $this->dbhost->query($qry);
}
$qry = "DELETE FROM " . $this->sessiontable . " WHERE sesskey = '$key'";
$qid = $this->dbhost->query($qry);
return $qid;
}
/**************************************
* sess_gc
***************************************
* Notes:
***************************************
* Used In
***************************************
* @author Richard Thomas
*
* @param $maxlifetime INT
*
* @return BOOL
*/
function sess_gc($maxlifetime)
{
$temp = time();
$qry = "SELECT sesskey FROM " . $this->sessiontable . " WHERE expiry < " . $temp;
$result = $this->dbhost->query($qry);
for ($i = 0; $i < $result->num_rows(); $i++)
{
$row = $result->fetch_assoc();
$key = $row['sesskey'];
if (is_object($this->memcache))
{
$this->memcache->delete('sess_key_' . $key);
}
$qry = "SELECT userkey,started,ip,host FROM " . $this->sessiontable . " WHERE sesskey = '$key'";
$qid = $this->dbhost->query($qry);
if ($qid)
{
$row = $qid->fetch_assoc();
$userkey = $row['userkey'];
$started = $row['started'];
$ip = $row['ip'];
$host = $row['host'];
if ($userkey <> "" AND $this->recordtable)
{
$qry = "INSERT INTO " . $this->recordtable . " VALUES ('$userkey','$started','$temp','$ip','$host')";
$qid = $this->dbhost->query($qry);
}
}
}
$qry = "DELETE FROM " . $this->sessiontable . " WHERE expiry < " . $temp;
$qid = $this->dbhost->query($qry);
return $this->dbhost->affected_rows();
}
/**************************************
* sess_isonline
***************************************
* Notes:
***************************************
* Used In
***************************************
* @author Richard Thomas
*
* @param $login STRING
*
* @return BOOL
*/
function session_isonline($login)
{
$temp = time();
$query = "SELECT userkey FROM " . $this->sessiontable . " WHERE userkey = '$login' AND expiry > $temp";
$result = $this->dbhost->query($query);
return $result->num_rows();
}
}