php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #71088 Session gets corrupted using custom session handler, unicode chars + 2 pg_conn
Submitted: 2015-12-11 05:53 UTC Modified: 2015-12-15 00:36 UTC
Votes:1
Avg. Score:5.0 ± 0.0
Reproduced:1 of 1 (100.0%)
Same Version:0 (0.0%)
Same OS:1 (100.0%)
From: cdutary at grupocti dot com Assigned: yohgaki (profile)
Status: Not a bug Package: Session related
PHP Version: 7.0.0 OS: Windows 2008
Private report: No CVE-ID: None
View Developer Edit
Welcome! If you don't have a Git account, you can't do anything here.
If you reported this bug, you can edit this bug over here.
(description)
Block user comment
Status: Assign to:
Package:
Bug Type:
Summary:
From: cdutary at grupocti dot com
New email:
PHP Version: OS:

 

 [2015-12-11 05:53 UTC] cdutary at grupocti dot com
Description:
------------
PHP: VC14 x64 Thread Safe (2015-Dec-03 20:07:26)

To reproduce this problem we need 3 elements:
- A custom session handler using postgres
- Unicode characters inside our session variable.
- A seccond postgres connection made when the session has ben started.

So what happends is:

The session gets corrupt at the end of the script when these 3 elements are present, throwing: "PHP Warning:  session_start(): Failed to decode session object. Session has been destroyed in some location at some line" when you reload the page.

Reloading the page once more will give you: PHP Warning:  pg_fetch_result(): Unable to jump to row 0 on PostgreSQL result index something in some path:


If we take the seccond postgres connection OUT of the equation then session data will remain untouched. So just comment that line and watch the page reload and reload over time. Uncomment the seccond connection and it will crash.



Test script:
---------------
This code is about 50 lines and you shall download it from:

https://grupocti.com/sesiones.txt

Just fill in 2 diffrent pg_conn.


A StackOverflow question is also open and contains all the details:
http://stackoverflow.com/questions/34185536/php7-is-breaking-my-sessions-when-custom-session-handler-is-used-and-a-seccond-p


Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2015-12-11 13:13 UTC] mattw at epiphanycardio dot com
We have experienced a very similar issue using php 5.6.x  It appeared with 5.6.12, 5.6.11 was fine.  We use mysql for the custom handler.  I inserted debugging into the session handler and discovered an extra session_destroy is called implicitly by the session handler.  I have turned off all explicit session_destroy() php function calls, yet a session_destroy is still being called.
 [2015-12-14 09:29 UTC] yohgaki@php.net
-Status: Open +Status: Feedback -Assigned To: +Assigned To: yohgaki
 [2015-12-14 09:29 UTC] yohgaki@php.net
Your session save handler implementation has some problems. In order to lock and keep data consistency, PostgreSQL (and other database systems) should do followings in save handler.


1. It must use SERIALIZABLE transaction isolation level. Otherwise, you may end up with inconsistent session data. (PostgreSQL should not cause broken data without serializable transaction, though. It just may end up with inconsistent data like $_SESSION['access_counter']++, etc.) i.e. Start serializable transaction on read() and commit at write(). Use SELECT FOR UPDATE on read() to lock the accessing row also.

2. It must create new session data record (i.e. INSERT) in session read(), not in write(). Write() must UPDATE session data always.

3. It must return TRUE for successful operations always except read() which returns session data for success and gc() which returns number of deleted records. i.e. It should not return FALSE like

public function open($savePath, $sessionName) {
return (isset($this->nombre_de_sesion) && strlen($this->nombre_de_sesion) > 2) ? true : false;

    public function destroy($session_id) {
        return pg_affected_rows(pg_query($this->db, 'DELETE FROM "sesiones_soporte" WHERE session_id = ' . pg_escape_literal($session_id) . '')) ? true : false;

Returning FALSE means "operation, that will never fail under normal circumstance, failed". For newly created session data, read() must return empty string. i.e. return "";

Without serializable transaction and lock, destroy()'s pg_affected_rows() may result in 0. This is not an error without serializable transaction and lock. destroy() must return TRUE in this case.

4. (Optional) It's better to use persistent connection for better performance and you don't have to close database connections. Note: Programmers must manage number of database connections anyway. Otherwise, apps may end up with random session open() (i.e. pg_connect()) error because PostgreSQL server rejects connection that exceeds max connections defined in postgresql.conf unlike Apache httpd.

5. (Optional, but strongly recommended) It should detect pg_query() errors directly. i.e. Do not use pg_query() result like

if (pg_affected_rows(pg_query($this->db, 'UPDATE "sesiones_soporte" SET ....

otherwise, you cannot tell what's wrong. e.g. Access to records that has been deleted by gc() already. This shouldn't happen with serializable transaction isolation level, though. Instead, process _all_ pg_query() errors directly and properly. Make sure return FALSE only when the database operation is failed.

6. (Irrelevant, but should be fixed) gc() takes "maximum of session life time in seconds". Calling gc() like $this->gc(time()) will do nothing. i.e. Remove $this->gc(time()) in open(). Session module calls gc() whenever it is needed.


That said, it's strange that you seems to get corrupted database record. Could you fix save handler issues. If you still have this issue with proper save handler, please report contents of broken data. (I guess you don't have the record)

It seems save handler and transaction isolation level problems are causing this to me.
 [2015-12-14 10:48 UTC] yohgaki@php.net
I just wrote an example save handler for PostgreSQL.

https://gist.github.com/yohgaki/a7b130bc93b2f9467ccc

Try this one and see if you have problems.
 [2015-12-14 18:41 UTC] cdutary at grupocti dot com
-Status: Feedback +Status: Assigned
 [2015-12-14 18:41 UTC] cdutary at grupocti dot com
I took your code and added the last 2 ingredients to make PHP fail and it did:

    ob_start();
    session_start();
    echo isset($_SESSION['aaa']) ? : 'Setting value' . $_SESSION['aaa'] = 'áéíóú'; //added unicode chars to our session data.
    var_dump($_SESSION['cnt'] ++);
    $pg_conn = pg_connect("host=´host2 port=5432 dbname=database2 user=user2 password=pass2"); //added a seccond pg_conn which somehow makes session unreadable


The output is:

PHP Warning:  session_start(): Failed to decode session object. 
Session has been destroyed in sesions.php on line 179
PHP Notice:  Undefined index: cnt in sesions.php on line 183


For now the workaround I found is to wrap up the session info in base64() before saving it into php_session and unwrap it at read. This works with both session save handlers, your's and mine. 

The original problem still remains and I can't find a reason for it yet.
 [2015-12-14 22:08 UTC] yohgaki@php.net
-Status: Assigned +Status: Feedback
 [2015-12-14 22:08 UTC] yohgaki@php.net
I don't get suspicious error.

--- modified test codes ----
ob_start();
session_start();
isset($_SESSION['aaa']) ? : 'Setting value' . $_SESSION['aaa'] = 'áéíóú'; //added unicode chars to our session data.
$_SESSION['cnt']++;
var_dump($_SESSION);
$pg_conn = pg_connect("host=localhost port=5432 dbname=yohgaki user=yohgaki");
------------------------------


1st access
------------------------------
Notice: Undefined index: cnt in /home/yohgaki/workspace/ext/git/oss/php.net/php-src/t3.php on line 156
array(2) { ["aaa"]=> string(10) "áéíóú" ["cnt"]=> int(1) } 
-------------------------------

2nd access
-------------------------------
array(2) { ["aaa"]=> string(10) "áéíóú" ["cnt"]=> int(2) } 
-------------------------------

It seems you are having problem with BYTEA escaping. What is your PostgreSQL version, both client library(libpq) and server? Does client library (libpq) and server version match?

Check phpinfo() output for client library version.
Do "SELECT version()" for server version.

BYTEA escaping has been changed for better performance. It seems your environment has version mismatch problem. If client library and server version matches, it should work.
 [2015-12-14 22:56 UTC] cdutary at grupocti dot com
-Status: Feedback +Status: Assigned
 [2015-12-14 22:56 UTC] cdutary at grupocti dot com
Well, the seccond connection is made to a older version of postgres, session management is made on the same version (9.4.4) both client and server so I guess this is the one that should matter.
My guess is that PHP, in order to be compatible with the seccond conection is using old "techniques" against the new server as well.
I was expecting for php to handle both connections separately but I guess that is just not possible.


FIRST SERVER (session storage): PostgreSQL 9.4.4 on x86_64-unknown-linux-gnu, compiled by gcc (GCC) 4.4.7 20120313 (Red Hat 4.4.7-11), 64-bit

SECCOND SERVER: PostgreSQL 8.4.20 on x86_64-redhat-linux-gnu, compiled by GCC gcc (GCC) 4.4.7 20120313 (Red Hat 4.4.7-11), 64-bit

CLIENT(php): PostgreSQL(libpq) Version 9.4.4
 [2015-12-15 00:30 UTC] yohgaki@php.net
@cdutary 

I briefly checked PostgreSQL 8.4 and 9.x libpq code. PostgreSQL 8.4's PQunescapeBytea() does not have support new escape method, but it seems 9.x code has older escape support.

I'm not sure why you get errors. Since I'm a pgsql module maintainer, I may investigate what's wrong. Please open new pgsql module bug report, if you think your problem is some kind of bug.

I'll close this bug.
 [2015-12-15 00:31 UTC] yohgaki@php.net
-Status: Assigned +Status: Not a bug
 [2015-12-15 00:31 UTC] yohgaki@php.net
Some kind of BYTEA escape/unescape issue.
 [2015-12-15 00:36 UTC] yohgaki@php.net
@cdutary

I updated the sample code pg_esacpe_bytea()

https://gist.github.com/yohgaki/a7b130bc93b2f9467ccc

to use database connection. libpq's code use connection version ID for proper escape method. This will fix your issue.
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Thu Dec 26 09:01:29 2024 UTC