views:

1280

answers:

5

When a user uploads a file, randomly it gets replaced by another user's upload, I've finally tracked down the issue to PHP and the tmp file name being reused. Is there a way to fix this? Is there a way to make better random names? It seems to degrade over time, as in the random file name seed gets weaker? This is on PHP 5.2.8 and FreeBSD 7.0

Here is a log showing how the same tmp file name gets used and is overwritten by another upload: http://pastebin.com/m65790440

Any help is GREATLY appreciated. I've been trying to fix this for over 4 months and has gotten worse over time. Thank you.

EDIT: Keep in mind that this is not a PHP code issue, this is happening before it reaches any PHP code, the file received via $_FILES['name']['tmp_name'] is incorrect when it is received and its been traced back that it is being overwritten with someone else's upload before it reaches the upload processing script

A: 

I would recommend using a GUID generator for the filename seeing that you are getting so many.

webclimber
Is there a way to override how PHP names file uploads coming in? as of right now php uses a 'phpxxxxx' schema
noaheverett
+1  A: 

Is PHP running under apache, as mod_php?

You may try to create a per-process temporary upload directory whose name contains your php getmypid(), then ini_set your PHP process' upload_tmp_dir to that directory. This will not work if a new php process is spawned for every request.

vladr
Is there a way to override how PHP names file uploads coming in?
noaheverett
no, but you may be able to override (at runtime) the temporary directory where temporary files are dumped
vladr
A: 

Move your files to a user dir after they have been uploaded. Those temp files should be removed.

Andrew Clark
This is the correct fix. PHP's temporary upload files are only meant to stay where they are for the amount of time it takes your processing script to move them to where they actually belong.
chaos
At some point, PHP's policy became to delete the temp files at the end of the request if you haven't moved/renamed them, so your entire current mechanism will also break if you upgrade past that version.
chaos
They are being moved. The file that the PHP upload processing script receives from $_FILES['name']['tmp_name'] is wrong to begin with
noaheverett
+3  A: 

It sounds like something is seriously wrong with either your PHP installation or whichever system call PHP is internally using to generate the random file names (most likely tempnam).

For everyone else: PHP handles uploaded files internally before the user code is ever processed. These names are stored in $_FILES['file']['tmp_name'] (where 'file' is the (quoted) name of the file input element on the form).

R. Bemrose
Yes I believe you are correct, now I need to figure out how to fix it =B
noaheverett
I've been seeing if I could find anything on Google, but I haven't. Does the problem still occur if you set upload_tmp_dir to a different directory?
R. Bemrose
Yeah just tested it out and the problem still happens when the upload_tmp_dir is set to something different, would "noatime" set on the /var partition (where the files are uploaded to) have an affect on this?
noaheverett
I wouldn't think so, but I'd have to check. Also, I noticed in the pastebin that the group on those files is set to wheel... another reason to use a different temp directory.
R. Bemrose
About the wheel group issue: http://www.nabble.com/Problem-when-uploading-files-with-Apache-td21898420.html
R. Bemrose
I've corrected the group issue and it is now www:www, although that probably wasn't meant to be a fix, the issue is still happening
noaheverett
As much as I hate suggesting it... is it possible to rebuild PHP from the ports collection?
R. Bemrose
@noaheverett, have you tried modifying upload_tmp_dir using ini_set and the process ID?
vladr
A: 

After chasing the relevant code down to _gettemp in FreeBSD 7's libc implementation, I'm unclear regarding how the contents of the file tmp_name could be invalid. (To trace it, you might download a copy of PHP 5.2.8 and read in main/rfc1867.c - line 1018 calls in main/php_open_temporary_file.c, the function starting on line 227, which does it's main work in the function starting on line 97, which, however, is essentially just a wrapper for mkstemp on your system, which is found in the FreeBSD libc implementation on line 66 (linked), which uses _gettemp (same as above) to actually generate the random filename. However the manpage for mkstemp mentions in the BUGS section that the arc4random() function is not reentrant. It might be a possibility that 2 simultaneous requests are entering the critical code section and returning the same tmp_name - I know too little about how Apache works with either mod_php or php-cgi to comment there (though using FastCGI/php-cgi might work - I can't comment successfully on this at this time).

However, aiming for the simpliest solution, if you are not quite experiencing the file tmp_name itself being invalid, but colliding instead with other uploaded files (for example, if using the filename portion of tmp_name as your only source of uniqueness in the stored filename), you could be facing collisions due to the birthday paradox. In another question you mention having some 5,000,000 files to move, and in still another question you mention recieving 30-40k uploads a day. This strikes me as a prime situation for a birthday paradox collision. The mktemp man page mentions that (if using six 'Xs' as PHP does) there are 56,800,235,584 possible filenames (62 ** 6, or 62 ** n where n = number of 'Xs', etc). However, given that you have more than some 5 million files, the probability of a collision is approximately 100% (another heuristic suggests you'll have already experienced some order of 220 collisions already, if ((files*(files-1))/2)/(62**6) means anything, where files = 5,000,000). If this is the problem you are facing (probable, if not adding further entropy to the generated uploaded filename), you might try something like move_uploaded_file($file['tmp_name'], UPLOADS.sha1(mt_rand().$file['tmp_name']).strrchr($file['name'], '.')) - the idea being to add more randomness to the random filename, preventing collisions. An alternative could be to add two more 'Xs' to line 134 of main/php_open_temporary_file.c and recompile.

michaelc