ID: 10404
Updated by: kalowsky
Reported By: [EMAIL PROTECTED]
Old-Status: Feedback
Status: Closed
Bug Type: *Session related
Operating system: 
PHP Version: 4.0.4pl1
Assigned To: sas
Comments:

no user feedback... considered fixed in CVS.  if this is untrue, please reopen the bug.

Previous Comments:
---------------------------------------------------------------------------

[2001-05-30 12:03:00] [EMAIL PROTECTED]
umm, that last post should also say "could you try this with a recent..." :)  sorry 
about that!

---------------------------------------------------------------------------

[2001-05-30 12:02:29] [EMAIL PROTECTED]
there was a recent cvs commit which found a small error in the file locking (submitted 
by sascha).  please try one of the RCs for 4.0.6, current cvs, or wait for the 4.0.6 
and see if this continues there.... 

---------------------------------------------------------------------------

[2001-04-19 12:45:01] [EMAIL PROTECTED]
this should not happen as the session extension
uses file locking to prevent this

Sascha?

---------------------------------------------------------------------------

[2001-04-19 11:41:11] [EMAIL PROTECTED]

Summary:  On a high-speed network system, a page with a header(Location: ...) call 
does not always write the session file before the referred-to page reads it.  The 
result is a loss of session continuity.  I have not been able to reproduce this 
behavior across the internet.  Additionally, I have not been able to reproduce this 
behavior on the local net with IE as the client, but it is reproducable with NS 4.7 
running on Linux 6.0 and win98.

My PHP configuration line:  ./configure --with-apxs --enable-bcmath --with-bz2 
--with-config-file-path=/etc --disable-debug --enable-debugger --enable-ftp --with-gd 
--with-gettext --enable-inline-optimization --with-jpeg-dir=/usr --with-mysql=/usr 
--enable-magic-quotes --with-png --with-regex=system --enable-safe-mode 
--disable-short-tags --disable-static --enable-sysvsem --enable-sysvshm 
--enable-track-vars --enable-trans-sid --enable-yp --with-zlib

The server is an Athlon 600 with 128MB, running a stock RH7.0 system with apache 
1.13.12 as installed with RH7.0.  The php.ini file is identical to php.ini-dist.  I 
compiled MySQL 3.23.33 (relevant for the session-based variant discussed later).  The 
clients are an Athlon 600 with 384 MB (Linux) and a K6-200 with 128MB (win98), each 
with NS4.7.  The network is switched at 100 Mb/s.

The three following files can reproduce the behavior.  I'm sorry that they're so long, 
but this is the minimum set.  The originals were much, much longer.

begin index.html +++++++++++++++++++++++++++++++++++++
<HTML>
<HEAD>
<TITLE>Home Page</TITLE>
</HEAD>
<BODY>
<HR>

<a href="foo.html"><IMG SRC="foo.gif"></a>
<HR>
Home<BR>
<a href="users.php">Users</a><BR>

<HR>

<HR>
</BODY>
</HTML>
end index.html ++++++++++++++++++++++++++++++++++++++++

begin users.php +++++++++++++++++++++++++++++++++++++++
<?php

$fd = fopen("/tmp/debuglog", "a");
fwrite($fd , "=====================================================n");

# fire up the session tracking and register the
# session variables
session_start();
session_register(array('user' , 'pass' , 'returnfromlogin' , 'loginfailures'));
session_register("count");
$count++;
$returnfromlogin=$PHP_SELF;

if(TRUE) {
  $sessdata = session_encode();
  fwrite($fd , "      PHPSESSID:$PHPSESSID:n");
  fwrite($fd , "         sessid:" . session_id() . ":n");
  fwrite($fd , "           user:$user:n");
  fwrite($fd , "           pass:$pass:n");
  fwrite($fd , "returnfromlogin:$returnfromlogin:n");
  fwrite($fd , "  loginfailures:$loginfailures:n");
  fwrite($fd , " session_encode:" . session_encode() . ":n");
  fwrite($fd , "      microtime:" . microtime() . ":n");
  header("Location: login.php");
  # without this exit, PHP keeps running and wastes cpu
  # and bandwidth
  exit;
}

?>

<HTML>
<HEAD>
<TITLE>Administration - Users</TITLE>
</HEAD>
<BODY >
<P>The body goes here</P>
</BODY>
</HTML>
end users.php ++++++++++++++++++++++++++++++++++++++++

begin login.php ++++++++++++++++++++++++++++++++++++++
<?php

# if PHPSESSID is set, investigate the session state
# file (/tmp/sess_...)
# when things are not working properly, the session
# state file will be of
# zero length because the write from the previous page
# has not completed yet
if(isset($PHPSESSID)) {
  $sfilename = "/tmp/sess_" . $PHPSESSID;
  clearstatcache();
  $mtime = microtime();
  if(file_exists($sfilename)) {
    $exists = "True";
    $length = filesize($sfilename);
    $sfile = fopen($sfilename , "r");
    $scontent = fread($sfile , filesize($sfilename));
    fclose($sfile);
    if($exists=="True" && $length==0) {
      # the usleep fixes everything
      # usleep(1);
    }
  } else {
    $exists = "False";
  }
}

# open the debugging log and write out the current state
$fd = fopen("/tmp/debuglog", "a");
fwrite($fd , "---------------------------------------------n");
fwrite($fd , "Initialn");
fwrite($fd , "      PHPSESSID:$PHPSESSID:n");
fwrite($fd , " sessfileexists:$exists:n");
fwrite($fd , " sessfilelength:$length:n");
fwrite($fd , "sessfilecontent:$scontent:n");

# fire up the session tracking
session_start();
$count++;

# clear the stat cache and re-investigate the session
# state file now that
# session_start has been called
clearstatcache();
if(file_exists($sfilename)) {
  $exists = "True";
  $length = filesize($sfilename);
}
fwrite($fd , "--------------------------n");
fwrite($fd , "After session_startn");
fwrite($fd , "      PHPSESSID:$PHPSESSID:n");
fwrite($fd , "         sessid:" . session_id() . ":n");
fwrite($fd , "           user:$user:n");
fwrite($fd , "           pass:$pass:n");
fwrite($fd , "returnfromlogin:$returnfromlogin:n");
fwrite($fd , "  loginfailures:$loginfailures:n");
fwrite($fd , " sessfileexists:$exists:n");
fwrite($fd , " sessfilelength:$length:n");
fwrite($fd , " session_encode:" . session_encode() . ":n");

usleep(1);
# clear the stat cache and re-investigate the session
# state file now that
# usleep(1) has been called
clearstatcache();
if(file_exists($sfilename)) {
  $exists = "True";
  $length = filesize($sfilename);
  $sfile = fopen($sfilename , "r");
  $scontent = fread($sfile , filesize($sfilename));
  fclose($sfile);
}
fwrite($fd , "--------------------------n");
fwrite($fd , "After usleep(1)n");
fwrite($fd , " sessfileexists:$exists:n");
fwrite($fd , " sessfilelength:$length:n");
fwrite($fd , "sessfilecontent:$scontent:n");

?>

<HTML>
<HEAD>
<TITLE>Administration - Login</TITLE>
</HEAD>
<BODY >
<TABLE border="5">
<TR>
        <TD>
                <center><font size="+3">Administration - Login</font></center>

<?php

# the next line should hold 'user.php', but it doesn't
# most of the time
print "<P>Return to: $returnfromlogin</P>n";
print "<P>Loginpage: $loginpage</P>n";
?>

<FORM METHOD="POST">
<P>Usename:  <INPUT TYPE="text" SIZE=16 NAME="username"></P>
<P>Password: <INPUT TYPE="password" SIZE=16 NAME="password"></P>
<INPUT TYPE="hidden" NAME="validate" VALUE=1>
<P><INPUT TYPE="submit" VALUE="Login"></P>
</FORM>

        </TD>
</TR>

</TABLE>
</BODY>
</HTML>

end login.php ++++++++++++++++++++++++++++++++++++++++++

The original idea was that I would store the desired page uri in a session variable 
and redirect un-authenticated users to the login page.  Upon successfully login in, 
the user could be redirected back to the page they really wanted.  For debugging 
purposes, the login.php page shows the name of the page that dumped the user to the 
login page.  With the files provided above, it should always show "Return to: 
somepath/users.php", but will often be "Return to:" - thus indicating a failure.

I have not been able to reproduce the behavior by either typing in the uri "users.php" 
or creating a bookmark to it.  It is reproducible if you start from the index.html and 
click on the 'Users' link.  Adding a 'foo.gif' image to load on the index.html page 
increases the failure rate to 4 out of 5.  Without the foo.gif image, the failure rate 
is about 1 out of 10.  I start a fresh copy of netscape for each test, thus removing 
the session cookie.

There are debugging statements in the php code which log to the file /tmp/debuglog.  
The log when the session functions properly looks like:

=====================================================
      PHPSESSID::
         sessid:1ed96ede8bcb57ebe228d3d72778f786:
           user::
           pass::
returnfromlogin:/lyons/session-bug-demo-file/users.php:
  loginfailures::
 
session_encode:!user|!pass|returnfromlogin|s:38:"/lyons/session-bug-demo-file/users.php";!loginfailures|count|i:1;:

      microtime:0.19839100 987667153:
---------------------------------------------
Initial
      PHPSESSID:1ed96ede8bcb57ebe228d3d72778f786:
 sessfileexists:True:
 sessfilelength:99:
sessfilecontent:!user|!pass|returnfromlogin|s:38:"/lyons/session-bug-demo-file/users.php";!loginfailures|count|i:1;:

--------------------------
After session_start
      PHPSESSID:1ed96ede8bcb57ebe228d3d72778f786:
         sessid:1ed96ede8bcb57ebe228d3d72778f786:
           user::
           pass::
returnfromlogin:/lyons/session-bug-demo-file/users.php:
  loginfailures::
 sessfileexists:True:
 sessfilelength:99:
 
session_encode:!user|!pass|returnfromlogin|s:38:"/lyons/session-bug-demo-file/users.php";!loginfailures|count|i:2;:

--------------------------
After usleep(1)
 sessfileexists:True:
 sessfilelength:99:
sessfilecontent:!user|!pass|returnfromlogin|s:38:"/lyons/session-bug-demo-file/users.php";!loginfailures|count|i:1;:


users.php write the first block between the === and the ---.  It shows that the 
session id has been created, the returnfromlogin variable has a value, and that 
session_encode knows about all of the session variables.

login.php writes the remaining blocks.  The 'Initial' block shows that the session id 
has been retrieved from the cookie, that the session file exists and that it is 99 
bytes long.  It also reads the file which has the appropriate contents.

The 'After session_start' block shows that the variables have been successfully 
recovered by session_start, and then re-confirms the session file's existence, length, 
and contents.

The 'After usleep(1)' block confirms once-again the session file's existence, length, 
and contents.

But, when the session doesn't work properly, here is what the log shows:

=====================================================
      PHPSESSID::
         sessid:42ea7fe7ec4d1bfec7732b5e6c425d92:
           user::
           pass::
returnfromlogin:/lyons/session-bug-demo-file/users.php:
  loginfailures::
 
session_encode:!user|!pass|returnfromlogin|s:38:"/lyons/session-bug-demo-file/users.php";!loginfailures|count|i:1;:

      microtime:0.18177800 987667522:
---------------------------------------------
Initial
      PHPSESSID:42ea7fe7ec4d1bfec7732b5e6c425d92:
 sessfileexists:True:
 sessfilelength:0:
sessfilecontent::
--------------------------
After session_start
      PHPSESSID:42ea7fe7ec4d1bfec7732b5e6c425d92:
         sessid:42ea7fe7ec4d1bfec7732b5e6c425d92:
           user::
           pass::
returnfromlogin::
  loginfailures::
 sessfileexists:True:
 sessfilelength:0:
 session_encode::
--------------------------
After usleep(1)
 sessfileexists:True:
 sessfilelength:99:
sessfilecontent:!user|!pass|returnfromlogin|s:38:"/lyons/session-bug-demo-file/users.php";!loginfailures|count|i:1;:


The users.php write of the first block between the === and the --- is just as before.

The 'Initial' block shows that the session file exists, but is of 0 length.  That 
certainly explains why the login.php page isn't able to load the session variables.

The 'After session_start' block shows that the session_start was not able to load the 
session variables, and that the session file is still 0 length.

The 'After usleep(1)' block (which really does follow a 1 micro-second sleep), shows 
that the session file has finally been written by the first page, but long after the 
second page needed it.  The session file shows up with the proper length and contents, 
only to be overwritten an instant later when the login.php page saves a 0 length file 
because it doesn't know about the variables previously registered or their values.

It is so interesting that a 1 microsecond sleep gives the first page enough of a kick 
to get its session information written.  A quick-and-dirty fix has been to add a 
usleep(1) before the session_start in login.php, which gets the users.php session 
write to finish before the new session loads.

I hope that you are able to reproduce this on your local networks.  It doesn't appear 
to be an issue when the latency of the internet is involved, but it could certainly 
become an issue on high-speed internal company networks.

My thinking is that there are two solutions.  The first is a rigorous flushing setup 
that guarantees the first page is finished before the second one begins.  It would 
probably slow down the whole php session system.  The second solution revolves around 
removing the ambiguity between a session with no registered values and a session file 
that has yet to be written - both are 0 length.  Session files that are yet to be 
written will always be 0 length, so maybe the answer is to put some content into the 
session file for a session with no registered variables, maybe just 2 bytes of content 
"!!", which could never occur in a real session file, would allow the session_start to 
differentiate between these two situations.  If session_start sees a 0 length file, it 
sleeps and checks again a few times before giving up.  If session_start sees !!, it 
knows there are no registered variables and it keeps on going.

I've also tested the behavior with the PHP4 MySQL Session Handler, Version 1.00, by 
Ying Zhang.  I was surprised that the behavior is worse, failing almost 100% of the 
time - and no amount of usleep seems to help in the least.

I really am sorry that this posting is so long, but this is a very sensitive behavior 
that is difficult to reproduce, and may be entirely fine on your systems.  I am 
certain that you can see how this behavior could occur, even if you are not able to 
reproduce it.

I will be happy to assist in any way that I can.  I think PHP is awesome and hope to 
help it grow by fortifying it against this behavior.

Sincerely,
Bryan


---------------------------------------------------------------------------

The remainder of the comments for this report are too long.  To view the rest of the 
comments, please view the bug report online.


ATTENTION! Do NOT reply to this email!
To reply, use the web interface found at http://bugs.php.net/?id=10404&edit=2


-- 
PHP Development Mailing List <http://www.php.net/>
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]
To contact the list administrators, e-mail: [EMAIL PROTECTED]

Reply via email to