During the course of fixing a security issue (#671906: mysql credentials leak in drush sqlc), I had to create two temporary files with drush_save_data_to_temp_file(). It seems this is impossible in Drush 4, and is the reason why that issue isn't yet resolved.

I suspect the problem is related to the fact that the file descriptor in that function is static. Yet, when I comment out that declaration, it still fails to create the second file.

Attached is a patch that adds the test for this bug, a silly eval because #1133284: separate functional from unit tests was never committed.

CommentFileSizeAuthor
save_test.patch654 bytesanarcat

Comments

anarcat’s picture

btw, this is not a bug in tmpfile() from php, as the following test works:

  function testTmpFile() {
    $eval = '
$tmpfile0 = tmpfile();
$tmpfile1 = tmpfile();
$tmpnam0 = stream_get_meta_data($tmpfile0);
$tmpnam1 = stream_get_meta_data($tmpfile1);
print json_encode(file_exists($tmpnam0["uri"]) && file_exists($tmpnam1["uri"]));
';
    $this->drush('php-eval', array($eval));
    $exist = json_decode($this->getOutput());
    $this->assertTrue($exist);
  }
anarcat’s picture

Even more confusing and marvelous: when commenting out the "static" declaration, no file is created at *all*. Huge wtf.

Simpler test code also shows that the first tempfile is actually removed when the second tmp call is issued:

$tmpfile0 = drush_save_data_to_temp_file("foo");
print "file 0: $tmpfile0 exists? " . file_exists($tmpfile0) . "\n";
$tmpfile1 = drush_save_data_to_temp_file("bar");

print "file 0: $tmpfile0 exists? " . file_exists($tmpfile0) . "\n";
print "file 1: $tmpfile1 exists? " . file_exists($tmpfile1) . "\n";

yields:

file 0: /tmp/php0sbcYN exists? 1
file 0: /tmp/php0sbcYN exists?
file 1: /tmp/phpNOXNE2 exists? 1
anarcat’s picture

Status: Active » Needs review

So I think the problem resides in the "magic" of tmpfile(), which "magically" removes the file descriptor when it is closed (or in our case, garbage collected). Lots of fun. The following patch works, for example:

index 8dfaf35..830437b 100644
--- a/includes/drush.inc
+++ b/includes/drush.inc
@@ -1798,9 +1798,10 @@ function drush_mkdir($path) {
  *   A path to the file.
  */
 function drush_save_data_to_temp_file($data) {
-  static $fp;
+  static $fplist;
 
   $fp = tmpfile();
+  $fplist[] = $fp;
   fwrite($fp, $data);
   $meta_data = stream_get_meta_data($fp);
   $file = $meta_data['uri'];

...simply because we keep a reference to all the temporary files we create.

Drush 5 is not affected, because it doesn't use tmpfile() but tmpname(), which has the disadvantage of being vulnerable to a symlink attack, as we do not get an open file descriptor but a pathname which may be replaced during the very very short window during which we try to open the file. It is unclear to me whether we should use Drush 5's version of the above hack. Both alternatives seem problematic.

Anyways, with the above patch, the provided test passes and the whole test suite is in the green again.

greg.1.anderson’s picture

Version: All-versions-4.x-dev » 8.x-6.x-dev
Status: Needs review » Needs work

#1133284: separate functional from unit tests was committed; any interest in revisiting this test, and re-rolling as described in #0?

Close if not interested -- I'm just doing some issue-queue cleaning.

anarcat’s picture

Status: Needs work » Closed (fixed)

From my latest comments, it seemed that Drush 5 wasn't affected by this bug, so Drush 6 is likely unaffected too.

However, both are probably vulnerably to a symlink attack, so there's a security issue here, but that's a separate issue.

I am closing this one as fixed in 5.x, according to my latest comments.