WebServer: fixed longstanding non-security fwrite buffer overflow bug
authorDan
Tue, 26 May 2009 15:24:26 -0400
changeset 68 32f6e2ee15ab
parent 67 c4aefad02ce4
child 69 73780a159e15
WebServer: fixed longstanding non-security fwrite buffer overflow bug
multithreading.php
webserver.php
--- a/multithreading.php	Sun Jan 04 22:56:57 2009 -0500
+++ b/multithreading.php	Tue May 26 15:24:26 2009 -0400
@@ -37,12 +37,13 @@
 
 function Threader_SigUsr2()
 {
-  global $threader_instances;
+  global $threader_instances, $threader_notick;
+  if ( @$threader_notick )
+    return;
   foreach ( $threader_instances as &$mt )
   {
     if ( is_object($mt) )
     {
-      $parchild = $mt->is_child() ? 'child' : 'parent';
       $mt->event_sigusr2();
     }
   }
@@ -136,8 +137,8 @@
       
       $threader_instances[] =& $this;
       
+      pcntl_signal(SIGCHLD, 'Threader_SigChld');
       pcntl_signal(SIGUSR2, 'Threader_SigUsr2');
-      pcntl_signal(SIGCHLD, 'Threader_SigChld');
     }
     
     $this->json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
@@ -273,6 +274,7 @@
     if ( $this->is_child() )
     {
       // this is easy - the parent sent the signal.
+      @stream_set_blocking($this->parent_sock, 0);
       $command = rtrim(fgets($this->parent_sock, 102400), "\n");
     }
     else
@@ -281,8 +283,7 @@
       // of time and try to read; if we get something, awesome.
       foreach ( $this->ipc_sockets as $pid => $socket )
       {
-        // 1000 microseconds = 1/80th of the time it takes you to blink.
-        @stream_set_timeout($socket, 0, 1000);
+        @stream_set_blocking($socket, 0);
         $command = rtrim(@fgets($socket, 102400), "\n");
         if ( !empty($command) )
         {
--- a/webserver.php	Sun Jan 04 22:56:57 2009 -0500
+++ b/webserver.php	Tue May 26 15:24:26 2009 -0400
@@ -334,6 +334,7 @@
     $this->server_string = "PhpHttpd/" . HTTPD_VERSION . " PHP/" . PHP_VERSION . "\r\n";
     $this->parent_pid = getmypid();
     $this->threader = new Threader();
+    $this->threader->ipc_register('ws_reboot', array(&$this, 'reboot'));
     
     // create a UUID
     $uuid_base = md5(microtime() . ( function_exists('mt_rand') ? mt_rand() : rand() ));
@@ -351,7 +352,7 @@
   
   function __destruct()
   {
-    if ( !is_object($this->threader) )
+    if ( !$this->threader )
     {
       return false;
     }
@@ -437,6 +438,10 @@
     }
     else if ( !$this->threader->is_child() && $oldfork )
     {
+      // we are the parent and have been asked to respawn. there are (presumably)
+      // still children running around; when one of them dies, we'll receive a
+      // SIGCHLD, but often times this is already being called from the SIGUSR2
+      // handler. whoops.
       if ( function_exists('status') )
         status('Waiting on all children');
       
@@ -448,8 +453,7 @@
     }
     else
     {
-      // this is a childless parent; delay any action until the current
-      // request has been sent (do nothing now)
+      // not sure what to do in this particular scenario.
     }
   }
   
@@ -578,7 +582,7 @@
       {
         if ( $start_time + HTTPD_KEEP_ALIVE_TIMEOUT < microtime(true) || $remote->is_eof() )
         {
-          // request expired -- end the process here
+          // request expired -- end the iteration here
           if ( !$this->threader->is_child() )
             $remote->destroy();
           
@@ -1256,7 +1260,8 @@
           // $output = dechex(strlen($output)) . "\r\n$output";
           
           // write body
-          $socket->write($output);
+          if ( !empty($output) )
+            $socket->write($output);
           
           $this->headers_sent = false;
         }
@@ -2134,13 +2139,11 @@
   
   function write($data)
   {
-    $data = str_split($data, 8096);
-    foreach ( $data as $chunk )
+    $size = strlen($data);
+    $written = 0;
+    while ( $written < $size )
     {
-      while ( !@fwrite($this->sock, $chunk) )
-      {
-        usleep(50000);
-      }
+      $written += @fwrite($this->sock, substr($data, $written));
     }
     return true;
   }